@haus-tech/haus-workflow 4.0.0 → 4.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.
Files changed (3) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/cli.js +189 -168
  3. package/package.json +2 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.0.1](https://github.com/WeAreHausTech/haus-workflow/compare/v4.0.0...v4.0.1) (2026-08-20)
4
+
5
+ ### Bug Fixes
6
+
7
+ - land remaining [#299](https://github.com/WeAreHausTech/haus-workflow/issues/299) cleanup backlog ([#322](https://github.com/WeAreHausTech/haus-workflow/issues/322)) ([0b9a747](https://github.com/WeAreHausTech/haus-workflow/commit/0b9a747be0ad4d61c52113a62d126f8133f6e175))
8
+ - treat mixed escaping-path entries as whole-entry refusals ([#321](https://github.com/WeAreHausTech/haus-workflow/issues/321)) ([59593e8](https://github.com/WeAreHausTech/haus-workflow/commit/59593e8495f88422943132d8a9a62badee887431))
9
+
3
10
  ## [4.0.0](https://github.com/WeAreHausTech/haus-workflow/compare/v3.1.2...v4.0.0) (2026-08-20)
4
11
 
5
12
  ### ⚠ BREAKING CHANGES
package/dist/cli.js CHANGED
@@ -1913,7 +1913,7 @@ var init_sync = __esm({
1913
1913
 
1914
1914
  // src/cli.ts
1915
1915
  import { readFileSync as readFileSync5 } from "node:fs";
1916
- import path79 from "node:path";
1916
+ import path80 from "node:path";
1917
1917
  import { Command } from "commander";
1918
1918
 
1919
1919
  // src/commands/apply.ts
@@ -3050,8 +3050,8 @@ function buildDenyRules() {
3050
3050
  for (const command of DENY_COMMANDS) {
3051
3051
  rules.push(`Bash(${command}:*)`);
3052
3052
  }
3053
- for (const path80 of DENY_PATHS) {
3054
- const pattern = DENY_DIRS.has(path80) ? `${path80}/**` : path80;
3053
+ for (const path81 of DENY_PATHS) {
3054
+ const pattern = DENY_DIRS.has(path81) ? `${path81}/**` : path81;
3055
3055
  for (const tool of FILE_TOOLS) {
3056
3056
  rules.push(`${tool}(${pattern})`);
3057
3057
  }
@@ -4441,7 +4441,7 @@ async function cleanupMigratedCatalogItems(root, prevLock, migrations, dryRun, q
4441
4441
  const relPaths = entry.paths ?? [];
4442
4442
  const existing = await resolveExistingTrackedPaths(root, migration.oldId, relPaths, warn);
4443
4443
  if (existing.length === 0) continue;
4444
- const gate = await hashGateEntry(root, { id: migration.oldId, hash: entry.hash }, existing);
4444
+ const gate = await hashGateEntry(root, { hash: entry.hash }, existing);
4445
4445
  if (!gate.ok) {
4446
4446
  if (gate.reason === "no-hash") {
4447
4447
  warn(
@@ -4468,7 +4468,7 @@ async function cleanupMigratedCatalogItems(root, prevLock, migrations, dryRun, q
4468
4468
  return;
4469
4469
  }
4470
4470
  const allAbsPaths = toRemove.flatMap(({ existing }) => existing.map(({ abs }) => abs));
4471
- await backupTrackedPaths(root, allAbsPaths, say, "migrate");
4471
+ void await backupTrackedPaths(root, allAbsPaths, say, "migrate");
4472
4472
  for (const { existing } of toRemove) {
4473
4473
  for (const { abs } of existing) {
4474
4474
  await fs22.remove(abs);
@@ -4487,11 +4487,13 @@ async function cleanupStaleCatalogItems(root, manifestById, dryRun, quiet, activ
4487
4487
  );
4488
4488
  let keepShared = false;
4489
4489
  for (const entry of supersededEntries) {
4490
+ const id = entry.id;
4491
+ if (!id) continue;
4490
4492
  const relPaths = entry.paths ?? [];
4491
4493
  if (relPaths.length === 0) continue;
4492
- const existing = await resolveExistingTrackedPaths(root, entry.id, relPaths, warn);
4494
+ const existing = await resolveExistingTrackedPaths(root, id, relPaths, warn);
4493
4495
  if (existing.length === 0) continue;
4494
- const gate = await hashGateEntry(root, { id: entry.id, hash: entry.hash }, existing);
4496
+ const gate = await hashGateEntry(root, { hash: entry.hash }, existing);
4495
4497
  if (!gate.ok) keepShared = true;
4496
4498
  }
4497
4499
  for (const entry of prevLock) {
@@ -4503,12 +4505,15 @@ async function cleanupStaleCatalogItems(root, manifestById, dryRun, quiet, activ
4503
4505
  if (!removedFromManifest && !deprecated && !superseded) continue;
4504
4506
  const relPaths = entry.paths ?? [];
4505
4507
  if (relPaths.length === 0) continue;
4506
- if (await hasProtectedUnsafePath(root, relPaths)) refusedIds.add(entry.id);
4507
4508
  const existing = await resolveExistingTrackedPaths(root, entry.id, relPaths, warn);
4509
+ if (await hasProtectedUnsafePath(root, relPaths)) {
4510
+ refusedIds.add(entry.id);
4511
+ continue;
4512
+ }
4508
4513
  if (existing.length === 0) continue;
4509
4514
  const pruneReason = superseded ? "superseded" : deprecated ? "deprecated" : "stale";
4510
4515
  const label = superseded ? "Superseded" : deprecated ? "Deprecated" : "Stale";
4511
- const gate = await hashGateEntry(root, { id: entry.id, hash: entry.hash }, existing);
4516
+ const gate = await hashGateEntry(root, { hash: entry.hash }, existing);
4512
4517
  if (!gate.ok) {
4513
4518
  if (gate.reason === "no-hash") {
4514
4519
  warn(
@@ -4533,7 +4538,7 @@ async function cleanupStaleCatalogItems(root, manifestById, dryRun, quiet, activ
4533
4538
  return true;
4534
4539
  }) : existing;
4535
4540
  if (!dryRun && removable.length > 0) {
4536
- await backupTrackedPaths(
4541
+ void await backupTrackedPaths(
4537
4542
  root,
4538
4543
  removable.map((e) => e.abs),
4539
4544
  say,
@@ -4563,10 +4568,13 @@ async function pruneOrphanedCatalogItems(root, prevLock, notOrphanedIds, dryRun,
4563
4568
  if (!entry.id) continue;
4564
4569
  const relPaths = entry.paths ?? [];
4565
4570
  if (relPaths.length === 0) continue;
4566
- if (await hasProtectedUnsafePath(root, relPaths)) refusedIds.add(entry.id);
4567
4571
  const existing = await resolveExistingTrackedPaths(root, entry.id, relPaths, warn);
4572
+ if (await hasProtectedUnsafePath(root, relPaths)) {
4573
+ refusedIds.add(entry.id);
4574
+ continue;
4575
+ }
4568
4576
  if (existing.length === 0) continue;
4569
- const gate = await hashGateEntry(root, { id: entry.id, hash: entry.hash }, existing);
4577
+ const gate = await hashGateEntry(root, { hash: entry.hash }, existing);
4570
4578
  if (!gate.ok) {
4571
4579
  const detail = gate.reason === "no-hash" ? "has no lock hash" : "was modified locally";
4572
4580
  warn(
@@ -4587,7 +4595,7 @@ async function pruneOrphanedCatalogItems(root, prevLock, notOrphanedIds, dryRun,
4587
4595
  return refusedIds;
4588
4596
  }
4589
4597
  const allAbsPaths = toRemove.flatMap(({ existing }) => existing.map(({ abs }) => abs));
4590
- await backupTrackedPaths(root, allAbsPaths, say, "prune");
4598
+ void await backupTrackedPaths(root, allAbsPaths, say, "prune");
4591
4599
  for (const { id, existing } of toRemove) {
4592
4600
  await deleteTrackedPaths(root, id, existing, { verb: "Pruned", reason: "orphaned" }, say);
4593
4601
  }
@@ -4604,7 +4612,8 @@ var GITIGNORED_ARTIFACT_PATHS = [
4604
4612
  ".haus-workflow/context-map.json",
4605
4613
  ".haus-workflow/recommendation.json",
4606
4614
  ".haus-workflow/sources-report.json",
4607
- ".haus-workflow/deep-context.json"
4615
+ ".haus-workflow/deep-context.json",
4616
+ ".haus-workflow/backups/"
4608
4617
  ];
4609
4618
  function findLineMarker3(content2, marker, from = 0) {
4610
4619
  let idx = content2.indexOf(marker, from);
@@ -4649,7 +4658,7 @@ function buildBlockFor(existing, beginMarker, endMarker, comment, patterns) {
4649
4658
  if (needed.length === 0) return null;
4650
4659
  return [beginMarker, comment, ...needed, endMarker].join("\n");
4651
4660
  }
4652
- var ARTIFACT_BLOCK_COMMENT = "# haus scan artifacts \u2014 machine-local, must never be committed (see ADR-0025)";
4661
+ var ARTIFACT_BLOCK_COMMENT = "# haus machine-local artifacts \u2014 must never be committed (see ADR-0025, ADR-0058)";
4653
4662
  function injectBlockFor(existing, beginMarker, endMarker, comment, patterns) {
4654
4663
  const block = buildBlockFor(existing, beginMarker, endMarker, comment, patterns);
4655
4664
  const range = findBlockRangeFor(existing, beginMarker, endMarker);
@@ -4742,7 +4751,7 @@ async function untrackMachineLocalArtifacts(root, dryRun, notify) {
4742
4751
  for (const rel of tracked) {
4743
4752
  if (dryRun) {
4744
4753
  notify(
4745
- `Would untrack ${rel} from git \u2014 it contains machine-local scan output and should never have been committed.`
4754
+ `Would untrack ${rel} from git \u2014 it is machine-local haus output and should never have been committed.`
4746
4755
  );
4747
4756
  continue;
4748
4757
  }
@@ -4750,7 +4759,7 @@ async function untrackMachineLocalArtifacts(root, dryRun, notify) {
4750
4759
  if (result.exitCode === 0) {
4751
4760
  untracked.push(rel);
4752
4761
  notify(
4753
- `Untracked ${rel} from git \u2014 it contains machine-local scan output and should never have been committed. Run \`git status\` to review before committing this change.`
4762
+ `Untracked ${rel} from git \u2014 it is machine-local haus output and should never have been committed. Run \`git status\` to review before committing this change.`
4754
4763
  );
4755
4764
  } else {
4756
4765
  notify(`Could not untrack ${rel} from git: ${result.stderr.trim() || result.stdout.trim()}`);
@@ -7665,12 +7674,12 @@ async function runDoctor(options) {
7665
7674
  const trackedArtifacts = await listTrackedArtifactPaths(root);
7666
7675
  if (trackedArtifacts.length > 0) {
7667
7676
  flag(
7668
- `- GITIGNORE: ${trackedArtifacts.length} machine-local scan artifact(s) still tracked in git (${trackedArtifacts.join(", ")})`,
7669
- `${trackedArtifacts.length} machine-local scan artifact(s) are still tracked in git`,
7677
+ `- GITIGNORE: ${trackedArtifacts.length} machine-local artifact(s) still tracked in git (${trackedArtifacts.join(", ")})`,
7678
+ `${trackedArtifacts.length} machine-local artifact(s) are still tracked in git`,
7670
7679
  `git rm --cached ${trackedArtifacts.join(" ")} (or run \`haus apply --write\` to do this and update .gitignore for you)`
7671
7680
  );
7672
7681
  } else {
7673
- ok("- GITIGNORE: OK (no machine-local scan artifacts tracked in git)");
7682
+ ok("- GITIGNORE: OK (no machine-local artifacts tracked in git)");
7674
7683
  }
7675
7684
  const gitignoredHausPaths = await findGitignoredHausPaths(root);
7676
7685
  if (gitignoredHausPaths.length > 0) {
@@ -8291,7 +8300,8 @@ function summarizeLockDiff(before, after) {
8291
8300
  }
8292
8301
 
8293
8302
  // src/update/lockfile.ts
8294
- import { mkdir, readFile as readFile2, copyFile } from "node:fs/promises";
8303
+ import { constants as fsConstants } from "node:fs";
8304
+ import { copyFile, mkdir, readFile as readFile2 } from "node:fs/promises";
8295
8305
  import path47 from "node:path";
8296
8306
  init_fs();
8297
8307
  init_paths();
@@ -8325,16 +8335,30 @@ async function readLockRaw(root) {
8325
8335
  return "[]";
8326
8336
  }
8327
8337
  }
8328
- async function applyLock(root) {
8338
+ async function backupLockfile(root) {
8329
8339
  const lockPath = hausPath(root, "haus.lock.json");
8330
- const before = await readLockRaw(root);
8331
- const lock = await readJson(lockPath) ?? [];
8332
8340
  try {
8333
8341
  const backupDir = hausPath(root, "backups");
8334
8342
  await mkdir(backupDir, { recursive: true });
8335
- await copyFile(lockPath, path47.join(backupDir, `haus.lock.${Date.now()}.json`));
8343
+ let stamp = Date.now();
8344
+ for (; ; ) {
8345
+ const dest = path47.join(backupDir, `haus.lock.${stamp}.json`);
8346
+ try {
8347
+ await copyFile(lockPath, dest, fsConstants.COPYFILE_EXCL);
8348
+ return;
8349
+ } catch (err) {
8350
+ if (err.code !== "EEXIST") throw err;
8351
+ stamp += 1;
8352
+ }
8353
+ }
8336
8354
  } catch {
8337
8355
  }
8356
+ }
8357
+ async function applyLock(root) {
8358
+ const lockPath = hausPath(root, "haus.lock.json");
8359
+ const before = await readLockRaw(root);
8360
+ const lock = await readJson(lockPath) ?? [];
8361
+ await backupLockfile(root);
8338
8362
  const enriched = await Promise.all(
8339
8363
  lock.map(async (x) => {
8340
8364
  const paths = Array.isArray(x.paths) ? x.paths.map(String) : [];
@@ -8357,12 +8381,7 @@ async function removeLockEntries(root, ids) {
8357
8381
  const removed = [...wanted].filter((id) => present.has(id));
8358
8382
  const missing = [...wanted].filter((id) => !present.has(id));
8359
8383
  if (removed.length === 0) return { removed, missing, before, after: before };
8360
- try {
8361
- const backupDir = hausPath(root, "backups");
8362
- await mkdir(backupDir, { recursive: true });
8363
- await copyFile(lockPath, path47.join(backupDir, `haus.lock.${Date.now()}.json`));
8364
- } catch {
8365
- }
8384
+ await backupLockfile(root);
8366
8385
  const survivors = lock.filter((item) => !wanted.has(item.id));
8367
8386
  await writeJson(lockPath, survivors);
8368
8387
  return { removed, missing, before, after: `${JSON.stringify(survivors, null, 2)}
@@ -10799,6 +10818,7 @@ async function runRecommend(options) {
10799
10818
  }
10800
10819
 
10801
10820
  // src/commands/remove.ts
10821
+ import path54 from "node:path";
10802
10822
  init_fs();
10803
10823
  init_logger();
10804
10824
  init_paths();
@@ -10810,7 +10830,7 @@ async function runRemove(ids, options = {}) {
10810
10830
  const dryRun = Boolean(options.dryRun);
10811
10831
  const say = options.json ? () => {
10812
10832
  } : log;
10813
- const wanted = parseIdList(ids);
10833
+ const wanted = [...new Set(parseIdList(ids))];
10814
10834
  if (wanted.length === 0) {
10815
10835
  error("Specify at least one catalog item id \u2014 see `haus doctor` for what is installed.");
10816
10836
  process.exitCode = 1;
@@ -10856,7 +10876,7 @@ async function runRemove(ids, options = {}) {
10856
10876
  cleared.push({ id, existing: [] });
10857
10877
  continue;
10858
10878
  }
10859
- const gate = await hashGateEntry(root, { id, hash: entry.hash }, existing);
10879
+ const gate = await hashGateEntry(root, { hash: entry.hash }, existing);
10860
10880
  if (!gate.ok) {
10861
10881
  const detail = gate.reason === "no-hash" ? "has no lock hash" : "was modified locally";
10862
10882
  warn(
@@ -10902,7 +10922,8 @@ async function runRemove(ids, options = {}) {
10902
10922
  }
10903
10923
  }
10904
10924
  const allAbs = cleared.flatMap(({ existing }) => existing.map(({ abs }) => abs));
10905
- report.backupDir = await backupTrackedPaths(root, allAbs, say, "remove");
10925
+ const backupRoot = await backupTrackedPaths(root, allAbs, say, "remove");
10926
+ report.backupDir = backupRoot === null ? null : path54.basename(backupRoot);
10906
10927
  for (const { id, existing } of cleared) {
10907
10928
  await deleteTrackedPaths(root, id, existing, { verb: "Removed", reason: "" }, say);
10908
10929
  }
@@ -10987,7 +11008,7 @@ async function runScan(options) {
10987
11008
  }
10988
11009
 
10989
11010
  // src/commands/undo.ts
10990
- import path54 from "node:path";
11011
+ import path55 from "node:path";
10991
11012
  import fs40 from "fs-extra";
10992
11013
 
10993
11014
  // src/claude/managed-paths.ts
@@ -11019,18 +11040,18 @@ async function collectLockTrackedPaths(root) {
11019
11040
  const relPaths = row.paths ?? [];
11020
11041
  const existingRel = [];
11021
11042
  for (const rel of relPaths) {
11022
- if (await fs40.pathExists(path54.resolve(root, rel))) existingRel.push(rel);
11043
+ if (await fs40.pathExists(path55.resolve(root, rel))) existingRel.push(rel);
11023
11044
  }
11024
11045
  if (existingRel.length === 0) continue;
11025
11046
  if (row.hash === void 0) {
11026
- removable.push(...existingRel.map((rel) => path54.resolve(root, rel)));
11047
+ removable.push(...existingRel.map((rel) => path55.resolve(root, rel)));
11027
11048
  continue;
11028
11049
  }
11029
11050
  const currentHash = await hashInstalledPaths(root, existingRel);
11030
11051
  if (currentHash === row.hash) {
11031
- removable.push(...existingRel.map((rel) => path54.resolve(root, rel)));
11052
+ removable.push(...existingRel.map((rel) => path55.resolve(root, rel)));
11032
11053
  } else {
11033
- preserved.push(...existingRel.map((rel) => path54.resolve(root, rel)));
11054
+ preserved.push(...existingRel.map((rel) => path55.resolve(root, rel)));
11034
11055
  log(`Preserving locally-modified ${row.id ?? "(unknown item)"}: ${existingRel.join(", ")}`);
11035
11056
  }
11036
11057
  }
@@ -11043,7 +11064,7 @@ async function settingsHasHausContent(root) {
11043
11064
  return settings._haus != null;
11044
11065
  }
11045
11066
  async function claudeMdHasHausBlock(root) {
11046
- const filePath = path54.join(root, "CLAUDE.md");
11067
+ const filePath = path55.join(root, "CLAUDE.md");
11047
11068
  if (!await fs40.pathExists(filePath)) return false;
11048
11069
  const text = await fs40.readFile(filePath, "utf8");
11049
11070
  return text.includes(BLOCK_BEGIN);
@@ -11056,15 +11077,15 @@ async function stripProjectSettings(root) {
11056
11077
  const hasContent = Object.keys(settings).length > 0;
11057
11078
  if (hasContent) {
11058
11079
  await writeProjectSettings(root, settings);
11059
- log(`Stripped haus rules from ${path54.relative(root, settingsPath)} (user settings preserved).`);
11080
+ log(`Stripped haus rules from ${path55.relative(root, settingsPath)} (user settings preserved).`);
11060
11081
  return true;
11061
11082
  }
11062
11083
  await fs40.remove(settingsPath);
11063
- log(`Removed ${path54.relative(root, settingsPath)} (no user-owned settings remained).`);
11084
+ log(`Removed ${path55.relative(root, settingsPath)} (no user-owned settings remained).`);
11064
11085
  return true;
11065
11086
  }
11066
11087
  async function stripRootClaudeMd(root) {
11067
- const filePath = path54.join(root, "CLAUDE.md");
11088
+ const filePath = path55.join(root, "CLAUDE.md");
11068
11089
  if (!await fs40.pathExists(filePath)) return false;
11069
11090
  const prev = await fs40.readFile(filePath, "utf8");
11070
11091
  if (!prev.includes(BLOCK_BEGIN)) return false;
@@ -11092,12 +11113,12 @@ async function backupManagedFilesBeforeUndo(root, managedAbsPaths) {
11092
11113
  const backupRoot = hausPath(root, "backups", `undo-${stamp}`);
11093
11114
  for (const abs of managedAbsPaths) {
11094
11115
  if (!await fs40.pathExists(abs)) continue;
11095
- const rel = path54.relative(root, abs);
11096
- const backupPath = path54.join(backupRoot, rel);
11097
- await fs40.ensureDir(path54.dirname(backupPath));
11116
+ const rel = path55.relative(root, abs);
11117
+ const backupPath = path55.join(backupRoot, rel);
11118
+ await fs40.ensureDir(path55.dirname(backupPath));
11098
11119
  await fs40.copy(abs, backupPath);
11099
11120
  }
11100
- log(`Backed up ${managedAbsPaths.length} managed file(s) to ${path54.relative(root, backupRoot)}.`);
11121
+ log(`Backed up ${managedAbsPaths.length} managed file(s) to ${path55.relative(root, backupRoot)}.`);
11101
11122
  }
11102
11123
  async function runUndo(options) {
11103
11124
  const root = options.root ?? process.cwd();
@@ -11110,7 +11131,7 @@ async function runUndo(options) {
11110
11131
  log("Nothing to remove: no haus-managed files found in this directory.");
11111
11132
  return;
11112
11133
  }
11113
- const relTargets = managed.map((p) => path54.relative(root, p));
11134
+ const relTargets = managed.map((p) => path55.relative(root, p));
11114
11135
  const summaryParts = [...relTargets];
11115
11136
  if (stripSettings) summaryParts.push(".claude/settings.json (haus rules only)");
11116
11137
  if (stripClaudeMd) summaryParts.push("CLAUDE.md (haus import block only)");
@@ -11140,7 +11161,7 @@ User-owned .claude/ files will be preserved.`
11140
11161
  for (const abs of managed) {
11141
11162
  if (!await fs40.pathExists(abs)) continue;
11142
11163
  await fs40.remove(abs);
11143
- log(`Removed ${path54.relative(root, abs)}`);
11164
+ log(`Removed ${path55.relative(root, abs)}`);
11144
11165
  }
11145
11166
  if (stripSettings) await stripProjectSettings(root);
11146
11167
  if (stripClaudeMd) await stripRootClaudeMd(root);
@@ -11154,7 +11175,7 @@ init_fs();
11154
11175
  init_logger();
11155
11176
  init_paths();
11156
11177
  import crypto3 from "node:crypto";
11157
- import path55 from "node:path";
11178
+ import path56 from "node:path";
11158
11179
  import fs41 from "fs-extra";
11159
11180
  async function runUninstall(options = {}) {
11160
11181
  const { force = false, removePlugins = removeGlobalPlugins } = options;
@@ -11211,14 +11232,14 @@ async function runUninstall(options = {}) {
11211
11232
  continue;
11212
11233
  }
11213
11234
  await fs41.remove(entry.destPath);
11214
- await pruneEmptyDir(path55.dirname(entry.destPath));
11235
+ await pruneEmptyDir(path56.dirname(entry.destPath));
11215
11236
  result.deleted.push(entry.destPath);
11216
11237
  }
11217
11238
  const settings = await readSettings();
11218
11239
  const stripped = stripHausHooks(stripHausAsk(stripHausAllow(stripHausDeny(settings))));
11219
11240
  await writeSettings(stripped);
11220
11241
  result.hooksStripped = true;
11221
- const hausDir = path55.join(globalClaudeDir(), "haus");
11242
+ const hausDir = path56.join(globalClaudeDir(), "haus");
11222
11243
  const manifestPath2 = hausManifestPath();
11223
11244
  if (fs41.pathExistsSync(manifestPath2)) {
11224
11245
  await fs41.remove(manifestPath2);
@@ -11295,7 +11316,7 @@ async function runUninstallCommand(options) {
11295
11316
  }
11296
11317
 
11297
11318
  // src/commands/validate-catalog.ts
11298
- import path57 from "node:path";
11319
+ import path58 from "node:path";
11299
11320
 
11300
11321
  // src/catalog/validate-core.ts
11301
11322
  init_forbidden_content();
@@ -11303,7 +11324,7 @@ init_manifest_item_fields();
11303
11324
  init_path_safety();
11304
11325
  init_validation_rules2();
11305
11326
  import fs42 from "node:fs";
11306
- import path56 from "node:path";
11327
+ import path57 from "node:path";
11307
11328
  var ITEM_SEMVER_RE = /^\d+\.\d+\.\d+$/;
11308
11329
  var ITEM_ID_PATTERN = /^haus\./;
11309
11330
  function auditForbiddenStacks(items) {
@@ -11454,7 +11475,7 @@ function readForAudit(absPath, label, failures) {
11454
11475
  }
11455
11476
  }
11456
11477
  function auditTemplateContent(manifestDir, absPath, itemId) {
11457
- const rel = path56.relative(manifestDir, absPath);
11478
+ const rel = path57.relative(manifestDir, absPath);
11458
11479
  const failures = [];
11459
11480
  const text = readForAudit(absPath, `${itemId}: ${rel}`, failures);
11460
11481
  if (text === null) return failures;
@@ -11476,25 +11497,25 @@ function auditShippedFiles(manifestDir, items) {
11476
11497
  const failures = [];
11477
11498
  for (const item of items) {
11478
11499
  if (!item.path || !isSafeCatalogPath(item.path)) continue;
11479
- const absPath = path56.join(manifestDir, item.path);
11500
+ const absPath = path57.join(manifestDir, item.path);
11480
11501
  if (item.type === "skill") {
11481
- const skillMd = path56.join(absPath, "SKILL.md");
11502
+ const skillMd = path57.join(absPath, "SKILL.md");
11482
11503
  if (!fs42.existsSync(skillMd)) {
11483
- failures.push(`${item.id}: missing ${path56.relative(manifestDir, skillMd)}`);
11504
+ failures.push(`${item.id}: missing ${path57.relative(manifestDir, skillMd)}`);
11484
11505
  continue;
11485
11506
  }
11486
11507
  const text = readForAudit(skillMd, `${item.id}: SKILL.md`, failures);
11487
11508
  if (text === null) continue;
11488
11509
  failures.push(...checkRequiredFrontmatter(text, `${item.id}: SKILL.md`));
11489
11510
  failures.push(
11490
- ...auditForbiddenTagsInText(text, `${item.id}: ${path56.relative(manifestDir, skillMd)}`)
11511
+ ...auditForbiddenTagsInText(text, `${item.id}: ${path57.relative(manifestDir, skillMd)}`)
11491
11512
  );
11492
11513
  } else if (item.type === "agent") {
11493
11514
  if (!fs42.existsSync(absPath)) {
11494
11515
  failures.push(`${item.id}: missing agent file ${item.path}`);
11495
11516
  continue;
11496
11517
  }
11497
- const rel = path56.relative(manifestDir, absPath);
11518
+ const rel = path57.relative(manifestDir, absPath);
11498
11519
  const text = readForAudit(absPath, `${item.id}: ${rel}`, failures);
11499
11520
  if (text === null) continue;
11500
11521
  failures.push(...checkRequiredFrontmatter(text, `${item.id}: ${rel}`));
@@ -11510,7 +11531,7 @@ function auditShippedFiles(manifestDir, items) {
11510
11531
  failures.push(`${item.id}: missing command file ${item.path}`);
11511
11532
  continue;
11512
11533
  }
11513
- const rel = path56.relative(manifestDir, absPath);
11534
+ const rel = path57.relative(manifestDir, absPath);
11514
11535
  const text = readForAudit(absPath, `${item.id}: ${rel}`, failures);
11515
11536
  if (text === null) continue;
11516
11537
  failures.push(...checkRequiredFrontmatter(text, `${item.id}: ${rel}`));
@@ -11568,7 +11589,7 @@ function walkAllFiles(dir, fn, failures) {
11568
11589
  }
11569
11590
  for (const entry of entries) {
11570
11591
  if (entry.name === ".DS_Store") continue;
11571
- const full = path56.join(dir, entry.name);
11592
+ const full = path57.join(dir, entry.name);
11572
11593
  if (entry.isDirectory()) walkAllFiles(full, fn, failures);
11573
11594
  else fn(full);
11574
11595
  }
@@ -11592,12 +11613,12 @@ function auditDiskOrphans(manifestDir, items) {
11592
11613
  const failures = [];
11593
11614
  const claimedPaths = items.filter((item) => item.path && isSafeCatalogPath(item.path)).map((item) => item.path.replace(/\\/g, "/"));
11594
11615
  for (const dir of CONTENT_ROOTS) {
11595
- const abs = path56.join(manifestDir, dir);
11616
+ const abs = path57.join(manifestDir, dir);
11596
11617
  if (!fs42.existsSync(abs)) continue;
11597
11618
  walkAllFiles(
11598
11619
  abs,
11599
11620
  (file) => {
11600
- const relPath = path56.relative(manifestDir, file).replace(/\\/g, "/");
11621
+ const relPath = path57.relative(manifestDir, file).replace(/\\/g, "/");
11601
11622
  if (!isPathClaimed(relPath, claimedPaths)) {
11602
11623
  failures.push(`orphaned on disk, claimed by no manifest item: ${relPath}`);
11603
11624
  }
@@ -11618,7 +11639,7 @@ function walkMd(dir, fn, failures) {
11618
11639
  return;
11619
11640
  }
11620
11641
  for (const entry of entries) {
11621
- const full = path56.join(dir, entry.name);
11642
+ const full = path57.join(dir, entry.name);
11622
11643
  if (entry.isDirectory()) walkMd(full, fn, failures);
11623
11644
  else if (entry.name.endsWith(".md")) fn(full);
11624
11645
  }
@@ -11628,13 +11649,13 @@ function auditMarkdownContent(manifestDir, items) {
11628
11649
  const pathSourceMap = buildItemPathSourceMap(items);
11629
11650
  const pathTypeMap = buildItemPathTypeMap(items);
11630
11651
  for (const dir of ["skills", "agents", "templates", "commands", "sources"]) {
11631
- const abs = path56.join(manifestDir, dir);
11652
+ const abs = path57.join(manifestDir, dir);
11632
11653
  if (!fs42.existsSync(abs)) continue;
11633
11654
  const dirType = DIR_ITEM_TYPE[dir] ?? "";
11634
11655
  walkMd(
11635
11656
  abs,
11636
11657
  (file) => {
11637
- const rel = path56.relative(manifestDir, file);
11658
+ const rel = path57.relative(manifestDir, file);
11638
11659
  const text = readForAudit(file, rel, failures);
11639
11660
  if (text === null) return;
11640
11661
  const norm = rel.replace(/\\/g, "/");
@@ -11682,8 +11703,8 @@ async function runValidateCatalog(manifestPath2) {
11682
11703
  process.exitCode = 1;
11683
11704
  return;
11684
11705
  }
11685
- const abs = path57.resolve(process.cwd(), manifestPath2);
11686
- const manifestDir = path57.dirname(abs);
11706
+ const abs = path58.resolve(process.cwd(), manifestPath2);
11707
+ const manifestDir = path58.dirname(abs);
11687
11708
  const data = await readJson(abs);
11688
11709
  if (!data?.items) {
11689
11710
  error(`Could not read catalog manifest at ${abs}`);
@@ -11704,7 +11725,7 @@ init_logger();
11704
11725
 
11705
11726
  // src/workspace/worktree/add.ts
11706
11727
  init_fs();
11707
- import path64 from "node:path";
11728
+ import path65 from "node:path";
11708
11729
  import fs46 from "fs-extra";
11709
11730
 
11710
11731
  // src/workspace/worktree/git-worktree.ts
@@ -11825,12 +11846,12 @@ async function hasUnpushedWork(cwd) {
11825
11846
  }
11826
11847
 
11827
11848
  // src/workspace/worktree/hydrate.ts
11828
- import path60 from "node:path";
11849
+ import path61 from "node:path";
11829
11850
  import fs44 from "fs-extra";
11830
11851
 
11831
11852
  // src/workspace/worktree/cow-copy.ts
11832
11853
  init_exec();
11833
- import path58 from "node:path";
11854
+ import path59 from "node:path";
11834
11855
  import fs43 from "fs-extra";
11835
11856
  async function getMacFilesystemType(dir) {
11836
11857
  const df = await runCommand("df", ["-P", dir]);
@@ -11865,11 +11886,11 @@ async function cowCopyDir(src, dest) {
11865
11886
  const srcExists = await fs43.pathExists(src);
11866
11887
  if (!srcExists)
11867
11888
  return { strategy: "unsupported", attempted: false, ok: false, error: "source missing" };
11868
- const strategy = await detectCowStrategy(path58.dirname(dest));
11889
+ const strategy = await detectCowStrategy(path59.dirname(dest));
11869
11890
  if (strategy === "unsupported" || strategy === "unknown-platform") {
11870
11891
  return { strategy, attempted: false, ok: false };
11871
11892
  }
11872
- await fs43.ensureDir(path58.dirname(dest));
11893
+ await fs43.ensureDir(path59.dirname(dest));
11873
11894
  const args = strategy === "darwin-clonefile" ? ["-c", "-R", src, dest] : ["-a", "--reflink=always", src, dest];
11874
11895
  const result = await runCommand("cp", args);
11875
11896
  return {
@@ -11883,7 +11904,7 @@ async function cowCopyDir(src, dest) {
11883
11904
  // src/workspace/worktree/install.ts
11884
11905
  init_exec();
11885
11906
  import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
11886
- import path59 from "node:path";
11907
+ import path60 from "node:path";
11887
11908
  import fg6 from "fast-glob";
11888
11909
  function detectInstallPlan(signals) {
11889
11910
  if (signals.hasYarnLock) {
@@ -11904,10 +11925,10 @@ function detectInstallPlan(signals) {
11904
11925
  return { manager: "none" };
11905
11926
  }
11906
11927
  async function computeLockfileSignals(dir) {
11907
- const hasYarnLock = existsSync3(path59.join(dir, "yarn.lock"));
11908
- const hasPnpmLock = existsSync3(path59.join(dir, "pnpm-lock.yaml"));
11909
- const hasPackageLock = existsSync3(path59.join(dir, "package-lock.json"));
11910
- const hasComposerLock = existsSync3(path59.join(dir, "composer.lock"));
11928
+ const hasYarnLock = existsSync3(path60.join(dir, "yarn.lock"));
11929
+ const hasPnpmLock = existsSync3(path60.join(dir, "pnpm-lock.yaml"));
11930
+ const hasPackageLock = existsSync3(path60.join(dir, "package-lock.json"));
11931
+ const hasComposerLock = existsSync3(path60.join(dir, "composer.lock"));
11911
11932
  const dotnetFiles = await fg6(["**/*.csproj", "**/*.sln"], {
11912
11933
  cwd: dir,
11913
11934
  onlyFiles: true,
@@ -11921,7 +11942,7 @@ async function computeLockfileSignals(dir) {
11921
11942
  });
11922
11943
  const hasDotnetProject = dotnetFiles.length > 0;
11923
11944
  let hasPackageManagerField = false;
11924
- const pkgPath = path59.join(dir, "package.json");
11945
+ const pkgPath = path60.join(dir, "package.json");
11925
11946
  if (existsSync3(pkgPath)) {
11926
11947
  try {
11927
11948
  const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
@@ -11964,7 +11985,7 @@ function missingYarnrcEnvVars(content2, env) {
11964
11985
  return findRequiredYarnrcEnvVars(content2).filter((name) => env[name] === void 0);
11965
11986
  }
11966
11987
  function missingYarnrcEnvVarsInDir(dir, env) {
11967
- const yarnrcPath = path59.join(dir, ".yarnrc.yml");
11988
+ const yarnrcPath = path60.join(dir, ".yarnrc.yml");
11968
11989
  if (!existsSync3(yarnrcPath)) return [];
11969
11990
  try {
11970
11991
  return missingYarnrcEnvVars(readFileSync3(yarnrcPath, "utf8"), env);
@@ -12054,8 +12075,8 @@ async function hydrateMember(mainMemberPath, worktreeMemberPath, opts = {}) {
12054
12075
  const start = Date.now();
12055
12076
  const targets = [];
12056
12077
  for (const target of HYDRATION_TARGETS) {
12057
- const src = path60.join(mainMemberPath, target);
12058
- const dest = path60.join(worktreeMemberPath, target);
12078
+ const src = path61.join(mainMemberPath, target);
12079
+ const dest = path61.join(worktreeMemberPath, target);
12059
12080
  if (!await fs44.pathExists(src)) continue;
12060
12081
  const destExists = await fs44.pathExists(dest);
12061
12082
  if (!opts.force && destExists) continue;
@@ -12073,11 +12094,11 @@ async function hydrateMember(mainMemberPath, worktreeMemberPath, opts = {}) {
12073
12094
 
12074
12095
  // src/workspace/worktree/local-files.ts
12075
12096
  init_exec();
12076
- import path61 from "node:path";
12097
+ import path62 from "node:path";
12077
12098
  import fs45 from "fs-extra";
12078
12099
  var MACHINE_LOCAL_FILES = [
12079
12100
  "docker-compose.local.yml",
12080
- path61.join(".claude", "settings.local.json")
12101
+ path62.join(".claude", "settings.local.json")
12081
12102
  ];
12082
12103
  async function isTracked(repoRoot, relFile) {
12083
12104
  const gitPath = relFile.replace(/\\/g, "/");
@@ -12087,11 +12108,11 @@ async function isTracked(repoRoot, relFile) {
12087
12108
  async function copyMachineLocalFiles(mainMemberPath, worktreeMemberPath) {
12088
12109
  const copied = [];
12089
12110
  for (const rel of MACHINE_LOCAL_FILES) {
12090
- const src = path61.join(mainMemberPath, rel);
12111
+ const src = path62.join(mainMemberPath, rel);
12091
12112
  if (!await fs45.pathExists(src)) continue;
12092
12113
  if (await isTracked(mainMemberPath, rel)) continue;
12093
- const dest = path61.join(worktreeMemberPath, rel);
12094
- await fs45.ensureDir(path61.dirname(dest));
12114
+ const dest = path62.join(worktreeMemberPath, rel);
12115
+ await fs45.ensureDir(path62.dirname(dest));
12095
12116
  await fs45.copy(src, dest, { overwrite: true });
12096
12117
  copied.push(rel);
12097
12118
  }
@@ -12100,15 +12121,15 @@ async function copyMachineLocalFiles(mainMemberPath, worktreeMemberPath) {
12100
12121
 
12101
12122
  // src/workspace/worktree/root.ts
12102
12123
  import { existsSync as existsSync4 } from "node:fs";
12103
- import path62 from "node:path";
12124
+ import path63 from "node:path";
12104
12125
  async function resolveWorkspaceForWorktree(start) {
12105
12126
  const rootInfo = await resolveRoots(start);
12106
12127
  if (!rootInfo.isGitRepo) {
12107
12128
  return { ok: false, reason: "Not inside a git repository." };
12108
12129
  }
12109
12130
  const workspaceRoot = rootInfo.mainRoot;
12110
- const hasYaml = existsSync4(path62.join(workspaceRoot, WORKSPACE_FILE));
12111
- const hasManifest = existsSync4(path62.join(workspaceRoot, REPOS_MANIFEST_FILE));
12131
+ const hasYaml = existsSync4(path63.join(workspaceRoot, WORKSPACE_FILE));
12132
+ const hasManifest = existsSync4(path63.join(workspaceRoot, REPOS_MANIFEST_FILE));
12112
12133
  if (!hasYaml && !hasManifest) {
12113
12134
  return {
12114
12135
  ok: false,
@@ -12134,10 +12155,10 @@ function selectMembers(members, only) {
12134
12155
 
12135
12156
  // src/workspace/worktree/state.ts
12136
12157
  init_fs();
12137
- import path63 from "node:path";
12158
+ import path64 from "node:path";
12138
12159
  var WORKTREE_STATE_FILE = ".haus-worktree.json";
12139
12160
  function worktreesDir(mainRoot) {
12140
- return path63.join(mainRoot, ".claude", "worktrees");
12161
+ return path64.join(mainRoot, ".claude", "worktrees");
12141
12162
  }
12142
12163
  function isValidWorktreeSlug(slug) {
12143
12164
  if (slug === "" || slug === "." || slug === "..") return false;
@@ -12150,15 +12171,15 @@ function worktreePath(mainRoot, slug) {
12150
12171
  `Invalid worktree slug ${JSON.stringify(slug)} \u2014 slugs may only contain letters, digits, ".", "_", "-" (no path separators, and not "." or "..").`
12151
12172
  );
12152
12173
  }
12153
- return path63.join(worktreesDir(mainRoot), slug);
12174
+ return path64.join(worktreesDir(mainRoot), slug);
12154
12175
  }
12155
12176
  async function readWorktreeState(worktreeRoot) {
12156
- const state = await readJson(path63.join(worktreeRoot, WORKTREE_STATE_FILE));
12177
+ const state = await readJson(path64.join(worktreeRoot, WORKTREE_STATE_FILE));
12157
12178
  if (state == null || typeof state !== "object" || !Array.isArray(state.members)) return void 0;
12158
12179
  return state;
12159
12180
  }
12160
12181
  async function writeWorktreeState(worktreeRoot, state) {
12161
- await writeJson(path63.join(worktreeRoot, WORKTREE_STATE_FILE), state);
12182
+ await writeJson(path64.join(worktreeRoot, WORKTREE_STATE_FILE), state);
12162
12183
  }
12163
12184
 
12164
12185
  // src/workspace/worktree/add.ts
@@ -12192,7 +12213,7 @@ async function runAdd(opts) {
12192
12213
  const memberResults = [];
12193
12214
  for (const member of selected) {
12194
12215
  const start = Date.now();
12195
- const memberWorktreePath = path64.join(wsWorktreePath, member.folder);
12216
+ const memberWorktreePath = path65.join(wsWorktreePath, member.folder);
12196
12217
  const outcome = await addWorktree(member.absPath, memberWorktreePath, branch, {
12197
12218
  preferDefaultBranchFrom: true
12198
12219
  });
@@ -12222,7 +12243,7 @@ async function runAdd(opts) {
12222
12243
  await mapWithConcurrency(okResults, async (result) => {
12223
12244
  const member = selected.find((m) => m.id === result.member);
12224
12245
  if (!member) return;
12225
- const memberWorktreePath = path64.join(wsWorktreePath, member.folder);
12246
+ const memberWorktreePath = path65.join(wsWorktreePath, member.folder);
12226
12247
  const hydrationStart = Date.now();
12227
12248
  if (opts.hydrate !== false) {
12228
12249
  result.hydration = await hydrateMember(member.absPath, memberWorktreePath);
@@ -12289,25 +12310,25 @@ async function runDryRun(args) {
12289
12310
 
12290
12311
  // src/workspace/worktree/doctor.ts
12291
12312
  import { existsSync as existsSync6 } from "node:fs";
12292
- import path66 from "node:path";
12313
+ import path67 from "node:path";
12293
12314
 
12294
12315
  // src/workspace/worktree/orphans.ts
12295
12316
  import { existsSync as existsSync5 } from "node:fs";
12296
- import path65 from "node:path";
12317
+ import path66 from "node:path";
12297
12318
  var WORKSPACE_REPO_ID = "(workspace)";
12298
12319
  async function findOrphanedRegistrations(repos, wsWorktreesDir) {
12299
12320
  const orphans = [];
12300
12321
  for (const repo of repos) {
12301
12322
  const entries = await listWorktrees(repo.absPath);
12302
12323
  for (const entry of entries) {
12303
- const entryPath = path65.normalize(entry.path);
12304
- const marker = `${path65.sep}.claude${path65.sep}worktrees${path65.sep}`;
12324
+ const entryPath = path66.normalize(entry.path);
12325
+ const marker = `${path66.sep}.claude${path66.sep}worktrees${path66.sep}`;
12305
12326
  const idx = entryPath.indexOf(marker);
12306
12327
  if (idx === -1) continue;
12307
12328
  const rest = entryPath.slice(idx + marker.length);
12308
- const slug = rest.split(path65.sep)[0];
12329
+ const slug = rest.split(path66.sep)[0];
12309
12330
  if (!slug) continue;
12310
- if (existsSync5(path65.join(wsWorktreesDir, slug))) continue;
12331
+ if (existsSync5(path66.join(wsWorktreesDir, slug))) continue;
12311
12332
  orphans.push({
12312
12333
  repoId: repo.id,
12313
12334
  ownerRepo: repo.absPath,
@@ -12322,7 +12343,7 @@ async function findOrphanedRegistrations(repos, wsWorktreesDir) {
12322
12343
  // src/workspace/worktree/doctor.ts
12323
12344
  var HYDRATION_MARKERS = ["node_modules", "vendor", "obj", "bin"];
12324
12345
  function looksHydrated(memberPath) {
12325
- return HYDRATION_MARKERS.some((marker) => existsSync6(path66.join(memberPath, marker)));
12346
+ return HYDRATION_MARKERS.some((marker) => existsSync6(path67.join(memberPath, marker)));
12326
12347
  }
12327
12348
  async function runDoctor2() {
12328
12349
  const rootInfo = await resolveRoots();
@@ -12338,7 +12359,7 @@ async function runDoctor2() {
12338
12359
  };
12339
12360
  }
12340
12361
  const workspaceRoot = rootInfo.mainRoot;
12341
- const isWorkspace = existsSync6(path66.join(workspaceRoot, WORKSPACE_FILE)) || existsSync6(path66.join(workspaceRoot, REPOS_MANIFEST_FILE));
12362
+ const isWorkspace = existsSync6(path67.join(workspaceRoot, WORKSPACE_FILE)) || existsSync6(path67.join(workspaceRoot, REPOS_MANIFEST_FILE));
12342
12363
  if (!isWorkspace) {
12343
12364
  return {
12344
12365
  isWorkspace: false,
@@ -12353,7 +12374,7 @@ async function runDoctor2() {
12353
12374
  };
12354
12375
  }
12355
12376
  const wsWorktreesDir = worktreesDir(workspaceRoot);
12356
- const underWorktreesDir = rootInfo.repoRoot.startsWith(`${wsWorktreesDir}${path66.sep}`);
12377
+ const underWorktreesDir = rootInfo.repoRoot.startsWith(`${wsWorktreesDir}${path67.sep}`);
12357
12378
  const inWorkspaceWorktree = rootInfo.isLinkedWorktree && underWorktreesDir;
12358
12379
  const workspaceConfig = await readWorkspaceConfig(workspaceRoot);
12359
12380
  const autoHydrate = workspaceConfig?.worktree?.autoHydrate === true;
@@ -12364,10 +12385,10 @@ async function runDoctor2() {
12364
12385
  let slug;
12365
12386
  if (inWorkspaceWorktree) {
12366
12387
  const wtPath = rootInfo.repoRoot;
12367
- slug = rootInfo.worktreeName ?? path66.basename(wtPath);
12388
+ slug = rootInfo.worktreeName ?? path67.basename(wtPath);
12368
12389
  const state = await readWorktreeState(wtPath);
12369
12390
  for (const member of members) {
12370
- const memberPath = path66.join(wtPath, member.folder);
12391
+ const memberPath = path67.join(wtPath, member.folder);
12371
12392
  const materialized = existsSync6(memberPath);
12372
12393
  const expectedBranch = state?.members.find((m) => m.folder === member.folder)?.branch ?? state?.branch;
12373
12394
  const actualBranch = materialized ? await currentBranch(memberPath) : void 0;
@@ -12407,7 +12428,7 @@ async function runDoctor2() {
12407
12428
  wsWorktreesDir
12408
12429
  );
12409
12430
  const orphans = orphanScan.map(
12410
- (o) => `${o.repoId}: worktree registered at ${o.registeredPath} but ${path66.join(wsWorktreesDir, o.slug)} no longer exists.`
12431
+ (o) => `${o.repoId}: worktree registered at ${o.registeredPath} but ${path67.join(wsWorktreesDir, o.slug)} no longer exists.`
12411
12432
  );
12412
12433
  if (orphans.length > 0) {
12413
12434
  problems.push(...orphans);
@@ -12428,7 +12449,7 @@ async function runDoctor2() {
12428
12449
  // src/workspace/worktree/hydrate-command.ts
12429
12450
  init_fs();
12430
12451
  import { existsSync as existsSync7 } from "node:fs";
12431
- import path67 from "node:path";
12452
+ import path68 from "node:path";
12432
12453
  async function runHydrateWorktree(opts) {
12433
12454
  const rootInfo = await resolveRoots();
12434
12455
  if (!rootInfo.isGitRepo || !rootInfo.isLinkedWorktree) {
@@ -12439,7 +12460,7 @@ async function runHydrateWorktree(opts) {
12439
12460
  }
12440
12461
  const workspaceRoot = rootInfo.mainRoot;
12441
12462
  const wtPath = rootInfo.repoRoot;
12442
- if (!wtPath.startsWith(`${worktreesDir(workspaceRoot)}${path67.sep}`)) {
12463
+ if (!wtPath.startsWith(`${worktreesDir(workspaceRoot)}${path68.sep}`)) {
12443
12464
  return {
12444
12465
  ok: false,
12445
12466
  error: "Current worktree is not one of this workspace's .claude/worktrees/<slug> checkouts."
@@ -12451,13 +12472,13 @@ async function runHydrateWorktree(opts) {
12451
12472
  return { ok: false, error: `Unknown --only repo(s): ${unknown.join(", ")}` };
12452
12473
  }
12453
12474
  const state = await readWorktreeState(wtPath);
12454
- const slug = state?.slug ?? path67.basename(wtPath);
12475
+ const slug = state?.slug ?? path68.basename(wtPath);
12455
12476
  const detectedBranch = await currentBranch(wtPath);
12456
12477
  const targetBranch = state?.branch ?? (detectedBranch !== "HEAD" ? detectedBranch : void 0) ?? slug;
12457
12478
  if (opts.dryRun) {
12458
12479
  const results2 = await Promise.all(
12459
12480
  selected.map(async (member) => {
12460
- const memberPath = path67.join(wtPath, member.folder);
12481
+ const memberPath = path68.join(wtPath, member.folder);
12461
12482
  if (existsSync7(memberPath)) {
12462
12483
  return { member: member.id, folder: member.folder, status: "planned" };
12463
12484
  }
@@ -12478,7 +12499,7 @@ async function runHydrateWorktree(opts) {
12478
12499
  const results = await mapWithConcurrency(
12479
12500
  selected,
12480
12501
  async (member) => {
12481
- const memberPath = path67.join(wtPath, member.folder);
12502
+ const memberPath = path68.join(wtPath, member.folder);
12482
12503
  let branchAction;
12483
12504
  if (!existsSync7(memberPath)) {
12484
12505
  if (!opts.materialize) {
@@ -12541,7 +12562,7 @@ async function runHydrateWorktree(opts) {
12541
12562
  }
12542
12563
 
12543
12564
  // src/workspace/worktree/list.ts
12544
- import path68 from "node:path";
12565
+ import path69 from "node:path";
12545
12566
  import fs47 from "fs-extra";
12546
12567
  async function runList2() {
12547
12568
  const resolved = await resolveWorkspaceForWorktree();
@@ -12554,12 +12575,12 @@ async function runList2() {
12554
12575
  const configuredMembers = await readMembers(rootInfo);
12555
12576
  const worktrees = [];
12556
12577
  for (const slug of slugs) {
12557
- const wtPath = path68.join(dir, slug);
12578
+ const wtPath = path69.join(dir, slug);
12558
12579
  const state = await readWorktreeState(wtPath);
12559
12580
  const members = [];
12560
12581
  const source = state?.members ?? configuredMembers.map((m) => ({ id: m.id, folder: m.folder }));
12561
12582
  for (const m of source) {
12562
- const memberPath = path68.join(wtPath, m.folder);
12583
+ const memberPath = path69.join(wtPath, m.folder);
12563
12584
  const materialized = await fs47.pathExists(memberPath);
12564
12585
  const actualBranch = materialized ? await currentBranch(memberPath) : void 0;
12565
12586
  members.push({
@@ -12621,7 +12642,7 @@ async function runPrune2(opts) {
12621
12642
  }
12622
12643
 
12623
12644
  // src/workspace/worktree/remove.ts
12624
- import path69 from "node:path";
12645
+ import path70 from "node:path";
12625
12646
  import fs48 from "fs-extra";
12626
12647
  async function runRemove2(opts) {
12627
12648
  const resolved = await resolveWorkspaceForWorktree();
@@ -12648,7 +12669,7 @@ async function runRemove2(opts) {
12648
12669
  targets.push({
12649
12670
  repo: configMember?.id ?? stateMember.id,
12650
12671
  ownerRepo,
12651
- path: path69.join(wsWorktreePath, stateMember.folder)
12672
+ path: path70.join(wsWorktreePath, stateMember.folder)
12652
12673
  });
12653
12674
  }
12654
12675
  const existingTargets = [];
@@ -12986,7 +13007,7 @@ async function runWorktree(action, opts = {}) {
12986
13007
 
12987
13008
  // src/commands/workspace.ts
12988
13009
  import { existsSync as existsSync11, statSync as statSync3 } from "node:fs";
12989
- import path78 from "node:path";
13010
+ import path79 from "node:path";
12990
13011
  init_fs();
12991
13012
  init_logger();
12992
13013
 
@@ -13045,7 +13066,7 @@ ${summaries.map(
13045
13066
 
13046
13067
  // src/commands/workspace/doctor.ts
13047
13068
  import { existsSync as existsSync8 } from "node:fs";
13048
- import path71 from "node:path";
13069
+ import path72 from "node:path";
13049
13070
  init_logger();
13050
13071
  init_paths();
13051
13072
 
@@ -13053,14 +13074,14 @@ init_paths();
13053
13074
  init_fs();
13054
13075
  init_paths();
13055
13076
  import { readFileSync as readFileSync4 } from "node:fs";
13056
- import path70 from "node:path";
13077
+ import path71 from "node:path";
13057
13078
  var MANIFEST_FILE = "workspace.manifest.json";
13058
13079
  function manifestPath(workspaceRoot) {
13059
13080
  return hausPath(workspaceRoot, MANIFEST_FILE);
13060
13081
  }
13061
13082
  function hausVersion() {
13062
13083
  try {
13063
- const pkg = JSON.parse(readFileSync4(path70.join(packageRoot(), "package.json"), "utf8"));
13084
+ const pkg = JSON.parse(readFileSync4(path71.join(packageRoot(), "package.json"), "utf8"));
13064
13085
  return pkg.version ?? "0.0.0";
13065
13086
  } catch {
13066
13087
  return "0.0.0";
@@ -13152,7 +13173,7 @@ async function runWorkspaceDoctor(workspaceRoot, opts = {}) {
13152
13173
  const manifestByName = new Map(manifest.repos.map((r) => [r.name, r]));
13153
13174
  const catalogRefByRepo = [];
13154
13175
  for (const repo of config.repos) {
13155
- const repoRoot = path71.resolve(workspaceRoot, repo.path);
13176
+ const repoRoot = path72.resolve(workspaceRoot, repo.path);
13156
13177
  const entry = manifestByName.get(repo.name);
13157
13178
  if (!entry) {
13158
13179
  flag({
@@ -13225,7 +13246,7 @@ async function runWorkspaceDoctor(workspaceRoot, opts = {}) {
13225
13246
  });
13226
13247
  continue;
13227
13248
  }
13228
- if (!existsSync8(path71.join(workspaceRoot, entry.path))) {
13249
+ if (!existsSync8(path72.join(workspaceRoot, entry.path))) {
13229
13250
  flag({
13230
13251
  repo: entry.repo,
13231
13252
  kind: "missing-linked-context-copy",
@@ -13271,17 +13292,17 @@ function emit(args) {
13271
13292
  init_logger();
13272
13293
 
13273
13294
  // src/workspace/link-context/link.ts
13274
- import path74 from "node:path";
13295
+ import path75 from "node:path";
13275
13296
  import fs51 from "fs-extra";
13276
13297
  init_fs();
13277
13298
  init_logger();
13278
13299
 
13279
13300
  // src/workspace/link-context/plan.ts
13280
- import path73 from "node:path";
13301
+ import path74 from "node:path";
13281
13302
  import fs50 from "fs-extra";
13282
13303
 
13283
13304
  // src/workspace/link-context/scan-source.ts
13284
- import path72 from "node:path";
13305
+ import path73 from "node:path";
13285
13306
  import fs49 from "fs-extra";
13286
13307
  var ASSET_SUBDIR = {
13287
13308
  skill: "skills",
@@ -13290,36 +13311,36 @@ var ASSET_SUBDIR = {
13290
13311
  };
13291
13312
  async function hasLinkableContext(member) {
13292
13313
  for (const subdir of Object.values(ASSET_SUBDIR)) {
13293
- if (await fs49.pathExists(path72.join(member.absPath, ".claude", subdir))) return true;
13314
+ if (await fs49.pathExists(path73.join(member.absPath, ".claude", subdir))) return true;
13294
13315
  }
13295
13316
  return false;
13296
13317
  }
13297
13318
  async function collectSkills(member) {
13298
- const dir = path72.join(member.absPath, ".claude", "skills");
13319
+ const dir = path73.join(member.absPath, ".claude", "skills");
13299
13320
  if (!await fs49.pathExists(dir)) return [];
13300
13321
  const names = await fs49.readdir(dir);
13301
13322
  const out = [];
13302
13323
  for (const name of [...names].sort()) {
13303
- const abs = path72.join(dir, name);
13324
+ const abs = path73.join(dir, name);
13304
13325
  const stat = await fs49.lstat(abs);
13305
13326
  if (!stat.isDirectory()) continue;
13306
- if (!await fs49.pathExists(path72.join(abs, "SKILL.md"))) continue;
13307
- out.push({ type: "skill", name, sourceRelPath: path72.posix.join(".claude", "skills", name) });
13327
+ if (!await fs49.pathExists(path73.join(abs, "SKILL.md"))) continue;
13328
+ out.push({ type: "skill", name, sourceRelPath: path73.posix.join(".claude", "skills", name) });
13308
13329
  }
13309
13330
  return out;
13310
13331
  }
13311
13332
  async function collectFlatMarkdown(member, type) {
13312
13333
  const subdir = ASSET_SUBDIR[type];
13313
- const dir = path72.join(member.absPath, ".claude", subdir);
13334
+ const dir = path73.join(member.absPath, ".claude", subdir);
13314
13335
  if (!await fs49.pathExists(dir)) return [];
13315
13336
  const names = await fs49.readdir(dir);
13316
13337
  const out = [];
13317
13338
  for (const fileName of [...names].sort()) {
13318
13339
  if (!fileName.endsWith(".md")) continue;
13319
- const abs = path72.join(dir, fileName);
13340
+ const abs = path73.join(dir, fileName);
13320
13341
  if (!(await fs49.lstat(abs)).isFile()) continue;
13321
13342
  const name = fileName.slice(0, -".md".length);
13322
- out.push({ type, name, sourceRelPath: path72.posix.join(".claude", subdir, fileName) });
13343
+ out.push({ type, name, sourceRelPath: path73.posix.join(".claude", subdir, fileName) });
13323
13344
  }
13324
13345
  return out;
13325
13346
  }
@@ -13334,9 +13355,9 @@ async function collectSourceAssets(member) {
13334
13355
 
13335
13356
  // src/workspace/link-context/plan.ts
13336
13357
  function repoFolderPrefix(member) {
13337
- const base = path73.posix.basename(member.folder);
13358
+ const base = path74.posix.basename(member.folder);
13338
13359
  if (base && base !== "." && base !== "/") return base;
13339
- const fromAbs = path73.basename(member.absPath);
13360
+ const fromAbs = path74.basename(member.absPath);
13340
13361
  return fromAbs || member.id;
13341
13362
  }
13342
13363
  function assetKey(type, destKey) {
@@ -13393,7 +13414,7 @@ async function buildLinkPlan(members) {
13393
13414
  name: asset.name,
13394
13415
  destKey,
13395
13416
  sourceRelPath: asset.sourceRelPath,
13396
- sourceAbsPath: path73.join(member.absPath, asset.sourceRelPath),
13417
+ sourceAbsPath: path74.join(member.absPath, asset.sourceRelPath),
13397
13418
  sourceHash
13398
13419
  });
13399
13420
  }
@@ -13404,7 +13425,7 @@ async function buildLinkPlan(members) {
13404
13425
  function destRelPathFor(type, destKey) {
13405
13426
  const subdir = `${type}s`;
13406
13427
  const leaf = type === "skill" ? destKey : `${destKey}.md`;
13407
- return path74.posix.join(".claude", subdir, leaf);
13428
+ return path75.posix.join(".claude", subdir, leaf);
13408
13429
  }
13409
13430
  async function runLinkContext(workspaceRoot, opts = {}) {
13410
13431
  const write = resolveWriteMode(opts, "preview-by-default").kind === "write";
@@ -13430,9 +13451,9 @@ async function runLinkContext(workspaceRoot, opts = {}) {
13430
13451
  const linkedAt = (/* @__PURE__ */ new Date()).toISOString();
13431
13452
  for (const entry of plan.entries) {
13432
13453
  const destRel = destRelPathFor(entry.type, entry.destKey);
13433
- const destAbs = path74.join(workspaceRoot, destRel);
13454
+ const destAbs = path75.join(workspaceRoot, destRel);
13434
13455
  if (write) {
13435
- await fs51.ensureDir(path74.dirname(destAbs));
13456
+ await fs51.ensureDir(path75.dirname(destAbs));
13436
13457
  if (await fs51.pathExists(destAbs)) await fs51.remove(destAbs);
13437
13458
  await fs51.copy(entry.sourceAbsPath, destAbs, {
13438
13459
  overwrite: true,
@@ -13521,10 +13542,10 @@ async function runWorkspaceLinkContext(workspaceRoot, opts = {}) {
13521
13542
 
13522
13543
  // src/commands/workspace/setup.ts
13523
13544
  import { existsSync as existsSync9, statSync } from "node:fs";
13524
- import path76 from "node:path";
13545
+ import path77 from "node:path";
13525
13546
 
13526
13547
  // src/claude/write-workspace-claude-md.ts
13527
- import path75 from "node:path";
13548
+ import path76 from "node:path";
13528
13549
  import fs52 from "fs-extra";
13529
13550
  init_fs();
13530
13551
  init_logger();
@@ -13547,7 +13568,7 @@ ${WORKSPACE_BLOCK_END}`;
13547
13568
  async function writeWorkspaceClaudeMd(workspaceRoot, opts) {
13548
13569
  const block = buildWorkspaceImportBlock(opts.client, opts.members);
13549
13570
  const dryRun = opts.dryRun ?? false;
13550
- const filePath = opts.collision ? hausPath(workspaceRoot, "WORKSPACE.md") : path75.join(workspaceRoot, "CLAUDE.md");
13571
+ const filePath = opts.collision ? hausPath(workspaceRoot, "WORKSPACE.md") : path76.join(workspaceRoot, "CLAUDE.md");
13551
13572
  const say = opts.quiet ? () => {
13552
13573
  } : log;
13553
13574
  const existed = await fs52.pathExists(filePath);
@@ -13580,20 +13601,20 @@ async function writeWorkspaceClaudeMd(workspaceRoot, opts) {
13580
13601
  init_fs();
13581
13602
  init_logger();
13582
13603
  function resolveWorkspaceRoot(start = process.cwd()) {
13583
- let dir = path76.resolve(start);
13604
+ let dir = path77.resolve(start);
13584
13605
  for (; ; ) {
13585
- if (existsSync9(path76.join(dir, WORKSPACE_FILE))) return dir;
13586
- const parent = path76.dirname(dir);
13587
- if (parent === dir) return path76.resolve(start);
13606
+ if (existsSync9(path77.join(dir, WORKSPACE_FILE))) return dir;
13607
+ const parent = path77.dirname(dir);
13608
+ if (parent === dir) return path77.resolve(start);
13588
13609
  dir = parent;
13589
13610
  }
13590
13611
  }
13591
13612
  function isRootRepo(workspaceRoot, repoPath) {
13592
- return path76.resolve(workspaceRoot, repoPath) === path76.resolve(workspaceRoot);
13613
+ return path77.resolve(workspaceRoot, repoPath) === path77.resolve(workspaceRoot);
13593
13614
  }
13594
13615
  async function runWorkspaceSetup(workspaceRoot, options = {}) {
13595
13616
  const apply = options.write ?? false;
13596
- const configText = await readText(path76.join(workspaceRoot, WORKSPACE_FILE));
13617
+ const configText = await readText(path77.join(workspaceRoot, WORKSPACE_FILE));
13597
13618
  if (!configText) {
13598
13619
  error(`Missing ${WORKSPACE_FILE}. Run \`haus workspace discover\` or \`init\` first.`);
13599
13620
  process.exitCode = 1;
@@ -13620,7 +13641,7 @@ async function runWorkspaceSetup(workspaceRoot, options = {}) {
13620
13641
  const statuses = [];
13621
13642
  const aggregateInputs = [];
13622
13643
  for (const repo of repos) {
13623
- const repoRoot = path76.resolve(workspaceRoot, repo.path);
13644
+ const repoRoot = path77.resolve(workspaceRoot, repo.path);
13624
13645
  say(`
13625
13646
  \u2192 ${repo.name} (${repo.path})`);
13626
13647
  try {
@@ -13710,7 +13731,7 @@ async function runWorkspaceSetup(workspaceRoot, options = {}) {
13710
13731
  const status = statusByName.get(repo.name);
13711
13732
  const role = repo.role ?? status?.roles?.[0] ?? "auto";
13712
13733
  if (status?.status === "ok") {
13713
- const lock = await checkLock(path76.resolve(workspaceRoot, repo.path));
13734
+ const lock = await checkLock(path77.resolve(workspaceRoot, repo.path));
13714
13735
  manifestRepos.push({
13715
13736
  name: repo.name,
13716
13737
  path: repo.path,
@@ -13766,7 +13787,7 @@ async function runWorkspaceSetup(workspaceRoot, options = {}) {
13766
13787
  try {
13767
13788
  const linkResult = await runLinkContext(workspaceRoot, { write: true, json: options.json });
13768
13789
  if (linkResult.ok) {
13769
- written.push(...linkResult.added.map((rel) => path76.join(workspaceRoot, rel)));
13790
+ written.push(...linkResult.added.map((rel) => path77.join(workspaceRoot, rel)));
13770
13791
  if (!options.json) {
13771
13792
  say(
13772
13793
  `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` : ""}.`
@@ -13798,7 +13819,7 @@ Workspace setup complete: ${ok} ok, ${failed} failed. Wrote ${written.length} fi
13798
13819
 
13799
13820
  // src/commands/workspace/undo.ts
13800
13821
  import { existsSync as existsSync10, statSync as statSync2 } from "node:fs";
13801
- import path77 from "node:path";
13822
+ import path78 from "node:path";
13802
13823
  import fs53 from "fs-extra";
13803
13824
  init_fs();
13804
13825
  init_logger();
@@ -13829,7 +13850,7 @@ async function runWorkspaceUndo(workspaceRoot, options = {}) {
13829
13850
  }
13830
13851
  if (options.dryRun) {
13831
13852
  for (const repo of config.repos) {
13832
- const repoRoot = path77.resolve(workspaceRoot, repo.path);
13853
+ const repoRoot = path78.resolve(workspaceRoot, repo.path);
13833
13854
  log(`
13834
13855
  \u2192 ${repo.name} (${repo.path})`);
13835
13856
  try {
@@ -13861,11 +13882,11 @@ async function runWorkspaceUndo(workspaceRoot, options = {}) {
13861
13882
  ];
13862
13883
  for (const abs of workspaceArtifacts) {
13863
13884
  if (await fs53.pathExists(abs)) {
13864
- log(`[dry-run] Would remove ${path77.relative(workspaceRoot, abs)}`);
13885
+ log(`[dry-run] Would remove ${path78.relative(workspaceRoot, abs)}`);
13865
13886
  }
13866
13887
  }
13867
13888
  if (!await fs53.pathExists(hausPath(workspaceRoot, "WORKSPACE.md"))) {
13868
- const claudeMd = await readText(path77.join(workspaceRoot, "CLAUDE.md"));
13889
+ const claudeMd = await readText(path78.join(workspaceRoot, "CLAUDE.md"));
13869
13890
  if (claudeMd?.includes(WORKSPACE_BLOCK_BEGIN) || claudeMd && hasLegacyWorkspaceBlock(claudeMd)) {
13870
13891
  log("[dry-run] Would remove the workspace haus import block from CLAUDE.md");
13871
13892
  }
@@ -13884,7 +13905,7 @@ async function runWorkspaceUndo(workspaceRoot, options = {}) {
13884
13905
  }
13885
13906
  let anyRepoFailed = false;
13886
13907
  for (const repo of config.repos) {
13887
- const repoRoot = path77.resolve(workspaceRoot, repo.path);
13908
+ const repoRoot = path78.resolve(workspaceRoot, repo.path);
13888
13909
  log(`
13889
13910
  \u2192 ${repo.name} (${repo.path})`);
13890
13911
  try {
@@ -13913,20 +13934,20 @@ async function runWorkspaceUndo(workspaceRoot, options = {}) {
13913
13934
  const abs = hausPath(workspaceRoot, rel);
13914
13935
  if (await fs53.pathExists(abs)) {
13915
13936
  await fs53.remove(abs);
13916
- log(`Removed ${path77.relative(workspaceRoot, abs)}`);
13937
+ log(`Removed ${path78.relative(workspaceRoot, abs)}`);
13917
13938
  }
13918
13939
  }
13919
13940
  const manifestFile = manifestPath(workspaceRoot);
13920
13941
  if (await fs53.pathExists(manifestFile)) {
13921
13942
  await fs53.remove(manifestFile);
13922
- log(`Removed ${path77.relative(workspaceRoot, manifestFile)}`);
13943
+ log(`Removed ${path78.relative(workspaceRoot, manifestFile)}`);
13923
13944
  }
13924
13945
  const workspaceMdPath = hausPath(workspaceRoot, "WORKSPACE.md");
13925
13946
  if (await fs53.pathExists(workspaceMdPath)) {
13926
13947
  await fs53.remove(workspaceMdPath);
13927
- log(`Removed ${path77.relative(workspaceRoot, workspaceMdPath)}`);
13948
+ log(`Removed ${path78.relative(workspaceRoot, workspaceMdPath)}`);
13928
13949
  } else {
13929
- const claudeMdPath = path77.join(workspaceRoot, "CLAUDE.md");
13950
+ const claudeMdPath = path78.join(workspaceRoot, "CLAUDE.md");
13930
13951
  if (existsSync10(claudeMdPath)) {
13931
13952
  const prev = await readText(claudeMdPath);
13932
13953
  if (prev?.includes(WORKSPACE_BLOCK_BEGIN)) {
@@ -13995,7 +14016,7 @@ relationships: []
13995
14016
  log("Workspace initialized.");
13996
14017
  }
13997
14018
  async function scanWorkspace(workspaceRoot, opts) {
13998
- const configText = await readText(path78.join(workspaceRoot, WORKSPACE_FILE));
14019
+ const configText = await readText(path79.join(workspaceRoot, WORKSPACE_FILE));
13999
14020
  if (!configText) {
14000
14021
  error(`Missing ${WORKSPACE_FILE}. Run \`haus workspace discover\` or \`init\` first.`);
14001
14022
  process.exitCode = 1;
@@ -14017,7 +14038,7 @@ async function scanWorkspace(workspaceRoot, opts) {
14017
14038
  const inputs = [];
14018
14039
  const failures = [];
14019
14040
  for (const repo of config.repos) {
14020
- const repoRoot = path78.resolve(workspaceRoot, repo.path);
14041
+ const repoRoot = path79.resolve(workspaceRoot, repo.path);
14021
14042
  try {
14022
14043
  if (!existsSync11(repoRoot) || !statSync3(repoRoot).isDirectory()) {
14023
14044
  throw new Error("path is not a directory");
@@ -14085,7 +14106,7 @@ init_logger();
14085
14106
  init_paths();
14086
14107
  function cliVersion() {
14087
14108
  try {
14088
- const pkgPath = path79.join(packageRoot(), "package.json");
14109
+ const pkgPath = path80.join(packageRoot(), "package.json");
14089
14110
  const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
14090
14111
  return pkg.version ?? "0.0.0";
14091
14112
  } catch {
@@ -14095,7 +14116,7 @@ function cliVersion() {
14095
14116
  var program = new Command();
14096
14117
  function validateRuntimeNodeVersion() {
14097
14118
  try {
14098
- const pkgPath = path79.join(packageRoot(), "package.json");
14119
+ const pkgPath = path80.join(packageRoot(), "package.json");
14099
14120
  const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
14100
14121
  const requiredRange = pkg.engines?.node;
14101
14122
  if (requiredRange && !satisfiesVersion(process.version, requiredRange)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haus-tech/haus-workflow",
3
- "version": "4.0.0",
3
+ "version": "4.0.1",
4
4
  "description": "Haus AI workflow CLI for Claude Code.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -37,6 +37,7 @@
37
37
  "verify": "node scripts/verify.mjs",
38
38
  "verify:full": "node scripts/verify.mjs --coverage",
39
39
  "check:contract-behavior": "node --import tsx scripts/contract-behavior-check.mjs",
40
+ "check:mutation-table": "node scripts/check-mutation-table.mjs",
40
41
  "postinstall": "node ./scripts/postinstall.mjs || true",
41
42
  "prepare": "lefthook install || true",
42
43
  "security:audit": "yarn npm audit -A -R --severity high --environment production"