@haus-tech/haus-workflow 0.22.1 → 0.23.0

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,13 +2,13 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import { readFileSync as readFileSync4 } from "fs";
5
- import path35 from "path";
5
+ import path36 from "path";
6
6
  import { Command } from "commander";
7
7
 
8
8
  // src/commands/apply.ts
9
- import path13 from "path";
9
+ import path14 from "path";
10
10
  import checkbox from "@inquirer/checkbox";
11
- import fs12 from "fs-extra";
11
+ import fs13 from "fs-extra";
12
12
 
13
13
  // src/catalog/remote-catalog.ts
14
14
  import os from "os";
@@ -86,6 +86,8 @@ var error = (msg, ...args) => {
86
86
 
87
87
  // src/catalog/constants.ts
88
88
  var CATALOG_REPO_URL = "https://raw.githubusercontent.com/wearehaustech/haus-workflow-catalog";
89
+ var CATALOG_GITHUB_API_URL = "https://api.github.com/repos/WeAreHausTech/haus-workflow-catalog";
90
+ var SUPERPOWERS_SHARED_CATALOG_REL = "skills/superpowers/shared";
89
91
  var CATALOG_REF = process.env.HAUS_CATALOG_REF;
90
92
  var CATALOG_CACHE_SUBDIR = ".claude/haus/catalog-cache";
91
93
 
@@ -405,6 +407,7 @@ function getCacheDir() {
405
407
  return process.env["HAUS_CATALOG_CACHE_DIR_OVERRIDE"] ?? path2.join(os.homedir(), CATALOG_CACHE_SUBDIR);
406
408
  }
407
409
  var cachedCatalogRef;
410
+ var cachedBlobPaths;
408
411
  function getResolvedCatalogRef() {
409
412
  return cachedCatalogRef ?? process.env["HAUS_CATALOG_REF"] ?? "main";
410
413
  }
@@ -436,6 +439,15 @@ async function fetchText(url) {
436
439
  return null;
437
440
  }
438
441
  }
