@haus-tech/haus-workflow 0.31.0 → 0.31.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
@@ -2,11 +2,11 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import { readFileSync as readFileSync4 } from "node:fs";
5
- import path36 from "node:path";
5
+ import path37 from "node:path";
6
6
  import { Command } from "commander";
7
7
 
8
8
  // src/commands/apply.ts
9
- import path13 from "node:path";
9
+ import path14 from "node:path";
10
10
  import checkbox from "@inquirer/checkbox";
11
11
 
12
12
  // src/catalog/remote-catalog.ts
@@ -182,7 +182,7 @@ var validation_rules_default = {
182
182
  "vendure3",
183
183
  "nestjs",
184
184
  "graphql",
185
- "nx21",
185
+ "nx",
186
186
  "turbo",
187
187
  "nextjs",
188
188
  "react19",
@@ -1043,8 +1043,8 @@ async function getCacheManifestAge() {
1043
1043
  }
1044
1044
 
1045
1045
  // src/claude/write-claude-files.ts
1046
- import path12 from "node:path";
1047
- import fs12 from "fs-extra";
1046
+ import path13 from "node:path";
1047
+ import fs13 from "fs-extra";
1048
1048
 
1049
1049
  // src/catalog/load-catalog.ts
1050
1050
  import path4 from "node:path";
@@ -1660,8 +1660,8 @@ function buildDenyRules() {
1660
1660
  for (const command of DENY_COMMANDS) {
1661
1661
  rules.push(`Bash(${command}:*)`);
1662
1662
  }
1663
- for (const path37 of DENY_PATHS) {
1664
- const pattern = DENY_DIRS.has(path37) ? `${path37}/**` : path37;
1663
+ for (const path38 of DENY_PATHS) {
1664
+ const pattern = DENY_DIRS.has(path38) ? `${path38}/**` : path38;
1665
1665
  for (const tool of FILE_TOOLS) {
1666
1666
  rules.push(`${tool}(${pattern})`);
1667
1667
  }
@@ -1851,9 +1851,74 @@ async function verifyProjectSettingsHooksContract(root) {
1851
1851
  return { ok: true, message: "settings.json carries required haus hook contract." };
1852
1852
  }
1853
1853
 
1854
- // src/claude/write-root-claude-md.ts
1854
+ // src/claude/write-prettierignore.ts
1855
1855
  import path9 from "node:path";
1856
1856
  import fs8 from "fs-extra";
1857
+ var PRETTIERIGNORE_BEGIN = "# HAUS:BEGIN haus-managed v=1";
1858
+ var PRETTIERIGNORE_END = "# HAUS:END haus-managed";
1859
+ var IGNORED_PATHS = [".haus-workflow/"];
1860
+ function buildPrettierIgnoreBlock() {
1861
+ return [
1862
+ PRETTIERIGNORE_BEGIN,
1863
+ "# haus-owned files \u2014 do not reformat (keeps tamper-detection hashes stable)",
1864
+ ...IGNORED_PATHS,
1865
+ PRETTIERIGNORE_END
1866
+ ].join("\n");
1867
+ }
1868
+ function findLineMarker(content2, marker, from = 0) {
1869
+ let idx = content2.indexOf(marker, from);
1870
+ while (idx !== -1) {
1871
+ const lineStart = idx === 0 || content2[idx - 1] === "\n";
1872
+ const after = idx + marker.length;
1873
+ const lineEnd = after === content2.length || content2[after] === "\n" || content2[after] === "\r";
1874
+ if (lineStart && lineEnd) return { start: idx, end: after };
1875
+ idx = content2.indexOf(marker, idx + marker.length);
1876
+ }
1877
+ return null;
1878
+ }
1879
+ function findBlockRange(content2) {
1880
+ const begin = findLineMarker(content2, PRETTIERIGNORE_BEGIN);
1881
+ if (!begin) return null;
1882
+ const end = findLineMarker(content2, PRETTIERIGNORE_END, begin.end);
1883
+ if (!end) return null;
1884
+ return { start: begin.start, end: end.end };
1885
+ }
1886
+ function injectPrettierIgnoreBlock(existing, block) {
1887
+ const range = findBlockRange(existing);
1888
+ if (range) {
1889
+ const before = existing.slice(0, range.start);
1890
+ const after = existing.slice(range.end);
1891
+ return `${before}${block}${after}`.replace(/\n*$/, "\n");
1892
+ }
1893
+ const loneBegin = findLineMarker(existing, PRETTIERIGNORE_BEGIN);
1894
+ if (loneBegin) {
1895
+ const before = existing.slice(0, loneBegin.start).trimEnd();
1896
+ if (before.length === 0) return `${block}
1897
+ `;
1898
+ return `${before}
1899
+
1900
+ ${block}
1901
+ `;
1902
+ }
1903
+ const trimmed = existing.trimEnd();
1904
+ if (trimmed.length === 0) return `${block}
1905
+ `;
1906
+ return `${trimmed}
1907
+
1908
+ ${block}
1909
+ `;
1910
+ }
1911
+ async function writePrettierIgnore(root, dryRun) {
1912
+ const filePath = path9.join(root, ".prettierignore");
1913
+ const prev = await fs8.pathExists(filePath) ? await fs8.readFile(filePath, "utf8") : "";
1914
+ const next = injectPrettierIgnoreBlock(prev, buildPrettierIgnoreBlock());
1915
+ await writeManagedText(root, filePath, next, dryRun);
1916
+ return filePath;
1917
+ }
1918
+
1919
+ // src/claude/write-root-claude-md.ts
1920
+ import path10 from "node:path";
1921
+ import fs9 from "fs-extra";
1857
1922
  var BLOCK_BEGIN = "<!-- HAUS:BEGIN haus-imports v=1 -->";
1858
1923
  var BLOCK_END = "<!-- HAUS:END haus-imports -->";
1859
1924
  var IMPORT_CONTENT = `@.haus-workflow/WORKFLOW.md
@@ -1863,7 +1928,7 @@ function buildImportBlock() {
1863
1928
  ${IMPORT_CONTENT}
1864
1929
  ${BLOCK_END}`;
1865
1930
  }
1866
- function findLineMarker(content2, marker, from = 0) {
1931
+ function findLineMarker2(content2, marker, from = 0) {
1867
1932
  let idx = content2.indexOf(marker, from);
1868
1933
  while (idx !== -1) {
1869
1934
  const lineStart = idx === 0 || content2[idx - 1] === "\n";
@@ -1875,9 +1940,9 @@ function findLineMarker(content2, marker, from = 0) {
1875
1940
  return null;
1876
1941
  }
1877
1942
  function findImportBlockRange(content2) {
1878
- const begin = findLineMarker(content2, BLOCK_BEGIN);
1943
+ const begin = findLineMarker2(content2, BLOCK_BEGIN);
1879
1944
  if (!begin) return null;
1880
- const end = findLineMarker(content2, BLOCK_END, begin.end);
1945
+ const end = findLineMarker2(content2, BLOCK_END, begin.end);
1881
1946
  if (!end) return null;
1882
1947
  return { beginStart: begin.start, beginEnd: begin.end, endStart: end.start, endEnd: end.end };
1883
1948
  }
@@ -1897,7 +1962,7 @@ function injectHausBlock(existing, block) {
1897
1962
  const after = existing.slice(range.endEnd);
1898
1963
  return `${before}${block}${after}`;
1899
1964
  }
1900
- const loneBegin = findLineMarker(existing, BLOCK_BEGIN);
1965
+ const loneBegin = findLineMarker2(existing, BLOCK_BEGIN);
1901
1966
  if (loneBegin) {
1902
1967
  const before = existing.slice(0, loneBegin.start).trimEnd();
1903
1968
  if (before.length === 0) return `${block}
@@ -1918,21 +1983,21 @@ ${block}
1918
1983
  `;
1919
1984
  }
1920
1985
  async function writeRootClaudeMd(root, dryRun) {
1921
- const filePath = path9.join(root, "CLAUDE.md");
1986
+ const filePath = path10.join(root, "CLAUDE.md");
1922
1987
  const block = buildImportBlock();
1923
- const prev = await fs8.pathExists(filePath) ? await fs8.readFile(filePath, "utf8") : "";
1988
+ const prev = await fs9.pathExists(filePath) ? await fs9.readFile(filePath, "utf8") : "";
1924
1989
  const next = injectHausBlock(prev, block);
1925
1990
  await writeManagedText(root, filePath, next, dryRun);
1926
1991
  return filePath;
1927
1992
  }
1928
1993
 
1929
1994
  // src/claude/write-workflow-config.ts
1930
- import path11 from "node:path";
1931
- import fs10 from "fs-extra";
1995
+ import path12 from "node:path";
1996
+ import fs11 from "fs-extra";
1932
1997
 
1933
1998
  // src/claude/derive-workflow-config.ts
1934
- import path10 from "node:path";
1935
- import fs9 from "fs-extra";
1999
+ import path11 from "node:path";
2000
+ import fs10 from "fs-extra";
1936
2001
  function binCmd(pm, bin, args) {
1937
2002
  const tail = args ? ` ${args}` : "";
1938
2003
  if (pm === "yarn") return `yarn ${bin}${tail}`;
@@ -1941,7 +2006,7 @@ function binCmd(pm, bin, args) {
1941
2006
  }
1942
2007
  async function deriveWorkflowConfig(root, ctx) {
1943
2008
  const pm = ctx.packageManager === "unknown" ? "npm" : ctx.packageManager;
1944
- const pkg = await readJson(path10.join(root, "package.json"));
2009
+ const pkg = await readJson(path11.join(root, "package.json"));
1945
2010
  const scripts = pkg?.scripts ?? {};
1946
2011
  const deps = new Set(ctx.dependencies);
1947
2012
  const stacks = Object.values(ctx.detectedStacks ?? {}).flat();
@@ -1951,7 +2016,7 @@ async function deriveWorkflowConfig(root, ctx) {
1951
2016
  return null;
1952
2017
  };
1953
2018
  const hasDep = (name) => deps.has(name);
1954
- const exists = (rel) => fs9.pathExistsSync(path10.join(root, rel));
2019
+ const exists = (rel) => fs10.pathExistsSync(path11.join(root, rel));
1955
2020
  const hasPlaywright = hasDep("@playwright/test") || stacks.includes("playwright");
1956
2021
  const hasCypress = hasDep("cypress");
1957
2022
  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;
@@ -2019,7 +2084,7 @@ var FALLBACK_CONTEXT = {
2019
2084
  async function writeWorkflowConfig(root, dryRun, opts = {}) {
2020
2085
  const destPath = hausPath(root, "workflow-config.md");
2021
2086
  const printable = displayPath(root, destPath);
2022
- const exists = await fs10.pathExists(destPath);
2087
+ const exists = await fs11.pathExists(destPath);
2023
2088
  if (exists && !opts.refill) {
2024
2089
  if (dryRun) log(printable + ": exists (project-owned, skipping)");
2025
2090
  return null;
@@ -2027,11 +2092,11 @@ async function writeWorkflowConfig(root, dryRun, opts = {}) {
2027
2092
  const ctx = await readJson(hausPath(root, "context-map.json")) ?? {
2028
2093
  ...FALLBACK_CONTEXT,
2029
2094
  root,
2030
- repoName: path11.basename(root)
2095
+ repoName: path12.basename(root)
2031
2096
  };
2032
2097
  const values = await deriveWorkflowConfig(root, ctx);
2033
2098
  if (exists) {
2034
- const current = await fs10.readFile(destPath, "utf8");
2099
+ const current = await fs11.readFile(destPath, "utf8");
2035
2100
  const refilled = refillContent(current, values);
2036
2101
  if (refilled === current) {
2037
2102
  if (dryRun) log(printable + ": no blank fields to refill");
@@ -2053,7 +2118,7 @@ async function writeWorkflowConfig(root, dryRun, opts = {}) {
2053
2118
  }
2054
2119
 
2055
2120
  // src/claude/write-workflow.ts
2056
- import fs11 from "fs-extra";
2121
+ import fs12 from "fs-extra";
2057
2122
  var STABLE_ID = "template.workflow";
2058
2123
  function makeWorkflowHeader(pkgVersion, contentHash) {
2059
2124
  return `<!-- HAUS-MANAGED id=${STABLE_ID} v=${SCHEMA_VERSION} source=@haus-tech/haus-workflow@${pkgVersion} hash=${contentHash} -->`;
@@ -2072,8 +2137,8 @@ async function writeWorkflow(root, pkgVersion, dryRun, force = false) {
2072
2137
  ${templateContent}`;
2073
2138
  const destPath = hausPath(root, "WORKFLOW.md");
2074
2139
  const printable = displayPath(root, destPath);
2075
- if (await fs11.pathExists(destPath)) {
2076
- const existing = await fs11.readFile(destPath, "utf8");
2140
+ if (await fs12.pathExists(destPath)) {
2141
+ const existing = await fs12.readFile(destPath, "utf8");
2077
2142
  const firstLine = existing.split("\n")[0] ?? "";
2078
2143
  const parsed = parseHausManagedHeader(firstLine);
2079
2144
  if (!parsed) {
@@ -2107,7 +2172,7 @@ ${templateContent}`;
2107
2172
  }
2108
2173
  }
2109
2174
  if (dryRun) {
2110
- const prev = await fs11.pathExists(destPath) ? await fs11.readFile(destPath, "utf8") : "";
2175
+ const prev = await fs12.pathExists(destPath) ? await fs12.readFile(destPath, "utf8") : "";
2111
2176
  if (!prev) {
2112
2177
  log(createUnifiedDiff(printable, "", next));
2113
2178
  } else {
@@ -2140,7 +2205,7 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
2140
2205
  estimatedTokenReductionPct: 0
2141
2206
  };
2142
2207
  const pkgRoot = packageRoot();
2143
- const hausVersion2 = (await readJson(path12.join(pkgRoot, "package.json")))?.version ?? "0.0.0";
2208
+ const hausVersion2 = (await readJson(path13.join(pkgRoot, "package.json")))?.version ?? "0.0.0";
2144
2209
  const coreFiles = [
2145
2210
  claudePath(root, "settings.json"),
2146
2211
  claudePath(root, "rules", "haus.md"),
@@ -2151,10 +2216,12 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
2151
2216
  const workflowConfigPath = await writeWorkflowConfig(root, dryRun, {
2152
2217
  refill: opts.refillConfig
2153
2218
  });
2219
+ const prettierIgnorePath = await writePrettierIgnore(root, dryRun);
2154
2220
  const p6Files = [
2155
2221
  rootClaudeMdPath,
2156
2222
  ...workflowPath ? [workflowPath] : [],
2157
- ...workflowConfigPath ? [workflowConfigPath] : []
2223
+ ...workflowConfigPath ? [workflowConfigPath] : [],
2224
+ prettierIgnorePath
2158
2225
  ];
2159
2226
  const files = dryRun ? [...coreFiles, ...p6Files] : [...coreFiles, ...p6Files, hausPath(root, "haus.lock.json")];
2160
2227
  if (dryRun) {
@@ -2171,8 +2238,8 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
2171
2238
  dryRun
2172
2239
  );
2173
2240
  const legacyReviewPath = claudePath(root, "commands", "haus-review.md");
2174
- if (await fs12.pathExists(legacyReviewPath)) {
2175
- const content2 = await fs12.readFile(legacyReviewPath, "utf8");
2241
+ if (await fs13.pathExists(legacyReviewPath)) {
2242
+ const content2 = await fs13.readFile(legacyReviewPath, "utf8");
2176
2243
  const stub = 'Run `haus context --task "code review"` then review diff.';
2177
2244
  if (content2 === stub || content2 === `${stub}
2178
2245
  ` || content2 === `${stub}\r
@@ -2180,7 +2247,7 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
2180
2247
  if (dryRun) {
2181
2248
  log(`[dry-run] would remove stale ${displayPath(root, legacyReviewPath)}`);
2182
2249
  } else {
2183
- await fs12.remove(legacyReviewPath);
2250
+ await fs13.remove(legacyReviewPath);
2184
2251
  }
2185
2252
  }
2186
2253
  }
@@ -2208,8 +2275,8 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
2208
2275
  dryRun
2209
2276
  );
2210
2277
  const legacySecurityPath = claudePath(root, "rules", "security.md");
2211
- if (await fs12.pathExists(legacySecurityPath)) {
2212
- const content2 = await fs12.readFile(legacySecurityPath, "utf8");
2278
+ if (await fs13.pathExists(legacySecurityPath)) {
2279
+ const content2 = await fs13.readFile(legacySecurityPath, "utf8");
2213
2280
  const stub = "- Never read secrets.\n- Block dangerous shell commands.";
2214
2281
  if (content2 === stub || content2 === `${stub}
2215
2282
  ` || content2 === `${stub}\r
@@ -2217,7 +2284,7 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
2217
2284
  if (dryRun) {
2218
2285
  log(`[dry-run] would remove stale ${displayPath(root, legacySecurityPath)}`);
2219
2286
  } else {
2220
- await fs12.remove(legacySecurityPath);
2287
+ await fs13.remove(legacySecurityPath);
2221
2288
  }
2222
2289
  }
2223
2290
  }
@@ -2232,11 +2299,11 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
2232
2299
  ];
2233
2300
  for (const rel of LEGACY_PRUNED_ARTIFACTS) {
2234
2301
  const legacyPath = hausPath(root, rel);
2235
- if (await fs12.pathExists(legacyPath)) {
2302
+ if (await fs13.pathExists(legacyPath)) {
2236
2303
  if (dryRun) {
2237
2304
  log(`[dry-run] would remove stale ${displayPath(root, legacyPath)}`);
2238
2305
  } else {
2239
- await fs12.remove(legacyPath);
2306
+ await fs13.remove(legacyPath);
2240
2307
  }
2241
2308
  }
2242
2309
  }
@@ -2278,10 +2345,10 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
2278
2345
  );
2279
2346
  continue;
2280
2347
  }
2281
- const destination = claudePath(root, target, path12.basename(sourcePath));
2282
- if (await fs12.pathExists(sourcePath)) {
2348
+ const destination = claudePath(root, target, path13.basename(sourcePath));
2349
+ if (await fs13.pathExists(sourcePath)) {
2283
2350
  if (dryRun) {
2284
- const exists = await fs12.pathExists(destination);
2351
+ const exists = await fs13.pathExists(destination);
2285
2352
  log(
2286
2353
  `${displayPath(root, destination)}: ${exists ? "would overwrite" : "would create"} (${item.id})`
2287
2354
  );
@@ -2291,17 +2358,17 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
2291
2358
  dryRun: false
2292
2359
  });
2293
2360
  } else {
2294
- await fs12.ensureDir(path12.dirname(destination));
2295
- await fs12.copy(sourcePath, destination, { overwrite: true, errorOnExist: false });
2361
+ await fs13.ensureDir(path13.dirname(destination));
2362
+ await fs13.copy(sourcePath, destination, { overwrite: true, errorOnExist: false });
2296
2363
  }
2297
2364
  files.push(destination);
2298
- const relPaths = [path12.relative(root, destination)];
2365
+ const relPaths = [path13.relative(root, destination)];
2299
2366
  if (!superpowersSharedInstalled && manifestItem.originSourceId === SUPERPOWERS_ORIGIN_SOURCE_ID && item.type === "skill") {
2300
2367
  const sharedRel = await installSuperpowersShared(contentRoot, root, dryRun);
2301
2368
  if (sharedRel) {
2302
2369
  superpowersSharedInstalled = true;
2303
2370
  relPaths.push(sharedRel);
2304
- files.push(path12.join(root, sharedRel));
2371
+ files.push(path13.join(root, sharedRel));
2305
2372
  }
2306
2373
  }
2307
2374
  const current = installedPathsByItem.get(item.id) ?? [];
@@ -2368,7 +2435,7 @@ async function cleanupStaleCatalogItems(root, manifestById, dryRun) {
2368
2435
  if (relPaths.length === 0) continue;
2369
2436
  const existing = [];
2370
2437
  for (const rel of relPaths) {
2371
- if (await fs12.pathExists(path12.join(root, rel))) existing.push(rel);
2438
+ if (await fs13.pathExists(path13.join(root, rel))) existing.push(rel);
2372
2439
  }
2373
2440
  if (existing.length === 0) continue;
2374
2441
  const pruneReason = deprecated ? "deprecated" : "stale";
@@ -2386,13 +2453,13 @@ async function cleanupStaleCatalogItems(root, manifestById, dryRun) {
2386
2453
  continue;
2387
2454
  }
2388
2455
  for (const rel of existing) {
2389
- const abs = path12.join(root, rel);
2456
+ const abs = path13.join(root, rel);
2390
2457
  if (dryRun) {
2391
2458
  log(`[dry-run] would remove ${pruneReason} ${displayPath(root, abs)} (${entry.id})`);
2392
2459
  continue;
2393
2460
  }
2394
- await fs12.remove(abs);
2395
- await pruneEmptyDir(path12.dirname(abs));
2461
+ await fs13.remove(abs);
2462
+ await pruneEmptyDir(path13.dirname(abs));
2396
2463
  log(`Removed ${pruneReason} ${displayPath(root, abs)} (${entry.id})`);
2397
2464
  }
2398
2465
  }
@@ -2407,7 +2474,7 @@ function parseIdList(value) {
2407
2474
 
2408
2475
  // src/commands/apply.ts
2409
2476
  async function cacheHasItems() {
2410
- const data = await readJson(path13.join(getCacheDir(), "manifest.json"));
2477
+ const data = await readJson(path14.join(getCacheDir(), "manifest.json"));
2411
2478
  return Array.isArray(data?.items) && data.items.length > 0;
2412
2479
  }
2413
2480
  function installableRecommendedItems(recommended) {
@@ -2534,7 +2601,7 @@ async function runCatalogAudit() {
2534
2601
 
2535
2602
  // src/commands/clone.ts
2536
2603
  import { existsSync as existsSync2 } from "node:fs";
2537
- import path14 from "node:path";
2604
+ import path15 from "node:path";
2538
2605
 
2539
2606
  // src/utils/exec.ts
2540
2607
  import { execa } from "execa";
@@ -2587,9 +2654,9 @@ async function runClone(url, opts = {}) {
2587
2654
  process.exitCode = 1;
2588
2655
  return;
2589
2656
  }
2590
- const target = path14.resolve(opts.dir?.trim() || repoNameFromUrl(url));
2657
+ const target = path15.resolve(opts.dir?.trim() || repoNameFromUrl(url));
2591
2658
  if (existsSync2(target)) {
2592
- log(`\u2022 ${path14.basename(target)} already present at ${target} \u2014 skipped`);
2659
+ log(`\u2022 ${path15.basename(target)} already present at ${target} \u2014 skipped`);
2593
2660
  return;
2594
2661
  }
2595
2662
  if (opts.dryRun) {
@@ -2606,11 +2673,11 @@ async function runClone(url, opts = {}) {
2606
2673
  }
2607
2674
 
2608
2675
  // src/commands/doctor.ts
2609
- import path19 from "node:path";
2610
- import fs14 from "fs-extra";
2676
+ import path20 from "node:path";
2677
+ import fs15 from "fs-extra";
2611
2678
 
2612
2679
  // src/scanner/scan-project.ts
2613
- import path18 from "node:path";
2680
+ import path19 from "node:path";
2614
2681
 
2615
2682
  // src/utils/audit-checks.ts
2616
2683
  function isRecord(v) {
@@ -2637,8 +2704,8 @@ function compareVersions(a, b) {
2637
2704
  }
2638
2705
 
2639
2706
  // src/scanner/detect-package-manager.ts
2640
- import path15 from "node:path";
2641
- import fs13 from "fs-extra";
2707
+ import path16 from "node:path";
2708
+ import fs14 from "fs-extra";
2642
2709
  function detectPackageManager(root, packageManagerField) {
2643
2710
  const field = String(packageManagerField ?? "").trim();
2644
2711
  if (field.startsWith("yarn@")) {
@@ -2656,9 +2723,9 @@ function detectPackageManager(root, packageManagerField) {
2656
2723
  if (satisfiesVersion(version, ">=9")) return "npm";
2657
2724
  return "unknown";
2658
2725
  }
2659
- if (fs13.existsSync(path15.join(root, "yarn.lock"))) return "yarn";
2660
- if (fs13.existsSync(path15.join(root, "pnpm-lock.yaml"))) return "pnpm";
2661
- if (fs13.existsSync(path15.join(root, "package-lock.json"))) return "npm";
2726
+ if (fs14.existsSync(path16.join(root, "yarn.lock"))) return "yarn";
2727
+ if (fs14.existsSync(path16.join(root, "pnpm-lock.yaml"))) return "pnpm";
2728
+ if (fs14.existsSync(path16.join(root, "package-lock.json"))) return "npm";
2662
2729
  return "unknown";
2663
2730
  }
2664
2731
 
@@ -2860,7 +2927,7 @@ function runDetection(ctx, rules = STACK_RULES) {
2860
2927
  }
2861
2928
 
2862
2929
  // src/scanner/detection.ts
2863
- import path16 from "node:path";
2930
+ import path17 from "node:path";
2864
2931
  var UNSUPPORTED_MARKERS = {
2865
2932
  "requirements.txt": "python",
2866
2933
  "pyproject.toml": "python",
@@ -2914,14 +2981,14 @@ function finalizeRoles(registryRoles, deps, files) {
2914
2981
  function collectUnsupportedSignals(files) {
2915
2982
  return [
2916
2983
  ...new Set(
2917
- files.map((f) => UNSUPPORTED_MARKERS[path16.basename(f)]).filter((s) => Boolean(s))
2984
+ files.map((f) => UNSUPPORTED_MARKERS[path17.basename(f)]).filter((s) => Boolean(s))
2918
2985
  )
2919
2986
  ].sort();
2920
2987
  }
2921
2988
 
2922
2989
  // src/scanner/render.ts
2923
2990
  import { readFile } from "node:fs/promises";
2924
- import path17 from "node:path";
2991
+ import path18 from "node:path";
2925
2992
  async function buildContentBlob(root, files) {
2926
2993
  const candidates = files.filter(
2927
2994
  (f) => f.endsWith(".ts") || f.endsWith(".js") || f.endsWith(".php") || f.endsWith(".json") || f.endsWith(".yml") || f.endsWith(".yaml")
@@ -2929,7 +2996,7 @@ async function buildContentBlob(root, files) {
2929
2996
  const slice = candidates.slice(0, 300);
2930
2997
  const parts = await mapWithConcurrency(slice, async (rel) => {
2931
2998
  try {
2932
- return await readFile(path17.join(root, rel), "utf8");
2999
+ return await readFile(path18.join(root, rel), "utf8");
2933
3000
  } catch {
2934
3001
  return "";
2935
3002
  }
@@ -3016,8 +3083,8 @@ var SAFE_FILES = [
3016
3083
  "Gemfile"
3017
3084
  ];
3018
3085
  async function scanProject(root) {
3019
- const pkg = await readJson(path18.join(root, "package.json"));
3020
- const composer = await readJson(path18.join(root, "composer.json"));
3086
+ const pkg = await readJson(path19.join(root, "package.json"));
3087
+ const composer = await readJson(path19.join(root, "composer.json"));
3021
3088
  const files = await listFiles(root, SAFE_FILES);
3022
3089
  const safeFiles = files.filter((f) => !blocked(f));
3023
3090
  const deps = dependencySet(pkg, composer);
@@ -3046,7 +3113,7 @@ async function scanProject(root) {
3046
3113
  const context = {
3047
3114
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3048
3115
  root,
3049
- repoName: String(pkg?.name ?? path18.basename(root)),
3116
+ repoName: String(pkg?.name ?? path19.basename(root)),
3050
3117
  packageManager,
3051
3118
  repoRoles: roles,
3052
3119
  detectedStacks: stacks,
@@ -3143,7 +3210,7 @@ async function runDoctor(options) {
3143
3210
  } else {
3144
3211
  ok(`- HOOKS OK: ${hooks.message}`);
3145
3212
  }
3146
- const rootClaudeMdPath = path19.join(root, "CLAUDE.md");
3213
+ const rootClaudeMdPath = path20.join(root, "CLAUDE.md");
3147
3214
  const rootClaudeMdContent = await readText(rootClaudeMdPath);
3148
3215
  if (!rootClaudeMdContent) {
3149
3216
  flag(
@@ -3171,7 +3238,7 @@ async function runDoctor(options) {
3171
3238
  const block = rootClaudeMdContent.slice(beginIdx, endIdx + BLOCK_END.length);
3172
3239
  const importTargets = [...block.matchAll(/@\.haus-workflow\/(\S+)/g)].map((m) => m[1]);
3173
3240
  for (const target of importTargets) {
3174
- if (!await fs14.pathExists(hausPath(root, target))) {
3241
+ if (!await fs15.pathExists(hausPath(root, target))) {
3175
3242
  flag(
3176
3243
  `- CLAUDE.md import: @.haus-workflow/${target} does not resolve (run \`haus apply --write\`)`,
3177
3244
  `A file CLAUDE.md links to (${target}) is missing, so part of the guidance won't load`,
@@ -3182,7 +3249,7 @@ async function runDoctor(options) {
3182
3249
  }
3183
3250
  }
3184
3251
  const workflowPath = hausPath(root, "WORKFLOW.md");
3185
- const workflowExists = await fs14.pathExists(workflowPath);
3252
+ const workflowExists = await fs15.pathExists(workflowPath);
3186
3253
  if (!workflowExists) {
3187
3254
  flag(
3188
3255
  "- .haus-workflow/WORKFLOW.md: missing (run `haus apply --write`)",
@@ -3205,15 +3272,15 @@ async function runDoctor(options) {
3205
3272
  "haus apply --write --force"
3206
3273
  );
3207
3274
  } else {
3208
- const cachePath = path19.join(getCacheDir(), "templates/agentic-workflow-standard.md");
3209
- const bundledPath = path19.join(
3275
+ const cachePath = path20.join(getCacheDir(), "templates/agentic-workflow-standard.md");
3276
+ const bundledPath = path20.join(
3210
3277
  packageRoot(),
3211
3278
  "library",
3212
3279
  "global",
3213
3280
  "templates",
3214
3281
  "agentic-workflow-standard.md"
3215
3282
  );
3216
- const templatePath = await fs14.pathExists(cachePath) ? cachePath : bundledPath;
3283
+ const templatePath = await fs15.pathExists(cachePath) ? cachePath : bundledPath;
3217
3284
  const templateContent = await readText(templatePath);
3218
3285
  if (storedHashMatch && templateContent) {
3219
3286
  const currentHash = hashText(normaliseLF(templateContent));
@@ -3233,7 +3300,7 @@ async function runDoctor(options) {
3233
3300
  }
3234
3301
  }
3235
3302
  const workflowConfigPath = hausPath(root, "workflow-config.md");
3236
- const workflowConfigExists = await fs14.pathExists(workflowConfigPath);
3303
+ const workflowConfigExists = await fs15.pathExists(workflowConfigPath);
3237
3304
  if (!workflowConfigExists) {
3238
3305
  flag(
3239
3306
  "- .haus-workflow/workflow-config.md: missing (run `haus apply --write`)",
@@ -3241,7 +3308,7 @@ async function runDoctor(options) {
3241
3308
  "haus apply --write"
3242
3309
  );
3243
3310
  } else {
3244
- const cfg = await fs14.readFile(workflowConfigPath, "utf8");
3311
+ const cfg = await fs15.readFile(workflowConfigPath, "utf8");
3245
3312
  const unfilled = cfg.split("\n").filter((l) => l.includes("<!-- fill in")).length;
3246
3313
  if (unfilled > 0) {
3247
3314
  flag(
@@ -3253,6 +3320,18 @@ async function runDoctor(options) {
3253
3320
  ok("- .haus-workflow/workflow-config.md: OK (project-owned)");
3254
3321
  }
3255
3322
  }
3323
+ if (workflowExists) {
3324
+ const prettierIgnore = await readText(path20.join(root, ".prettierignore")) ?? "";
3325
+ if (!prettierIgnore.split("\n").some((l) => l.trim() === ".haus-workflow/")) {
3326
+ flag(
3327
+ "- .prettierignore: not protecting .haus-workflow/ (run `haus apply --write`)",
3328
+ 'The formatter may reformat managed files and trigger false "modified locally" reports',
3329
+ "haus apply --write"
3330
+ );
3331
+ } else {
3332
+ ok("- .prettierignore: protects .haus-workflow/");
3333
+ }
3334
+ }
3256
3335
  const cacheAgeMs = await getCacheManifestAge();
3257
3336
  if (cacheAgeMs === null) {
3258
3337
  flag(
@@ -3272,7 +3351,7 @@ async function runDoctor(options) {
3272
3351
  ok(`- CATALOG CACHE: OK (${cacheAgeDays}d old)`);
3273
3352
  }
3274
3353
  }
3275
- const pkgJson = await readJson(path19.join(packageRoot(), "package.json"));
3354
+ const pkgJson = await readJson(path20.join(packageRoot(), "package.json"));
3276
3355
  const currentVersion = pkgJson?.version ?? "0.0.0";
3277
3356
  const npmStatus = await fetchNpmVersionStatus(currentVersion);
3278
3357
  if (npmStatus.updateAvailable && npmStatus.latest !== null) {
@@ -3494,8 +3573,8 @@ async function runGuard(kind, _options) {
3494
3573
  }
3495
3574
 
3496
3575
  // src/commands/init.ts
3497
- import path20 from "node:path";
3498
- import fs15 from "fs-extra";
3576
+ import path21 from "node:path";
3577
+ import fs16 from "fs-extra";
3499
3578
 
3500
3579
  // src/utils/prompts.ts
3501
3580
  import { stdin as input, stdout as output } from "node:process";
@@ -4019,8 +4098,8 @@ async function runSetupProject(options) {
4019
4098
  // src/commands/init.ts
4020
4099
  async function runInit(options) {
4021
4100
  const root = process.cwd();
4022
- const hausDir = path20.join(root, ".haus-workflow");
4023
- const alreadyInit = await fs15.pathExists(hausDir);
4101
+ const hausDir = path21.join(root, ".haus-workflow");
4102
+ const alreadyInit = await fs16.pathExists(hausDir);
4024
4103
  if (alreadyInit) {
4025
4104
  log("Haus AI already initialized in this project.");
4026
4105
  log("Run `haus setup-project` to reconfigure.");
@@ -4032,8 +4111,8 @@ async function runInit(options) {
4032
4111
 
4033
4112
  // src/install/apply.ts
4034
4113
  import crypto2 from "node:crypto";
4035
- import path21 from "node:path";
4036
- import fs16 from "fs-extra";
4114
+ import path22 from "node:path";
4115
+ import fs17 from "fs-extra";
4037
4116
 
4038
4117
  // src/install/header.ts
4039
4118
  var MD_PREFIX = "<!-- HAUS-MANAGED";
@@ -4119,40 +4198,40 @@ function hashContent(content2) {
4119
4198
  }
4120
4199
  function sourceVersion() {
4121
4200
  try {
4122
- const pkgPath = path21.join(packageRoot(), "package.json");
4123
- const pkg = JSON.parse(fs16.readFileSync(pkgPath, "utf8"));
4201
+ const pkgPath = path22.join(packageRoot(), "package.json");
4202
+ const pkg = JSON.parse(fs17.readFileSync(pkgPath, "utf8"));
4124
4203
  return `${pkg.name ?? "haus"}@${pkg.version ?? "0.0.0"}`;
4125
4204
  } catch {
4126
4205
  return "haus@0.0.0";
4127
4206
  }
4128
4207
  }
4129
4208
  function globalSrcDir() {
4130
- return path21.join(packageRoot(), "library", "global");
4209
+ return path22.join(packageRoot(), "library", "global");
4131
4210
  }
4132
4211
  function collectSourceFiles(srcDir, claudeDir) {
4133
4212
  const entries = [];
4134
- const skillsDir = path21.join(srcDir, "skills");
4135
- if (fs16.pathExistsSync(skillsDir)) {
4136
- for (const skillName of fs16.readdirSync(skillsDir)) {
4137
- const skillFile = path21.join(skillsDir, skillName, "SKILL.md");
4138
- if (fs16.pathExistsSync(skillFile)) {
4213
+ const skillsDir = path22.join(srcDir, "skills");
4214
+ if (fs17.pathExistsSync(skillsDir)) {
4215
+ for (const skillName of fs17.readdirSync(skillsDir)) {
4216
+ const skillFile = path22.join(skillsDir, skillName, "SKILL.md");
4217
+ if (fs17.pathExistsSync(skillFile)) {
4139
4218
  entries.push({
4140
4219
  stableId: `skill.${skillName}`,
4141
- srcRelPath: path21.join("library", "global", "skills", skillName, "SKILL.md"),
4142
- destPath: path21.join(claudeDir, "skills", skillName, "SKILL.md")
4220
+ srcRelPath: path22.join("library", "global", "skills", skillName, "SKILL.md"),
4221
+ destPath: path22.join(claudeDir, "skills", skillName, "SKILL.md")
4143
4222
  });
4144
4223
  }
4145
4224
  }
4146
4225
  }
4147
- const commandsDir = path21.join(srcDir, "commands");
4148
- if (fs16.pathExistsSync(commandsDir)) {
4149
- for (const fileName of fs16.readdirSync(commandsDir)) {
4226
+ const commandsDir = path22.join(srcDir, "commands");
4227
+ if (fs17.pathExistsSync(commandsDir)) {
4228
+ for (const fileName of fs17.readdirSync(commandsDir)) {
4150
4229
  if (!fileName.endsWith(".md")) continue;
4151
4230
  const commandName = fileName.slice(0, -".md".length);
4152
4231
  entries.push({
4153
4232
  stableId: `command.${commandName}`,
4154
- srcRelPath: path21.join("library", "global", "commands", fileName),
4155
- destPath: path21.join(claudeDir, "commands", fileName)
4233
+ srcRelPath: path22.join("library", "global", "commands", fileName),
4234
+ destPath: path22.join(claudeDir, "commands", fileName)
4156
4235
  });
4157
4236
  }
4158
4237
  }
@@ -4176,7 +4255,7 @@ async function applyInstall(options = {}) {
4176
4255
  };
4177
4256
  const manifestFiles = [];
4178
4257
  for (const entry of sourceFiles) {
4179
- const srcPath = path21.join(packageRoot(), entry.srcRelPath);
4258
+ const srcPath = path22.join(packageRoot(), entry.srcRelPath);
4180
4259
  const rawContent = await readText(srcPath);
4181
4260
  if (rawContent === void 0) {
4182
4261
  warn(`Source file not found: ${entry.srcRelPath}`);
@@ -4196,7 +4275,7 @@ async function applyInstall(options = {}) {
4196
4275
  }
4197
4276
  continue;
4198
4277
  }
4199
- const destExists = fs16.pathExistsSync(entry.destPath);
4278
+ const destExists = fs17.pathExistsSync(entry.destPath);
4200
4279
  if (destExists) {
4201
4280
  const currentContent = await readText(entry.destPath);
4202
4281
  if (currentContent !== void 0) {
@@ -4232,7 +4311,7 @@ async function applyInstall(options = {}) {
4232
4311
  schemaVersion: SCHEMA_VERSION2
4233
4312
  });
4234
4313
  }
4235
- const fragmentPath = path21.join(srcDir, "settings-fragments", "hooks.json");
4314
+ const fragmentPath = path22.join(srcDir, "settings-fragments", "hooks.json");
4236
4315
  const fragments = await loadHooksFragment(fragmentPath);
4237
4316
  const settings = await readSettings();
4238
4317
  const { settings: hookSettings, addedIds } = mergeHooks(settings, fragments);
@@ -4244,13 +4323,13 @@ async function applyInstall(options = {}) {
4244
4323
  const currentDestPaths = new Set(sourceFiles.map((f) => f.destPath));
4245
4324
  for (const entry of existingManifest.files) {
4246
4325
  if (currentDestPaths.has(entry.destPath)) continue;
4247
- if (!fs16.pathExistsSync(entry.destPath)) continue;
4326
+ if (!fs17.pathExistsSync(entry.destPath)) continue;
4248
4327
  const content2 = await readText(entry.destPath);
4249
4328
  if (!content2) continue;
4250
4329
  const hasHeader = parseMarkdownHeader(content2) !== void 0;
4251
4330
  const currentHash = hashContent(content2);
4252
4331
  if (hasHeader && currentHash === entry.hash) {
4253
- if (!dryRun) await fs16.remove(entry.destPath);
4332
+ if (!dryRun) await fs17.remove(entry.destPath);
4254
4333
  result.deleted.push(entry.destPath);
4255
4334
  } else {
4256
4335
  warn(`Orphaned file ${entry.destPath} was user-modified \u2014 leaving in place`);
@@ -4418,8 +4497,8 @@ async function runRefresh() {
4418
4497
  }
4419
4498
 
4420
4499
  // src/install/scaffold.ts
4421
- import path22 from "node:path";
4422
- import fs17 from "fs-extra";
4500
+ import path23 from "node:path";
4501
+ import fs18 from "fs-extra";
4423
4502
  async function scaffoldConfigItems(projectRoot, catalogRoot, items, opts = {}) {
4424
4503
  const scaffolded = [];
4425
4504
  const skipped = [];
@@ -4432,27 +4511,27 @@ async function scaffoldConfigItems(projectRoot, catalogRoot, items, opts = {}) {
4432
4511
  skipped.push(name);
4433
4512
  if (outcome === "skipped-exists") skippedExisting.push(name);
4434
4513
  };
4435
- const realRoot = await fs17.realpath(catalogRoot).catch(() => null);
4514
+ const realRoot = await fs18.realpath(catalogRoot).catch(() => null);
4436
4515
  for (const item of items) {
4437
4516
  if (item.type !== "config") continue;
4438
- const sourcePath = path22.resolve(catalogRoot, item.path);
4439
- const rel = path22.relative(catalogRoot, sourcePath);
4440
- const escapes = rel === "" || path22.isAbsolute(rel) || rel.split(/[/\\]/).some((seg) => seg === "..");
4517
+ const sourcePath = path23.resolve(catalogRoot, item.path);
4518
+ const rel = path23.relative(catalogRoot, sourcePath);
4519
+ const escapes = rel === "" || path23.isAbsolute(rel) || rel.split(/[/\\]/).some((seg) => seg === "..");
4441
4520
  if (escapes) {
4442
4521
  warn(`Skipping ${item.id}: path '${item.path}' escapes the catalog root`);
4443
4522
  continue;
4444
4523
  }
4445
- const realSource = await fs17.realpath(sourcePath).catch(() => null);
4524
+ const realSource = await fs18.realpath(sourcePath).catch(() => null);
4446
4525
  if (!realRoot || !realSource) {
4447
4526
  warn(`Skipping ${item.id}: source not found at ${sourcePath}`);
4448
4527
  continue;
4449
4528
  }
4450
- const realRel = path22.relative(realRoot, realSource);
4451
- if (realRel !== "" && (path22.isAbsolute(realRel) || realRel.split(/[/\\]/)[0] === "..")) {
4529
+ const realRel = path23.relative(realRoot, realSource);
4530
+ if (realRel !== "" && (path23.isAbsolute(realRel) || realRel.split(/[/\\]/)[0] === "..")) {
4452
4531
  warn(`Skipping ${item.id}: path '${item.path}' resolves outside the catalog root`);
4453
4532
  continue;
4454
4533
  }
4455
- const stat = await fs17.lstat(sourcePath).catch(() => null);
4534
+ const stat = await fs18.lstat(sourcePath).catch(() => null);
4456
4535
  if (!stat) {
4457
4536
  warn(`Skipping ${item.id}: source not found at ${sourcePath}`);
4458
4537
  continue;
@@ -4462,14 +4541,14 @@ async function scaffoldConfigItems(projectRoot, catalogRoot, items, opts = {}) {
4462
4541
  continue;
4463
4542
  }
4464
4543
  if (stat.isFile()) {
4465
- const filename = path22.basename(sourcePath);
4466
- const dest = path22.join(projectRoot, filename);
4544
+ const filename = path23.basename(sourcePath);
4545
+ const dest = path23.join(projectRoot, filename);
4467
4546
  record(filename, await scaffoldFile(sourcePath, dest, item.id, projectRoot, opts));
4468
4547
  } else if (stat.isDirectory()) {
4469
- const entries = await fs17.readdir(sourcePath);
4548
+ const entries = await fs18.readdir(sourcePath);
4470
4549
  for (const entry of entries) {
4471
- const src = path22.join(sourcePath, entry);
4472
- const dest = path22.join(projectRoot, entry);
4550
+ const src = path23.join(sourcePath, entry);
4551
+ const dest = path23.join(projectRoot, entry);
4473
4552
  record(entry, await scaffoldFile(src, dest, item.id, projectRoot, opts));
4474
4553
  }
4475
4554
  }
@@ -4477,42 +4556,42 @@ async function scaffoldConfigItems(projectRoot, catalogRoot, items, opts = {}) {
4477
4556
  return { scaffolded, skipped, skippedExisting };
4478
4557
  }
4479
4558
  async function scaffoldFile(src, dest, itemId, projectRoot, opts) {
4480
- const destDisplay = path22.relative(projectRoot, dest) || path22.basename(dest);
4481
- const srcStat = await fs17.lstat(src).catch(() => null);
4559
+ const destDisplay = path23.relative(projectRoot, dest) || path23.basename(dest);
4560
+ const srcStat = await fs18.lstat(src).catch(() => null);
4482
4561
  if (!srcStat) {
4483
- warn(`Skipping ${path22.basename(src)}: source not found`);
4562
+ warn(`Skipping ${path23.basename(src)}: source not found`);
4484
4563
  return "skipped-refused";
4485
4564
  }
4486
4565
  if (srcStat.isSymbolicLink()) {
4487
- warn(`Skipping ${path22.basename(src)}: source is a symlink`);
4566
+ warn(`Skipping ${path23.basename(src)}: source is a symlink`);
4488
4567
  return "skipped-refused";
4489
4568
  }
4490
- const exists = await fs17.pathExists(dest);
4569
+ const exists = await fs18.pathExists(dest);
4491
4570
  if (exists && !opts.force) {
4492
4571
  warn(`Skipping ${destDisplay}: already exists \u2014 re-run with --force to overwrite it`);
4493
4572
  return "skipped-exists";
4494
4573
  }
4495
4574
  if (opts.dryRun) {
4496
- log(`[dry-run] would ${exists ? "overwrite" : "create"} ${path22.basename(dest)} (${itemId})`);
4575
+ log(`[dry-run] would ${exists ? "overwrite" : "create"} ${path23.basename(dest)} (${itemId})`);
4497
4576
  return "scaffolded";
4498
4577
  }
4499
4578
  if (exists && opts.force) {
4500
- await fs17.remove(dest);
4579
+ await fs18.remove(dest);
4501
4580
  }
4502
- await fs17.ensureDir(path22.dirname(dest));
4503
- await fs17.copy(src, dest, {
4581
+ await fs18.ensureDir(path23.dirname(dest));
4582
+ await fs18.copy(src, dest, {
4504
4583
  overwrite: true,
4505
4584
  dereference: false,
4506
4585
  // A vanished/unreadable entry mid-walk must skip that path, not crash the copy.
4507
4586
  filter: (s) => {
4508
4587
  try {
4509
- return !fs17.lstatSync(s).isSymbolicLink();
4588
+ return !fs18.lstatSync(s).isSymbolicLink();
4510
4589
  } catch {
4511
4590
  return false;
4512
4591
  }
4513
4592
  }
4514
4593
  });
4515
- log(`${exists ? "Overwrote" : "Created"} ${path22.basename(dest)} (${itemId})`);
4594
+ log(`${exists ? "Overwrote" : "Created"} ${path23.basename(dest)} (${itemId})`);
4516
4595
  return "scaffolded";
4517
4596
  }
4518
4597
 
@@ -4569,8 +4648,8 @@ async function runScan(options) {
4569
4648
  }
4570
4649
 
4571
4650
  // src/commands/undo.ts
4572
- import path23 from "node:path";
4573
- import fs18 from "fs-extra";
4651
+ import path24 from "node:path";
4652
+ import fs19 from "fs-extra";
4574
4653
 
4575
4654
  // src/claude/managed-paths.ts
4576
4655
  var PROJECT_MANAGED_CLAUDE_REL = ["rules/haus.md", "commands/haus-doctor.md"];
@@ -4587,74 +4666,74 @@ async function collectManagedPaths(root) {
4587
4666
  const lock = await readJson(hausPath(root, "haus.lock.json"));
4588
4667
  for (const row of lock ?? []) {
4589
4668
  for (const rel of row.paths ?? []) {
4590
- paths.add(path23.resolve(root, rel));
4669
+ paths.add(path24.resolve(root, rel));
4591
4670
  }
4592
4671
  }
4593
4672
  const existing = [];
4594
4673
  for (const abs of paths) {
4595
- if (await fs18.pathExists(abs)) existing.push(abs);
4674
+ if (await fs19.pathExists(abs)) existing.push(abs);
4596
4675
  }
4597
4676
  return existing;
4598
4677
  }
4599
4678
  async function settingsHasHausContent(root) {
4600
4679
  const settingsPath = claudePath(root, "settings.json");
4601
- if (!await fs18.pathExists(settingsPath)) return false;
4680
+ if (!await fs19.pathExists(settingsPath)) return false;
4602
4681
  const settings = await readProjectSettings(root);
4603
4682
  return settings._haus != null;
4604
4683
  }
4605
4684
  async function claudeMdHasHausBlock(root) {
4606
- const filePath = path23.join(root, "CLAUDE.md");
4607
- if (!await fs18.pathExists(filePath)) return false;
4608
- const text = await fs18.readFile(filePath, "utf8");
4685
+ const filePath = path24.join(root, "CLAUDE.md");
4686
+ if (!await fs19.pathExists(filePath)) return false;
4687
+ const text = await fs19.readFile(filePath, "utf8");
4609
4688
  return text.includes(BLOCK_BEGIN);
4610
4689
  }
4611
4690
  async function stripProjectSettings(root) {
4612
4691
  const settingsPath = claudePath(root, "settings.json");
4613
- if (!await fs18.pathExists(settingsPath)) return false;
4692
+ if (!await fs19.pathExists(settingsPath)) return false;
4614
4693
  let settings = await readProjectSettings(root);
4615
4694
  settings = stripHausHooks(stripHausAsk(stripHausAllow(stripHausDeny(settings))));
4616
4695
  const hasContent = Object.keys(settings).length > 0;
4617
4696
  if (hasContent) {
4618
4697
  await writeProjectSettings(root, settings);
4619
- log(`Stripped haus rules from ${path23.relative(root, settingsPath)} (user settings preserved).`);
4698
+ log(`Stripped haus rules from ${path24.relative(root, settingsPath)} (user settings preserved).`);
4620
4699
  return true;
4621
4700
  }
4622
- await fs18.remove(settingsPath);
4623
- log(`Removed ${path23.relative(root, settingsPath)} (no user-owned settings remained).`);
4701
+ await fs19.remove(settingsPath);
4702
+ log(`Removed ${path24.relative(root, settingsPath)} (no user-owned settings remained).`);
4624
4703
  return true;
4625
4704
  }
4626
4705
  async function stripRootClaudeMd(root) {
4627
- const filePath = path23.join(root, "CLAUDE.md");
4628
- if (!await fs18.pathExists(filePath)) return false;
4629
- const prev = await fs18.readFile(filePath, "utf8");
4706
+ const filePath = path24.join(root, "CLAUDE.md");
4707
+ if (!await fs19.pathExists(filePath)) return false;
4708
+ const prev = await fs19.readFile(filePath, "utf8");
4630
4709
  if (!prev.includes(BLOCK_BEGIN)) return false;
4631
4710
  const next = stripHausBlock(prev);
4632
4711
  if (next.length === 0) {
4633
- await fs18.remove(filePath);
4712
+ await fs19.remove(filePath);
4634
4713
  log("Removed CLAUDE.md (only contained haus import block).");
4635
4714
  } else {
4636
- await fs18.writeFile(filePath, next, "utf8");
4715
+ await fs19.writeFile(filePath, next, "utf8");
4637
4716
  log("Removed haus import block from CLAUDE.md (user content preserved).");
4638
4717
  }
4639
4718
  return true;
4640
4719
  }
4641
4720
  async function pruneDirIfEmpty(dir) {
4642
- if (!await fs18.pathExists(dir)) return;
4643
- const entries = await fs18.readdir(dir);
4644
- if (entries.length === 0) await fs18.remove(dir);
4721
+ if (!await fs19.pathExists(dir)) return;
4722
+ const entries = await fs19.readdir(dir);
4723
+ if (entries.length === 0) await fs19.remove(dir);
4645
4724
  }
4646
4725
  async function backupManagedFilesBeforeUndo(root, managedAbsPaths) {
4647
4726
  if (managedAbsPaths.length === 0) return;
4648
4727
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
4649
4728
  const backupRoot = hausPath(root, "backups", `undo-${stamp}`);
4650
4729
  for (const abs of managedAbsPaths) {
4651
- if (!await fs18.pathExists(abs)) continue;
4652
- const rel = path23.relative(root, abs);
4653
- const backupPath = path23.join(backupRoot, rel);
4654
- await fs18.ensureDir(path23.dirname(backupPath));
4655
- await fs18.copy(abs, backupPath);
4730
+ if (!await fs19.pathExists(abs)) continue;
4731
+ const rel = path24.relative(root, abs);
4732
+ const backupPath = path24.join(backupRoot, rel);
4733
+ await fs19.ensureDir(path24.dirname(backupPath));
4734
+ await fs19.copy(abs, backupPath);
4656
4735
  }
4657
- log(`Backed up ${managedAbsPaths.length} managed file(s) to ${path23.relative(root, backupRoot)}.`);
4736
+ log(`Backed up ${managedAbsPaths.length} managed file(s) to ${path24.relative(root, backupRoot)}.`);
4658
4737
  }
4659
4738
  async function runUndo(options) {
4660
4739
  const root = process.cwd();
@@ -4665,7 +4744,7 @@ async function runUndo(options) {
4665
4744
  log("Nothing to remove: no haus-managed files found in this directory.");
4666
4745
  return;
4667
4746
  }
4668
- const relTargets = managed.map((p) => path23.relative(root, p));
4747
+ const relTargets = managed.map((p) => path24.relative(root, p));
4669
4748
  const summaryParts = [...relTargets];
4670
4749
  if (stripSettings) summaryParts.push(".claude/settings.json (haus rules only)");
4671
4750
  if (stripClaudeMd) summaryParts.push("CLAUDE.md (haus import block only)");
@@ -4682,9 +4761,9 @@ User-owned .claude/ files will be preserved.`
4682
4761
  }
4683
4762
  await backupManagedFilesBeforeUndo(root, managed);
4684
4763
  for (const abs of managed) {
4685
- if (!await fs18.pathExists(abs)) continue;
4686
- await fs18.remove(abs);
4687
- log(`Removed ${path23.relative(root, abs)}`);
4764
+ if (!await fs19.pathExists(abs)) continue;
4765
+ await fs19.remove(abs);
4766
+ log(`Removed ${path24.relative(root, abs)}`);
4688
4767
  }
4689
4768
  if (stripSettings) await stripProjectSettings(root);
4690
4769
  if (stripClaudeMd) await stripRootClaudeMd(root);
@@ -4695,8 +4774,8 @@ User-owned .claude/ files will be preserved.`
4695
4774
 
4696
4775
  // src/install/uninstall.ts
4697
4776
  import crypto3 from "node:crypto";
4698
- import path24 from "node:path";
4699
- import fs19 from "fs-extra";
4777
+ import path25 from "node:path";
4778
+ import fs20 from "fs-extra";
4700
4779
  async function runUninstall(options = {}) {
4701
4780
  const { force = false } = options;
4702
4781
  const manifest = await readManifest();
@@ -4706,7 +4785,7 @@ async function runUninstall(options = {}) {
4706
4785
  return result;
4707
4786
  }
4708
4787
  for (const entry of manifest.files) {
4709
- const exists = fs19.pathExistsSync(entry.destPath);
4788
+ const exists = fs20.pathExistsSync(entry.destPath);
4710
4789
  if (!exists) continue;
4711
4790
  const content2 = await readText(entry.destPath);
4712
4791
  if (content2 === void 0) continue;
@@ -4724,22 +4803,22 @@ async function runUninstall(options = {}) {
4724
4803
  result.skipped.push(entry.destPath);
4725
4804
  continue;
4726
4805
  }
4727
- await fs19.remove(entry.destPath);
4728
- await pruneEmptyDir(path24.dirname(entry.destPath));
4806
+ await fs20.remove(entry.destPath);
4807
+ await pruneEmptyDir(path25.dirname(entry.destPath));
4729
4808
  result.deleted.push(entry.destPath);
4730
4809
  }
4731
4810
  const settings = await readSettings();
4732
4811
  const stripped = stripHausHooks(stripHausAsk(stripHausAllow(stripHausDeny(settings))));
4733
4812
  await writeSettings(stripped);
4734
4813
  result.hooksStripped = true;
4735
- const hausDir = path24.join(globalClaudeDir(), "haus");
4814
+ const hausDir = path25.join(globalClaudeDir(), "haus");
4736
4815
  const manifestPath2 = hausManifestPath();
4737
- if (fs19.pathExistsSync(manifestPath2)) {
4738
- await fs19.remove(manifestPath2);
4816
+ if (fs20.pathExistsSync(manifestPath2)) {
4817
+ await fs20.remove(manifestPath2);
4739
4818
  }
4740
- if (fs19.pathExistsSync(hausDir)) {
4741
- const remaining = await fs19.readdir(hausDir);
4742
- if (remaining.length === 0) await fs19.remove(hausDir);
4819
+ if (fs20.pathExistsSync(hausDir)) {
4820
+ const remaining = await fs20.readdir(hausDir);
4821
+ if (remaining.length === 0) await fs20.remove(hausDir);
4743
4822
  }
4744
4823
  return result;
4745
4824
  }
@@ -4770,12 +4849,12 @@ async function runUninstallCommand(options) {
4770
4849
  }
4771
4850
 
4772
4851
  // src/commands/update.ts
4773
- import path26 from "node:path";
4852
+ import path27 from "node:path";
4774
4853
 
4775
4854
  // src/claude/refresh-project.ts
4776
- import fs20 from "fs-extra";
4855
+ import fs21 from "fs-extra";
4777
4856
  async function isHausProject(root) {
4778
- if (await fs20.pathExists(hausPath(root, "recommendation.json"))) return true;
4857
+ if (await fs21.pathExists(hausPath(root, "recommendation.json"))) return true;
4779
4858
  const result = await readJsonDetailed(claudePath(root, "settings.json"));
4780
4859
  if (result.status === "ok") return result.value._haus != null;
4781
4860
  return false;
@@ -4810,7 +4889,7 @@ function summarizeLockDiff(before, after) {
4810
4889
 
4811
4890
  // src/update/lockfile.ts
4812
4891
  import { mkdir, readFile as readFile2, copyFile } from "node:fs/promises";
4813
- import path25 from "node:path";
4892
+ import path26 from "node:path";
4814
4893
  async function checkLock(root) {
4815
4894
  const lock = await readJson(hausPath(root, "haus.lock.json")) ?? [];
4816
4895
  const hasValidVersions = lock.every(
@@ -4841,7 +4920,7 @@ async function applyLock(root) {
4841
4920
  try {
4842
4921
  const backupDir = hausPath(root, "backups");
4843
4922
  await mkdir(backupDir, { recursive: true });
4844
- await copyFile(lockPath, path25.join(backupDir, `haus.lock.${Date.now()}.json`));
4923
+ await copyFile(lockPath, path26.join(backupDir, `haus.lock.${Date.now()}.json`));
4845
4924
  } catch {
4846
4925
  }
4847
4926
  const enriched = await Promise.all(
@@ -4863,7 +4942,7 @@ function diffLock(before, after) {
4863
4942
  }
4864
4943
  async function hasLocalOverrides(root) {
4865
4944
  try {
4866
- await readFile2(path25.join(root, ".claude", "settings.json"), "utf8");
4945
+ await readFile2(path26.join(root, ".claude", "settings.json"), "utf8");
4867
4946
  return true;
4868
4947
  } catch {
4869
4948
  return false;
@@ -4875,7 +4954,7 @@ var NPM_PACKAGE_NAME2 = "@haus-tech/haus-workflow";
4875
4954
  async function runUpdate(options) {
4876
4955
  const root = process.cwd();
4877
4956
  if (options.check) {
4878
- const pkgJson2 = await readJson(path26.join(packageRoot(), "package.json"));
4957
+ const pkgJson2 = await readJson(path27.join(packageRoot(), "package.json"));
4879
4958
  const currentVersion2 = pkgJson2?.version ?? "0.0.0";
4880
4959
  const [status, npmVersion, latestCatalogTag, globalInstallDrift] = await Promise.all([
4881
4960
  checkLock(root),
@@ -4905,7 +4984,7 @@ async function runUpdate(options) {
4905
4984
  if (status.driftCount > 0) process.exitCode = 1;
4906
4985
  return;
4907
4986
  }
4908
- const pkgJson = await readJson(path26.join(packageRoot(), "package.json"));
4987
+ const pkgJson = await readJson(path27.join(packageRoot(), "package.json"));
4909
4988
  const currentVersion = pkgJson?.version ?? "0.0.0";
4910
4989
  const npmStatus = await fetchNpmVersionStatus(currentVersion);
4911
4990
  if (npmStatus.updateAvailable && npmStatus.latest !== null) {
@@ -4982,16 +5061,16 @@ async function detectGlobalInstallDrift() {
4982
5061
  }
4983
5062
 
4984
5063
  // src/commands/validate-catalog.ts
4985
- import path28 from "node:path";
5064
+ import path29 from "node:path";
4986
5065
 
4987
5066
  // src/catalog/validate-core.ts
4988
- import fs21 from "node:fs";
4989
- import path27 from "node:path";
5067
+ import fs22 from "node:fs";
5068
+ import path28 from "node:path";
4990
5069
  var ITEM_SEMVER_RE = /^\d+\.\d+\.\d+$/;
4991
5070
  var MANIFEST_SEMVER_RE = /^\d+\.\d+\.\d+(?:-[\w.-]+)?(?:\+[\w.-]+)?$/;
4992
5071
  function isSafeCatalogPath2(itemPath) {
4993
- if (!itemPath || path27.isAbsolute(itemPath) || itemPath.includes("\\")) return false;
4994
- const normalized = path27.normalize(itemPath);
5072
+ if (!itemPath || path28.isAbsolute(itemPath) || itemPath.includes("\\")) return false;
5073
+ const normalized = path28.normalize(itemPath);
4995
5074
  return !normalized.startsWith("..") && !normalized.includes("/..");
4996
5075
  }
4997
5076
  function auditForbiddenStacks(items) {
@@ -5103,8 +5182,8 @@ function checkRequiredFrontmatter(text, label) {
5103
5182
  return failures;
5104
5183
  }
5105
5184
  function auditTemplateContent(manifestDir, absPath, itemId) {
5106
- const rel = path27.relative(manifestDir, absPath);
5107
- const text = fs21.readFileSync(absPath, "utf8");
5185
+ const rel = path28.relative(manifestDir, absPath);
5186
+ const text = fs22.readFileSync(absPath, "utf8");
5108
5187
  const failures = [];
5109
5188
  const lines = text.split(/\r?\n/);
5110
5189
  for (let i = 0; i < lines.length; i++) {
@@ -5124,44 +5203,44 @@ function auditShippedFiles(manifestDir, items) {
5124
5203
  const failures = [];
5125
5204
  for (const item of items) {
5126
5205
  if (!item.path || !isSafeCatalogPath2(item.path)) continue;
5127
- const absPath = path27.join(manifestDir, item.path);
5206
+ const absPath = path28.join(manifestDir, item.path);
5128
5207
  if (item.type === "skill") {
5129
- const skillMd = path27.join(absPath, "SKILL.md");
5130
- if (!fs21.existsSync(skillMd)) {
5131
- failures.push(`${item.id}: missing ${path27.relative(manifestDir, skillMd)}`);
5208
+ const skillMd = path28.join(absPath, "SKILL.md");
5209
+ if (!fs22.existsSync(skillMd)) {
5210
+ failures.push(`${item.id}: missing ${path28.relative(manifestDir, skillMd)}`);
5132
5211
  continue;
5133
5212
  }
5134
- const text = fs21.readFileSync(skillMd, "utf8");
5213
+ const text = fs22.readFileSync(skillMd, "utf8");
5135
5214
  failures.push(...checkRequiredFrontmatter(text, `${item.id}: SKILL.md`));
5136
5215
  failures.push(
5137
- ...auditForbiddenTagsInText(text, `${item.id}: ${path27.relative(manifestDir, skillMd)}`)
5216
+ ...auditForbiddenTagsInText(text, `${item.id}: ${path28.relative(manifestDir, skillMd)}`)
5138
5217
  );
5139
5218
  } else if (item.type === "agent") {
5140
- if (!fs21.existsSync(absPath)) {
5219
+ if (!fs22.existsSync(absPath)) {
5141
5220
  failures.push(`${item.id}: missing agent file ${item.path}`);
5142
5221
  continue;
5143
5222
  }
5144
- const text = fs21.readFileSync(absPath, "utf8");
5145
- const rel = path27.relative(manifestDir, absPath);
5223
+ const text = fs22.readFileSync(absPath, "utf8");
5224
+ const rel = path28.relative(manifestDir, absPath);
5146
5225
  failures.push(...checkRequiredFrontmatter(text, `${item.id}: ${rel}`));
5147
5226
  failures.push(...auditForbiddenTagsInText(text, `${item.id}: ${rel}`));
5148
5227
  } else if (item.type === "template") {
5149
- if (!fs21.existsSync(absPath)) {
5228
+ if (!fs22.existsSync(absPath)) {
5150
5229
  failures.push(`${item.id}: missing template file ${item.path}`);
5151
5230
  continue;
5152
5231
  }
5153
5232
  failures.push(...auditTemplateContent(manifestDir, absPath, item.id));
5154
5233
  } else if (item.type === "command") {
5155
- if (!fs21.existsSync(absPath)) {
5234
+ if (!fs22.existsSync(absPath)) {
5156
5235
  failures.push(`${item.id}: missing command file ${item.path}`);
5157
5236
  continue;
5158
5237
  }
5159
- const text = fs21.readFileSync(absPath, "utf8");
5160
- const rel = path27.relative(manifestDir, absPath);
5238
+ const text = fs22.readFileSync(absPath, "utf8");
5239
+ const rel = path28.relative(manifestDir, absPath);
5161
5240
  failures.push(...checkRequiredFrontmatter(text, `${item.id}: ${rel}`));
5162
5241
  failures.push(...auditTemplateContent(manifestDir, absPath, item.id));
5163
5242
  } else if (item.type === "config") {
5164
- if (!fs21.existsSync(absPath)) {
5243
+ if (!fs22.existsSync(absPath)) {
5165
5244
  failures.push(`${item.id}: missing config path ${item.path}`);
5166
5245
  }
5167
5246
  }
@@ -5175,8 +5254,8 @@ var DIR_ITEM_TYPE = {
5175
5254
  commands: "command"
5176
5255
  };
5177
5256
  function walkMd(dir, fn) {
5178
- for (const entry of fs21.readdirSync(dir, { withFileTypes: true })) {
5179
- const full = path27.join(dir, entry.name);
5257
+ for (const entry of fs22.readdirSync(dir, { withFileTypes: true })) {
5258
+ const full = path28.join(dir, entry.name);
5180
5259
  if (entry.isDirectory()) walkMd(full, fn);
5181
5260
  else if (entry.name.endsWith(".md")) fn(full);
5182
5261
  }
@@ -5185,12 +5264,12 @@ function auditMarkdownContent(manifestDir, items) {
5185
5264
  const failures = [];
5186
5265
  const pathSourceMap = buildItemPathSourceMap(items);
5187
5266
  for (const dir of ["skills", "agents", "templates", "commands"]) {
5188
- const abs = path27.join(manifestDir, dir);
5189
- if (!fs21.existsSync(abs)) continue;
5267
+ const abs = path28.join(manifestDir, dir);
5268
+ if (!fs22.existsSync(abs)) continue;
5190
5269
  const dirType = DIR_ITEM_TYPE[dir] ?? "";
5191
5270
  walkMd(abs, (file) => {
5192
- const text = fs21.readFileSync(file, "utf8");
5193
- const rel = path27.relative(manifestDir, file);
5271
+ const text = fs22.readFileSync(file, "utf8");
5272
+ const rel = path28.relative(manifestDir, file);
5194
5273
  const norm = rel.replace(/\\/g, "/");
5195
5274
  const source = resolveMarkdownItemSource(norm, pathSourceMap);
5196
5275
  const checkNonTsxNpx = !isNpxTsxOnlyExempt(dirType, source);
@@ -5227,8 +5306,8 @@ async function runValidateCatalog(manifestPath2) {
5227
5306
  process.exitCode = 1;
5228
5307
  return;
5229
5308
  }
5230
- const abs = path28.resolve(process.cwd(), manifestPath2);
5231
- const manifestDir = path28.dirname(abs);
5309
+ const abs = path29.resolve(process.cwd(), manifestPath2);
5310
+ const manifestDir = path29.dirname(abs);
5232
5311
  const data = await readJson(abs);
5233
5312
  if (!data?.items) {
5234
5313
  error(`Could not read catalog manifest at ${abs}`);
@@ -5246,7 +5325,7 @@ async function runValidateCatalog(manifestPath2) {
5246
5325
 
5247
5326
  // src/commands/workspace.ts
5248
5327
  import { existsSync as existsSync5, statSync as statSync2 } from "node:fs";
5249
- import path35 from "node:path";
5328
+ import path36 from "node:path";
5250
5329
 
5251
5330
  // src/commands/workspace/aggregate.ts
5252
5331
  async function writeWorkspaceArtifacts(workspaceRoot, repos, relationships = []) {
@@ -5300,7 +5379,7 @@ ${summaries.map(
5300
5379
  }
5301
5380
 
5302
5381
  // src/commands/workspace/config.ts
5303
- import path29 from "node:path";
5382
+ import path30 from "node:path";
5304
5383
  import YAML from "yaml";
5305
5384
  var WORKSPACE_FILE = "haus.workspace.yaml";
5306
5385
  function parseWorkspaceConfig(text) {
@@ -5323,11 +5402,11 @@ function parseWorkspaceConfig(text) {
5323
5402
  };
5324
5403
  }
5325
5404
  async function readWorkspaceConfig(workspaceRoot) {
5326
- return parseWorkspaceConfig(await readText(path29.join(workspaceRoot, WORKSPACE_FILE)));
5405
+ return parseWorkspaceConfig(await readText(path30.join(workspaceRoot, WORKSPACE_FILE)));
5327
5406
  }
5328
5407
 
5329
5408
  // src/commands/workspace/discover.ts
5330
- import path30 from "node:path";
5409
+ import path31 from "node:path";
5331
5410
  import fg4 from "fast-glob";
5332
5411
  import YAML2 from "yaml";
5333
5412
  var DEFAULT_MAX_DEPTH = 3;
@@ -5355,8 +5434,8 @@ async function discoverRepos(workspaceRoot, maxDepth = DEFAULT_MAX_DEPTH) {
5355
5434
  const gitDirs = /* @__PURE__ */ new Set();
5356
5435
  const manifestDirs = /* @__PURE__ */ new Set();
5357
5436
  for (const match of matches) {
5358
- const base = path30.posix.basename(match);
5359
- const dir = path30.posix.dirname(match);
5437
+ const base = path31.posix.basename(match);
5438
+ const dir = path31.posix.dirname(match);
5360
5439
  const owner = dir === "." ? "." : dir;
5361
5440
  if (base === ".git") gitDirs.add(owner);
5362
5441
  else manifestDirs.add(owner);
@@ -5372,9 +5451,9 @@ async function discoverRepos(workspaceRoot, maxDepth = DEFAULT_MAX_DEPTH) {
5372
5451
  }
5373
5452
  repoRoots.sort((a, b) => a.localeCompare(b));
5374
5453
  return mapWithConcurrency(repoRoots, async (relDir) => {
5375
- const absDir = path30.resolve(workspaceRoot, relDir);
5376
- const pkg = await readJson(path30.join(absDir, "package.json"));
5377
- const name = typeof pkg?.name === "string" && pkg.name.length > 0 ? pkg.name : path30.basename(relDir === "." ? workspaceRoot : absDir);
5454
+ const absDir = path31.resolve(workspaceRoot, relDir);
5455
+ const pkg = await readJson(path31.join(absDir, "package.json"));
5456
+ const name = typeof pkg?.name === "string" && pkg.name.length > 0 ? pkg.name : path31.basename(relDir === "." ? workspaceRoot : absDir);
5378
5457
  let role = "auto";
5379
5458
  try {
5380
5459
  const scan = await scanProject(absDir);
@@ -5417,7 +5496,7 @@ function renderWorkspaceYaml(config) {
5417
5496
  });
5418
5497
  }
5419
5498
  async function runDiscover(workspaceRoot, opts = {}) {
5420
- const yamlPath = path30.join(workspaceRoot, "haus.workspace.yaml");
5499
+ const yamlPath = path31.join(workspaceRoot, "haus.workspace.yaml");
5421
5500
  const existingText = await readText(yamlPath);
5422
5501
  const existing = parseWorkspaceConfig(existingText);
5423
5502
  if (existingText && !existing) {
@@ -5451,18 +5530,18 @@ async function runDiscover(workspaceRoot, opts = {}) {
5451
5530
 
5452
5531
  // src/commands/workspace/doctor.ts
5453
5532
  import { existsSync as existsSync3 } from "node:fs";
5454
- import path32 from "node:path";
5533
+ import path33 from "node:path";
5455
5534
 
5456
5535
  // src/commands/workspace/manifest.ts
5457
5536
  import { readFileSync as readFileSync3 } from "node:fs";
5458
- import path31 from "node:path";
5537
+ import path32 from "node:path";
5459
5538
  var MANIFEST_FILE = "workspace.manifest.json";
5460
5539
  function manifestPath(workspaceRoot) {
5461
5540
  return hausPath(workspaceRoot, MANIFEST_FILE);
5462
5541
  }
5463
5542
  function hausVersion() {
5464
5543
  try {
5465
- const pkg = JSON.parse(readFileSync3(path31.join(packageRoot(), "package.json"), "utf8"));
5544
+ const pkg = JSON.parse(readFileSync3(path32.join(packageRoot(), "package.json"), "utf8"));
5466
5545
  return pkg.version ?? "0.0.0";
5467
5546
  } catch {
5468
5547
  return "0.0.0";
@@ -5528,7 +5607,7 @@ async function runWorkspaceDoctor(workspaceRoot, opts = {}) {
5528
5607
  }
5529
5608
  const manifestByName = new Map(manifest.repos.map((r) => [r.name, r]));
5530
5609
  for (const repo of config.repos) {
5531
- const repoRoot = path32.resolve(workspaceRoot, repo.path);
5610
+ const repoRoot = path33.resolve(workspaceRoot, repo.path);
5532
5611
  const entry = manifestByName.get(repo.name);
5533
5612
  if (!entry) {
5534
5613
  flag({
@@ -5602,11 +5681,11 @@ function emit(args) {
5602
5681
 
5603
5682
  // src/commands/workspace/setup.ts
5604
5683
  import { existsSync as existsSync4, statSync } from "node:fs";
5605
- import path34 from "node:path";
5684
+ import path35 from "node:path";
5606
5685
 
5607
5686
  // src/claude/write-workspace-claude-md.ts
5608
- import path33 from "node:path";
5609
- import fs22 from "fs-extra";
5687
+ import path34 from "node:path";
5688
+ import fs23 from "fs-extra";
5610
5689
  function buildWorkspaceImportBlock(client, members) {
5611
5690
  const memberLines = members.map((m) => `- ${m.name} (${m.path})`);
5612
5691
  const body = [
@@ -5624,8 +5703,8 @@ ${BLOCK_END}`;
5624
5703
  async function writeWorkspaceClaudeMd(workspaceRoot, opts) {
5625
5704
  const block = buildWorkspaceImportBlock(opts.client, opts.members);
5626
5705
  const dryRun = opts.dryRun ?? false;
5627
- const filePath = opts.collision ? hausPath(workspaceRoot, "WORKSPACE.md") : path33.join(workspaceRoot, "CLAUDE.md");
5628
- const prev = await fs22.pathExists(filePath) ? await fs22.readFile(filePath, "utf8") : "";
5706
+ const filePath = opts.collision ? hausPath(workspaceRoot, "WORKSPACE.md") : path34.join(workspaceRoot, "CLAUDE.md");
5707
+ const prev = await fs23.pathExists(filePath) ? await fs23.readFile(filePath, "utf8") : "";
5629
5708
  const next = opts.collision ? `${block}
5630
5709
  ` : injectHausBlock(prev, block);
5631
5710
  const printable = displayPath(workspaceRoot, filePath);
@@ -5650,20 +5729,20 @@ async function writeWorkspaceClaudeMd(workspaceRoot, opts) {
5650
5729
 
5651
5730
  // src/commands/workspace/setup.ts
5652
5731
  function resolveWorkspaceRoot(start = process.cwd()) {
5653
- let dir = path34.resolve(start);
5732
+ let dir = path35.resolve(start);
5654
5733
  for (; ; ) {
5655
- if (existsSync4(path34.join(dir, WORKSPACE_FILE))) return dir;
5656
- const parent = path34.dirname(dir);
5657
- if (parent === dir) return path34.resolve(start);
5734
+ if (existsSync4(path35.join(dir, WORKSPACE_FILE))) return dir;
5735
+ const parent = path35.dirname(dir);
5736
+ if (parent === dir) return path35.resolve(start);
5658
5737
  dir = parent;
5659
5738
  }
5660
5739
  }
5661
5740
  function isRootRepo(workspaceRoot, repoPath) {
5662
- return path34.resolve(workspaceRoot, repoPath) === path34.resolve(workspaceRoot);
5741
+ return path35.resolve(workspaceRoot, repoPath) === path35.resolve(workspaceRoot);
5663
5742
  }
5664
5743
  async function runWorkspaceSetup(workspaceRoot, options = {}) {
5665
5744
  const apply = options.write ?? false;
5666
- const configText = await readText(path34.join(workspaceRoot, WORKSPACE_FILE));
5745
+ const configText = await readText(path35.join(workspaceRoot, WORKSPACE_FILE));
5667
5746
  if (!configText) {
5668
5747
  error(`Missing ${WORKSPACE_FILE}. Run \`haus workspace discover\` or \`init\` first.`);
5669
5748
  process.exitCode = 1;
@@ -5680,7 +5759,7 @@ async function runWorkspaceSetup(workspaceRoot, options = {}) {
5680
5759
  const statuses = [];
5681
5760
  const aggregateInputs = [];
5682
5761
  for (const repo of repos) {
5683
- const repoRoot = path34.resolve(workspaceRoot, repo.path);
5762
+ const repoRoot = path35.resolve(workspaceRoot, repo.path);
5684
5763
  log(`
5685
5764
  \u2192 ${repo.name} (${repo.path})`);
5686
5765
  try {
@@ -5748,7 +5827,7 @@ async function runWorkspaceSetup(workspaceRoot, options = {}) {
5748
5827
  const status = statusByName.get(repo.name);
5749
5828
  const role = repo.role ?? status?.roles?.[0] ?? "auto";
5750
5829
  if (status?.status === "ok") {
5751
- const lock = await checkLock(path34.resolve(workspaceRoot, repo.path));
5830
+ const lock = await checkLock(path35.resolve(workspaceRoot, repo.path));
5752
5831
  manifestRepos.push({
5753
5832
  name: repo.name,
5754
5833
  path: repo.path,
@@ -5828,7 +5907,7 @@ relationships: []
5828
5907
  log("Workspace initialized.");
5829
5908
  }
5830
5909
  async function scanWorkspace(workspaceRoot, opts) {
5831
- const configText = await readText(path35.join(workspaceRoot, WORKSPACE_FILE));
5910
+ const configText = await readText(path36.join(workspaceRoot, WORKSPACE_FILE));
5832
5911
  if (!configText) {
5833
5912
  error(`Missing ${WORKSPACE_FILE}. Run \`haus workspace discover\` or \`init\` first.`);
5834
5913
  process.exitCode = 1;
@@ -5849,7 +5928,7 @@ async function scanWorkspace(workspaceRoot, opts) {
5849
5928
  }
5850
5929
  const inputs = [];
5851
5930
  for (const repo of config.repos) {
5852
- const repoRoot = path35.resolve(workspaceRoot, repo.path);
5931
+ const repoRoot = path36.resolve(workspaceRoot, repo.path);
5853
5932
  if (!existsSync5(repoRoot) || !statSync2(repoRoot).isDirectory()) {
5854
5933
  throw new Error(`Repo path is not a directory: ${repo.path}`);
5855
5934
  }
@@ -5899,7 +5978,7 @@ async function runWorkspace(action, options = {}) {
5899
5978
  // src/cli.ts
5900
5979
  function cliVersion() {
5901
5980
  try {
5902
- const pkgPath = path36.join(packageRoot(), "package.json");
5981
+ const pkgPath = path37.join(packageRoot(), "package.json");
5903
5982
  const pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
5904
5983
  return pkg.version ?? "0.0.0";
5905
5984
  } catch {
@@ -5909,7 +5988,7 @@ function cliVersion() {
5909
5988
  var program = new Command();
5910
5989
  function validateRuntimeNodeVersion() {
5911
5990
  try {
5912
- const pkgPath = path36.join(packageRoot(), "package.json");
5991
+ const pkgPath = path37.join(packageRoot(), "package.json");
5913
5992
  const pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
5914
5993
  const requiredRange = pkg.engines?.node;
5915
5994
  if (requiredRange && !satisfiesVersion(process.version, requiredRange)) {