@haus-tech/haus-workflow 0.10.0 → 0.11.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,11 +2,11 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import { readFileSync as readFileSync3 } from "fs";
5
- import path27 from "path";
5
+ import path28 from "path";
6
6
  import { Command } from "commander";
7
7
 
8
8
  // src/commands/apply.ts
9
- import path10 from "path";
9
+ import path11 from "path";
10
10
  import checkbox from "@inquirer/checkbox";
11
11
 
12
12
  // src/catalog/remote-catalog.ts
@@ -70,8 +70,12 @@ async function syncRemoteCatalog() {
70
70
  return { newItems: [], unchanged: 0, failed: [] };
71
71
  }
72
72
  await fs.ensureDir(CACHE_DIR);
73
- await fs.writeFile(path.join(CACHE_DIR, "manifest.json"), `${JSON.stringify({ items }, null, 2)}
74
- `, "utf8");
73
+ await fs.writeFile(
74
+ path.join(CACHE_DIR, "manifest.json"),
75
+ `${JSON.stringify({ items }, null, 2)}
76
+ `,
77
+ "utf8"
78
+ );
75
79
  const newItems = [];
76
80
  let unchanged = 0;
77
81
  const failed = [];
@@ -154,8 +158,8 @@ async function getCacheManifestAge() {
154
158
  }
155
159
 
156
160
  // src/claude/write-claude-files.ts
157
- import path9 from "path";
158
- import fs8 from "fs-extra";
161
+ import path10 from "path";
162
+ import fs9 from "fs-extra";
159
163
 
160
164
  // src/update/hash-installed.ts
161
165
  import path3 from "path";
@@ -374,10 +378,14 @@ import fs4 from "fs-extra";
374
378
  async function assertPostApplySettingsMatchCanonical(root, canonical) {
375
379
  const written = await readJson(claudePath(root, "settings.json"));
376
380
  if (written == null || typeof written !== "object") {
377
- throw new Error("haus: post-apply self-check failed: .claude/settings.json missing or unreadable");
381
+ throw new Error(
382
+ "haus: post-apply self-check failed: .claude/settings.json missing or unreadable"
383
+ );
378
384
  }
379
385
  if (!isDeepStrictEqual(canonical, written)) {
380
- throw new Error("haus: post-apply self-check failed: .claude/settings.json does not match canonical hook contract");
386
+ throw new Error(
387
+ "haus: post-apply self-check failed: .claude/settings.json does not match canonical hook contract"
388
+ );
381
389
  }
382
390
  }