442
+ async function fetchBytes(url) {
443
+ try {
444
+ const res = await fetch(url, { signal: AbortSignal.timeout(1e4) });
445
+ if (!res.ok) return null;
446
+ return Buffer.from(await res.arrayBuffer());
447
+ } catch {
448
+ return null;
449
+ }
450
+ }
439
451
  async function fetchRemoteManifest() {
440
452
  const base = await remoteBase();
441
453
  const text = await fetchText(`${base}/manifest.json`);
@@ -480,29 +492,246 @@ function isSafeCatalogPath(itemPath) {
480
492
  const normalized = path2.normalize(itemPath);
481
493
  return !normalized.startsWith("..") && !normalized.includes("/..");
482
494
  }
495
+ function isSafeRelativeFilePath(rel) {
496
+ if (!rel || rel.startsWith("/") || rel.includes("\\") || rel.includes("//")) return false;
497
+ if (path2.isAbsolute(rel)) return false;
498
+ const normalized = path2.posix.normalize(rel.replace(/\\/g, "/"));
499
+ return normalized !== ".." && !normalized.startsWith("../") && !normalized.includes("/../");
500
+ }
501
+ function githubApiHeaders() {
502
+ const headers = { Accept: "application/vnd.github+json" };
503
+ const auth = process.env["HAUS_GITHUB_TOKEN"] ?? process.env["GITHUB_TOKEN"];
504
+ if (auth) headers["Authorization"] = `Bearer ${auth}`;
505
+ return headers;
506
+ }
507
+ function sanitizeRelativeFilePaths(files, label) {
508
+ const safe = [];
509
+ for (const rel of files) {
510
+ if (!isSafeRelativeFilePath(rel)) {
511
+ warn(`Rejected unsafe path in ${label}: ${rel}`);
512
+ return null;
513
+ }
514
+ safe.push(rel);
515
+ }
516
+ return safe;
517
+ }
483
518
  function safeJoin(base, itemPath) {
484
519
  if (!isSafeCatalogPath(itemPath)) return null;
485
520
  const resolved = path2.resolve(base, itemPath);
486
521
  return resolved.startsWith(base + path2.sep) || resolved === base ? resolved : null;
487
522
  }
488
523
  var KNOWN_ITEM_TYPES = /* @__PURE__ */ new Set(["skill", "agent", "template", "command"]);
489
- function isExternalReference(ref) {
490
- return /^[a-z][a-z0-9+.-]*:\/\//i.test(ref);
491
- }
492
- async function downloadSkillReferences(item, destDir, base) {
493
- for (const ref of item.references ?? []) {
494
- if (isExternalReference(ref)) continue;
495
- const refDest = safeJoin(destDir, ref);
496
- if (!refDest) {
497
- warn(`Skipping reference "${ref}" for ${item.id}: path traversal detected`);
498
- continue;
524
+ function isMarkdownPath(rel) {
525
+ return rel.toLowerCase().endsWith(".md");
526
+ }
527
+ async function listFilesRecursive(dir, base = dir) {
528
+ const out = [];
529
+ if (!await fs2.pathExists(dir)) return out;
530
+ for (const entry of await fs2.readdir(dir, { withFileTypes: true })) {
531
+ const full = path2.join(dir, entry.name);
532
+ if (entry.isDirectory()) {
533
+ out.push(...await listFilesRecursive(full, base));
534
+ } else if (entry.isFile()) {
535
+ out.push(path2.relative(base, full).replace(/\\/g, "/"));
499
536
  }
500
- const text = await fetchText(`${base}/${item.path}/${ref}`);
501
- if (text === null) {
502
- warn(`Failed to fetch reference "${ref}" for ${item.id}`);
503
- continue;
537
+ }
538
+ return out.sort();
539
+ }
540
+ async function listMockPrefixFiles(base, prefix) {
541
+ const text = await fetchText(`${base}/__haus_tree__/${encodeURIComponent(prefix)}`);
542
+ if (text === null) return null;
543
+ try {
544
+ const parsed = JSON.parse(text);
545
+ if (!Array.isArray(parsed) || !parsed.every((e) => typeof e === "string")) return null;
546
+ return parsed;
547
+ } catch {
548
+ return null;
549
+ }
550
+ }
551
+ async function fetchGitHubRecursiveBlobPaths(ref) {
552
+ try {
553
+ const headers = githubApiHeaders();
554
+ const commitRes = await fetch(`${CATALOG_GITHUB_API_URL}/commits/${encodeURIComponent(ref)}`, {
555
+ signal: AbortSignal.timeout(15e3),
556
+ headers
557
+ });
558
+ if (!commitRes.ok) return null;
559
+ const commit = await commitRes.json();
560
+ const treeSha = commit.commit.tree.sha;
561
+ const treeRes = await fetch(`${CATALOG_GITHUB_API_URL}/git/trees/${treeSha}?recursive=1`, {
562
+ signal: AbortSignal.timeout(3e4),
563
+ headers
564
+ });
565
+ if (!treeRes.ok) return null;
566
+ const tree = await treeRes.json();
567
+ if (tree.truncated) {
568
+ warn("Catalog GitHub tree listing was truncated \u2014 refusing partial cache sync");
569
+ return null;
570
+ }
571
+ return tree.tree.filter((e) => e.type === "blob").map((e) => e.path);
572
+ } catch {
573
+ return null;
574
+ }
575
+ }
576
+ async function fetchCatalogBlobPaths(_base) {
577
+ if (cachedBlobPaths) return cachedBlobPaths;
578
+ if (process.env["HAUS_CATALOG_REMOTE_BASE"]) return null;
579
+ const ref = getResolvedCatalogRef();
580
+ const paths = await fetchGitHubRecursiveBlobPaths(ref);
581
+ if (paths) cachedBlobPaths = paths;
582
+ return paths;
583
+ }
584
+ async function listFilesUnderCatalogPrefix(prefix, base) {
585
+ const normalized = prefix.replace(/\\/g, "/").replace(/\/+$/, "");
586
+ const prefixSlash = `${normalized}/`;
587
+ let relFiles;
588
+ if (process.env["HAUS_CATALOG_REMOTE_BASE"]) {
589
+ relFiles = await listMockPrefixFiles(base, normalized);
590
+ } else {
591
+ const blobs = await fetchCatalogBlobPaths(base);
592
+ if (!blobs) return null;
593
+ relFiles = blobs.filter((p) => p.startsWith(prefixSlash)).map((p) => p.slice(prefixSlash.length)).sort();
594
+ }
595
+ if (!relFiles) return null;
596
+ return sanitizeRelativeFilePaths(relFiles, normalized);
597
+ }
598
+ async function fetchPrefixFiles(catalogPrefix, relFiles, base, label) {
599
+ const fetched = [];
600
+ for (const rel of relFiles) {
601
+ const url = `${base}/${catalogPrefix}/${rel}`;
602
+ if (isMarkdownPath(rel)) {
603
+ const text = await fetchText(url);
604
+ if (text === null) {
605
+ warn(`Failed to fetch ${rel} for ${label}`);
606
+ return null;
607
+ }
608
+ fetched.push({ rel, kind: "text", body: text });
609
+ } else {
610
+ const bytes = await fetchBytes(url);
611
+ if (bytes === null) {
612
+ warn(`Failed to fetch ${rel} for ${label}`);
613
+ return null;
614
+ }
615
+ fetched.push({ rel, kind: "binary", body: bytes });
616
+ }
617
+ }
618
+ return fetched;
619
+ }
620
+ function validateMarkdownFiles(item, fetched) {
621
+ for (const file of fetched) {
622
+ if (file.kind !== "text" || !isMarkdownPath(file.rel)) continue;
623
+ const verdict = validateCatalogItem(item, file.body);
624
+ if (!verdict.ok) {
625
+ warn(`Rejected ${item.id} at ingest: ${verdict.reason}`);
626
+ return false;
627
+ }
628
+ }
629
+ return true;
630
+ }
631
+ async function directoryMatchesFetched(destDir, fetched) {
632
+ if (!await fs2.pathExists(destDir)) return false;
633
+ const existing = await listFilesRecursive(destDir);
634
+ const relSet = new Set(fetched.map((f) => f.rel));
635
+ if (existing.length !== fetched.length) return false;
636
+ for (const rel of existing) {
637
+ if (!relSet.has(rel)) return false;
638
+ }
639
+ for (const file of fetched) {
640
+ const dest = path2.join(destDir, file.rel);
641
+ if (!await fs2.pathExists(dest)) return false;
642
+ if (file.kind === "text") {
643
+ const local = await fs2.readFile(dest, "utf8");
644
+ if (local !== file.body) return false;
645
+ } else {
646
+ const local = await fs2.readFile(dest);
647
+ if (!local.equals(file.body)) return false;
648
+ }
649
+ }
650
+ return true;
651
+ }
652
+ async function writeFetchedDirectory(destDir, fetched) {
653
+ if (await fs2.pathExists(destDir)) {
654
+ await fs2.remove(destDir);
655
+ }
656
+ await fs2.ensureDir(destDir);
657
+ for (const file of fetched) {
658
+ const dest = path2.join(destDir, file.rel);
659
+ await fs2.ensureDir(path2.dirname(dest));
660
+ if (file.kind === "text") {
661
+ await fs2.writeFile(dest, file.body, "utf8");
662
+ } else {
663
+ await fs2.writeFile(dest, file.body);
664
+ }
665
+ }
666
+ }
667
+ async function syncDirectoryFromPrefix(item, catalogPrefix, destDir, base, opts) {
668
+ const relFiles = await listFilesUnderCatalogPrefix(catalogPrefix, base);
669
+ if (!relFiles) {
670
+ warn(`Failed to list files for ${item.id}`);
671
+ return "failed";
672
+ }
673
+ if (!relFiles.includes("SKILL.md") && catalogPrefix !== SUPERPOWERS_SHARED_CATALOG_REL) {
674
+ warn(`Failed to fetch content for ${item.id}: missing SKILL.md`);
675
+ return "failed";
676
+ }
677
+ if (relFiles.length === 0) {
678
+ return "unchanged";
679
+ }
680
+ const fetched = await fetchPrefixFiles(catalogPrefix, relFiles, base, item.id);
681
+ if (!fetched) return "failed";
682
+ if (opts.validateMarkdown && "type" in item) {
683
+ if (!validateMarkdownFiles(item, fetched)) return "failed";
684
+ } else if (opts.validateMarkdown) {
685
+ for (const file of fetched) {
686
+ if (file.kind !== "text" || !isMarkdownPath(file.rel)) continue;
687
+ const verdict = validateCatalogItem(
688
+ { id: item.id, type: "skill", path: catalogPrefix },
689
+ file.body
690
+ );
691
+ if (!verdict.ok) {
692
+ warn(`Rejected ${item.id} at ingest: ${verdict.reason}`);
693
+ return "failed";
694
+ }
504
695
  }
505
- await writeTextIfChanged(refDest, text);
696
+ }
697
+ const existed = await fs2.pathExists(destDir);
698
+ if (await directoryMatchesFetched(destDir, fetched)) {
699
+ return "unchanged";
700
+ }
701
+ await writeFetchedDirectory(destDir, fetched);
702
+ return existed ? "updated" : "created";
703
+ }
704
+ async function syncSkillDirectory(item, base) {
705
+ const destDir = safeJoin(getCacheDir(), item.path);
706
+ if (!destDir) {
707
+ warn(`Skipping ${item.id}: path traversal detected`);
708
+ return "failed";
709
+ }
710
+ try {
711
+ return await syncDirectoryFromPrefix(item, item.path, destDir, base, {
712
+ validateMarkdown: true
713
+ });
714
+ } catch (err) {
715
+ warn(`Failed to cache ${item.id}: ${err instanceof Error ? err.message : String(err)}`);
716
+ return "failed";
717
+ }
718
+ }
719
+ async function syncSuperpowersShared(base) {
720
+ const relFiles = await listFilesUnderCatalogPrefix(SUPERPOWERS_SHARED_CATALOG_REL, base);
721
+ if (!relFiles || relFiles.length === 0) return "skipped";
722
+ const destDir = safeJoin(getCacheDir(), SUPERPOWERS_SHARED_CATALOG_REL);
723
+ if (!destDir) return "failed";
724
+ try {
725
+ return await syncDirectoryFromPrefix(
726
+ { id: "haus.superpowers-shared", path: SUPERPOWERS_SHARED_CATALOG_REL },
727
+ SUPERPOWERS_SHARED_CATALOG_REL,
728
+ destDir,
729
+ base,
730
+ { validateMarkdown: true }
731
+ );
732
+ } catch (err) {
733
+ warn(`Failed to cache superpowers shared: ${err instanceof Error ? err.message : String(err)}`);
734
+ return "failed";
506
735
  }
507
736
  }
508
737
  async function syncOneItem(item, base) {
@@ -518,30 +747,7 @@ async function syncOneItem(item, base) {
518
747
  return "failed";
519
748
  }
520
749
  if (item.type === "skill") {
521
- const destDir = safeJoin(getCacheDir(), item.path);
522
- if (!destDir) {
523
- warn(`Skipping ${item.id}: path traversal detected`);
524
- return "failed";
525
- }
526
- const dest2 = path2.join(destDir, "SKILL.md");
527
- const text2 = await fetchText(`${base}/${item.path}/SKILL.md`);
528
- if (!text2) {
529
- warn(`Failed to fetch content for ${item.id}`);
530
- return "failed";
531
- }
532
- const verdict2 = validateCatalogItem(item, text2);
533
- if (!verdict2.ok) {
534
- warn(`Rejected ${item.id} at ingest: ${verdict2.reason}`);
535
- return "failed";
536
- }
537
- try {
538
- const outcome = await writeTextIfChanged(dest2, text2);
539
- await downloadSkillReferences(item, destDir, base);
540
- return outcome;
541
- } catch (err) {
542
- warn(`Failed to cache ${item.id}: ${err instanceof Error ? err.message : String(err)}`);
543
- return "failed";
544
- }
750
+ return syncSkillDirectory(item, base);
545
751
  }
546
752
  const dest = safeJoin(getCacheDir(), item.path);
547
753
  if (!dest) {
@@ -566,6 +772,7 @@ async function syncOneItem(item, base) {
566
772
  }
567
773
  }
568
774
  async function syncRemoteCatalog() {
775
+ cachedBlobPaths = void 0;
569
776
  const manifest = await fetchRemoteManifest();
570
777
  if (!manifest) {
571
778
  warn("Remote catalog fetch failed \u2014 using bundled catalog");
@@ -593,6 +800,10 @@ async function syncRemoteCatalog() {
593
800
  const failed = [];
594
801
  const base = await remoteBase();
595
802
  const outcomes = await mapWithConcurrency(items, (item) => syncOneItem(item, base), 8);
803
+ const sharedOutcome = await syncSuperpowersShared(base);
804
+ if (sharedOutcome === "failed") {
805
+ warn("Failed to cache superpowers shared support files");
806
+ }
596
807
  for (let i = 0; i < items.length; i++) {
597
808
  const item = items[i];
598
809
  const outcome = outcomes[i];
@@ -609,7 +820,7 @@ async function fetchLatestCatalogTag() {
609
820
  try {
610
821
  const res = await fetch(CATALOG_TAGS_API_URL, {
611
822
  signal: AbortSignal.timeout(5e3),
612
- headers: { Accept: "application/vnd.github+json" }
823
+ headers: githubApiHeaders()
613
824
  });
614
825
  if (!res.ok) return null;
615
826
  const tags = await res.json();
@@ -718,9 +929,10 @@ function mergeHooks(settings, fragments) {
718
929
  updated._haus = {
719
930
  hooks: [...existing, ...addedIds],
720
931
  hookCommands: [...existingCommands, ...addedCommands],
721
- // Preserve deny/allow tracking so hook, deny, and allow merges are order-independent.
932
+ // Preserve deny/allow/ask tracking so hook, deny, allow, and ask merges are order-independent.
722
933
  ...settings._haus?.denyRules ? { denyRules: settings._haus.denyRules } : {},
723
- ...settings._haus?.allowRules ? { allowRules: settings._haus.allowRules } : {}
934
+ ...settings._haus?.allowRules ? { allowRules: settings._haus.allowRules } : {},
935
+ ...settings._haus?.askRules ? { askRules: settings._haus.askRules } : {}
724
936
  };
725
937
  return { settings: updated, addedIds };
726
938
  }
@@ -743,7 +955,8 @@ function mergeDenyRules(settings, rules) {
743
955
  hooks: settings._haus?.hooks ?? [],
744
956
  ...settings._haus?.hookCommands ? { hookCommands: settings._haus.hookCommands } : {},
745
957
  denyRules: [...trackedDeny, ...addedRules],
746
- ...settings._haus?.allowRules ? { allowRules: settings._haus.allowRules } : {}
958
+ ...settings._haus?.allowRules ? { allowRules: settings._haus.allowRules } : {},
959
+ ...settings._haus?.askRules ? { askRules: settings._haus.askRules } : {}
747
960
  };
748
961
  return { settings: updated, addedRules };
749
962
  }
@@ -766,7 +979,8 @@ function mergeAllowRules(settings, rules) {
766
979
  hooks: settings._haus?.hooks ?? [],
767
980
  ...settings._haus?.hookCommands ? { hookCommands: settings._haus.hookCommands } : {},
768
981
  ...settings._haus?.denyRules ? { denyRules: settings._haus.denyRules } : {},
769
- allowRules: [...trackedAllow, ...addedRules]
982
+ allowRules: [...trackedAllow, ...addedRules],
983
+ ...settings._haus?.askRules ? { askRules: settings._haus.askRules } : {}
770
984
  };
771
985
  return { settings: updated, addedRules };
772
986
  }
@@ -783,7 +997,7 @@ function stripHausAllow(settings) {
783
997
  else delete updated.permissions;
784
998
  const haus = { ...prevHaus };
785
999
  delete haus.allowRules;
786
- const stillTracking = (haus.hooks?.length ?? 0) > 0 || (haus.hookCommands?.length ?? 0) > 0 || (haus.denyRules?.length ?? 0) > 0;
1000
+ const stillTracking = (haus.hooks?.length ?? 0) > 0 || (haus.hookCommands?.length ?? 0) > 0 || (haus.denyRules?.length ?? 0) > 0 || (haus.askRules?.length ?? 0) > 0;
787
1001
  if (stillTracking) updated._haus = haus;
788
1002
  else delete updated._haus;
789
1003
  return updated;
@@ -801,7 +1015,7 @@ function stripHausDeny(settings) {
801
1015
  else delete updated.permissions;
802
1016
  const haus = { ...prevHaus };
803
1017
  delete haus.denyRules;
804
- const stillTracking = (haus.hooks?.length ?? 0) > 0 || (haus.hookCommands?.length ?? 0) > 0 || (haus.allowRules?.length ?? 0) > 0;
1018
+ const stillTracking = (haus.hooks?.length ?? 0) > 0 || (haus.hookCommands?.length ?? 0) > 0 || (haus.allowRules?.length ?? 0) > 0 || (haus.askRules?.length ?? 0) > 0;
805
1019
  if (stillTracking) updated._haus = haus;
806
1020
  else delete updated._haus;
807
1021
  return updated;
@@ -823,6 +1037,48 @@ function stripHausHooks(settings) {
823
1037
  void _;
824
1038
  return rest;
825
1039
  }
1040
+ function mergeAskRules(settings, rules) {
1041
+ const existingAsk = settings.permissions?.ask ?? [];
1042
+ const seen = new Set(existingAsk);
1043
+ const trackedAsk = settings._haus?.askRules ?? [];
1044
+ const addedRules = [];
1045
+ for (const rule of rules) {
1046
+ if (seen.has(rule)) continue;
1047
+ seen.add(rule);
1048
+ addedRules.push(rule);
1049
+ }
1050
+ const updated = { ...settings };
1051
+ updated.permissions = {
1052
+ ...settings.permissions ?? {},
1053
+ ask: [...existingAsk, ...addedRules]
1054
+ };
1055
+ updated._haus = {
1056
+ hooks: settings._haus?.hooks ?? [],
1057
+ ...settings._haus?.hookCommands ? { hookCommands: settings._haus.hookCommands } : {},
1058
+ ...settings._haus?.denyRules ? { denyRules: settings._haus.denyRules } : {},
1059
+ ...settings._haus?.allowRules ? { allowRules: settings._haus.allowRules } : {},
1060
+ askRules: [...trackedAsk, ...addedRules]
1061
+ };
1062
+ return { settings: updated, addedRules };
1063
+ }
1064
+ function stripHausAsk(settings) {
1065
+ const prevHaus = settings._haus;
1066
+ if (!prevHaus?.askRules || prevHaus.askRules.length === 0) return settings;
1067
+ const ownedSet = new Set(prevHaus.askRules);
1068
+ const updated = { ...settings };
1069
+ const remainingAsk = (settings.permissions?.ask ?? []).filter((rule) => !ownedSet.has(rule));
1070
+ const permissions = { ...settings.permissions ?? {} };
1071
+ if (remainingAsk.length > 0) permissions.ask = remainingAsk;
1072
+ else delete permissions.ask;
1073
+ if (Object.keys(permissions).length > 0) updated.permissions = permissions;
1074
+ else delete updated.permissions;
1075
+ const haus = { ...prevHaus };
1076
+ delete haus.askRules;
1077
+ const stillTracking = (haus.hooks?.length ?? 0) > 0 || (haus.hookCommands?.length ?? 0) > 0 || (haus.denyRules?.length ?? 0) > 0 || (haus.allowRules?.length ?? 0) > 0;
1078
+ if (stillTracking) updated._haus = haus;
1079
+ else delete updated._haus;
1080
+ return updated;
1081
+ }
826
1082
  async function loadHooksFragment(fragmentPath) {
827
1083
  let raw;
828
1084
  try {
@@ -835,44 +1091,48 @@ async function loadHooksFragment(fragmentPath) {
835
1091
  }
836
1092
 
837
1093
  // src/security/dangerous-commands.ts
838
- var DANGEROUS_COMMANDS = [
839
- "rm -rf",
1094
+ var DENY_COMMANDS = [
840
1095
  "sudo",
841
1096
  "chmod -R 777",
842
- "chown -R",
843
1097
  "git push --force",
844
- "git reset --hard",
845
- "docker system prune",
846
1098
  "drop database",
847
1099
  "truncate table",
848
- "php artisan migrate --force",
849
1100
  "npm publish",
850
1101
  "yarn npm publish",
851
1102
  "pnpm publish"
852
1103
  ];
1104
+ var ASK_COMMANDS = [
1105
+ "rm -rf",
1106
+ "chown -R",
1107
+ "git reset --hard",
1108
+ "docker system prune",
1109
+ "php artisan migrate --force"
1110
+ ];
853
1111
 
854
1112
  // src/security/sensitive-paths.ts
855
- var SENSITIVE_PATHS = [
856
- ".env",
857
- ".env.*",
1113
+ var DENY_PATHS = [
858
1114
  "*.pem",
859
1115
  "*.key",
860
1116
  "*.p12",
861
1117
  "*.pfx",
862
1118
  "id_rsa",
863
1119
  "id_ed25519",
864
- "*.sql",
865
- "*.dump",
866
- "*.backup",
867
- "*.bak",
868
- "storage/logs",
869
- "wp-content/uploads",
870
- "uploads",
871
1120
  "customer-data",
872
- "exports",
873
1121
  "secrets",
874
1122
  "certs"
875
1123
  ];
1124
+ var DENY_DIRS = /* @__PURE__ */ new Set(["customer-data", "secrets", "certs"]);
1125
+ var ASK_PATHS = [
1126
+ { pattern: ".env", tools: ["Edit", "Write"] },
1127
+ { pattern: ".env.*", tools: ["Edit", "Write"] },
1128
+ { pattern: "storage/logs/**", tools: ["Edit", "Write"] },
1129
+ { pattern: "exports/**", tools: ["Edit", "Write"] },
1130
+ { pattern: "*.dump", tools: ["Read", "Edit", "Write"] },
1131
+ { pattern: "*.backup", tools: ["Read", "Edit", "Write"] },
1132
+ { pattern: "*.bak", tools: ["Read", "Edit", "Write"] },
1133
+ { pattern: "wp-content/uploads/**", tools: ["Read", "Edit", "Write"] },
1134
+ { pattern: "uploads/**", tools: ["Read", "Edit", "Write"] }
1135
+ ];
876
1136
  var SENSITIVE_PATH_REGEXES = [
877
1137
  /^\.env(\.|$)/,
878
1138
  /(^|\/)\.env(\.|$)/,
@@ -900,24 +1160,29 @@ var SENSITIVE_ITEM_KEYWORDS = [
900
1160
  ".key"
901
1161
  ];
902
1162
 
1163
+ // src/security/ask-rules.ts
1164
+ function buildAskRules() {
1165
+ const rules = [];
1166
+ for (const command of ASK_COMMANDS) {
1167
+ rules.push(`Bash(${command}:*)`);
1168
+ }
1169
+ for (const { pattern, tools } of ASK_PATHS) {
1170
+ for (const tool of tools) {
1171
+ rules.push(`${tool}(${pattern})`);
1172
+ }
1173
+ }
1174
+ return [...new Set(rules)];
1175
+ }
1176
+
903
1177
  // src/security/deny-rules.ts
904
- var SENSITIVE_DIRS = /* @__PURE__ */ new Set([
905
- "storage/logs",
906
- "wp-content/uploads",
907
- "uploads",
908
- "customer-data",
909
- "exports",
910
- "secrets",
911
- "certs"
912
- ]);
913
1178
  var FILE_TOOLS = ["Read", "Edit", "Write"];
914
1179
  function buildDenyRules() {
915
1180
  const rules = [];
916
- for (const command of DANGEROUS_COMMANDS) {
1181
+ for (const command of DENY_COMMANDS) {
917
1182
  rules.push(`Bash(${command}:*)`);
918
1183
  }
919
- for (const path36 of SENSITIVE_PATHS) {
920
- const pattern = SENSITIVE_DIRS.has(path36) ? `${path36}/**` : path36;
1184
+ for (const path37 of DENY_PATHS) {
1185
+ const pattern = DENY_DIRS.has(path37) ? `${path37}/**` : path37;
921
1186
  for (const tool of FILE_TOOLS) {
922
1187
  rules.push(`${tool}(${pattern})`);
923
1188
  }
@@ -1005,7 +1270,8 @@ async function mergeProjectSettings(root) {
1005
1270
  const base = await readProjectSettings(root);
1006
1271
  const { settings: withHooks } = mergeHooks(base, PROJECT_HOOK_FRAGMENTS);
1007
1272
  const { settings: withDeny } = mergeDenyRules(withHooks, buildDenyRules());
1008
- const { settings: merged } = mergeAllowRules(withDeny, buildAllowRules());
1273
+ const { settings: withAllow } = mergeAllowRules(withDeny, buildAllowRules());
1274
+ const { settings: merged } = mergeAskRules(withAllow, buildAskRules());
1009
1275
  return merged;
1010
1276
  }
1011
1277
  async function applyProjectSettingsMerge(root) {
@@ -1015,8 +1281,8 @@ async function applyProjectSettingsMerge(root) {
1015
1281
  }
1016
1282
 
1017
1283
  // src/claude/write-claude-files.ts
1018
- import path12 from "path";
1019
- import fs11 from "fs-extra";
1284
+ import path13 from "path";
1285
+ import fs12 from "fs-extra";
1020
1286
 
1021
1287
  // src/catalog/load-catalog.ts
1022
1288
  import path6 from "path";
@@ -1183,8 +1449,59 @@ async function writeManagedJson(root, filePath, value, dryRun) {
1183
1449
  await writeManagedText(root, filePath, nextText, dryRun);
1184
1450
  }
1185
1451
 
1186
- // src/claude/verify-hooks-contract.ts
1452
+ // src/claude/superpowers-install.ts
1453
+ import path9 from "path";
1454
+ import fg3 from "fast-glob";
1187
1455
  import fs6 from "fs-extra";
1456
+ var SUPERPOWERS_ORIGIN_SOURCE_ID = "superpowers-pcvelz";
1457
+ var PATH_REWRITES = [
1458
+ ["skills/shared/", ".claude/skills/shared/"]
1459
+ ];
1460
+ function rewriteSuperpowersMarkdown(text) {
1461
+ let out = text;
1462
+ for (const [from, to] of PATH_REWRITES) {
1463
+ out = out.split(from).join(to);
1464
+ }
1465
+ return out;
1466
+ }
1467
+ async function rewriteMarkdownTree(dir) {
1468
+ const files = await fg3("**/*.md", { cwd: dir, onlyFiles: true, dot: true });
1469
+ for (const rel of files) {
1470
+ const abs = path9.join(dir, rel);
1471
+ const text = await fs6.readFile(abs, "utf8");
1472
+ const rewritten = rewriteSuperpowersMarkdown(text);
1473
+ if (rewritten !== text) {
1474
+ await fs6.writeFile(abs, rewritten, "utf8");
1475
+ }
1476
+ }
1477
+ }
1478
+ async function installCatalogSkill(sourcePath, destination, opts) {
1479
+ if (opts.dryRun) return;
1480
+ await fs6.ensureDir(path9.dirname(destination));
1481
+ if (await fs6.pathExists(destination)) {
1482
+ await fs6.remove(destination);
1483
+ }
1484
+ await fs6.copy(sourcePath, destination, { overwrite: true, errorOnExist: false });
1485
+ if (opts.originSourceId === SUPERPOWERS_ORIGIN_SOURCE_ID) {
1486
+ await rewriteMarkdownTree(destination);
1487
+ }
1488
+ }
1489
+ async function installSuperpowersShared(contentRoot, projectRoot, dryRun) {
1490
+ const source = path9.join(contentRoot, SUPERPOWERS_SHARED_CATALOG_REL);
1491
+ if (!await fs6.pathExists(source)) return null;
1492
+ const destination = claudePath(projectRoot, "skills", "shared");
1493
+ if (dryRun) return path9.relative(projectRoot, destination);
1494
+ await fs6.ensureDir(path9.dirname(destination));
1495
+ if (await fs6.pathExists(destination)) {
1496
+ await fs6.remove(destination);
1497
+ }
1498
+ await fs6.copy(source, destination, { overwrite: true, errorOnExist: false });
1499
+ await rewriteMarkdownTree(destination);
1500
+ return path9.relative(projectRoot, destination);
1501
+ }
1502
+
1503
+ // src/claude/verify-hooks-contract.ts
1504
+ import fs7 from "fs-extra";
1188
1505
 
1189
1506
  // src/claude/load-hooks.ts
1190
1507
  var CANONICAL_HOOKS = {
@@ -1212,7 +1529,7 @@ var STABLE_HOOK_IDS = {
1212
1529
  "haus guard bash --from-hook": "haus.guard-bash"
1213
1530
  };
1214
1531
  async function loadClaudeHooksSettings() {
1215
- return { ...CANONICAL_HOOKS, permissions: { deny: buildDenyRules() } };
1532
+ return { ...CANONICAL_HOOKS, permissions: { deny: buildDenyRules(), ask: buildAskRules() } };
1216
1533
  }
1217
1534
  function flattenRecommendedHooks(settings) {
1218
1535
  const out = [];
@@ -1278,7 +1595,7 @@ async function assertPostApplySettingsHausContract(root) {
1278
1595
  }
1279
1596
  async function verifyProjectSettingsHooksContract(root) {
1280
1597
  const settingsPath = claudePath(root, "settings.json");
1281
- if (!await fs6.pathExists(settingsPath)) {
1598
+ if (!await fs7.pathExists(settingsPath)) {
1282
1599
  return {
1283
1600
  ok: true,
1284
1601
  skipped: true,
@@ -1305,8 +1622,8 @@ async function verifyProjectSettingsHooksContract(root) {
1305
1622
  }
1306
1623
 
1307
1624
  // src/claude/write-root-claude-md.ts
1308
- import path9 from "path";
1309
- import fs7 from "fs-extra";
1625
+ import path10 from "path";
1626
+ import fs8 from "fs-extra";
1310
1627
  var BLOCK_BEGIN = "<!-- HAUS:BEGIN haus-imports v=1 -->";
1311
1628
  var BLOCK_END = "<!-- HAUS:END haus-imports -->";
1312
1629
  var IMPORT_CONTENT = `@.haus-workflow/WORKFLOW.md
@@ -1346,21 +1663,21 @@ ${block}
1346
1663
  `;
1347
1664
  }
1348
1665
  async function writeRootClaudeMd(root, dryRun) {
1349
- const filePath = path9.join(root, "CLAUDE.md");
1666
+ const filePath = path10.join(root, "CLAUDE.md");
1350
1667
  const block = buildImportBlock();
1351
- const prev = await fs7.pathExists(filePath) ? await fs7.readFile(filePath, "utf8") : "";
1668
+ const prev = await fs8.pathExists(filePath) ? await fs8.readFile(filePath, "utf8") : "";
1352
1669
  const next = injectHausBlock(prev, block);
1353
1670
  await writeManagedText(root, filePath, next, dryRun);
1354
1671
  return filePath;
1355
1672
  }
1356
1673
 
1357
1674
  // src/claude/write-workflow-config.ts
1358
- import path11 from "path";
1359
- import fs9 from "fs-extra";
1675
+ import path12 from "path";
1676
+ import fs10 from "fs-extra";
1360
1677
 
1361
1678
  // src/claude/derive-workflow-config.ts
1362
- import path10 from "path";
1363
- import fs8 from "fs-extra";
1679
+ import path11 from "path";
1680
+ import fs9 from "fs-extra";
1364
1681
  function binCmd(pm, bin, args) {
1365
1682
  const tail = args ? ` ${args}` : "";
1366
1683
  if (pm === "yarn") return `yarn ${bin}${tail}`;
@@ -1369,7 +1686,7 @@ function binCmd(pm, bin, args) {
1369
1686
  }
1370
1687
  async function deriveWorkflowConfig(root, ctx) {
1371
1688
  const pm = ctx.packageManager === "unknown" ? "npm" : ctx.packageManager;
1372
- const pkg = await readJson(path10.join(root, "package.json"));
1689
+ const pkg = await readJson(path11.join(root, "package.json"));
1373
1690
  const scripts = pkg?.scripts ?? {};
1374
1691
  const deps = new Set(ctx.dependencies);
1375
1692
  const stacks = Object.values(ctx.detectedStacks ?? {}).flat();
@@ -1379,7 +1696,7 @@ async function deriveWorkflowConfig(root, ctx) {
1379
1696
  return null;
1380
1697
  };
1381
1698
  const hasDep = (name) => deps.has(name);
1382
- const exists = (rel) => fs8.pathExistsSync(path10.join(root, rel));
1699
+ const exists = (rel) => fs9.pathExistsSync(path11.join(root, rel));
1383
1700
  const hasPlaywright = hasDep("@playwright/test") || stacks.includes("playwright");
1384
1701
  const hasCypress = hasDep("cypress");
1385
1702
  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;
@@ -1448,7 +1765,7 @@ var FALLBACK_CONTEXT = {
1448
1765
  async function writeWorkflowConfig(root, dryRun, opts = {}) {
1449
1766
  const destPath = hausPath(root, "workflow-config.md");
1450
1767
  const printable = displayPath(root, destPath);
1451
- const exists = await fs9.pathExists(destPath);
1768
+ const exists = await fs10.pathExists(destPath);
1452
1769
  if (exists && !opts.refill) {
1453
1770
  if (dryRun) log(printable + ": exists (project-owned, skipping)");
1454
1771
  return null;
@@ -1456,11 +1773,11 @@ async function writeWorkflowConfig(root, dryRun, opts = {}) {
1456
1773
  const ctx = await readJson(hausPath(root, "context-map.json")) ?? {
1457
1774
  ...FALLBACK_CONTEXT,
1458
1775
  root,
1459
- repoName: path11.basename(root)
1776
+ repoName: path12.basename(root)
1460
1777
  };
1461
1778
  const values = await deriveWorkflowConfig(root, ctx);
1462
1779
  if (exists) {
1463
- const current = await fs9.readFile(destPath, "utf8");
1780
+ const current = await fs10.readFile(destPath, "utf8");
1464
1781
  const refilled = refillContent(current, values);
1465
1782
  if (refilled === current) {
1466
1783
  if (dryRun) log(printable + ": no blank fields to refill");
@@ -1482,7 +1799,7 @@ async function writeWorkflowConfig(root, dryRun, opts = {}) {
1482
1799
  }
1483
1800
 
1484
1801
  // src/claude/write-workflow.ts
1485
- import fs10 from "fs-extra";
1802
+ import fs11 from "fs-extra";
1486
1803
  var STABLE_ID = "template.workflow";
1487
1804
  function makeWorkflowHeader(pkgVersion, contentHash) {
1488
1805
  return `<!-- HAUS-MANAGED id=${STABLE_ID} v=${SCHEMA_VERSION} source=@haus-tech/haus-workflow@${pkgVersion} hash=${contentHash} -->`;
@@ -1501,8 +1818,8 @@ async function writeWorkflow(root, pkgVersion, dryRun, force = false) {
1501
1818
  ${templateContent}`;
1502
1819
  const destPath = hausPath(root, "WORKFLOW.md");
1503
1820
  const printable = displayPath(root, destPath);
1504
- if (await fs10.pathExists(destPath)) {
1505
- const existing = await fs10.readFile(destPath, "utf8");
1821
+ if (await fs11.pathExists(destPath)) {
1822
+ const existing = await fs11.readFile(destPath, "utf8");
1506
1823
  const firstLine = existing.split("\n")[0] ?? "";
1507
1824
  const parsed = parseHausManagedHeader(firstLine);
1508
1825
  if (!parsed) {
@@ -1530,7 +1847,7 @@ ${templateContent}`;
1530
1847
  }
1531
1848
  }
1532
1849
  if (dryRun) {
1533
- const prev = await fs10.pathExists(destPath) ? await fs10.readFile(destPath, "utf8") : "";
1850
+ const prev = await fs11.pathExists(destPath) ? await fs11.readFile(destPath, "utf8") : "";
1534
1851
  if (!prev) {
1535
1852
  log(createUnifiedDiff(printable, "", next));
1536
1853
  } else {
@@ -1564,7 +1881,7 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
1564
1881
  estimatedTokenReductionPct: 0
1565
1882
  };
1566
1883
  const pkgRoot = packageRoot();
1567
- const hausVersion2 = (await readJson(path12.join(pkgRoot, "package.json")))?.version ?? "0.0.0";
1884
+ const hausVersion2 = (await readJson(path13.join(pkgRoot, "package.json")))?.version ?? "0.0.0";
1568
1885
  const coreFiles = [
1569
1886
  claudePath(root, "settings.json"),
1570
1887
  claudePath(root, "rules", "haus.md"),
@@ -1596,7 +1913,7 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
1596
1913
  await assertPostApplySettingsHausContract(root);
1597
1914
  }
1598
1915
  const configPath = hausPath(root, "config.json");
1599
- if (!await fs11.pathExists(configPath)) {
1916
+ if (!await fs12.pathExists(configPath)) {
1600
1917
  await writeManagedJson(root, configPath, DEFAULT_HOOKS_CONFIG, dryRun);
1601
1918
  }
1602
1919
  await writeManagedText(
@@ -1629,6 +1946,7 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
1629
1946
  const installedIds = /* @__PURE__ */ new Set();
1630
1947
  const catalogItems = selectedIds !== void 0 ? rec.recommended.filter((r) => selectedIds.includes(r.id)) : rec.recommended;
1631
1948
  let curatedReviewStatusSkips = 0;
1949
+ let superpowersSharedInstalled = false;
1632
1950
  for (const item of catalogItems) {
1633
1951
  const manifestItem = manifestById.get(item.id);
1634
1952
  if (!manifestItem?.path) continue;
@@ -1655,20 +1973,34 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
1655
1973
  );
1656
1974
  continue;
1657
1975
  }
1658
- const destination = claudePath(root, target, path12.basename(sourcePath));
1659
- if (await fs11.pathExists(sourcePath)) {
1976
+ const destination = claudePath(root, target, path13.basename(sourcePath));
1977
+ if (await fs12.pathExists(sourcePath)) {
1660
1978
  if (dryRun) {
1661
- const exists = await fs11.pathExists(destination);
1979
+ const exists = await fs12.pathExists(destination);
1662
1980
  log(
1663
1981
  `${displayPath(root, destination)}: ${exists ? "would overwrite" : "would create"} (${item.id})`
1664
1982
  );
1983
+ } else if (item.type === "skill") {
1984
+ await installCatalogSkill(sourcePath, destination, {
1985
+ originSourceId: manifestItem.originSourceId,
1986
+ dryRun: false
1987
+ });
1665
1988
  } else {
1666
- await fs11.ensureDir(path12.dirname(destination));
1667
- await fs11.copy(sourcePath, destination, { overwrite: true, errorOnExist: false });
1989
+ await fs12.ensureDir(path13.dirname(destination));
1990
+ await fs12.copy(sourcePath, destination, { overwrite: true, errorOnExist: false });
1668
1991
  }
1669
1992
  files.push(destination);
1993
+ const relPaths = [path13.relative(root, destination)];
1994
+ if (!superpowersSharedInstalled && manifestItem.originSourceId === SUPERPOWERS_ORIGIN_SOURCE_ID && item.type === "skill") {
1995
+ const sharedRel = await installSuperpowersShared(contentRoot, root, dryRun);
1996
+ if (sharedRel) {
1997
+ superpowersSharedInstalled = true;
1998
+ relPaths.push(sharedRel);
1999
+ files.push(path13.join(root, sharedRel));
2000
+ }
2001
+ }
1670
2002
  const current = installedPathsByItem.get(item.id) ?? [];
1671
- installedPathsByItem.set(item.id, [...current, path12.relative(root, destination)]);
2003
+ installedPathsByItem.set(item.id, [...current, ...relPaths]);
1672
2004
  installedIds.add(item.id);
1673
2005
  } else {
1674
2006
  warn(
@@ -1738,7 +2070,7 @@ async function cleanupStaleCatalogItems(root, knownIds, dryRun) {
1738
2070
  if (relPaths.length === 0) continue;
1739
2071
  const existing = [];
1740
2072
  for (const rel of relPaths) {
1741
- if (await fs11.pathExists(path12.join(root, rel))) existing.push(rel);
2073
+ if (await fs12.pathExists(path13.join(root, rel))) existing.push(rel);
1742
2074
  }
1743
2075
  if (existing.length === 0) continue;
1744
2076
  if (entry.hash === void 0) {
@@ -1755,13 +2087,13 @@ async function cleanupStaleCatalogItems(root, knownIds, dryRun) {
1755
2087
  continue;
1756
2088
  }
1757
2089
  for (const rel of existing) {
1758
- const abs = path12.join(root, rel);
2090
+ const abs = path13.join(root, rel);
1759
2091
  if (dryRun) {
1760
2092
  log(`[dry-run] would remove stale ${displayPath(root, abs)} (${entry.id})`);
1761
2093
  continue;
1762
2094
  }
1763
- await fs11.remove(abs);
1764
- await pruneEmptyDir(path12.dirname(abs));
2095
+ await fs12.remove(abs);
2096
+ await pruneEmptyDir(path13.dirname(abs));
1765
2097
  log(`Removed stale ${displayPath(root, abs)} (${entry.id})`);
1766
2098
  }
1767
2099
  }
@@ -1769,7 +2101,7 @@ async function cleanupStaleCatalogItems(root, knownIds, dryRun) {
1769
2101
 
1770
2102
  // src/commands/apply.ts
1771
2103
  async function cacheHasItems() {
1772
- const data = await readJson(path13.join(getCacheDir(), "manifest.json"));
2104
+ const data = await readJson(path14.join(getCacheDir(), "manifest.json"));
1773
2105
  return Array.isArray(data?.items) && data.items.length > 0;
1774
2106
  }
1775
2107
  async function runApply(options) {
@@ -1835,8 +2167,8 @@ async function runApply(options) {
1835
2167
  }
1836
2168
  }
1837
2169
  async function isHausProject(root) {
1838
- if (await fs12.pathExists(hausPath(root, "recommendation.json"))) return true;
1839
- if (await fs12.pathExists(claudePath(root, "settings.json"))) {
2170
+ if (await fs13.pathExists(hausPath(root, "recommendation.json"))) return true;
2171
+ if (await fs13.pathExists(claudePath(root, "settings.json"))) {
1840
2172
  const settings = await readProjectSettings(root);
1841
2173
  if (settings._haus != null) return true;
1842
2174
  }
@@ -1874,7 +2206,7 @@ async function runCatalogAudit() {
1874
2206
 
1875
2207
  // src/commands/clone.ts
1876
2208
  import { existsSync as existsSync2 } from "fs";
1877
- import path14 from "path";
2209
+ import path15 from "path";
1878
2210
 
1879
2211
  // src/utils/exec.ts
1880
2212
  import { execa } from "execa";
@@ -1902,6 +2234,20 @@ async function runGit(args, options = {}) {
1902
2234
  }
1903
2235
 
1904
2236
  // src/commands/clone.ts
2237
+ var GIT_LOCATION_VARS = [
2238
+ "GIT_DIR",
2239
+ "GIT_WORK_TREE",
2240
+ "GIT_INDEX_FILE",
2241
+ "GIT_COMMON_DIR",
2242
+ "GIT_OBJECT_DIRECTORY",
2243
+ "GIT_NAMESPACE",
2244
+ "GIT_PREFIX"
2245
+ ];
2246
+ function cloneEnv() {
2247
+ const env = { ...process.env };
2248
+ for (const key of GIT_LOCATION_VARS) delete env[key];
2249
+ return env;
2250
+ }
1905
2251
  function repoNameFromUrl(url) {
1906
2252
  const trimmed = url.trim().replace(/\.git$/, "").replace(/\/+$/, "");
1907
2253
  const tail = trimmed.split(/[/:]/).pop() ?? "";
@@ -1913,16 +2259,16 @@ async function runClone(url, opts = {}) {
1913
2259
  process.exitCode = 1;
1914
2260
  return;
1915
2261
  }
1916
- const target = path14.resolve(opts.dir?.trim() || repoNameFromUrl(url));
2262
+ const target = path15.resolve(opts.dir?.trim() || repoNameFromUrl(url));
1917
2263
  if (existsSync2(target)) {
1918
- log(`\u2022 ${path14.basename(target)} already present at ${target} \u2014 skipped`);
2264
+ log(`\u2022 ${path15.basename(target)} already present at ${target} \u2014 skipped`);
1919
2265
  return;
1920
2266
  }
1921
2267
  if (opts.dryRun) {
1922
2268
  log(`would clone ${url} \u2192 ${target}`);
1923
2269
  return;
1924
2270
  }
1925
- const res = await runGit(["clone", url, target]);
2271
+ const res = await runGit(["clone", url, target], { env: cloneEnv(), extendEnv: false });
1926
2272
  if (res.exitCode !== 0) {
1927
2273
  error(`clone failed for ${url}: ${(res.stderr || res.stdout).trim()}`);
1928
2274
  process.exitCode = 1;
@@ -1932,7 +2278,7 @@ async function runClone(url, opts = {}) {
1932
2278
  }
1933
2279
 
1934
2280
  // src/commands/config.ts
1935
- import path15 from "path";
2281
+ import path16 from "path";
1936
2282
  var CONFIG_PATH2 = ".haus-workflow/config.json";
1937
2283
  var HOOK_ALIASES = {
1938
2284
  "hook.context": "context"
@@ -1945,7 +2291,7 @@ async function runConfig(key, action) {
1945
2291
  );
1946
2292
  }
1947
2293
  const root = process.cwd();
1948
- const configPath = path15.join(root, CONFIG_PATH2);
2294
+ const configPath = path16.join(root, CONFIG_PATH2);
1949
2295
  const existing = await readJson(configPath);
1950
2296
  const cfg = existing ?? structuredClone(DEFAULT_HOOKS_CONFIG);
1951
2297
  cfg.hooks ??= {};
@@ -2325,7 +2671,7 @@ function selectRules(recommended, task, taskIntents) {
2325
2671
 
2326
2672
  // src/scanner/scan-project.ts
2327
2673
  import { readFile as readFile2 } from "fs/promises";
2328
- import path19 from "path";
2674
+ import path20 from "path";
2329
2675
 
2330
2676
  // src/utils/audit-checks.ts
2331
2677
  function isRecord(v) {
@@ -2352,8 +2698,8 @@ function compareVersions(a, b) {
2352
2698
  }
2353
2699
 
2354
2700
  // src/scanner/detect-package-manager.ts
2355
- import path16 from "path";
2356
- import fs13 from "fs-extra";
2701
+ import path17 from "path";
2702
+ import fs14 from "fs-extra";
2357
2703
  function detectPackageManager(root, packageManagerField) {
2358
2704
  const field = String(packageManagerField ?? "").trim();
2359
2705
  if (field.startsWith("yarn@")) {
@@ -2371,9 +2717,9 @@ function detectPackageManager(root, packageManagerField) {
2371
2717
  if (satisfiesVersion(version, ">=9")) return "npm";
2372
2718
  return "unknown";
2373
2719
  }
2374
- if (fs13.existsSync(path16.join(root, "yarn.lock"))) return "yarn";
2375
- if (fs13.existsSync(path16.join(root, "pnpm-lock.yaml"))) return "pnpm";
2376
- if (fs13.existsSync(path16.join(root, "package-lock.json"))) return "npm";
2720
+ if (fs14.existsSync(path17.join(root, "yarn.lock"))) return "yarn";
2721
+ if (fs14.existsSync(path17.join(root, "pnpm-lock.yaml"))) return "pnpm";
2722
+ if (fs14.existsSync(path17.join(root, "package-lock.json"))) return "npm";
2377
2723
  return "unknown";
2378
2724
  }
2379
2725
 
@@ -2570,7 +2916,7 @@ function runDetection(ctx, rules = STACK_RULES) {
2570
2916
  }
2571
2917
 
2572
2918
  // src/scanner/detection.ts
2573
- import path17 from "path";
2919
+ import path18 from "path";
2574
2920
  var UNSUPPORTED_MARKERS = {
2575
2921
  "requirements.txt": "python",
2576
2922
  "pyproject.toml": "python",
@@ -2624,14 +2970,14 @@ function finalizeRoles(registryRoles, deps, files) {
2624
2970
  function collectUnsupportedSignals(files) {
2625
2971
  return [
2626
2972
  ...new Set(
2627
- files.map((f) => UNSUPPORTED_MARKERS[path17.basename(f)]).filter((s) => Boolean(s))
2973
+ files.map((f) => UNSUPPORTED_MARKERS[path18.basename(f)]).filter((s) => Boolean(s))
2628
2974
  )
2629
2975
  ].sort();
2630
2976
  }
2631
2977
 
2632
2978
  // src/scanner/render.ts
2633
2979
  import { readFile } from "fs/promises";
2634
- import path18 from "path";
2980
+ import path19 from "path";
2635
2981
 
2636
2982
  // src/scanner/role-labels.ts
2637
2983
  var ROLE_LABELS = {
@@ -2693,7 +3039,7 @@ async function buildContentBlob(root, files) {
2693
3039
  const slice = candidates.slice(0, 300);
2694
3040
  const parts = await mapWithConcurrency(slice, async (rel) => {
2695
3041
  try {
2696
- return await readFile(path18.join(root, rel), "utf8");
3042
+ return await readFile(path19.join(root, rel), "utf8");
2697
3043
  } catch {
2698
3044
  return "";
2699
3045
  }
@@ -2791,8 +3137,8 @@ var SAFE_FILES = [
2791
3137
  "Gemfile"
2792
3138
  ];
2793
3139
  async function scanProject(root, mode = "fast") {
2794
- const pkg = await readJson(path19.join(root, "package.json"));
2795
- const composer = await readJson(path19.join(root, "composer.json"));
3140
+ const pkg = await readJson(path20.join(root, "package.json"));
3141
+ const composer = await readJson(path20.join(root, "composer.json"));
2796
3142
  const files = await listFiles(root, SAFE_FILES);
2797
3143
  const safeFiles = files.filter((f) => !blocked(f));
2798
3144
  const deps = dependencySet(pkg, composer);
@@ -2826,7 +3172,7 @@ async function scanProject(root, mode = "fast") {
2826
3172
  mode,
2827
3173
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2828
3174
  root,
2829
- repoName: String(pkg?.name ?? path19.basename(root)),
3175
+ repoName: String(pkg?.name ?? path20.basename(root)),
2830
3176
  packageManager,
2831
3177
  repoRoles: roles,
2832
3178
  detectedStacks: stacks,
@@ -2844,7 +3190,7 @@ async function scanProject(root, mode = "fast") {
2844
3190
  const scanHashes = Object.fromEntries(
2845
3191
  await mapWithConcurrency(
2846
3192
  safeFiles,
2847
- async (f) => [f, hashText(await readFile2(path19.join(root, f), "utf8"))]
3193
+ async (f) => [f, hashText(await readFile2(path20.join(root, f), "utf8"))]
2848
3194
  )
2849
3195
  );
2850
3196
  const repoSummary = renderSummary(context);
@@ -2929,8 +3275,8 @@ async function runContext(options) {
2929
3275
  }
2930
3276
 
2931
3277
  // src/commands/doctor.ts
2932
- import path20 from "path";
2933
- import fs14 from "fs-extra";
3278
+ import path21 from "path";
3279
+ import fs15 from "fs-extra";
2934
3280
 
2935
3281
  // src/update/npm-version.ts
2936
3282
  var NPM_PACKAGE_NAME = "@haus-tech/haus-workflow";
@@ -3010,7 +3356,7 @@ async function runDoctor(options) {
3010
3356
  const enabled = await isHookEnabled(root, key);
3011
3357
  ok(`- HOOK ${key}: ${enabled ? "enabled" : "disabled (default)"}`);
3012
3358
  }
3013
- const rootClaudeMdPath = path20.join(root, "CLAUDE.md");
3359
+ const rootClaudeMdPath = path21.join(root, "CLAUDE.md");
3014
3360
  const rootClaudeMdContent = await readText(rootClaudeMdPath);
3015
3361
  if (!rootClaudeMdContent) {
3016
3362
  flag(
@@ -3038,7 +3384,7 @@ async function runDoctor(options) {
3038
3384
  const block = rootClaudeMdContent.slice(beginIdx, endIdx + BLOCK_END.length);
3039
3385
  const importTargets = [...block.matchAll(/@\.haus-workflow\/(\S+)/g)].map((m) => m[1]);
3040
3386
  for (const target of importTargets) {
3041
- if (!await fs14.pathExists(hausPath(root, target))) {
3387
+ if (!await fs15.pathExists(hausPath(root, target))) {
3042
3388
  flag(
3043
3389
  `- CLAUDE.md import: @.haus-workflow/${target} does not resolve (run \`haus apply --write\`)`,
3044
3390
  `A file CLAUDE.md links to (${target}) is missing, so part of the guidance won't load`,
@@ -3049,7 +3395,7 @@ async function runDoctor(options) {
3049
3395
  }
3050
3396
  }
3051
3397
  const workflowPath = hausPath(root, "WORKFLOW.md");
3052
- const workflowExists = await fs14.pathExists(workflowPath);
3398
+ const workflowExists = await fs15.pathExists(workflowPath);
3053
3399
  if (!workflowExists) {
3054
3400
  flag(
3055
3401
  "- .haus-workflow/WORKFLOW.md: missing (run `haus apply --write`)",
@@ -3072,15 +3418,15 @@ async function runDoctor(options) {
3072
3418
  "haus apply --write --force"
3073
3419
  );
3074
3420
  } else {
3075
- const cachePath = path20.join(getCacheDir(), "templates/agentic-workflow-standard.md");
3076
- const bundledPath = path20.join(
3421
+ const cachePath = path21.join(getCacheDir(), "templates/agentic-workflow-standard.md");
3422
+ const bundledPath = path21.join(
3077
3423
  packageRoot(),
3078
3424
  "library",
3079
3425
  "global",
3080
3426
  "templates",
3081
3427
  "agentic-workflow-standard.md"
3082
3428
  );
3083
- const templatePath = await fs14.pathExists(cachePath) ? cachePath : bundledPath;
3429
+ const templatePath = await fs15.pathExists(cachePath) ? cachePath : bundledPath;
3084
3430
  const templateContent = await readText(templatePath);
3085
3431
  if (storedHashMatch && templateContent) {
3086
3432
  const currentHash = hashText(normaliseLF(templateContent));
@@ -3100,7 +3446,7 @@ async function runDoctor(options) {
3100
3446
  }
3101
3447
  }
3102
3448
  const workflowConfigPath = hausPath(root, "workflow-config.md");
3103
- const workflowConfigExists = await fs14.pathExists(workflowConfigPath);
3449
+ const workflowConfigExists = await fs15.pathExists(workflowConfigPath);
3104
3450
  if (!workflowConfigExists) {
3105
3451
  flag(
3106
3452
  "- .haus-workflow/workflow-config.md: missing (run `haus apply --write`)",
@@ -3108,7 +3454,7 @@ async function runDoctor(options) {
3108
3454
  "haus apply --write"
3109
3455
  );
3110
3456
  } else {
3111
- const cfg = await fs14.readFile(workflowConfigPath, "utf8");
3457
+ const cfg = await fs15.readFile(workflowConfigPath, "utf8");
3112
3458
  const unfilled = cfg.split("\n").filter((l) => l.includes("<!-- fill in")).length;
3113
3459
  if (unfilled > 0) {
3114
3460
  flag(
@@ -3139,7 +3485,7 @@ async function runDoctor(options) {
3139
3485
  ok(`- CATALOG CACHE: OK (${cacheAgeDays}d old)`);
3140
3486
  }
3141
3487
  }
3142
- const pkgJson = await readJson(path20.join(packageRoot(), "package.json"));
3488
+ const pkgJson = await readJson(path21.join(packageRoot(), "package.json"));
3143
3489
  const currentVersion = pkgJson?.version ?? "0.0.0";
3144
3490
  const npmStatus = await fetchNpmVersionStatus(currentVersion);
3145
3491
  if (npmStatus.updateAvailable && npmStatus.latest !== null) {
@@ -3225,15 +3571,16 @@ import { readFileSync as readFileSync2 } from "fs";
3225
3571
 
3226
3572
  // src/security/guard-bash.ts
3227
3573
  function guardBash(command) {
3228
- const matched = DANGEROUS_COMMANDS.find((token) => command.includes(token));
3574
+ const matched = DENY_COMMANDS.find((token) => command.includes(token));
3229
3575
  if (matched) return `I didn't run that \u2014 it can permanently change or delete things: ${command}`;
3230
3576
  return void 0;
3231
3577
  }
3232
3578
 
3233
3579
  // src/security/guard-file-access.ts
3234
3580
  function guardFileAccess(candidate) {
3235
- const matched = SENSITIVE_PATHS.find((token) => candidate.includes(token.replace("*", "")));
3236
- if (matched) return `I didn't open ${candidate} \u2014 it looks like it holds secrets or sensitive data`;
3581
+ const matched = DENY_PATHS.find((token) => candidate.includes(token.replace(/\*/g, "")));
3582
+ if (matched)
3583
+ return `I didn't open ${candidate} \u2014 it looks like it holds secrets or sensitive data`;
3237
3584
  return void 0;
3238
3585
  }
3239
3586
 
@@ -3282,8 +3629,8 @@ async function runGuard(kind, _options) {
3282
3629
  }
3283
3630
 
3284
3631
  // src/commands/init.ts
3285
- import path21 from "path";
3286
- import fs15 from "fs-extra";
3632
+ import path22 from "path";
3633
+ import fs16 from "fs-extra";
3287
3634
 
3288
3635
  // src/utils/prompts.ts
3289
3636
  import { stdin as input, stdout as output } from "process";
@@ -3644,8 +3991,8 @@ async function runSetupProject(options) {
3644
3991
  // src/commands/init.ts
3645
3992
  async function runInit(options) {
3646
3993
  const root = process.cwd();
3647
- const hausDir = path21.join(root, ".haus-workflow");
3648
- const alreadyInit = await fs15.pathExists(hausDir);
3994
+ const hausDir = path22.join(root, ".haus-workflow");
3995
+ const alreadyInit = await fs16.pathExists(hausDir);
3649
3996
  if (alreadyInit) {
3650
3997
  log("Haus AI already initialized in this project.");
3651
3998
  log("Run `haus setup-project` to reconfigure.");
@@ -3657,8 +4004,8 @@ async function runInit(options) {
3657
4004
 
3658
4005
  // src/install/apply.ts
3659
4006
  import crypto2 from "crypto";
3660
- import path22 from "path";
3661
- import fs16 from "fs-extra";
4007
+ import path23 from "path";
4008
+ import fs17 from "fs-extra";
3662
4009
 
3663
4010
  // src/install/header.ts
3664
4011
  var MD_PREFIX = "<!-- HAUS-MANAGED";
@@ -3746,40 +4093,40 @@ function hashContent(content2) {
3746
4093
  }
3747
4094
  function sourceVersion() {
3748
4095
  try {
3749
- const pkgPath = path22.join(packageRoot(), "package.json");
3750
- const pkg = JSON.parse(fs16.readFileSync(pkgPath, "utf8"));
4096
+ const pkgPath = path23.join(packageRoot(), "package.json");
4097
+ const pkg = JSON.parse(fs17.readFileSync(pkgPath, "utf8"));
3751
4098
  return `${pkg.name ?? "haus"}@${pkg.version ?? "0.0.0"}`;
3752
4099
  } catch {
3753
4100
  return "haus@0.0.0";
3754
4101
  }
3755
4102
  }
3756
4103
  function globalSrcDir() {
3757
- return path22.join(packageRoot(), "library", "global");
4104
+ return path23.join(packageRoot(), "library", "global");
3758
4105
  }
3759
4106
  function collectSourceFiles(srcDir, claudeDir) {
3760
4107
  const entries = [];
3761
- const skillsDir = path22.join(srcDir, "skills");
3762
- if (fs16.pathExistsSync(skillsDir)) {
3763
- for (const skillName of fs16.readdirSync(skillsDir)) {
3764
- const skillFile = path22.join(skillsDir, skillName, "SKILL.md");
3765
- if (fs16.pathExistsSync(skillFile)) {
4108
+ const skillsDir = path23.join(srcDir, "skills");
4109
+ if (fs17.pathExistsSync(skillsDir)) {
4110
+ for (const skillName of fs17.readdirSync(skillsDir)) {
4111
+ const skillFile = path23.join(skillsDir, skillName, "SKILL.md");
4112
+ if (fs17.pathExistsSync(skillFile)) {
3766
4113
  entries.push({
3767
4114
  stableId: `skill.${skillName}`,
3768
- srcRelPath: path22.join("library", "global", "skills", skillName, "SKILL.md"),
3769
- destPath: path22.join(claudeDir, "skills", skillName, "SKILL.md")
4115
+ srcRelPath: path23.join("library", "global", "skills", skillName, "SKILL.md"),
4116
+ destPath: path23.join(claudeDir, "skills", skillName, "SKILL.md")
3770
4117
  });
3771
4118
  }
3772
4119
  }
3773
4120
  }
3774
- const commandsDir = path22.join(srcDir, "commands");
3775
- if (fs16.pathExistsSync(commandsDir)) {
3776
- for (const fileName of fs16.readdirSync(commandsDir)) {
4121
+ const commandsDir = path23.join(srcDir, "commands");
4122
+ if (fs17.pathExistsSync(commandsDir)) {
4123
+ for (const fileName of fs17.readdirSync(commandsDir)) {
3777
4124
  if (!fileName.endsWith(".md")) continue;
3778
4125
  const commandName = fileName.slice(0, -".md".length);
3779
4126
  entries.push({
3780
4127
  stableId: `command.${commandName}`,
3781
- srcRelPath: path22.join("library", "global", "commands", fileName),
3782
- destPath: path22.join(claudeDir, "commands", fileName)
4128
+ srcRelPath: path23.join("library", "global", "commands", fileName),
4129
+ destPath: path23.join(claudeDir, "commands", fileName)
3783
4130
  });
3784
4131
  }
3785
4132
  }
@@ -3803,7 +4150,7 @@ async function applyInstall(options = {}) {
3803
4150
  };
3804
4151
  const manifestFiles = [];
3805
4152
  for (const entry of sourceFiles) {
3806
- const srcPath = path22.join(packageRoot(), entry.srcRelPath);
4153
+ const srcPath = path23.join(packageRoot(), entry.srcRelPath);
3807
4154
  const rawContent = await readText(srcPath);
3808
4155
  if (rawContent === void 0) {
3809
4156
  warn(`Source file not found: ${entry.srcRelPath}`);
@@ -3823,7 +4170,7 @@ async function applyInstall(options = {}) {
3823
4170
  }
3824
4171
  continue;
3825
4172
  }
3826
- const destExists = fs16.pathExistsSync(entry.destPath);
4173
+ const destExists = fs17.pathExistsSync(entry.destPath);
3827
4174
  if (destExists) {
3828
4175
  const currentContent = await readText(entry.destPath);
3829
4176
  if (currentContent !== void 0) {
@@ -3859,24 +4206,25 @@ async function applyInstall(options = {}) {
3859
4206
  schemaVersion: SCHEMA_VERSION2
3860
4207
  });
3861
4208
  }
3862
- const fragmentPath = path22.join(srcDir, "settings-fragments", "hooks.json");
4209
+ const fragmentPath = path23.join(srcDir, "settings-fragments", "hooks.json");
3863
4210
  const fragments = await loadHooksFragment(fragmentPath);
3864
4211
  const settings = await readSettings();
3865
4212
  const { settings: hookSettings, addedIds } = mergeHooks(settings, fragments);
3866
4213
  const { settings: deniedSettings } = mergeDenyRules(hookSettings, buildDenyRules());
3867
- const { settings: mergedSettings } = mergeAllowRules(deniedSettings, buildAllowRules());
4214
+ const { settings: allowedSettings } = mergeAllowRules(deniedSettings, buildAllowRules());
4215
+ const { settings: mergedSettings } = mergeAskRules(allowedSettings, buildAskRules());
3868
4216
  result.hookIds = addedIds;
3869
4217
  if (!check && existingManifest) {
3870
4218
  const currentDestPaths = new Set(sourceFiles.map((f) => f.destPath));
3871
4219
  for (const entry of existingManifest.files) {
3872
4220
  if (currentDestPaths.has(entry.destPath)) continue;
3873
- if (!fs16.pathExistsSync(entry.destPath)) continue;
4221
+ if (!fs17.pathExistsSync(entry.destPath)) continue;
3874
4222
  const content2 = await readText(entry.destPath);
3875
4223
  if (!content2) continue;
3876
4224
  const hasHeader = parseMarkdownHeader(content2) !== void 0;
3877
4225
  const currentHash = hashContent(content2);
3878
4226
  if (hasHeader && currentHash === entry.hash) {
3879
- if (!dryRun) await fs16.remove(entry.destPath);
4227
+ if (!dryRun) await fs17.remove(entry.destPath);
3880
4228
  result.deleted.push(entry.destPath);
3881
4229
  } else {
3882
4230
  warn(`Orphaned file ${entry.destPath} was user-modified \u2014 leaving in place`);
@@ -4003,8 +4351,8 @@ async function runScan(options) {
4003
4351
  }
4004
4352
 
4005
4353
  // src/commands/undo.ts
4006
- import path23 from "path";
4007
- import fs17 from "fs-extra";
4354
+ import path24 from "path";
4355
+ import fs18 from "fs-extra";
4008
4356
 
4009
4357
  // src/claude/managed-paths.ts
4010
4358
  var PROJECT_MANAGED_CLAUDE_REL = [
@@ -4030,61 +4378,61 @@ async function collectManagedPaths(root) {
4030
4378
  const lock = await readJson(hausPath(root, "haus.lock.json"));
4031
4379
  for (const row of lock ?? []) {
4032
4380
  for (const rel of row.paths ?? []) {
4033
- paths.add(path23.resolve(root, rel));
4381
+ paths.add(path24.resolve(root, rel));
4034
4382
  }
4035
4383
  }
4036
4384
  const existing = [];
4037
4385
  for (const abs of paths) {
4038
- if (await fs17.pathExists(abs)) existing.push(abs);
4386
+ if (await fs18.pathExists(abs)) existing.push(abs);
4039
4387
  }
4040
4388
  return existing;
4041
4389
  }
4042
4390
  async function settingsHasHausContent(root) {
4043
4391
  const settingsPath = claudePath(root, "settings.json");
4044
- if (!await fs17.pathExists(settingsPath)) return false;
4392
+ if (!await fs18.pathExists(settingsPath)) return false;
4045
4393
  const settings = await readProjectSettings(root);
4046
4394
  return settings._haus != null;
4047
4395
  }
4048
4396
  async function claudeMdHasHausBlock(root) {
4049
- const filePath = path23.join(root, "CLAUDE.md");
4050
- if (!await fs17.pathExists(filePath)) return false;
4051
- const text = await fs17.readFile(filePath, "utf8");
4397
+ const filePath = path24.join(root, "CLAUDE.md");
4398
+ if (!await fs18.pathExists(filePath)) return false;
4399
+ const text = await fs18.readFile(filePath, "utf8");
4052
4400
  return text.includes(BLOCK_BEGIN);
4053
4401
  }
4054
4402
  async function stripProjectSettings(root) {
4055
4403
  const settingsPath = claudePath(root, "settings.json");
4056
- if (!await fs17.pathExists(settingsPath)) return false;
4404
+ if (!await fs18.pathExists(settingsPath)) return false;
4057
4405
  let settings = await readProjectSettings(root);
4058
- settings = stripHausAllow(stripHausDeny(stripHausHooks(settings)));
4406
+ settings = stripHausHooks(stripHausAsk(stripHausAllow(stripHausDeny(settings))));
4059
4407
  const hasContent = Object.keys(settings).length > 0;
4060
4408
  if (hasContent) {
4061
4409
  await writeProjectSettings(root, settings);
4062
- log(`Stripped haus rules from ${path23.relative(root, settingsPath)} (user settings preserved).`);
4410
+ log(`Stripped haus rules from ${path24.relative(root, settingsPath)} (user settings preserved).`);
4063
4411
  return true;
4064
4412
  }
4065
- await fs17.remove(settingsPath);
4066
- log(`Removed ${path23.relative(root, settingsPath)} (no user-owned settings remained).`);
4413
+ await fs18.remove(settingsPath);
4414
+ log(`Removed ${path24.relative(root, settingsPath)} (no user-owned settings remained).`);
4067
4415
  return true;
4068
4416
  }
4069
4417
  async function stripRootClaudeMd(root) {
4070
- const filePath = path23.join(root, "CLAUDE.md");
4071
- if (!await fs17.pathExists(filePath)) return false;
4072
- const prev = await fs17.readFile(filePath, "utf8");
4418
+ const filePath = path24.join(root, "CLAUDE.md");
4419
+ if (!await fs18.pathExists(filePath)) return false;
4420
+ const prev = await fs18.readFile(filePath, "utf8");
4073
4421
  if (!prev.includes(BLOCK_BEGIN)) return false;
4074
4422
  const next = stripHausBlock(prev);
4075
4423
  if (next.length === 0) {
4076
- await fs17.remove(filePath);
4424
+ await fs18.remove(filePath);
4077
4425
  log("Removed CLAUDE.md (only contained haus import block).");
4078
4426
  } else {
4079
- await fs17.writeFile(filePath, next, "utf8");
4427
+ await fs18.writeFile(filePath, next, "utf8");
4080
4428
  log("Removed haus import block from CLAUDE.md (user content preserved).");
4081
4429
  }
4082
4430
  return true;
4083
4431
  }
4084
4432
  async function pruneDirIfEmpty(dir) {
4085
- if (!await fs17.pathExists(dir)) return;
4086
- const entries = await fs17.readdir(dir);
4087
- if (entries.length === 0) await fs17.remove(dir);
4433
+ if (!await fs18.pathExists(dir)) return;
4434
+ const entries = await fs18.readdir(dir);
4435
+ if (entries.length === 0) await fs18.remove(dir);
4088
4436
  }
4089
4437
  async function runUndo(options) {
4090
4438
  const root = process.cwd();
@@ -4095,7 +4443,7 @@ async function runUndo(options) {
4095
4443
  log("Nothing to remove: no haus-managed files found in this directory.");
4096
4444
  return;
4097
4445
  }
4098
- const relTargets = managed.map((p) => path23.relative(root, p));
4446
+ const relTargets = managed.map((p) => path24.relative(root, p));
4099
4447
  const summaryParts = [...relTargets];
4100
4448
  if (stripSettings) summaryParts.push(".claude/settings.json (haus rules only)");
4101
4449
  if (stripClaudeMd) summaryParts.push("CLAUDE.md (haus import block only)");
@@ -4111,9 +4459,9 @@ User-owned .claude/ files will be preserved.`
4111
4459
  }
4112
4460
  }
4113
4461
  for (const abs of managed) {
4114
- if (!await fs17.pathExists(abs)) continue;
4115
- await fs17.remove(abs);
4116
- log(`Removed ${path23.relative(root, abs)}`);
4462
+ if (!await fs18.pathExists(abs)) continue;
4463
+ await fs18.remove(abs);
4464
+ log(`Removed ${path24.relative(root, abs)}`);
4117
4465
  }
4118
4466
  if (stripSettings) await stripProjectSettings(root);
4119
4467
  if (stripClaudeMd) await stripRootClaudeMd(root);
@@ -4124,8 +4472,8 @@ User-owned .claude/ files will be preserved.`
4124
4472
 
4125
4473
  // src/install/uninstall.ts
4126
4474
  import crypto3 from "crypto";
4127
- import path24 from "path";
4128
- import fs18 from "fs-extra";
4475
+ import path25 from "path";
4476
+ import fs19 from "fs-extra";
4129
4477
  async function runUninstall(options = {}) {
4130
4478
  const { force = false } = options;
4131
4479
  const manifest = await readManifest();
@@ -4135,7 +4483,7 @@ async function runUninstall(options = {}) {
4135
4483
  return result;
4136
4484
  }
4137
4485
  for (const entry of manifest.files) {
4138
- const exists = fs18.pathExistsSync(entry.destPath);
4486
+ const exists = fs19.pathExistsSync(entry.destPath);
4139
4487
  if (!exists) continue;
4140
4488
  const content2 = await readText(entry.destPath);
4141
4489
  if (content2 === void 0) continue;
@@ -4153,22 +4501,22 @@ async function runUninstall(options = {}) {
4153
4501
  result.skipped.push(entry.destPath);
4154
4502
  continue;
4155
4503
  }
4156
- await fs18.remove(entry.destPath);
4157
- await pruneEmptyDir(path24.dirname(entry.destPath));
4504
+ await fs19.remove(entry.destPath);
4505
+ await pruneEmptyDir(path25.dirname(entry.destPath));
4158
4506
  result.deleted.push(entry.destPath);
4159
4507
  }
4160
4508
  const settings = await readSettings();
4161
- const stripped = stripHausHooks(stripHausAllow(stripHausDeny(settings)));
4509
+ const stripped = stripHausHooks(stripHausAsk(stripHausAllow(stripHausDeny(settings))));
4162
4510
  await writeSettings(stripped);
4163
4511
  result.hooksStripped = true;
4164
- const hausDir = path24.join(globalClaudeDir(), "haus");
4512
+ const hausDir = path25.join(globalClaudeDir(), "haus");
4165
4513
  const manifestPath2 = hausManifestPath();
4166
- if (fs18.pathExistsSync(manifestPath2)) {
4167
- await fs18.remove(manifestPath2);
4514
+ if (fs19.pathExistsSync(manifestPath2)) {
4515
+ await fs19.remove(manifestPath2);
4168
4516
  }
4169
- if (fs18.pathExistsSync(hausDir)) {
4170
- const remaining = await fs18.readdir(hausDir);
4171
- if (remaining.length === 0) await fs18.remove(hausDir);
4517
+ if (fs19.pathExistsSync(hausDir)) {
4518
+ const remaining = await fs19.readdir(hausDir);
4519
+ if (remaining.length === 0) await fs19.remove(hausDir);
4172
4520
  }
4173
4521
  return result;
4174
4522
  }
@@ -4199,7 +4547,7 @@ async function runUninstallCommand(options) {
4199
4547
  }
4200
4548
 
4201
4549
  // src/commands/update.ts
4202
- import path26 from "path";
4550
+ import path27 from "path";
4203
4551
 
4204
4552
  // src/update/diff-generated-files.ts
4205
4553
  function diffGeneratedFiles() {
@@ -4226,7 +4574,7 @@ function summarizeLockDiff(before, after) {
4226
4574
 
4227
4575
  // src/update/lockfile.ts
4228
4576
  import { mkdir, readFile as readFile3, copyFile } from "fs/promises";
4229
- import path25 from "path";
4577
+ import path26 from "path";
4230
4578
  async function checkLock(root) {
4231
4579
  const lock = await readJson(hausPath(root, "haus.lock.json")) ?? [];
4232
4580
  const hasValidVersions = lock.every(
@@ -4257,7 +4605,7 @@ async function applyLock(root) {
4257
4605
  try {
4258
4606
  const backupDir = hausPath(root, "backups");
4259
4607
  await mkdir(backupDir, { recursive: true });
4260
- await copyFile(lockPath, path25.join(backupDir, `haus.lock.${Date.now()}.json`));
4608
+ await copyFile(lockPath, path26.join(backupDir, `haus.lock.${Date.now()}.json`));
4261
4609
  } catch {
4262
4610
  }
4263
4611
  const enriched = await Promise.all(
@@ -4279,7 +4627,7 @@ function diffLock(before, after) {
4279
4627
  }
4280
4628
  async function hasLocalOverrides(root) {
4281
4629
  try {
4282
- await readFile3(path25.join(root, ".claude", "settings.json"), "utf8");
4630
+ await readFile3(path26.join(root, ".claude", "settings.json"), "utf8");
4283
4631
  return true;
4284
4632
  } catch {
4285
4633
  return false;
@@ -4291,7 +4639,7 @@ var NPM_PACKAGE_NAME2 = "@haus-tech/haus-workflow";
4291
4639
  async function runUpdate(options) {
4292
4640
  const root = process.cwd();
4293
4641
  if (options.check) {
4294
- const pkgJson2 = await readJson(path26.join(packageRoot(), "package.json"));
4642
+ const pkgJson2 = await readJson(path27.join(packageRoot(), "package.json"));
4295
4643
  const currentVersion2 = pkgJson2?.version ?? "0.0.0";
4296
4644
  const [status, npmVersion, latestCatalogTag, globalInstallDrift] = await Promise.all([
4297
4645
  checkLock(root),
@@ -4321,7 +4669,7 @@ async function runUpdate(options) {
4321
4669
  if (status.driftCount > 0) process.exitCode = 1;
4322
4670
  return;
4323
4671
  }
4324
- const pkgJson = await readJson(path26.join(packageRoot(), "package.json"));
4672
+ const pkgJson = await readJson(path27.join(packageRoot(), "package.json"));
4325
4673
  const currentVersion = pkgJson?.version ?? "0.0.0";
4326
4674
  const npmStatus = await fetchNpmVersionStatus(currentVersion);
4327
4675
  if (npmStatus.updateAvailable && npmStatus.latest !== null) {
@@ -4398,8 +4746,8 @@ async function detectGlobalInstallDrift() {
4398
4746
  }
4399
4747
 
4400
4748
  // src/commands/validate-catalog.ts
4401
- import fs19 from "fs";
4402
- import path27 from "path";
4749
+ import fs20 from "fs";
4750
+ import path28 from "path";
4403
4751
  function auditForbiddenStacks(items) {
4404
4752
  const failures = [];
4405
4753
  for (const item of items) {
@@ -4476,40 +4824,40 @@ function auditShippedFiles(manifestDir, items) {
4476
4824
  const failures = [];
4477
4825
  for (const item of items) {
4478
4826
  if (!item.path) continue;
4479
- const absPath = path27.join(manifestDir, item.path);
4827
+ const absPath = path28.join(manifestDir, item.path);
4480
4828
  if (item.type === "skill") {
4481
- const skillMd = path27.join(absPath, "SKILL.md");
4482
- if (!fs19.existsSync(skillMd)) {
4483
- failures.push(`${item.id}: missing ${path27.relative(manifestDir, skillMd)}`);
4829
+ const skillMd = path28.join(absPath, "SKILL.md");
4830
+ if (!fs20.existsSync(skillMd)) {
4831
+ failures.push(`${item.id}: missing ${path28.relative(manifestDir, skillMd)}`);
4484
4832
  continue;
4485
4833
  }
4486
- const text = fs19.readFileSync(skillMd, "utf8");
4834
+ const text = fs20.readFileSync(skillMd, "utf8");
4487
4835
  failures.push(...checkRequiredFrontmatter(text, `${item.id}: SKILL.md`));
4488
4836
  failures.push(
4489
- ...auditForbiddenTagsInText(text, `${item.id}: ${path27.relative(manifestDir, skillMd)}`)
4837
+ ...auditForbiddenTagsInText(text, `${item.id}: ${path28.relative(manifestDir, skillMd)}`)
4490
4838
  );
4491
4839
  } else if (item.type === "agent") {
4492
- if (!fs19.existsSync(absPath)) {
4840
+ if (!fs20.existsSync(absPath)) {
4493
4841
  failures.push(`${item.id}: missing agent file ${item.path}`);
4494
4842
  continue;
4495
4843
  }
4496
- const text = fs19.readFileSync(absPath, "utf8");
4497
- const rel = path27.relative(manifestDir, absPath);
4844
+ const text = fs20.readFileSync(absPath, "utf8");
4845
+ const rel = path28.relative(manifestDir, absPath);
4498
4846
  failures.push(...checkRequiredFrontmatter(text, `${item.id}: ${rel}`));
4499
4847
  failures.push(...auditForbiddenTagsInText(text, `${item.id}: ${rel}`));
4500
4848
  } else if (item.type === "template") {
4501
- if (!fs19.existsSync(absPath)) {
4849
+ if (!fs20.existsSync(absPath)) {
4502
4850
  failures.push(`${item.id}: missing template file ${item.path}`);
4503
4851
  continue;
4504
4852
  }
4505
4853
  failures.push(...auditTemplateContent(manifestDir, absPath, item.id));
4506
4854
  } else if (item.type === "command") {
4507
- if (!fs19.existsSync(absPath)) {
4855
+ if (!fs20.existsSync(absPath)) {
4508
4856
  failures.push(`${item.id}: missing command file ${item.path}`);
4509
4857
  continue;
4510
4858
  }
4511
- const text = fs19.readFileSync(absPath, "utf8");
4512
- const rel = path27.relative(manifestDir, absPath);
4859
+ const text = fs20.readFileSync(absPath, "utf8");
4860
+ const rel = path28.relative(manifestDir, absPath);
4513
4861
  failures.push(...checkRequiredFrontmatter(text, `${item.id}: ${rel}`));
4514
4862
  failures.push(...auditTemplateContent(manifestDir, absPath, item.id));
4515
4863
  }
@@ -4517,8 +4865,8 @@ function auditShippedFiles(manifestDir, items) {
4517
4865
  return failures;
4518
4866
  }
4519
4867
  function auditTemplateContent(manifestDir, absPath, itemId) {
4520
- const rel = path27.relative(manifestDir, absPath);
4521
- const text = fs19.readFileSync(absPath, "utf8");
4868
+ const rel = path28.relative(manifestDir, absPath);
4869
+ const text = fs20.readFileSync(absPath, "utf8");
4522
4870
  const failures = [];
4523
4871
  const lines = text.split(/\r?\n/);
4524
4872
  for (let i = 0; i < lines.length; i++) {
@@ -4540,11 +4888,11 @@ function auditMarkdownContent(manifestDir) {
4540
4888
  const failures = [];
4541
4889
  const dirs = ["skills", "agents", "templates", "commands"];
4542
4890
  for (const dir of dirs) {
4543
- const abs = path27.join(manifestDir, dir);
4544
- if (!fs19.existsSync(abs)) continue;
4891
+ const abs = path28.join(manifestDir, dir);
4892
+ if (!fs20.existsSync(abs)) continue;
4545
4893
  walkMd(abs, (file) => {
4546
- const text = fs19.readFileSync(file, "utf8");
4547
- const rel = path27.relative(manifestDir, file);
4894
+ const text = fs20.readFileSync(file, "utf8");
4895
+ const rel = path28.relative(manifestDir, file);
4548
4896
  const lines = text.split(/\r?\n/);
4549
4897
  for (let i = 0; i < lines.length; i++) {
4550
4898
  const line2 = lines[i] ?? "";
@@ -4563,8 +4911,8 @@ function auditMarkdownContent(manifestDir) {
4563
4911
  return failures;
4564
4912
  }
4565
4913
  function walkMd(dir, fn) {
4566
- for (const entry of fs19.readdirSync(dir, { withFileTypes: true })) {
4567
- const full = path27.join(dir, entry.name);
4914
+ for (const entry of fs20.readdirSync(dir, { withFileTypes: true })) {
4915
+ const full = path28.join(dir, entry.name);
4568
4916
  if (entry.isDirectory()) walkMd(full, fn);
4569
4917
  else if (entry.name.endsWith(".md")) fn(full);
4570
4918
  }
@@ -4575,8 +4923,8 @@ async function runValidateCatalog(manifestPath2) {
4575
4923
  process.exitCode = 1;
4576
4924
  return;
4577
4925
  }
4578
- const abs = path27.resolve(process.cwd(), manifestPath2);
4579
- const manifestDir = path27.dirname(abs);
4926
+ const abs = path28.resolve(process.cwd(), manifestPath2);
4927
+ const manifestDir = path28.dirname(abs);
4580
4928
  const data = await readJson(abs);
4581
4929
  if (!data?.items) {
4582
4930
  error(`Could not read catalog manifest at ${abs}`);
@@ -4606,7 +4954,7 @@ async function runValidateCatalog(manifestPath2) {
4606
4954
 
4607
4955
  // src/commands/workspace.ts
4608
4956
  import { existsSync as existsSync5, statSync as statSync2 } from "fs";
4609
- import path34 from "path";
4957
+ import path35 from "path";
4610
4958
 
4611
4959
  // src/commands/workspace/aggregate.ts
4612
4960
  async function writeWorkspaceArtifacts(workspaceRoot, repos, relationships = []) {
@@ -4660,7 +5008,7 @@ ${summaries.map(
4660
5008
  }
4661
5009
 
4662
5010
  // src/commands/workspace/config.ts
4663
- import path28 from "path";
5011
+ import path29 from "path";
4664
5012
  import YAML from "yaml";
4665
5013
  var WORKSPACE_FILE = "haus.workspace.yaml";
4666
5014
  function parseWorkspaceConfig(text) {
@@ -4683,12 +5031,12 @@ function parseWorkspaceConfig(text) {
4683
5031
  };
4684
5032
  }
4685
5033
  async function readWorkspaceConfig(workspaceRoot) {
4686
- return parseWorkspaceConfig(await readText(path28.join(workspaceRoot, WORKSPACE_FILE)));
5034
+ return parseWorkspaceConfig(await readText(path29.join(workspaceRoot, WORKSPACE_FILE)));
4687
5035
  }
4688
5036
 
4689
5037
  // src/commands/workspace/discover.ts
4690
- import path29 from "path";
4691
- import fg3 from "fast-glob";
5038
+ import path30 from "path";
5039
+ import fg4 from "fast-glob";
4692
5040
  import YAML2 from "yaml";
4693
5041
  var DEFAULT_MAX_DEPTH = 3;
4694
5042
  var REPO_MARKERS = ["**/.git", "**/package.json", "**/composer.json"];
@@ -4704,7 +5052,7 @@ function isDescendant(child, ancestor) {
4704
5052
  return child === ancestor ? false : child.startsWith(`${ancestor}/`);
4705
5053
  }
4706
5054
  async function discoverRepos(workspaceRoot, maxDepth = DEFAULT_MAX_DEPTH) {
4707
- const matches = await fg3(REPO_MARKERS, {
5055
+ const matches = await fg4(REPO_MARKERS, {
4708
5056
  cwd: workspaceRoot,
4709
5057
  dot: true,
4710
5058
  onlyFiles: false,
@@ -4715,8 +5063,8 @@ async function discoverRepos(workspaceRoot, maxDepth = DEFAULT_MAX_DEPTH) {
4715
5063
  const gitDirs = /* @__PURE__ */ new Set();
4716
5064
  const manifestDirs = /* @__PURE__ */ new Set();
4717
5065
  for (const match of matches) {
4718
- const base = path29.posix.basename(match);
4719
- const dir = path29.posix.dirname(match);
5066
+ const base = path30.posix.basename(match);
5067
+ const dir = path30.posix.dirname(match);
4720
5068
  const owner = dir === "." ? "." : dir;
4721
5069
  if (base === ".git") gitDirs.add(owner);
4722
5070
  else manifestDirs.add(owner);
@@ -4732,9 +5080,9 @@ async function discoverRepos(workspaceRoot, maxDepth = DEFAULT_MAX_DEPTH) {
4732
5080
  }
4733
5081
  repoRoots.sort((a, b) => a.localeCompare(b));
4734
5082
  return mapWithConcurrency(repoRoots, async (relDir) => {
4735
- const absDir = path29.resolve(workspaceRoot, relDir);
4736
- const pkg = await readJson(path29.join(absDir, "package.json"));
4737
- const name = typeof pkg?.name === "string" && pkg.name.length > 0 ? pkg.name : path29.basename(relDir === "." ? workspaceRoot : absDir);
5083
+ const absDir = path30.resolve(workspaceRoot, relDir);
5084
+ const pkg = await readJson(path30.join(absDir, "package.json"));
5085
+ const name = typeof pkg?.name === "string" && pkg.name.length > 0 ? pkg.name : path30.basename(relDir === "." ? workspaceRoot : absDir);
4738
5086
  let role = "auto";
4739
5087
  try {
4740
5088
  const scan = await scanProject(absDir, "fast");
@@ -4777,7 +5125,7 @@ function renderWorkspaceYaml(config2) {
4777
5125
  });
4778
5126
  }
4779
5127
  async function runDiscover(workspaceRoot, opts = {}) {
4780
- const yamlPath = path29.join(workspaceRoot, "haus.workspace.yaml");
5128
+ const yamlPath = path30.join(workspaceRoot, "haus.workspace.yaml");
4781
5129
  const existingText = await readText(yamlPath);
4782
5130
  const existing = parseWorkspaceConfig(existingText);
4783
5131
  if (existingText && !existing) {
@@ -4811,18 +5159,18 @@ async function runDiscover(workspaceRoot, opts = {}) {
4811
5159
 
4812
5160
  // src/commands/workspace/doctor.ts
4813
5161
  import { existsSync as existsSync3 } from "fs";
4814
- import path31 from "path";
5162
+ import path32 from "path";
4815
5163
 
4816
5164
  // src/commands/workspace/manifest.ts
4817
5165
  import { readFileSync as readFileSync3 } from "fs";
4818
- import path30 from "path";
5166
+ import path31 from "path";
4819
5167
  var MANIFEST_FILE = "workspace.manifest.json";
4820
5168
  function manifestPath(workspaceRoot) {
4821
5169
  return hausPath(workspaceRoot, MANIFEST_FILE);
4822
5170
  }
4823
5171
  function hausVersion() {
4824
5172
  try {
4825
- const pkg = JSON.parse(readFileSync3(path30.join(packageRoot(), "package.json"), "utf8"));
5173
+ const pkg = JSON.parse(readFileSync3(path31.join(packageRoot(), "package.json"), "utf8"));
4826
5174
  return pkg.version ?? "0.0.0";
4827
5175
  } catch {
4828
5176
  return "0.0.0";
@@ -4888,7 +5236,7 @@ async function runWorkspaceDoctor(workspaceRoot, opts = {}) {
4888
5236
  }
4889
5237
  const manifestByName = new Map(manifest.repos.map((r) => [r.name, r]));
4890
5238
  for (const repo of config2.repos) {
4891
- const repoRoot = path31.resolve(workspaceRoot, repo.path);
5239
+ const repoRoot = path32.resolve(workspaceRoot, repo.path);
4892
5240
  const entry = manifestByName.get(repo.name);
4893
5241
  if (!entry) {
4894
5242
  flag({
@@ -4962,11 +5310,11 @@ function emit(args) {
4962
5310
 
4963
5311
  // src/commands/workspace/setup.ts
4964
5312
  import { existsSync as existsSync4, statSync } from "fs";
4965
- import path33 from "path";
5313
+ import path34 from "path";
4966
5314
 
4967
5315
  // src/claude/write-workspace-claude-md.ts
4968
- import path32 from "path";
4969
- import fs20 from "fs-extra";
5316
+ import path33 from "path";
5317
+ import fs21 from "fs-extra";
4970
5318
  function buildWorkspaceImportBlock(client, members) {
4971
5319
  const memberLines = members.map((m) => `- ${m.name} (${m.path})`);
4972
5320
  const body = [
@@ -4984,8 +5332,8 @@ ${BLOCK_END}`;
4984
5332
  async function writeWorkspaceClaudeMd(workspaceRoot, opts) {
4985
5333
  const block = buildWorkspaceImportBlock(opts.client, opts.members);
4986
5334
  const dryRun = opts.dryRun ?? false;
4987
- const filePath = opts.collision ? hausPath(workspaceRoot, "WORKSPACE.md") : path32.join(workspaceRoot, "CLAUDE.md");
4988
- const prev = await fs20.pathExists(filePath) ? await fs20.readFile(filePath, "utf8") : "";
5335
+ const filePath = opts.collision ? hausPath(workspaceRoot, "WORKSPACE.md") : path33.join(workspaceRoot, "CLAUDE.md");
5336
+ const prev = await fs21.pathExists(filePath) ? await fs21.readFile(filePath, "utf8") : "";
4989
5337
  const next = opts.collision ? `${block}
4990
5338
  ` : injectHausBlock(prev, block);
4991
5339
  const printable = displayPath(workspaceRoot, filePath);
@@ -5010,21 +5358,21 @@ async function writeWorkspaceClaudeMd(workspaceRoot, opts) {
5010
5358
 
5011
5359
  // src/commands/workspace/setup.ts
5012
5360
  function resolveWorkspaceRoot(start = process.cwd()) {
5013
- let dir = path33.resolve(start);
5361
+ let dir = path34.resolve(start);
5014
5362
  for (; ; ) {
5015
- if (existsSync4(path33.join(dir, WORKSPACE_FILE))) return dir;
5016
- const parent = path33.dirname(dir);
5017
- if (parent === dir) return path33.resolve(start);
5363
+ if (existsSync4(path34.join(dir, WORKSPACE_FILE))) return dir;
5364
+ const parent = path34.dirname(dir);
5365
+ if (parent === dir) return path34.resolve(start);
5018
5366
  dir = parent;
5019
5367
  }
5020
5368
  }
5021
5369
  function isRootRepo(workspaceRoot, repoPath) {
5022
- return path33.resolve(workspaceRoot, repoPath) === path33.resolve(workspaceRoot);
5370
+ return path34.resolve(workspaceRoot, repoPath) === path34.resolve(workspaceRoot);
5023
5371
  }
5024
5372
  async function runWorkspaceSetup(workspaceRoot, options = {}) {
5025
5373
  const mode = options.mode ?? "fast";
5026
5374
  const apply = options.write ?? false;
5027
- const configText = await readText(path33.join(workspaceRoot, WORKSPACE_FILE));
5375
+ const configText = await readText(path34.join(workspaceRoot, WORKSPACE_FILE));
5028
5376
  if (!configText) {
5029
5377
  error(`Missing ${WORKSPACE_FILE}. Run \`haus workspace discover\` or \`init\` first.`);
5030
5378
  process.exitCode = 1;
@@ -5041,7 +5389,7 @@ async function runWorkspaceSetup(workspaceRoot, options = {}) {
5041
5389
  const statuses = [];
5042
5390
  const aggregateInputs = [];
5043
5391
  for (const repo of repos) {
5044
- const repoRoot = path33.resolve(workspaceRoot, repo.path);
5392
+ const repoRoot = path34.resolve(workspaceRoot, repo.path);
5045
5393
  log(`
5046
5394
  \u2192 ${repo.name} (${repo.path})`);
5047
5395
  try {
@@ -5110,7 +5458,7 @@ async function runWorkspaceSetup(workspaceRoot, options = {}) {
5110
5458
  const status = statusByName.get(repo.name);
5111
5459
  const role = repo.role ?? status?.roles?.[0] ?? "auto";
5112
5460
  if (status?.status === "ok") {
5113
- const lock = await checkLock(path33.resolve(workspaceRoot, repo.path));
5461
+ const lock = await checkLock(path34.resolve(workspaceRoot, repo.path));
5114
5462
  manifestRepos.push({
5115
5463
  name: repo.name,
5116
5464
  path: repo.path,
@@ -5190,7 +5538,7 @@ relationships: []
5190
5538
  log("Workspace initialized.");
5191
5539
  }
5192
5540
  async function scanWorkspace(workspaceRoot, opts) {
5193
- const configText = await readText(path34.join(workspaceRoot, WORKSPACE_FILE));
5541
+ const configText = await readText(path35.join(workspaceRoot, WORKSPACE_FILE));
5194
5542
  if (!configText) {
5195
5543
  error(`Missing ${WORKSPACE_FILE}. Run \`haus workspace discover\` or \`init\` first.`);
5196
5544
  process.exitCode = 1;
@@ -5211,7 +5559,7 @@ async function scanWorkspace(workspaceRoot, opts) {
5211
5559
  }
5212
5560
  const inputs = [];
5213
5561
  for (const repo of config2.repos) {
5214
- const repoRoot = path34.resolve(workspaceRoot, repo.path);
5562
+ const repoRoot = path35.resolve(workspaceRoot, repo.path);
5215
5563
  if (!existsSync5(repoRoot) || !statSync2(repoRoot).isDirectory()) {
5216
5564
  throw new Error(`Repo path is not a directory: ${repo.path}`);
5217
5565
  }
@@ -5262,7 +5610,7 @@ async function runWorkspace(action, options = {}) {
5262
5610
  // src/cli.ts
5263
5611
  function cliVersion() {
5264
5612
  try {
5265
- const pkgPath = path35.join(packageRoot(), "package.json");
5613
+ const pkgPath = path36.join(packageRoot(), "package.json");
5266
5614
  const pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
5267
5615
  return pkg.version ?? "0.0.0";
5268
5616
  } catch {
@@ -5272,7 +5620,7 @@ function cliVersion() {
5272
5620
  var program = new Command();
5273
5621
  function validateRuntimeNodeVersion() {
5274
5622
  try {
5275
- const pkgPath = path35.join(packageRoot(), "package.json");
5623
+ const pkgPath = path36.join(packageRoot(), "package.json");
5276
5624
  const pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
5277
5625
  const requiredRange = pkg.engines?.node;
5278
5626
  if (requiredRange && !satisfiesVersion(process.version, requiredRange)) {