383
391
  async function verifyProjectSettingsHooksContract(root) {
@@ -504,7 +512,8 @@ import path7 from "path";
504
512
  import fs6 from "fs-extra";
505
513
  var BLOCK_BEGIN = "<!-- HAUS:BEGIN haus-imports v=1 -->";
506
514
  var BLOCK_END = "<!-- HAUS:END haus-imports -->";
507
- var IMPORT_CONTENT = `@.haus-workflow/haus-way-of-work.md
515
+ var IMPORT_CONTENT = `@.haus-workflow/WORKFLOW.md
516
+ @.haus-workflow/workflow-config.md
508
517
  @.haus-workflow/project.md`;
509
518
  function buildImportBlock() {
510
519
  return `${BLOCK_BEGIN}
@@ -554,16 +563,52 @@ async function writeRootClaudeMd(root, dryRun) {
554
563
  return filePath;
555
564
  }
556
565
 
557
- // src/claude/write-way-of-work.ts
558
- import os3 from "os";
566
+ // src/claude/write-workflow-config.ts
559
567
  import path8 from "path";
560
568
  import fs7 from "fs-extra";
561
- var STABLE_ID2 = "template.way-of-work";
562
- var SCHEMA_VERSION2 = "1";
563
- var TEMPLATE_REL = "library/global/templates/haus-way-of-work.md";
564
- var CATALOG_CACHE_TEMPLATE = path8.join(os3.homedir(), CATALOG_CACHE_SUBDIR, "templates/haus-way-of-work.md");
565
- function makeWayOfWorkHeader(pkgVersion, contentHash) {
566
- return `<!-- HAUS-MANAGED id=${STABLE_ID2} v=${SCHEMA_VERSION2} source=@haus-tech/haus-workflow@${pkgVersion} hash=${contentHash} -->`;
569
+ function buildWorkflowConfig(ctx) {
570
+ const pm = ctx.packageManager === "unknown" ? "npm" : ctx.packageManager;
571
+ const testCmd = pm + " test";
572
+ const auditCmd = pm + " audit";
573
+ return "# Project workflow configuration\n\n> Project-specific values for the workflow standard in WORKFLOW.md.\n> Edit freely \u2014 this file is project-owned and will not be overwritten by haus.\n\n## Source-of-truth documents\n- Spec: <!-- fill in path, e.g. docs/SPEC.md -->\n- Design: <!-- fill in path, e.g. docs/DESIGN.md -->\n- UX flows: <!-- fill in path, e.g. docs/UX.md -->\n\n## Commands\n- Test (unit + integration): `" + testCmd + "`\n- Test (E2E): <!-- fill in command -->\n- Type check: <!-- fill in command, e.g. tsc --noEmit -->\n- Lint: <!-- fill in command, e.g. npm run lint -->\n- Lint fix: <!-- fill in command, e.g. npm run lint -- --fix -->\n- Format check: <!-- fill in command, e.g. prettier --check . -->\n- Security audit: `" + auditCmd + "`\n\n## Validation library\n<!-- fill in, e.g. zod, yup, joi -->\n\n## Highest-stakes logic\n<!-- fill in domain areas requiring TDD-only treatment, e.g. payment flows, auth, medical data -->\n\n## Pre-commit tool\n<!-- fill in, e.g. lefthook, husky -->\n";
574
+ }
575
+ async function writeWorkflowConfig(root, dryRun) {
576
+ const destPath = hausPath(root, "workflow-config.md");
577
+ const printable = displayPath(root, destPath);
578
+ if (await fs7.pathExists(destPath)) {
579
+ if (dryRun) log(printable + ": exists (project-owned, skipping)");
580
+ return destPath;
581
+ }
582
+ const ctx = await readJson(hausPath(root, "context-map.json")) ?? {
583
+ mode: "fast",
584
+ generatedAt: "",
585
+ root,
586
+ repoName: path8.basename(root),
587
+ packageManager: "unknown",
588
+ repoRoles: [],
589
+ confidence: 0,
590
+ detectedStacks: {},
591
+ dependencies: [],
592
+ securityRisks: [],
593
+ crossRepoHints: [],
594
+ warnings: []
595
+ };
596
+ const content = buildWorkflowConfig(ctx);
597
+ if (dryRun) {
598
+ log(printable + ": would create");
599
+ return destPath;
600
+ }
601
+ await writeText(destPath, content);
602
+ return destPath;
603
+ }
604
+
605
+ // src/claude/write-workflow.ts
606
+ import path9 from "path";
607
+ import fs8 from "fs-extra";
608
+
609
+ // src/claude/managed-template.ts
610
+ function normaliseLF(content) {
611
+ return content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
567
612
  }
568
613
  function parseHausManagedHeader(line) {
569
614
  const match = line.match(/<!-- HAUS-MANAGED id=([\w.:-]+)/);
@@ -571,23 +616,32 @@ function parseHausManagedHeader(line) {
571
616
  const hashMatch = line.match(/hash=(sha256-[a-f0-9]+)/);
572
617
  return { id: match[1], hash: hashMatch?.[1] };
573
618
  }
574
- async function writeWayOfWork(root, pkgVersion, dryRun) {
619
+
620
+ // src/claude/write-workflow.ts
621
+ var STABLE_ID2 = "template.workflow";
622
+ var SCHEMA_VERSION2 = "1";
623
+ var TEMPLATE_REL = "library/global/templates/agentic-workflow-standard.md";
624
+ var CATALOG_CACHE_TEMPLATE = path9.join(CACHE_DIR, "templates/agentic-workflow-standard.md");
625
+ function makeWorkflowHeader(pkgVersion, contentHash) {
626
+ return `<!-- HAUS-MANAGED id=${STABLE_ID2} v=${SCHEMA_VERSION2} source=@haus-tech/haus-workflow@${pkgVersion} hash=${contentHash} -->`;
627
+ }
628
+ async function writeWorkflow(root, pkgVersion, dryRun) {
575
629
  const cachePath = CATALOG_CACHE_TEMPLATE;
576
- const packagePath = path8.join(packageRoot(), TEMPLATE_REL);
577
- const templatePath = await fs7.pathExists(cachePath) ? cachePath : packagePath;
578
- if (!await fs7.pathExists(templatePath)) {
579
- warn(`Way-of-work template not found \u2014 run \`haus update\` to fetch from catalog`);
630
+ const packagePath = path9.join(packageRoot(), TEMPLATE_REL);
631
+ const templatePath = await fs8.pathExists(cachePath) ? cachePath : packagePath;
632
+ if (!await fs8.pathExists(templatePath)) {
633
+ warn(`Workflow template not found \u2014 run \`haus update\` to fetch from catalog`);
580
634
  return null;
581
635
  }
582
- const templateContent = await fs7.readFile(templatePath, "utf8");
583
- const contentHash = hashText(templateContent);
584
- const header = makeWayOfWorkHeader(pkgVersion, contentHash);
636
+ const templateContent = await fs8.readFile(templatePath, "utf8");
637
+ const contentHash = hashText(normaliseLF(templateContent));
638
+ const header = makeWorkflowHeader(pkgVersion, contentHash);
585
639
  const next = `${header}
586
640
  ${templateContent}`;
587
- const destPath = hausPath(root, "haus-way-of-work.md");
641
+ const destPath = hausPath(root, "WORKFLOW.md");
588
642
  const printable = displayPath(root, destPath);
589
- if (await fs7.pathExists(destPath)) {
590
- const existing = await fs7.readFile(destPath, "utf8");
643
+ if (await fs8.pathExists(destPath)) {
644
+ const existing = await fs8.readFile(destPath, "utf8");
591
645
  const firstLine = existing.split("\n")[0] ?? "";
592
646
  const parsed = parseHausManagedHeader(firstLine);
593
647
  if (!parsed) {
@@ -599,7 +653,7 @@ ${templateContent}`;
599
653
  return null;
600
654
  }
601
655
  const existingContent = existing.slice(firstLine.length + 1);
602
- if (parsed.hash && hashText(existingContent) !== parsed.hash) {
656
+ if (parsed.hash && hashText(normaliseLF(existingContent)) !== parsed.hash) {
603
657
  warn(`${printable}: content modified by user \u2014 skipping. Use --force to overwrite.`);
604
658
  return null;
605
659
  }
@@ -609,7 +663,7 @@ ${templateContent}`;
609
663
  }
610
664
  }
611
665
  if (dryRun) {
612
- const prev = await fs7.pathExists(destPath) ? await fs7.readFile(destPath, "utf8") : "";
666
+ const prev = await fs8.pathExists(destPath) ? await fs8.readFile(destPath, "utf8") : "";
613
667
  if (!prev) {
614
668
  log(createUnifiedDiff(printable, "", next));
615
669
  } else {
@@ -636,7 +690,7 @@ async function writeClaudeFiles(root, dryRun, selectedIds) {
636
690
  estimatedTokenReductionPct: 0
637
691
  };
638
692
  const pkgRoot = packageRoot();
639
- const hausVersion = (await readJson(path9.join(pkgRoot, "package.json")))?.version ?? "0.0.0";
693
+ const hausVersion = (await readJson(path10.join(pkgRoot, "package.json")))?.version ?? "0.0.0";
640
694
  const coreFiles = [
641
695
  claudePath(root, "settings.json"),
642
696
  claudePath(root, "rules", "haus.md"),
@@ -645,18 +699,34 @@ async function writeClaudeFiles(root, dryRun, selectedIds) {
645
699
  claudePath(root, "commands", "haus-review.md")
646
700
  ];
647
701
  const rootClaudeMdPath = await writeRootClaudeMd(root, dryRun);
648
- const wayOfWorkPath = await writeWayOfWork(root, hausVersion, dryRun);
702
+ const workflowPath = await writeWorkflow(root, hausVersion, dryRun);
703
+ const workflowConfigPath = await writeWorkflowConfig(root, dryRun);
649
704
  const projectFactsPath = await writeProjectFacts(root, hausVersion, dryRun);
650
- const p6Files = [rootClaudeMdPath, projectFactsPath, ...wayOfWorkPath ? [wayOfWorkPath] : []];
651
- const files = dryRun ? [...coreFiles, ...p6Files] : [...coreFiles, ...p6Files, hausPath(root, "selected-context.json"), hausPath(root, "haus.lock.json")];
705
+ const p6Files = [
706
+ rootClaudeMdPath,
707
+ projectFactsPath,
708
+ ...workflowPath ? [workflowPath] : [],
709
+ ...workflowConfigPath ? [workflowConfigPath] : []
710
+ ];
711
+ const files = dryRun ? [...coreFiles, ...p6Files] : [
712
+ ...coreFiles,
713
+ ...p6Files,
714
+ hausPath(root, "selected-context.json"),
715
+ hausPath(root, "haus.lock.json")
716
+ ];
652
717
  const hookSettings = await loadClaudeHooksSettings();
653
718
  await writeManagedJson(root, claudePath(root, "settings.json"), hookSettings, dryRun);
654
719
  if (!dryRun) await assertPostApplySettingsMatchCanonical(root, hookSettings);
655
720
  const configPath = hausPath(root, "config.json");
656
- if (!await fs8.pathExists(configPath)) {
721
+ if (!await fs9.pathExists(configPath)) {
657
722
  await writeManagedJson(root, configPath, DEFAULT_HOOKS_CONFIG, dryRun);
658
723
  }
659
- await writeManagedText(root, claudePath(root, "commands", "haus-doctor.md"), "Run `haus doctor`.", dryRun);
724
+ await writeManagedText(
725
+ root,
726
+ claudePath(root, "commands", "haus-doctor.md"),
727
+ "Run `haus doctor`.",
728
+ dryRun
729
+ );
660
730
  await writeManagedText(
661
731
  root,
662
732
  claudePath(root, "commands", "haus-review.md"),
@@ -676,11 +746,13 @@ async function writeClaudeFiles(root, dryRun, selectedIds) {
676
746
  dryRun
677
747
  );
678
748
  const fixtureManifestPath = process.env["HAUS_FIXTURE_CATALOG"];
679
- const manifestPath = fixtureManifestPath ?? path9.join(pkgRoot, "library", "catalog", "manifest.json");
680
- const manifestDir = path9.dirname(manifestPath);
749
+ const manifestPath = fixtureManifestPath ?? path10.join(pkgRoot, "library", "catalog", "manifest.json");
750
+ const manifestDir = path10.dirname(manifestPath);
681
751
  const manifest = await readJson(manifestPath) ?? { items: [] };
682
752
  const manifestById = new Map((manifest.items ?? []).map((item) => [item.id, item]));
683
- const cacheManifest = await readJson(path9.join(CACHE_DIR, "manifest.json"));
753
+ const cacheManifest = await readJson(
754
+ path10.join(CACHE_DIR, "manifest.json")
755
+ );
684
756
  const cacheManifestById = new Map((cacheManifest?.items ?? []).map((item) => [item.id, item]));
685
757
  const installedPathsByItem = /* @__PURE__ */ new Map();
686
758
  const installedIds = /* @__PURE__ */ new Set();
@@ -701,24 +773,28 @@ async function writeClaudeFiles(root, dryRun, selectedIds) {
701
773
  }
702
774
  }
703
775
  const cachedItem = cacheManifestById.get(item.id);
704
- const cachePath = cachedItem?.path ? path9.join(CACHE_DIR, cachedItem.path) : null;
705
- const sourcePath = cachePath && await fs8.pathExists(cachePath) ? cachePath : path9.join(manifestDir, manifestItem.path);
776
+ const cachePath = cachedItem?.path ? path10.join(CACHE_DIR, cachedItem.path) : null;
777
+ const sourcePath = cachePath && await fs9.pathExists(cachePath) ? cachePath : path10.join(manifestDir, manifestItem.path);
706
778
  const target = item.type === "agent" ? "agents" : item.type === "template" ? "templates" : "skills";
707
- const destination = claudePath(root, target, path9.basename(sourcePath));
708
- if (await fs8.pathExists(sourcePath)) {
779
+ const destination = claudePath(root, target, path10.basename(sourcePath));
780
+ if (await fs9.pathExists(sourcePath)) {
709
781
  if (dryRun) {
710
- const exists = await fs8.pathExists(destination);
711
- log(`${displayPath(root, destination)}: ${exists ? "would overwrite" : "would create"} (${item.id})`);
782
+ const exists = await fs9.pathExists(destination);
783
+ log(
784
+ `${displayPath(root, destination)}: ${exists ? "would overwrite" : "would create"} (${item.id})`
785
+ );
712
786
  } else {
713
- await fs8.ensureDir(path9.dirname(destination));
714
- await fs8.copy(sourcePath, destination, { overwrite: true, errorOnExist: false });
787
+ await fs9.ensureDir(path10.dirname(destination));
788
+ await fs9.copy(sourcePath, destination, { overwrite: true, errorOnExist: false });
715
789
  }
716
790
  files.push(destination);
717
791
  const current = installedPathsByItem.get(item.id) ?? [];
718
- installedPathsByItem.set(item.id, [...current, path9.relative(root, destination)]);
792
+ installedPathsByItem.set(item.id, [...current, path10.relative(root, destination)]);
719
793
  installedIds.add(item.id);
720
794
  } else {
721
- warn(`Skipping ${item.id}: source not found at ${sourcePath} \u2014 run \`haus update\` to populate catalog cache`);
795
+ warn(
796
+ `Skipping ${item.id}: source not found at ${sourcePath} \u2014 run \`haus update\` to populate catalog cache`
797
+ );
722
798
  }
723
799
  }
724
800
  if (dryRun) return [...new Set(files)];
@@ -726,7 +802,12 @@ async function writeClaudeFiles(root, dryRun, selectedIds) {
726
802
  await writeManagedJson(
727
803
  root,
728
804
  hausPath(root, "selected-context.json"),
729
- installedItems.map((r) => ({ id: r.id, type: r.type, reason: r.reason, confidenceLevel: r.confidenceLevel })),
805
+ installedItems.map((r) => ({
806
+ id: r.id,
807
+ type: r.type,
808
+ reason: r.reason,
809
+ confidenceLevel: r.confidenceLevel
810
+ })),
730
811
  false
731
812
  );
732
813
  const lock = await Promise.all(
@@ -759,7 +840,7 @@ async function writeClaudeFiles(root, dryRun, selectedIds) {
759
840
  return [...new Set(files)];
760
841
  }
761
842
  async function writeManagedText(root, filePath, nextText, dryRun) {
762
- const prev = await fs8.pathExists(filePath) ? await fs8.readFile(filePath, "utf8") : "";
843
+ const prev = await fs9.pathExists(filePath) ? await fs9.readFile(filePath, "utf8") : "";
763
844
  const printable = displayPath(root, filePath);
764
845
  if (dryRun) {
765
846
  if (!prev) {
@@ -786,7 +867,7 @@ async function writeManagedJson(root, filePath, value, dryRun) {
786
867
 
787
868
  // src/commands/apply.ts
788
869
  async function cacheHasItems() {
789
- const data = await readJson(path10.join(CACHE_DIR, "manifest.json"));
870
+ const data = await readJson(path11.join(CACHE_DIR, "manifest.json"));
790
871
  return Array.isArray(data?.items) && data.items.length > 0;
791
872
  }
792
873
  async function runApply(options) {
@@ -831,7 +912,9 @@ async function runApply(options) {
831
912
  const catalogItemCount = selectedIds !== void 0 ? selectedIds.length : rec?.recommended.length ?? 0;
832
913
  if (catalogItemCount > 0 && !await cacheHasItems()) {
833
914
  if (isDryRun) {
834
- warn("Catalog cache is empty \u2014 `haus apply --write` will skip catalog items. Run `haus update` first.");
915
+ warn(
916
+ "Catalog cache is empty \u2014 `haus apply --write` will skip catalog items. Run `haus update` first."
917
+ );
835
918
  } else {
836
919
  error(
837
920
  "Catalog cache is empty \u2014 cannot install catalog items. Run `haus update` first, or pass --allow-empty-cache to apply core files only."
@@ -851,9 +934,9 @@ async function runApply(options) {
851
934
  }
852
935
 
853
936
  // src/catalog/load-catalog.ts
854
- import os4 from "os";
855
- import path11 from "path";
856
- var CACHE_MANIFEST = path11.join(os4.homedir(), CATALOG_CACHE_SUBDIR, "manifest.json");
937
+ import os3 from "os";
938
+ import path12 from "path";
939
+ var CACHE_MANIFEST = path12.join(os3.homedir(), CATALOG_CACHE_SUBDIR, "manifest.json");
857
940
  async function loadCatalog(root) {
858
941
  const envPath = process.env["HAUS_FIXTURE_CATALOG"];
859
942
  if (envPath) {
@@ -862,10 +945,10 @@ async function loadCatalog(root) {
862
945
  }
863
946
  const cacheData = await readJson(CACHE_MANIFEST);
864
947
  if (cacheData?.items?.length) return cacheData.items;
865
- const localManifest = path11.join(root, "library/catalog/manifest.json");
948
+ const localManifest = path12.join(root, "library/catalog/manifest.json");
866
949
  const localData = await readJson(localManifest);
867
950
  if (localData?.items?.length) return localData.items;
868
- const packageManifest = path11.join(packageRoot(), "library/catalog/manifest.json");
951
+ const packageManifest = path12.join(packageRoot(), "library/catalog/manifest.json");
869
952
  const data = await readJson(packageManifest);
870
953
  return data?.items ?? [];
871
954
  }
@@ -893,7 +976,8 @@ async function runCatalogAudit() {
893
976
  const failures = [];
894
977
  for (const item of items) {
895
978
  const text = `${item.id} ${item.tags.join(" ")}`.toLowerCase();
896
- for (const word of FORBIDDEN) if (text.includes(word)) failures.push(`${item.id} has unsupported tag ${word}`);
979
+ for (const word of FORBIDDEN)
980
+ if (text.includes(word)) failures.push(`${item.id} has unsupported tag ${word}`);
897
981
  }
898
982
  if (failures.length) {
899
983
  failures.forEach((f) => error(f));
@@ -904,7 +988,7 @@ async function runCatalogAudit() {
904
988
  }
905
989
 
906
990
  // src/commands/config.ts
907
- import path12 from "path";
991
+ import path13 from "path";
908
992
  var CONFIG_PATH2 = ".haus-workflow/config.json";
909
993
  var HOOK_ALIASES = {
910
994
  "hook.context": "context",
@@ -913,10 +997,12 @@ var HOOK_ALIASES = {
913
997
  async function runConfig(key, action) {
914
998
  const hookKey = HOOK_ALIASES[key];
915
999
  if (!hookKey) {
916
- throw new Error(`Unknown config key "${key}". Valid keys: ${Object.keys(HOOK_ALIASES).join(", ")}`);
1000
+ throw new Error(
1001
+ `Unknown config key "${key}". Valid keys: ${Object.keys(HOOK_ALIASES).join(", ")}`
1002
+ );
917
1003
  }
918
1004
  const root = process.cwd();
919
- const configPath = path12.join(root, CONFIG_PATH2);
1005
+ const configPath = path13.join(root, CONFIG_PATH2);
920
1006
  const existing = await readJson(configPath);
921
1007
  const cfg = existing ?? structuredClone(DEFAULT_HOOKS_CONFIG);
922
1008
  cfg.hooks ??= {};
@@ -939,7 +1025,9 @@ function normalizeRecommendation(input2) {
939
1025
  message: reason.message ?? item.reason ?? "legacy recommendation reason",
940
1026
  weight: reason.weight ?? 0,
941
1027
  ...reason.signal ? { signal: reason.signal } : {}
942
- })) ?? [{ code: "legacy-reason", message: item.reason ?? "legacy recommendation reason", weight: 0 }];
1028
+ })) ?? [
1029
+ { code: "legacy-reason", message: item.reason ?? "legacy recommendation reason", weight: 0 }
1030
+ ];
943
1031
  const confidence = item.confidence ?? 0;
944
1032
  return {
945
1033
  id: item.id,
@@ -984,7 +1072,10 @@ function normalizeRecommendation(input2) {
984
1072
  estimatedContextTokens: input2.estimatedContextTokens ?? recommended.length * 320,
985
1073
  selectedRules: input2.selectedRules ?? recommended.length,
986
1074
  skippedRules: input2.skippedRules ?? skipped.length,
987
- estimatedTokenReductionPct: input2.estimatedTokenReductionPct ?? Math.max(0, Math.round(skipped.length / Math.max(recommended.length + skipped.length, 1) * 100))
1075
+ estimatedTokenReductionPct: input2.estimatedTokenReductionPct ?? Math.max(
1076
+ 0,
1077
+ Math.round(skipped.length / Math.max(recommended.length + skipped.length, 1) * 100)
1078
+ )
988
1079
  };
989
1080
  }
990
1081
  function buildRecommendationExplanation(recommendation) {
@@ -1269,7 +1360,7 @@ function computeRuleIntents(rule) {
1269
1360
 
1270
1361
  // src/scanner/scan-project.ts
1271
1362
  import { readFile } from "fs/promises";
1272
- import path14 from "path";
1363
+ import path15 from "path";
1273
1364
 
1274
1365
  // src/utils/audit-checks.ts
1275
1366
  function isRecord(v) {
@@ -1296,8 +1387,8 @@ function compareVersions(a, b) {
1296
1387
  }
1297
1388
 
1298
1389
  // src/scanner/detect-package-manager.ts
1299
- import path13 from "path";
1300
- import fs9 from "fs-extra";
1390
+ import path14 from "path";
1391
+ import fs10 from "fs-extra";
1301
1392
  function detectPackageManager(root, packageManagerField) {
1302
1393
  const field = String(packageManagerField ?? "").trim();
1303
1394
  if (field.startsWith("yarn@")) {
@@ -1315,9 +1406,9 @@ function detectPackageManager(root, packageManagerField) {
1315
1406
  if (satisfiesVersion(version, ">=9")) return "npm";
1316
1407
  return "unknown";
1317
1408
  }
1318
- if (fs9.existsSync(path13.join(root, "yarn.lock"))) return "yarn";
1319
- if (fs9.existsSync(path13.join(root, "pnpm-lock.yaml"))) return "pnpm";
1320
- if (fs9.existsSync(path13.join(root, "package-lock.json"))) return "npm";
1409
+ if (fs10.existsSync(path14.join(root, "yarn.lock"))) return "yarn";
1410
+ if (fs10.existsSync(path14.join(root, "pnpm-lock.yaml"))) return "pnpm";
1411
+ if (fs10.existsSync(path14.join(root, "package-lock.json"))) return "npm";
1321
1412
  return "unknown";
1322
1413
  }
1323
1414
 
@@ -1378,8 +1469,8 @@ function blocked(rel) {
1378
1469
  return SENSITIVE.some((x) => x.test(rel));
1379
1470
  }
1380
1471
  async function scanProject(root, mode = "fast") {
1381
- const pkg = await readJson(path14.join(root, "package.json"));
1382
- const composer = await readJson(path14.join(root, "composer.json"));
1472
+ const pkg = await readJson(path15.join(root, "package.json"));
1473
+ const composer = await readJson(path15.join(root, "composer.json"));
1383
1474
  const files = await listFiles(root, SAFE_FILES);
1384
1475
  const safeFiles = files.filter((f) => !blocked(f));
1385
1476
  const deps = dependencySet(pkg, composer);
@@ -1396,16 +1487,18 @@ async function scanProject(root, mode = "fast") {
1396
1487
  if (nodeEngine && !satisfiesVersion(process.version, nodeEngine)) {
1397
1488
  warnings.push(`Current Node ${process.version} does not satisfy package engine ${nodeEngine}`);
1398
1489
  }
1399
- if (safeFiles.some((f) => f.includes("docker-compose"))) crossRepoHints.push("Containerized services detected");
1490
+ if (safeFiles.some((f) => f.includes("docker-compose")))
1491
+ crossRepoHints.push("Containerized services detected");
1400
1492
  if (safeFiles.some((f) => f.includes("turbo.json") || f.includes("nx.json")))
1401
1493
  crossRepoHints.push("Monorepo orchestration detected");
1402
1494
  if (!safeFiles.some((f) => f.endsWith(".env.example"))) securityRisks.push("Missing env template");
1403
- if (safeFiles.some((f) => f.includes("wp-content/uploads"))) securityRisks.push("Uploads directory present");
1495
+ if (safeFiles.some((f) => f.includes("wp-content/uploads")))
1496
+ securityRisks.push("Uploads directory present");
1404
1497
  const context = {
1405
1498
  mode,
1406
1499
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1407
1500
  root,
1408
- repoName: String(pkg?.name ?? path14.basename(root)),
1501
+ repoName: String(pkg?.name ?? path15.basename(root)),
1409
1502
  packageManager,
1410
1503
  repoRoles: roles,
1411
1504
  confidence: computeConfidence(roles, stacks),
@@ -1420,7 +1513,11 @@ async function scanProject(root, mode = "fast") {
1420
1513
  composer: isRecord(composer?.require) ? Object.keys(composer.require) : []
1421
1514
  };
1422
1515
  const scanHashes = Object.fromEntries(
1423
- await Promise.all(safeFiles.map(async (f) => [f, hashText(await readFile(path14.join(root, f), "utf8"))]))
1516
+ await Promise.all(
1517
+ safeFiles.map(
1518
+ async (f) => [f, hashText(await readFile(path15.join(root, f), "utf8"))]
1519
+ )
1520
+ )
1424
1521
  );
1425
1522
  const repoSummary = renderSummary(context);
1426
1523
  await writeJson(hausPath(root, "context-map.json"), context);
@@ -1446,9 +1543,11 @@ function detectRoles(deps, files) {
1446
1543
  if (deps.includes("next") || files.some((f) => f.includes("next.config."))) roles.add("next-app");
1447
1544
  if (deps.includes("react")) roles.add("react-app");
1448
1545
  if (deps.includes("vite") || files.some((f) => f.includes("vite.config."))) roles.add("vite-app");
1449
- if (deps.includes("react-router") && deps.includes("@react-router/node")) roles.add("react-router-app");
1546
+ if (deps.includes("react-router") && deps.includes("@react-router/node"))
1547
+ roles.add("react-router-app");
1450
1548
  if (deps.includes("sanity")) roles.add("sanity-studio");
1451
- if (deps.includes("@strapi/strapi") || deps.some((d) => d.startsWith("@strapi/"))) roles.add("strapi-app");
1549
+ if (deps.includes("@strapi/strapi") || deps.some((d) => d.startsWith("@strapi/")))
1550
+ roles.add("strapi-app");
1452
1551
  if (deps.includes("expo")) roles.add("expo-app");
1453
1552
  if (deps.includes("@vendure/core")) roles.add("vendure-app");
1454
1553
  if (deps.some((d) => d.startsWith("@haus/vendure-")) || files.some((f) => f.includes("vendure-config")))
@@ -1457,7 +1556,8 @@ function detectRoles(deps, files) {
1457
1556
  if (deps.includes("graphql") || deps.includes("@nestjs/graphql")) roles.add("graphql-api");
1458
1557
  if (files.some((f) => f.endsWith("nx.json"))) roles.add("nx-monorepo");
1459
1558
  if (files.some((f) => f.endsWith("turbo.json"))) roles.add("turbo-monorepo");
1460
- if (files.some((f) => f.endsWith("artisan")) || deps.includes("laravel/framework")) roles.add("laravel-app");
1559
+ if (files.some((f) => f.endsWith("artisan")) || deps.includes("laravel/framework"))
1560
+ roles.add("laravel-app");
1461
1561
  if (deps.includes("laravel/nova")) roles.add("laravel-nova-app");
1462
1562
  const hasWpConfig = files.some((f) => f.endsWith("wp-config.php"));
1463
1563
  const hasBedrockLayout = files.some((f) => f.includes("web/app")) || deps.includes("roots/wordpress");
@@ -1493,7 +1593,8 @@ async function detectStacks(root, deps, files, packageManager) {
1493
1593
  if (deps.includes("react")) add("frontend", "react19");
1494
1594
  if (deps.includes("vue")) add("frontend", "vue");
1495
1595
  if (deps.includes("vite")) add("frontend", "vite8");
1496
- if (deps.includes("react-router") && deps.includes("@react-router/node")) add("frontend", "react-router-v7");
1596
+ if (deps.includes("react-router") && deps.includes("@react-router/node"))
1597
+ add("frontend", "react-router-v7");
1497
1598
  if (deps.includes("tailwindcss") || files.some((f) => f.includes("tailwind.config."))) {
1498
1599
  add("frontend", "tailwindcss");
1499
1600
  }
@@ -1512,8 +1613,10 @@ async function detectStacks(root, deps, files, packageManager) {
1512
1613
  if (deps.includes("react-native")) add("frontend", "react-native");
1513
1614
  if (deps.includes("i18next") || deps.includes("react-i18next")) add("tooling", "i18next");
1514
1615
  if (deps.includes("bullmq")) add("tooling", "bullmq");
1515
- if (files.some((f) => f === "Dockerfile" || f.startsWith("docker-compose"))) add("tooling", "docker");
1516
- if (deps.includes("pm2") || files.some((f) => f.includes("ecosystem.config"))) add("tooling", "pm2");
1616
+ if (files.some((f) => f === "Dockerfile" || f.startsWith("docker-compose")))
1617
+ add("tooling", "docker");
1618
+ if (deps.includes("pm2") || files.some((f) => f.includes("ecosystem.config")))
1619
+ add("tooling", "pm2");
1517
1620
  if (deps.some((d) => d.startsWith("@sentry/"))) add("tooling", "sentry");
1518
1621
  if (deps.includes("deployer/deployer")) add("tooling", "deployer-php");
1519
1622
  if (!deps.includes("prettier")) add("tooling", "missing-prettier");
@@ -1530,10 +1633,13 @@ async function detectStacks(root, deps, files, packageManager) {
1530
1633
  if (await hasNeedle(root, files, "NestFactory")) add("backend", "nestjs");
1531
1634
  if (await hasNeedle(root, files, "@VendurePlugin")) add("backend", "vendure3");
1532
1635
  if (deps.includes("graphql") || deps.includes("@nestjs/graphql")) add("backend", "graphql");
1533
- if (files.some((f) => f.endsWith(".graphql") || f.endsWith("schema.graphql"))) add("backend", "graphql");
1636
+ if (files.some((f) => f.endsWith(".graphql") || f.endsWith("schema.graphql")))
1637
+ add("backend", "graphql");
1534
1638
  if (deps.includes("laravel/framework")) add("backend", "laravel");
1535
- if (files.some((f) => f.includes("app/Providers/") || f.includes("routes/"))) add("backend", "laravel");
1536
- if (files.some((f) => f.endsWith("wp-config.php")) || deps.includes("roots/wordpress")) add("backend", "wordpress");
1639
+ if (files.some((f) => f.includes("app/Providers/") || f.includes("routes/")))
1640
+ add("backend", "laravel");
1641
+ if (files.some((f) => f.endsWith("wp-config.php")) || deps.includes("roots/wordpress"))
1642
+ add("backend", "wordpress");
1537
1643
  if (deps.includes("wpackagist-plugin/elementor") || deps.includes("wearehaus/elementor-pro") || deps.includes("wpackagist-theme/hello-elementor")) {
1538
1644
  add("backend", "elementor");
1539
1645
  }
@@ -1576,7 +1682,7 @@ async function hasNeedle(root, files, needle) {
1576
1682
  );
1577
1683
  for (const rel of candidates.slice(0, 300)) {
1578
1684
  try {
1579
- const content = await readFile(path14.join(root, rel), "utf8");
1685
+ const content = await readFile(path15.join(root, rel), "utf8");
1580
1686
  if (content.includes(needle)) return true;
1581
1687
  } catch {
1582
1688
  continue;
@@ -1669,16 +1775,19 @@ async function runContext(options) {
1669
1775
  }
1670
1776
 
1671
1777
  // src/commands/doctor.ts
1672
- import path15 from "path";
1673
- import fs10 from "fs-extra";
1778
+ import path16 from "path";
1779
+ import fs11 from "fs-extra";
1674
1780
 
1675
1781
  // src/update/npm-version.ts
1676
1782
  var NPM_PACKAGE_NAME = "@haus-tech/haus-workflow";
1677
1783
  async function fetchNpmVersionStatus(currentVersion) {
1678
1784
  try {
1679
- const res = await fetch(`https://registry.npmjs.org/${encodeURIComponent(NPM_PACKAGE_NAME)}/latest`, {
1680
- signal: AbortSignal.timeout(8e3)
1681
- });
1785
+ const res = await fetch(
1786
+ `https://registry.npmjs.org/${encodeURIComponent(NPM_PACKAGE_NAME)}/latest`,
1787
+ {
1788
+ signal: AbortSignal.timeout(8e3)
1789
+ }
1790
+ );
1682
1791
  if (!res.ok) return { current: currentVersion, latest: null, updateAvailable: false };
1683
1792
  const data = await res.json();
1684
1793
  const latest = data?.version;
@@ -1737,7 +1846,7 @@ async function runDoctor(options) {
1737
1846
  const enabled = await isHookEnabled(root, key);
1738
1847
  log(`- HOOK ${key}: ${enabled ? "enabled" : "disabled (default)"}`);
1739
1848
  }
1740
- const rootClaudeMdPath = path15.join(root, "CLAUDE.md");
1849
+ const rootClaudeMdPath = path16.join(root, "CLAUDE.md");
1741
1850
  const rootClaudeMdContent = await readText(rootClaudeMdPath);
1742
1851
  if (!rootClaudeMdContent) {
1743
1852
  warn("- CLAUDE.md: missing (run `haus apply --write` to create)");
@@ -1746,33 +1855,48 @@ async function runDoctor(options) {
1746
1855
  } else {
1747
1856
  log("- CLAUDE.md: import block present");
1748
1857
  }
1749
- const wayOfWorkPath = hausPath(root, "haus-way-of-work.md");
1750
- const wayOfWorkExists = await fs10.pathExists(wayOfWorkPath);
1751
- if (!wayOfWorkExists) {
1752
- warn("- .haus-workflow/haus-way-of-work.md: missing (run `haus apply --write`)");
1858
+ const workflowPath = hausPath(root, "WORKFLOW.md");
1859
+ const workflowExists = await fs11.pathExists(workflowPath);
1860
+ if (!workflowExists) {
1861
+ warn("- .haus-workflow/WORKFLOW.md: missing (run `haus apply --write`)");
1753
1862
  } else {
1754
- const wayOfWorkContent = await readText(wayOfWorkPath);
1755
- const firstLine = wayOfWorkContent?.split("\n")[0] ?? "";
1863
+ const workflowContent = await readText(workflowPath);
1864
+ const firstLine = workflowContent?.split("\n")[0] ?? "";
1756
1865
  if (!firstLine.includes("HAUS-MANAGED")) {
1757
- warn("- .haus-workflow/haus-way-of-work.md: no HAUS-MANAGED header (user-owned)");
1866
+ log("- .haus-workflow/WORKFLOW.md: OK (user-owned)");
1758
1867
  } else {
1759
1868
  const storedHashMatch = firstLine.match(/hash=(sha256-[a-f0-9]+)/);
1760
- const templatePath = path15.join(packageRoot(), "library", "global", "templates", "haus-way-of-work.md");
1869
+ const cachePath = path16.join(CACHE_DIR, "templates/agentic-workflow-standard.md");
1870
+ const bundledPath = path16.join(
1871
+ packageRoot(),
1872
+ "library",
1873
+ "global",
1874
+ "templates",
1875
+ "agentic-workflow-standard.md"
1876
+ );
1877
+ const templatePath = await fs11.pathExists(cachePath) ? cachePath : bundledPath;
1761
1878
  const templateContent = await readText(templatePath);
1762
1879
  if (storedHashMatch && templateContent) {
1763
- const currentHash = hashText(templateContent);
1880
+ const currentHash = hashText(normaliseLF(templateContent));
1764
1881
  if (storedHashMatch[1] !== currentHash) {
1765
- warn("- .haus-workflow/haus-way-of-work.md: stale (template updated \u2014 run `haus apply --write`)");
1882
+ warn("- .haus-workflow/WORKFLOW.md: stale (template updated \u2014 run `haus apply --write`)");
1766
1883
  } else {
1767
- log("- .haus-workflow/haus-way-of-work.md: OK");
1884
+ log("- .haus-workflow/WORKFLOW.md: OK");
1768
1885
  }
1769
1886
  } else {
1770
- log("- .haus-workflow/haus-way-of-work.md: OK");
1887
+ log("- .haus-workflow/WORKFLOW.md: OK");
1771
1888
  }
1772
1889
  }
1773
1890
  }
1891
+ const workflowConfigPath = hausPath(root, "workflow-config.md");
1892
+ const workflowConfigExists = await fs11.pathExists(workflowConfigPath);
1893
+ if (!workflowConfigExists) {
1894
+ warn("- .haus-workflow/workflow-config.md: missing (run `haus apply --write`)");
1895
+ } else {
1896
+ log("- .haus-workflow/workflow-config.md: OK (project-owned)");
1897
+ }
1774
1898
  const projectMdPath = hausPath(root, "project.md");
1775
- const projectMdExists = await fs10.pathExists(projectMdPath);
1899
+ const projectMdExists = await fs11.pathExists(projectMdPath);
1776
1900
  if (!projectMdExists) {
1777
1901
  warn("- .haus-workflow/project.md: missing (run `haus apply --write`)");
1778
1902
  } else {
@@ -1795,11 +1919,13 @@ async function runDoctor(options) {
1795
1919
  log(`- CATALOG CACHE: OK (${cacheAgeDays}d old)`);
1796
1920
  }
1797
1921
  }
1798
- const pkgJson = await readJson(path15.join(packageRoot(), "package.json"));
1922
+ const pkgJson = await readJson(path16.join(packageRoot(), "package.json"));
1799
1923
  const currentVersion = pkgJson?.version ?? "0.0.0";
1800
1924
  const npmStatus = await fetchNpmVersionStatus(currentVersion);
1801
1925
  if (npmStatus.updateAvailable && npmStatus.latest !== null) {
1802
- warn(`- CLI UPDATE: ${currentVersion} \u2192 ${npmStatus.latest} available (run: npm install -g ${NPM_PACKAGE_NAME})`);
1926
+ warn(
1927
+ `- CLI UPDATE: ${currentVersion} \u2192 ${npmStatus.latest} available (run: npm install -g ${NPM_PACKAGE_NAME})`
1928
+ );
1803
1929
  process.exitCode = 1;
1804
1930
  } else if (npmStatus.latest !== null) {
1805
1931
  log(`- CLI: ${currentVersion} (up to date)`);
@@ -1960,8 +2086,8 @@ async function runGuard(kind, _options) {
1960
2086
  }
1961
2087
 
1962
2088
  // src/commands/init.ts
1963
- import path16 from "path";
1964
- import fs11 from "fs-extra";
2089
+ import path17 from "path";
2090
+ import fs12 from "fs-extra";
1965
2091
 
1966
2092
  // src/utils/exec.ts
1967
2093
  import { execa } from "execa";
@@ -2030,7 +2156,9 @@ var ECOSYSTEM_COMPATIBLE_BACKENDS = {
2030
2156
  async function recommend(root, context) {
2031
2157
  const items = await loadCatalog(root);
2032
2158
  const setupAnswers = await readJson(hausPath(root, "setup-answers.json")) ?? {};
2033
- const sources = await readJson(hausPath(root, "sources-report.json")) ?? {};
2159
+ const sources = await readJson(
2160
+ hausPath(root, "sources-report.json")
2161
+ ) ?? {};
2034
2162
  const stackSet = buildStackSet(context);
2035
2163
  const depSet = new Set(context.dependencies.map((d) => d.toLowerCase()));
2036
2164
  const roleSet = new Set(context.repoRoles.map((r) => r.toLowerCase()));
@@ -2114,14 +2242,23 @@ async function recommend(root, context) {
2114
2242
  if (tagMatch) {
2115
2243
  pushReason("stack-match", "stack/dependency match", 30, `tag:${tagMatch}`);
2116
2244
  }
2117
- const goalMatch = item.tags.find((t) => goals.includes(t) || goals.includes(t.replace(/-/g, " ")));
2245
+ const goalMatch = item.tags.find(
2246
+ (t) => goals.includes(t) || goals.includes(t.replace(/-/g, " "))
2247
+ );
2118
2248
  if (goalMatch) {
2119
2249
  pushReason("goal-match", "guided goal match", 15, `goal:${goalMatch}`);
2120
2250
  }
2121
2251
  if (item.tags.includes(context.packageManager) || item.tags.includes(`${context.packageManager}4`) || item.tags.includes(`${context.packageManager}89`)) {
2122
- pushReason("package-manager-match", "package manager match", 10, `packageManager:${context.packageManager}`);
2252
+ pushReason(
2253
+ "package-manager-match",
2254
+ "package manager match",
2255
+ 10,
2256
+ `packageManager:${context.packageManager}`
2257
+ );
2123
2258
  }
2124
- const configSignal = item.tags.find((t) => context.warnings.join(" ").toLowerCase().includes(t.toLowerCase()));
2259
+ const configSignal = item.tags.find(
2260
+ (t) => context.warnings.join(" ").toLowerCase().includes(t.toLowerCase())
2261
+ );
2125
2262
  if (configSignal) {
2126
2263
  pushReason("config-signal-match", "config signal match", 20, `warning:${configSignal}`);
2127
2264
  }
@@ -2208,9 +2345,15 @@ async function recommend(root, context) {
2208
2345
  pushSkipReason("source-approval", "Source not approved", 100);
2209
2346
  }
2210
2347
  if (securityRiskCount > 0 && !isDefaultBaseline && (item.tags.includes("security") || item.id.includes("security"))) {
2211
- pushSkipReason("security-risk-penalty", "Security-tagged item penalized by active risk signals", 20);
2348
+ pushSkipReason(
2349
+ "security-risk-penalty",
2350
+ "Security-tagged item penalized by active risk signals",
2351
+ 20
2352
+ );
2212
2353
  }
2213
- const positiveReasonCodes = new Set(reasons.map((r) => r.code).filter((c) => c !== "default-baseline"));
2354
+ const positiveReasonCodes = new Set(
2355
+ reasons.map((r) => r.code).filter((c) => c !== "default-baseline")
2356
+ );
2214
2357
  const hasRoleSignal = positiveReasonCodes.has("repo-role-match");
2215
2358
  const hasDepOrStackSignal = positiveReasonCodes.has("stack-match") || positiveReasonCodes.has("requires-any-match");
2216
2359
  if (hasRoleSignal && !hasDepOrStackSignal && !isDefaultBaseline && requiresAny.length === 0) {
@@ -2281,7 +2424,11 @@ async function recommend(root, context) {
2281
2424
  };
2282
2425
  }
2283
2426
  function buildStackSet(context) {
2284
- return new Set([...context.repoRoles, ...Object.values(context.detectedStacks).flat()].map((x) => x.toLowerCase()));
2427
+ return new Set(
2428
+ [...context.repoRoles, ...Object.values(context.detectedStacks).flat()].map(
2429
+ (x) => x.toLowerCase()
2430
+ )
2431
+ );
2285
2432
  }
2286
2433
  function inferRepoEcosystems(roles) {
2287
2434
  const ecosystems = /* @__PURE__ */ new Set();
@@ -2483,8 +2630,8 @@ async function runSetupProject(options) {
2483
2630
  // src/commands/init.ts
2484
2631
  async function runInit(options) {
2485
2632
  const root = process.cwd();
2486
- const hausDir = path16.join(root, ".haus-workflow");
2487
- const alreadyInit = await fs11.pathExists(hausDir);
2633
+ const hausDir = path17.join(root, ".haus-workflow");
2634
+ const alreadyInit = await fs12.pathExists(hausDir);
2488
2635
  if (alreadyInit) {
2489
2636
  log("Haus AI already initialized in this project.");
2490
2637
  log("Run `haus setup-project` to reconfigure.");
@@ -2496,8 +2643,8 @@ async function runInit(options) {
2496
2643
 
2497
2644
  // src/install/apply.ts
2498
2645
  import crypto2 from "crypto";
2499
- import path19 from "path";
2500
- import fs13 from "fs-extra";
2646
+ import path20 from "path";
2647
+ import fs14 from "fs-extra";
2501
2648
 
2502
2649
  // src/install/header.ts
2503
2650
  var MD_PREFIX = "<!-- HAUS-MANAGED";
@@ -2530,14 +2677,14 @@ ${content}`;
2530
2677
  }
2531
2678
 
2532
2679
  // src/install/manifest.ts
2533
- import os5 from "os";
2534
- import path17 from "path";
2680
+ import os4 from "os";
2681
+ import path18 from "path";
2535
2682
  var MANIFEST_SCHEMA = "haus-install-manifest/1";
2536
2683
  function globalClaudeDir() {
2537
- return path17.join(os5.homedir(), ".claude");
2684
+ return path18.join(os4.homedir(), ".claude");
2538
2685
  }
2539
2686
  function hausManifestPath() {
2540
- return path17.join(globalClaudeDir(), "haus", "install-manifest.json");
2687
+ return path18.join(globalClaudeDir(), "haus", "install-manifest.json");
2541
2688
  }
2542
2689
  async function readManifest() {
2543
2690
  return readJson(hausManifestPath());
@@ -2556,10 +2703,10 @@ function buildManifest(source, files, hooks) {
2556
2703
  }
2557
2704
 
2558
2705
  // src/install/settings-merge.ts
2559
- import path18 from "path";
2560
- import fs12 from "fs-extra";
2706
+ import path19 from "path";
2707
+ import fs13 from "fs-extra";
2561
2708
  function settingsJsonPath() {
2562
- return path18.join(globalClaudeDir(), "settings.json");
2709
+ return path19.join(globalClaudeDir(), "settings.json");
2563
2710
  }
2564
2711
  async function readSettings() {
2565
2712
  const parsed = await readJson(settingsJsonPath());
@@ -2615,7 +2762,7 @@ function stripHausHooks(settings) {
2615
2762
  async function loadHooksFragment(fragmentPath) {
2616
2763
  let raw;
2617
2764
  try {
2618
- raw = await fs12.readJson(fragmentPath);
2765
+ raw = await fs13.readJson(fragmentPath);
2619
2766
  } catch {
2620
2767
  return [];
2621
2768
  }
@@ -2630,40 +2777,40 @@ function hashContent(content) {
2630
2777
  }
2631
2778
  function sourceVersion() {
2632
2779
  try {
2633
- const pkgPath = path19.join(packageRoot(), "package.json");
2634
- const pkg = JSON.parse(fs13.readFileSync(pkgPath, "utf8"));
2780
+ const pkgPath = path20.join(packageRoot(), "package.json");
2781
+ const pkg = JSON.parse(fs14.readFileSync(pkgPath, "utf8"));
2635
2782
  return `${pkg.name ?? "haus"}@${pkg.version ?? "0.0.0"}`;
2636
2783
  } catch {
2637
2784
  return "haus@0.0.0";
2638
2785
  }
2639
2786
  }
2640
2787
  function globalSrcDir() {
2641
- return path19.join(packageRoot(), "library", "global");
2788
+ return path20.join(packageRoot(), "library", "global");
2642
2789
  }
2643
2790
  function collectSourceFiles(srcDir, claudeDir) {
2644
2791
  const entries = [];
2645
- const skillsDir = path19.join(srcDir, "skills");
2646
- if (fs13.pathExistsSync(skillsDir)) {
2647
- for (const skillName of fs13.readdirSync(skillsDir)) {
2648
- const skillFile = path19.join(skillsDir, skillName, "SKILL.md");
2649
- if (fs13.pathExistsSync(skillFile)) {
2792
+ const skillsDir = path20.join(srcDir, "skills");
2793
+ if (fs14.pathExistsSync(skillsDir)) {
2794
+ for (const skillName of fs14.readdirSync(skillsDir)) {
2795
+ const skillFile = path20.join(skillsDir, skillName, "SKILL.md");
2796
+ if (fs14.pathExistsSync(skillFile)) {
2650
2797
  entries.push({
2651
2798
  stableId: `skill.${skillName}`,
2652
- srcRelPath: path19.join("library", "global", "skills", skillName, "SKILL.md"),
2653
- destPath: path19.join(claudeDir, "skills", skillName, "SKILL.md")
2799
+ srcRelPath: path20.join("library", "global", "skills", skillName, "SKILL.md"),
2800
+ destPath: path20.join(claudeDir, "skills", skillName, "SKILL.md")
2654
2801
  });
2655
2802
  }
2656
2803
  }
2657
2804
  }
2658
- const agentsDir = path19.join(srcDir, "agents");
2659
- if (fs13.pathExistsSync(agentsDir)) {
2660
- for (const agentFile of fs13.readdirSync(agentsDir)) {
2805
+ const agentsDir = path20.join(srcDir, "agents");
2806
+ if (fs14.pathExistsSync(agentsDir)) {
2807
+ for (const agentFile of fs14.readdirSync(agentsDir)) {
2661
2808
  if (!agentFile.endsWith(".md")) continue;
2662
2809
  const agentName = agentFile.replace(/\.md$/, "");
2663
2810
  entries.push({
2664
2811
  stableId: `agent.${agentName}`,
2665
- srcRelPath: path19.join("library", "global", "agents", agentFile),
2666
- destPath: path19.join(claudeDir, "agents", agentFile)
2812
+ srcRelPath: path20.join("library", "global", "agents", agentFile),
2813
+ destPath: path20.join(claudeDir, "agents", agentFile)
2667
2814
  });
2668
2815
  }
2669
2816
  }
@@ -2687,7 +2834,7 @@ async function applyInstall(options = {}) {
2687
2834
  };
2688
2835
  const manifestFiles = [];
2689
2836
  for (const entry of sourceFiles) {
2690
- const srcPath = path19.join(packageRoot(), entry.srcRelPath);
2837
+ const srcPath = path20.join(packageRoot(), entry.srcRelPath);
2691
2838
  const rawContent = await readText(srcPath);
2692
2839
  if (rawContent === void 0) {
2693
2840
  warn(`Source file not found: ${entry.srcRelPath}`);
@@ -2707,7 +2854,7 @@ async function applyInstall(options = {}) {
2707
2854
  }
2708
2855
  continue;
2709
2856
  }
2710
- const destExists = fs13.pathExistsSync(entry.destPath);
2857
+ const destExists = fs14.pathExistsSync(entry.destPath);
2711
2858
  if (destExists) {
2712
2859
  const currentContent = await readText(entry.destPath);
2713
2860
  if (currentContent !== void 0) {
@@ -2743,7 +2890,7 @@ async function applyInstall(options = {}) {
2743
2890
  schemaVersion: SCHEMA_VERSION3
2744
2891
  });
2745
2892
  }
2746
- const fragmentPath = path19.join(srcDir, "settings-fragments", "hooks.json");
2893
+ const fragmentPath = path20.join(srcDir, "settings-fragments", "hooks.json");
2747
2894
  const fragments = await loadHooksFragment(fragmentPath);
2748
2895
  const settings = await readSettings();
2749
2896
  const { settings: mergedSettings, addedIds } = mergeHooks(settings, fragments);
@@ -2752,13 +2899,13 @@ async function applyInstall(options = {}) {
2752
2899
  const currentDestPaths = new Set(sourceFiles.map((f) => f.destPath));
2753
2900
  for (const entry of existingManifest.files) {
2754
2901
  if (currentDestPaths.has(entry.destPath)) continue;
2755
- if (!fs13.pathExistsSync(entry.destPath)) continue;
2902
+ if (!fs14.pathExistsSync(entry.destPath)) continue;
2756
2903
  const content = await readText(entry.destPath);
2757
2904
  if (!content) continue;
2758
2905
  const hasHeader = parseMarkdownHeader(content) !== void 0;
2759
2906
  const currentHash = hashContent(content);
2760
2907
  if (hasHeader && currentHash === entry.hash) {
2761
- if (!dryRun) await fs13.remove(entry.destPath);
2908
+ if (!dryRun) await fs14.remove(entry.destPath);
2762
2909
  result.deleted.push(entry.destPath);
2763
2910
  } else {
2764
2911
  warn(`Orphaned file ${entry.destPath} was user-modified \u2014 leaving in place`);
@@ -2768,7 +2915,10 @@ async function applyInstall(options = {}) {
2768
2915
  }
2769
2916
  if (!dryRun && !check) {
2770
2917
  await writeSettings(mergedSettings);
2771
- const manifest = buildManifest(source, manifestFiles, [...existingManifest?.hooks ?? [], ...addedIds]);
2918
+ const manifest = buildManifest(source, manifestFiles, [
2919
+ ...existingManifest?.hooks ?? [],
2920
+ ...addedIds
2921
+ ]);
2772
2922
  await writeManifest(manifest);
2773
2923
  }
2774
2924
  return result;
@@ -2812,7 +2962,9 @@ async function runInstall(options) {
2812
2962
  process.exitCode = 1;
2813
2963
  } else if (!options.check && !options.dryRun) {
2814
2964
  const total = result.created.length + result.updated.length;
2815
- log(`haus install complete (${total} file(s) written, ${result.hookIds.length} hook(s) added)`);
2965
+ log(
2966
+ `haus install complete (${total} file(s) written, ${result.hookIds.length} hook(s) added)`
2967
+ );
2816
2968
  }
2817
2969
  } catch (err) {
2818
2970
  error(`haus install failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -2821,7 +2973,12 @@ async function runInstall(options) {
2821
2973
  }
2822
2974
 
2823
2975
  // src/memory/memory-store.ts
2824
- var FILES = ["project-learnings.md", "decisions.md", "recurring-issues.md", "client-context.md"];
2976
+ var FILES = [
2977
+ "project-learnings.md",
2978
+ "decisions.md",
2979
+ "recurring-issues.md",
2980
+ "client-context.md"
2981
+ ];
2825
2982
  async function ensureMemory(root) {
2826
2983
  await Promise.all(
2827
2984
  FILES.map(async (name) => {
@@ -2878,7 +3035,10 @@ async function runMemory(subcommand, options) {
2878
3035
  return;
2879
3036
  }
2880
3037
  const compact = `Task: ${options.task ?? "n/a"}
2881
- ${text}`.slice(0, options.fromHook ? 1200 : 4e3);
3038
+ ${text}`.slice(
3039
+ 0,
3040
+ options.fromHook ? 1200 : 4e3
3041
+ );
2882
3042
  log(compact);
2883
3043
  return;
2884
3044
  }
@@ -2928,20 +3088,20 @@ async function runScan(options) {
2928
3088
  }
2929
3089
 
2930
3090
  // src/commands/undo.ts
2931
- import path20 from "path";
2932
- import fs14 from "fs-extra";
3091
+ import path21 from "path";
3092
+ import fs15 from "fs-extra";
2933
3093
  var CLAUDE_DIR = ".claude";
2934
3094
  async function runUndo(options) {
2935
3095
  const root = process.cwd();
2936
- const targets = [path20.join(root, CLAUDE_DIR), path20.join(root, HAUS_DIR)];
2937
- const existing = targets.filter((p) => fs14.existsSync(p));
3096
+ const targets = [path21.join(root, CLAUDE_DIR), path21.join(root, HAUS_DIR)];
3097
+ const existing = targets.filter((p) => fs15.existsSync(p));
2938
3098
  if (existing.length === 0) {
2939
3099
  log("Nothing to remove: no .claude/ or .haus-workflow/ in this directory.");
2940
3100
  return;
2941
3101
  }
2942
3102
  if (!options.yes) {
2943
3103
  const ok = await confirm(
2944
- `Remove ${existing.map((p) => path20.relative(root, p)).join(" and ")}? This cannot be undone.`
3104
+ `Remove ${existing.map((p) => path21.relative(root, p)).join(" and ")}? This cannot be undone.`
2945
3105
  );
2946
3106
  if (!ok) {
2947
3107
  log("Cancelled.");
@@ -2949,15 +3109,15 @@ async function runUndo(options) {
2949
3109
  }
2950
3110
  }
2951
3111
  for (const p of existing) {
2952
- await fs14.remove(p);
2953
- log(`Removed ${path20.relative(root, p)}`);
3112
+ await fs15.remove(p);
3113
+ log(`Removed ${path21.relative(root, p)}`);
2954
3114
  }
2955
3115
  }
2956
3116
 
2957
3117
  // src/install/uninstall.ts
2958
3118
  import crypto3 from "crypto";
2959
- import path21 from "path";
2960
- import fs15 from "fs-extra";
3119
+ import path22 from "path";
3120
+ import fs16 from "fs-extra";
2961
3121
  async function runUninstall(options = {}) {
2962
3122
  const { force = false } = options;
2963
3123
  const manifest = await readManifest();
@@ -2967,7 +3127,7 @@ async function runUninstall(options = {}) {
2967
3127
  return result;
2968
3128
  }
2969
3129
  for (const entry of manifest.files) {
2970
- const exists = fs15.pathExistsSync(entry.destPath);
3130
+ const exists = fs16.pathExistsSync(entry.destPath);
2971
3131
  if (!exists) continue;
2972
3132
  const content = await readText(entry.destPath);
2973
3133
  if (content === void 0) continue;
@@ -2979,26 +3139,28 @@ async function runUninstall(options = {}) {
2979
3139
  }
2980
3140
  const currentHash = `sha256-${crypto3.createHash("sha256").update(content).digest("hex")}`;
2981
3141
  if (currentHash !== entry.hash && !force) {
2982
- warn(`Skipping user-edited haus file (hash mismatch): ${entry.destPath} \u2014 use --force to delete`);
3142
+ warn(
3143
+ `Skipping user-edited haus file (hash mismatch): ${entry.destPath} \u2014 use --force to delete`
3144
+ );
2983
3145
  result.skipped.push(entry.destPath);
2984
3146
  continue;
2985
3147
  }
2986
- await fs15.remove(entry.destPath);
2987
- await pruneEmptyDir(path21.dirname(entry.destPath));
3148
+ await fs16.remove(entry.destPath);
3149
+ await pruneEmptyDir(path22.dirname(entry.destPath));
2988
3150
  result.deleted.push(entry.destPath);
2989
3151
  }
2990
3152
  const settings = await readSettings();
2991
3153
  const stripped = stripHausHooks(settings);
2992
3154
  await writeSettings(stripped);
2993
3155
  result.hooksStripped = true;
2994
- const hausDir = path21.join(globalClaudeDir(), "haus");
3156
+ const hausDir = path22.join(globalClaudeDir(), "haus");
2995
3157
  const manifestPath = hausManifestPath();
2996
- if (fs15.pathExistsSync(manifestPath)) {
2997
- await fs15.remove(manifestPath);
3158
+ if (fs16.pathExistsSync(manifestPath)) {
3159
+ await fs16.remove(manifestPath);
2998
3160
  }
2999
- if (fs15.pathExistsSync(hausDir)) {
3000
- const remaining = await fs15.readdir(hausDir);
3001
- if (remaining.length === 0) await fs15.remove(hausDir);
3161
+ if (fs16.pathExistsSync(hausDir)) {
3162
+ const remaining = await fs16.readdir(hausDir);
3163
+ if (remaining.length === 0) await fs16.remove(hausDir);
3002
3164
  }
3003
3165
  return result;
3004
3166
  }
@@ -3017,8 +3179,8 @@ function printUninstallResult(result) {
3017
3179
  }
3018
3180
  async function pruneEmptyDir(dir) {
3019
3181
  try {
3020
- const entries = await fs15.readdir(dir);
3021
- if (entries.length === 0) await fs15.remove(dir);
3182
+ const entries = await fs16.readdir(dir);
3183
+ if (entries.length === 0) await fs16.remove(dir);
3022
3184
  } catch {
3023
3185
  }
3024
3186
  }
@@ -3036,7 +3198,7 @@ async function runUninstallCommand(options) {
3036
3198
  }
3037
3199
 
3038
3200
  // src/commands/update.ts
3039
- import path23 from "path";
3201
+ import path24 from "path";
3040
3202
 
3041
3203
  // src/update/diff-generated-files.ts
3042
3204
  function diffGeneratedFiles() {
@@ -3063,10 +3225,12 @@ function summarizeLockDiff(before, after) {
3063
3225
 
3064
3226
  // src/update/lockfile.ts
3065
3227
  import { mkdir, readFile as readFile2, copyFile } from "fs/promises";
3066
- import path22 from "path";
3228
+ import path23 from "path";
3067
3229
  async function checkLock(root) {
3068
3230
  const lock = await readJson(hausPath(root, "haus.lock.json")) ?? [];
3069
- const hasValidVersions = lock.every((item) => !item.version || normalizeVersion(item.version) !== null);
3231
+ const hasValidVersions = lock.every(
3232
+ (item) => !item.version || normalizeVersion(item.version) !== null
3233
+ );
3070
3234
  const catalogRef = lock[0]?.catalogRef ?? null;
3071
3235
  return { ok: lock.length > 0 && hasValidVersions, count: lock.length, catalogRef };
3072
3236
  }
@@ -3082,7 +3246,7 @@ async function applyLock(root) {
3082
3246
  try {
3083
3247
  const backupDir = hausPath(root, "backups");
3084
3248
  await mkdir(backupDir, { recursive: true });
3085
- await copyFile(lockPath, path22.join(backupDir, `haus.lock.${Date.now()}.json`));
3249
+ await copyFile(lockPath, path23.join(backupDir, `haus.lock.${Date.now()}.json`));
3086
3250
  } catch {
3087
3251
  }
3088
3252
  const enriched = await Promise.all(
@@ -3104,7 +3268,7 @@ function diffLock(before, after) {
3104
3268
  }
3105
3269
  async function hasLocalOverrides(root) {
3106
3270
  try {
3107
- await readFile2(path22.join(root, ".claude", "settings.json"), "utf8");
3271
+ await readFile2(path23.join(root, ".claude", "settings.json"), "utf8");
3108
3272
  return true;
3109
3273
  } catch {
3110
3274
  return false;
@@ -3116,7 +3280,7 @@ var NPM_PACKAGE_NAME2 = "@haus-tech/haus-workflow";
3116
3280
  async function runUpdate(options) {
3117
3281
  const root = process.cwd();
3118
3282
  if (options.check) {
3119
- const pkgJson2 = await readJson(path23.join(packageRoot(), "package.json"));
3283
+ const pkgJson2 = await readJson(path24.join(packageRoot(), "package.json"));
3120
3284
  const currentVersion2 = pkgJson2?.version ?? "0.0.0";
3121
3285
  const [status, npmVersion, latestCatalogTag] = await Promise.all([
3122
3286
  checkLock(root),
@@ -3143,7 +3307,7 @@ async function runUpdate(options) {
3143
3307
  if (!status.ok) process.exitCode = 1;
3144
3308
  return;
3145
3309
  }
3146
- const pkgJson = await readJson(path23.join(packageRoot(), "package.json"));
3310
+ const pkgJson = await readJson(path24.join(packageRoot(), "package.json"));
3147
3311
  const currentVersion = pkgJson?.version ?? "0.0.0";
3148
3312
  const npmStatus = await fetchNpmVersionStatus(currentVersion);
3149
3313
  if (npmStatus.updateAvailable && npmStatus.latest !== null) {
@@ -3173,13 +3337,15 @@ async function runUpdate(options) {
3173
3337
  }
3174
3338
 
3175
3339
  // src/commands/validate-catalog.ts
3176
- import fs16 from "fs";
3177
- import path25 from "path";
3340
+ import fs17 from "fs";
3341
+ import path26 from "path";
3178
3342
 
3179
3343
  // src/catalog/allowed-stacks.ts
3180
- import path24 from "path";
3344
+ import path25 from "path";
3181
3345
  async function readAllowedStacks(root) {
3182
- const data = await readJson(path24.join(root, "library", "catalog", "allowed-stacks.json"));
3346
+ const data = await readJson(
3347
+ path25.join(root, "library", "catalog", "allowed-stacks.json")
3348
+ );
3183
3349
  return data?.stacks ?? [];
3184
3350
  }
3185
3351
 
@@ -3204,7 +3370,12 @@ var FORBIDDEN_TAGS = [
3204
3370
  var BANNED_AGENT_PHRASES = ["autonomous", "swarm", "delegate", "orchestrat", "marketplace"];
3205
3371
  var REQUIRED_SKILL_SECTIONS = ["## Use when", "## Do not use when"];
3206
3372
  var REQUIRED_AGENT_SECTIONS = ["## Use when", "## Do not use when", "## Verification"];
3207
- var RISKY_INSTALL_PATTERNS = [/\bnpx\s+-y\b/i, /\bnpx\s+--yes\b/i, /\byarn\s+dlx\b/i, /\bpnpm\s+dlx\b/i];
3373
+ var RISKY_INSTALL_PATTERNS = [
3374
+ /\bnpx\s+-y\b/i,
3375
+ /\bnpx\s+--yes\b/i,
3376
+ /\byarn\s+dlx\b/i,
3377
+ /\bpnpm\s+dlx\b/i
3378
+ ];
3208
3379
  var ALLOWED_NPX_PATTERN = /\bnpx\s+tsx\b/i;
3209
3380
  var ANY_NPX_PATTERN = /\bnpx\s+\S+/i;
3210
3381
  var HTTP_URL_PATTERN = /^http:\/\//i;
@@ -3278,33 +3449,34 @@ function auditShippedFiles(manifestDir, items) {
3278
3449
  const failures = [];
3279
3450
  for (const item of items) {
3280
3451
  if (!item.path) continue;
3281
- const absPath = path25.join(manifestDir, item.path);
3452
+ const absPath = path26.join(manifestDir, item.path);
3282
3453
  if (item.type === "skill") {
3283
- const skillMd = path25.join(absPath, "SKILL.md");
3284
- if (!fs16.existsSync(skillMd)) {
3285
- failures.push(`${item.id}: missing ${path25.relative(manifestDir, skillMd)}`);
3454
+ const skillMd = path26.join(absPath, "SKILL.md");
3455
+ if (!fs17.existsSync(skillMd)) {
3456
+ failures.push(`${item.id}: missing ${path26.relative(manifestDir, skillMd)}`);
3286
3457
  continue;
3287
3458
  }
3288
- const text = fs16.readFileSync(skillMd, "utf8");
3459
+ const text = fs17.readFileSync(skillMd, "utf8");
3289
3460
  for (const section of REQUIRED_SKILL_SECTIONS) {
3290
3461
  if (!text.includes(section)) failures.push(`${item.id}: SKILL.md missing ${section}`);
3291
3462
  }
3292
3463
  } else if (item.type === "agent") {
3293
- if (!fs16.existsSync(absPath)) {
3464
+ if (!fs17.existsSync(absPath)) {
3294
3465
  failures.push(`${item.id}: missing agent file ${item.path}`);
3295
3466
  continue;
3296
3467
  }
3297
- const text = fs16.readFileSync(absPath, "utf8");
3468
+ const text = fs17.readFileSync(absPath, "utf8");
3298
3469
  if (!text.startsWith("---")) failures.push(`${item.id}: agent file missing YAML frontmatter`);
3299
3470
  for (const section of REQUIRED_AGENT_SECTIONS) {
3300
3471
  if (!text.includes(section)) failures.push(`${item.id}: agent file missing ${section}`);
3301
3472
  }
3302
3473
  const lower = text.toLowerCase();
3303
3474
  for (const phrase of BANNED_AGENT_PHRASES) {
3304
- if (lower.includes(phrase)) failures.push(`${item.id}: agent file contains disallowed phrase "${phrase}"`);
3475
+ if (lower.includes(phrase))
3476
+ failures.push(`${item.id}: agent file contains disallowed phrase "${phrase}"`);
3305
3477
  }
3306
3478
  } else if (item.type === "template") {
3307
- if (!fs16.existsSync(absPath)) {
3479
+ if (!fs17.existsSync(absPath)) {
3308
3480
  failures.push(`${item.id}: missing template file ${item.path}`);
3309
3481
  }
3310
3482
  }
@@ -3315,11 +3487,11 @@ function auditMarkdownContent(manifestDir) {
3315
3487
  const failures = [];
3316
3488
  const dirs = ["skills", "agents"];
3317
3489
  for (const dir of dirs) {
3318
- const abs = path25.join(manifestDir, dir);
3319
- if (!fs16.existsSync(abs)) continue;
3490
+ const abs = path26.join(manifestDir, dir);
3491
+ if (!fs17.existsSync(abs)) continue;
3320
3492
  walkMd(abs, (file) => {
3321
- const text = fs16.readFileSync(file, "utf8");
3322
- const rel = path25.relative(manifestDir, file);
3493
+ const text = fs17.readFileSync(file, "utf8");
3494
+ const rel = path26.relative(manifestDir, file);
3323
3495
  const lines = text.split(/\r?\n/);
3324
3496
  for (let i = 0; i < lines.length; i++) {
3325
3497
  const line = lines[i] ?? "";
@@ -3338,8 +3510,8 @@ function auditMarkdownContent(manifestDir) {
3338
3510
  return failures;
3339
3511
  }
3340
3512
  function walkMd(dir, fn) {
3341
- for (const entry of fs16.readdirSync(dir, { withFileTypes: true })) {
3342
- const full = path25.join(dir, entry.name);
3513
+ for (const entry of fs17.readdirSync(dir, { withFileTypes: true })) {
3514
+ const full = path26.join(dir, entry.name);
3343
3515
  if (entry.isDirectory()) walkMd(full, fn);
3344
3516
  else if (entry.name.endsWith(".md")) fn(full);
3345
3517
  }
@@ -3350,8 +3522,8 @@ async function runValidateCatalog(manifestPath) {
3350
3522
  process.exitCode = 1;
3351
3523
  return;
3352
3524
  }
3353
- const abs = path25.resolve(process.cwd(), manifestPath);
3354
- const manifestDir = path25.dirname(abs);
3525
+ const abs = path26.resolve(process.cwd(), manifestPath);
3526
+ const manifestDir = path26.dirname(abs);
3355
3527
  const data = await readJson(abs);
3356
3528
  if (!data?.items) {
3357
3529
  error(`Could not read catalog manifest at ${abs}`);
@@ -3374,7 +3546,13 @@ async function runValidateCatalog(manifestPath) {
3374
3546
  }
3375
3547
  }
3376
3548
  }
3377
- const allFailures = [...structureFailures, ...stackFailures, ...fileFailures, ...contentFailures, ...tagFailures];
3549
+ const allFailures = [
3550
+ ...structureFailures,
3551
+ ...stackFailures,
3552
+ ...fileFailures,
3553
+ ...contentFailures,
3554
+ ...tagFailures
3555
+ ];
3378
3556
  if (allFailures.length) {
3379
3557
  allFailures.forEach((f) => error(f));
3380
3558
  process.exitCode = 1;
@@ -3384,7 +3562,7 @@ async function runValidateCatalog(manifestPath) {
3384
3562
  }
3385
3563
 
3386
3564
  // src/commands/workspace.ts
3387
- import path26 from "path";
3565
+ import path27 from "path";
3388
3566
  import YAML from "yaml";
3389
3567
  async function runWorkspace(action) {
3390
3568
  if (action === "init") {
@@ -3417,7 +3595,7 @@ relationships: []
3417
3595
  const summaries = [];
3418
3596
  const ownership = {};
3419
3597
  for (const repo of repos) {
3420
- const repoRoot = path26.resolve(process.cwd(), repo.path);
3598
+ const repoRoot = path27.resolve(process.cwd(), repo.path);
3421
3599
  const result = await scanProject(repoRoot, "fast");
3422
3600
  summaries.push({
3423
3601
  name: repo.name,
@@ -3453,7 +3631,7 @@ ${summaries.map(
3453
3631
  // src/cli.ts
3454
3632
  function cliVersion() {
3455
3633
  try {
3456
- const pkgPath = path27.join(packageRoot(), "package.json");
3634
+ const pkgPath = path28.join(packageRoot(), "package.json");
3457
3635
  const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
3458
3636
  return pkg.version ?? "0.0.0";
3459
3637
  } catch {
@@ -3463,7 +3641,7 @@ function cliVersion() {
3463
3641
  var program = new Command();
3464
3642
  function validateRuntimeNodeVersion() {
3465
3643
  try {
3466
- const pkgPath = path27.join(packageRoot(), "package.json");
3644
+ const pkgPath = path28.join(packageRoot(), "package.json");
3467
3645
  const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
3468
3646
  const requiredRange = pkg.engines?.node;
3469
3647
  if (requiredRange && !satisfiesVersion(process.version, requiredRange)) {
@@ -3481,7 +3659,10 @@ program.command("scan").option("--json").action(runScan);
3481
3659
  program.command("recommend").option("--json").action(runRecommend);
3482
3660
  program.command("setup-project").option("--guided").option("--fast").option("--json").action(runSetupProject);
3483
3661
  program.command("doctor").option("--hooks", "Verify .claude/settings.json matches the hook contract").action(runDoctor);
3484
- program.command("apply").option("--dry-run").option("--write").option("--select", "Interactively select catalog items before applying").option("--allow-empty-cache", "Apply core files only when catalog cache is empty (skip catalog items without error)").action(runApply);
3662
+ program.command("apply").option("--dry-run").option("--write").option("--select", "Interactively select catalog items before applying").option(
3663
+ "--allow-empty-cache",
3664
+ "Apply core files only when catalog cache is empty (skip catalog items without error)"
3665
+ ).action(runApply);
3485
3666
  program.command("undo").option("-y, --yes", "Skip confirmation").action(runUndo);
3486
3667
  program.command("explain-recommendation").option("--json").action(runExplainRecommendation);
3487
3668
  program.command("context").option("--task <task>").option("--from-hook").option("--json").option("--verbose").action(runContext);