@haus-tech/haus-workflow 0.12.1 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import { readFileSync as readFileSync3 } from "fs";
5
- import path29 from "path";
5
+ import path28 from "path";
6
6
  import { Command } from "commander";
7
7
 
8
8
  // src/commands/apply.ts
9
- import path11 from "path";
9
+ import path10 from "path";
10
10
  import checkbox from "@inquirer/checkbox";
11
11
 
12
12
  // src/catalog/remote-catalog.ts
@@ -75,6 +75,27 @@ function safeJoin(base, itemPath) {
75
75
  const resolved = path.resolve(base, itemPath);
76
76
  return resolved.startsWith(base + path.sep) || resolved === base ? resolved : null;
77
77
  }
78
+ function isExternalReference(ref) {
79
+ return /^[a-z][a-z0-9+.-]*:\/\//i.test(ref);
80
+ }
81
+ async function downloadSkillReferences(item, destDir) {
82
+ for (const ref of item.references ?? []) {
83
+ if (isExternalReference(ref)) continue;
84
+ const refDest = safeJoin(destDir, ref);
85
+ if (!refDest) {
86
+ warn(`Skipping reference "${ref}" for ${item.id}: path traversal detected`);
87
+ continue;
88
+ }
89
+ if (await fs.pathExists(refDest)) continue;
90
+ const text = await fetchText(`${REMOTE_BASE}/${item.path}/${ref}`);
91
+ if (text === null) {
92
+ warn(`Failed to fetch reference "${ref}" for ${item.id}`);
93
+ continue;
94
+ }
95
+ await fs.ensureDir(path.dirname(refDest));
96
+ await fs.writeFile(refDest, text, "utf8");
97
+ }
98
+ }
78
99
  async function syncRemoteCatalog() {
79
100
  const items = await fetchRemoteManifest();
80
101
  if (!items) {
@@ -108,6 +129,7 @@ async function syncRemoteCatalog() {
108
129
  }
109
130
  const dest = path.join(destDir, "SKILL.md");
110
131
  if (await fs.pathExists(dest)) {
132
+ await downloadSkillReferences(item, destDir);
111
133
  unchanged++;
112
134
  continue;
113
135
  }
@@ -120,6 +142,7 @@ async function syncRemoteCatalog() {
120
142
  }
121
143
  await fs.ensureDir(path.dirname(dest));
122
144
  await fs.writeFile(dest, text, "utf8");
145
+ await downloadSkillReferences(item, destDir);
123
146
  newItems.push(item.id);
124
147
  } else {
125
148
  const dest = safeJoin(CACHE_DIR, item.path);
@@ -171,8 +194,8 @@ async function getCacheManifestAge() {
171
194
  }
172
195
 
173
196
  // src/claude/write-claude-files.ts
174
- import path10 from "path";
175
- import fs10 from "fs-extra";
197
+ import path9 from "path";
198
+ import fs9 from "fs-extra";
176
199
 
177
200
  // src/update/hash-installed.ts
178
201
  import path3 from "path";
@@ -416,8 +439,8 @@ function buildDenyRules() {
416
439
  for (const command of DANGEROUS_COMMANDS) {
417
440
  rules.push(`Bash(${command}:*)`);
418
441
  }
419
- for (const path30 of SENSITIVE_PATHS) {
420
- const pattern = SENSITIVE_DIRS.has(path30) ? `${path30}/**` : path30;
442
+ for (const path29 of SENSITIVE_PATHS) {
443
+ const pattern = SENSITIVE_DIRS.has(path29) ? `${path29}/**` : path29;
421
444
  for (const tool of FILE_TOOLS) {
422
445
  rules.push(`${tool}(${pattern})`);
423
446
  }
@@ -515,109 +538,13 @@ async function verifyProjectSettingsHooksContract(root) {
515
538
  return { ok: true, message: "settings.json matches canonical hook contract." };
516
539
  }
517
540
 
518
- // src/claude/write-project-facts.ts
541
+ // src/claude/write-root-claude-md.ts
519
542
  import path6 from "path";
520
543
  import fs5 from "fs-extra";
521
- var STABLE_ID = "generated.project-facts";
522
- var SCHEMA_VERSION = "1";
523
- function makeHeader(pkgVersion) {
524
- return `<!-- HAUS-MANAGED id=${STABLE_ID} v=${SCHEMA_VERSION} source=@haus-tech/haus-workflow@${pkgVersion} -->`;
525
- }
526
- function renderProjectFacts(ctx, rec, pkgVersion) {
527
- const header = makeHeader(pkgVersion);
528
- const stackEntries = Object.entries(ctx.detectedStacks ?? {});
529
- const stackLines = stackEntries.length > 0 ? stackEntries.map(([stack, files]) => {
530
- const f = files;
531
- return `- **${stack}**: ${f.slice(0, 3).join(", ")}${f.length > 3 ? ", \u2026" : ""}`;
532
- }).join("\n") : "- none detected";
533
- const roles = ctx.repoRoles?.length > 0 ? ctx.repoRoles.join(", ") : "unknown";
534
- const recLines = rec.recommended.length > 0 ? rec.recommended.map((r) => `- \`${r.id}\` (${r.type}) \u2014 ${r.reason}`).join("\n") : "- none";
535
- const warningLines = [...ctx.warnings ?? [], ...rec.warnings ?? []];
536
- const warnSection = warningLines.length > 0 ? warningLines.map((w) => `- ${w}`).join("\n") : "- none";
537
- const repoName = ctx.repoName ?? path6.basename(ctx.root ?? "unknown");
538
- return `${header}
539
-
540
- # What haus found in this project
541
-
542
- > This is a plain summary of your project that haus wrote automatically, so Claude
543
- > always has the basics to hand. haus rewrites it on every \`haus apply\`, so don't
544
- > edit it by hand \u2014 your changes would be replaced next time.
545
-
546
- **Repo:** ${repoName}
547
- **Package manager:** ${ctx.packageManager ?? "unknown"}
548
- **Roles:** ${roles}
549
-
550
- ## Detected stacks
551
-
552
- ${stackLines}
553
-
554
- ## Recommended context
555
-
556
- ${recLines}
557
-
558
- ## Warnings
559
-
560
- ${warnSection}
561
- `;
562
- }
563
- async function writeProjectFacts(root, pkgVersion, dryRun) {
564
- const ctx = await readJson(hausPath(root, "context-map.json")) ?? {
565
- mode: "fast",
566
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
567
- root,
568
- repoName: path6.basename(root),
569
- packageManager: "unknown",
570
- repoRoles: [],
571
- confidence: 0,
572
- detectedStacks: {},
573
- dependencies: [],
574
- securityRisks: [],
575
- crossRepoHints: [],
576
- warnings: [],
577
- detectionStatus: "unknown",
578
- unsupportedSignals: []
579
- };
580
- const rec = await readJson(hausPath(root, "recommendation.json")) ?? {
581
- mode: "fast",
582
- recommended: [],
583
- skipped: [],
584
- warnings: [],
585
- estimatedContextTokens: 0,
586
- selectedRules: 0,
587
- skippedRules: 0,
588
- estimatedTokenReductionPct: 0
589
- };
590
- const destPath = hausPath(root, "project.md");
591
- const printable = displayPath(root, destPath);
592
- const next = renderProjectFacts(ctx, rec, pkgVersion);
593
- const prev = await fs5.pathExists(destPath) ? await fs5.readFile(destPath, "utf8") : "";
594
- if (dryRun) {
595
- if (!prev) {
596
- log(createUnifiedDiff(printable, "", next));
597
- } else if (hasTextChanged(prev, next)) {
598
- log(createUnifiedDiff(printable, prev, next));
599
- } else {
600
- log(`${printable}: unchanged`);
601
- }
602
- return destPath;
603
- }
604
- if (hasTextChanged(prev, next) && prev.length > 0) {
605
- const diffText = createUnifiedDiff(printable, prev, next);
606
- const summary = summarizeDiff(diffText);
607
- log(`Overwriting ${printable} (diff +${summary.additions} -${summary.deletions})`);
608
- }
609
- await writeText(destPath, next);
610
- return destPath;
611
- }
612
-
613
- // src/claude/write-root-claude-md.ts
614
- import path7 from "path";
615
- import fs6 from "fs-extra";
616
544
  var BLOCK_BEGIN = "<!-- HAUS:BEGIN haus-imports v=1 -->";
617
545
  var BLOCK_END = "<!-- HAUS:END haus-imports -->";
618
546
  var IMPORT_CONTENT = `@.haus-workflow/WORKFLOW.md
619
- @.haus-workflow/workflow-config.md
620
- @.haus-workflow/project.md`;
547
+ @.haus-workflow/workflow-config.md`;
621
548
  function buildImportBlock() {
622
549
  return `${BLOCK_BEGIN}
623
550
  ${IMPORT_CONTENT}
@@ -642,9 +569,9 @@ ${block}
642
569
  `;
643
570
  }
644
571
  async function writeRootClaudeMd(root, dryRun) {
645
- const filePath = path7.join(root, "CLAUDE.md");
572
+ const filePath = path6.join(root, "CLAUDE.md");
646
573
  const block = buildImportBlock();
647
- const prev = await fs6.pathExists(filePath) ? await fs6.readFile(filePath, "utf8") : "";
574
+ const prev = await fs5.pathExists(filePath) ? await fs5.readFile(filePath, "utf8") : "";
648
575
  const next = injectHausBlock(prev, block);
649
576
  const printable = displayPath(root, filePath);
650
577
  if (dryRun) {
@@ -667,22 +594,12 @@ async function writeRootClaudeMd(root, dryRun) {
667
594
  }
668
595
 
669
596
  // src/claude/write-workflow-config.ts
670
- import path9 from "path";
671
- import fs8 from "fs-extra";
672
-
673
- // src/claude/derive-workflow-config.ts
674
597
  import path8 from "path";
675
598
  import fs7 from "fs-extra";
676
- var VALIDATION_LIBS = [
677
- "zod",
678
- "valibot",
679
- "yup",
680
- "joi",
681
- "@hapi/joi",
682
- "class-validator",
683
- "superstruct",
684
- "ajv"
685
- ];
599
+
600
+ // src/claude/derive-workflow-config.ts
601
+ import path7 from "path";
602
+ import fs6 from "fs-extra";
686
603
  function binCmd(pm, bin, args) {
687
604
  const tail = args ? ` ${args}` : "";
688
605
  if (pm === "yarn") return `yarn ${bin}${tail}`;
@@ -691,7 +608,7 @@ function binCmd(pm, bin, args) {
691
608
  }
692
609
  async function deriveWorkflowConfig(root, ctx) {
693
610
  const pm = ctx.packageManager === "unknown" ? "npm" : ctx.packageManager;
694
- const pkg = await readJson(path8.join(root, "package.json"));
611
+ const pkg = await readJson(path7.join(root, "package.json"));
695
612
  const scripts = pkg?.scripts ?? {};
696
613
  const deps = new Set(ctx.dependencies);
697
614
  const stacks = Object.values(ctx.detectedStacks ?? {}).flat();
@@ -701,22 +618,13 @@ async function deriveWorkflowConfig(root, ctx) {
701
618
  return null;
702
619
  };
703
620
  const hasDep = (name) => deps.has(name);
704
- const exists = (rel) => fs7.pathExistsSync(path8.join(root, rel));
705
- const hasTypeScript = hasDep("typescript") || exists("tsconfig.json");
706
- const hasEslint = hasDep("eslint");
707
- const hasPrettier = hasDep("prettier");
621
+ const exists = (rel) => fs6.pathExistsSync(path7.join(root, rel));
708
622
  const hasPlaywright = hasDep("@playwright/test") || stacks.includes("playwright");
709
623
  const hasCypress = hasDep("cypress");
710
624
  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;
711
625
  return {
712
626
  test: script("test") ?? `${pm} test`,
713
627
  testE2E: firstScript("test:e2e", "e2e", "test:integration") ?? (hasPlaywright ? binCmd(pm, "playwright", "test") : null) ?? (hasCypress ? binCmd(pm, "cypress", "run") : null),
714
- typecheck: firstScript("typecheck", "type-check", "tsc") ?? (hasTypeScript ? binCmd(pm, "tsc", "--noEmit") : null),
715
- lint: script("lint") ?? (hasEslint ? binCmd(pm, "eslint", ".") : null),
716
- lintFix: firstScript("lint:fix", "lint-fix") ?? (scripts.lint ? `${pm} run lint -- --fix` : hasEslint ? binCmd(pm, "eslint", ". --fix") : null),
717
- formatCheck: firstScript("format:check", "format-check", "prettier:check") ?? (hasPrettier ? binCmd(pm, "prettier", "--check .") : null),
718
- securityAudit: `${pm} audit`,
719
- validationLibrary: VALIDATION_LIBS.find((lib) => deps.has(lib)) ?? null,
720
628
  preCommitTool,
721
629
  specPath: exists("docs/SPEC.md") ? "docs/SPEC.md" : null,
722
630
  designPath: exists("docs/DESIGN.md") ? "docs/DESIGN.md" : null,
@@ -731,13 +639,12 @@ function fields(v) {
731
639
  { prefix: "- Design: ", value: v.designPath, hint: "path, e.g. docs/DESIGN.md" },
732
640
  { prefix: "- UX flows: ", value: v.uxPath, hint: "path, e.g. docs/UX.md" },
733
641
  { prefix: "- Test (unit + integration): ", value: v.test, hint: "command", code: true },
734
- { prefix: "- Test (E2E): ", value: v.testE2E, hint: "command, e.g. playwright test", code: true },
735
- { prefix: "- Type check: ", value: v.typecheck, hint: "command, e.g. tsc --noEmit", code: true },
736
- { prefix: "- Lint: ", value: v.lint, hint: "command, e.g. eslint .", code: true },
737
- { prefix: "- Lint fix: ", value: v.lintFix, hint: "command, e.g. eslint . --fix", code: true },
738
- { prefix: "- Format check: ", value: v.formatCheck, hint: "command, e.g. prettier --check .", code: true },
739
- { prefix: "- Security audit: ", value: v.securityAudit, hint: "command", code: true },
740
- { prefix: "- Library: ", value: v.validationLibrary, hint: "e.g. zod, yup, joi" },
642
+ {
643
+ prefix: "- Test (E2E): ",
644
+ value: v.testE2E,
645
+ hint: "command, e.g. playwright test",
646
+ code: true
647
+ },
741
648
  { prefix: "- Tool: ", value: v.preCommitTool, hint: "e.g. lefthook, husky" }
742
649
  ];
743
650
  }
@@ -751,7 +658,7 @@ function line(f) {
751
658
  function buildWorkflowConfig(v) {
752
659
  const f = fields(v);
753
660
  const byPrefix = (p) => line(f.find((x) => x.prefix === p));
754
- return "# How this project works (commands & conventions)\n\n> The everyday commands and conventions for this project \u2014 the build, test, and\n> lint commands, where docs live, and so on. This file is yours to edit and haus\n> will not overwrite it. haus fills in what it can detect on first setup;\n> `haus apply --refill-config` fills any still-blank fields without touching\n> anything you've edited.\n\n## Source-of-truth documents\n" + byPrefix("- Spec: ") + "\n" + byPrefix("- Design: ") + "\n" + byPrefix("- UX flows: ") + "\n\n## Commands\n" + byPrefix("- Test (unit + integration): ") + "\n" + byPrefix("- Test (E2E): ") + "\n" + byPrefix("- Type check: ") + "\n" + byPrefix("- Lint: ") + "\n" + byPrefix("- Lint fix: ") + "\n" + byPrefix("- Format check: ") + "\n" + byPrefix("- Security audit: ") + "\n\n## Validation library\n" + byPrefix("- Library: ") + "\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" + byPrefix("- Tool: ") + "\n";
661
+ return "# How this project works (workflow methodology bindings)\n\n> The few project-specific values the workflow standard (WORKFLOW.md) binds to:\n> where the source-of-truth docs live, the test commands the TDD/verification gate\n> runs, the highest-stakes logic, and the pre-commit tool. This file is yours to\n> edit and haus will not overwrite it.\n>\n> Everyday commands (dev, build, lint, typecheck, format) and project documentation\n> live in `CLAUDE.md` + `docs/` \u2014 run **`/docs`** to generate/refresh them.\n\n## Source-of-truth documents\n" + byPrefix("- Spec: ") + "\n" + byPrefix("- Design: ") + "\n" + byPrefix("- UX flows: ") + "\n\n## Test commands (TDD / verification gate)\n" + byPrefix("- Test (unit + integration): ") + "\n" + byPrefix("- Test (E2E): ") + "\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" + byPrefix("- Tool: ") + "\n";
755
662
  }
756
663
  function refillContent(existing, v) {
757
664
  const f = fields(v);
@@ -769,7 +676,6 @@ var FALLBACK_CONTEXT = {
769
676
  repoName: "",
770
677
  packageManager: "unknown",
771
678
  repoRoles: [],
772
- confidence: 0,
773
679
  detectedStacks: {},
774
680
  dependencies: [],
775
681
  securityRisks: [],
@@ -781,7 +687,7 @@ var FALLBACK_CONTEXT = {
781
687
  async function writeWorkflowConfig(root, dryRun, opts = {}) {
782
688
  const destPath = hausPath(root, "workflow-config.md");
783
689
  const printable = displayPath(root, destPath);
784
- const exists = await fs8.pathExists(destPath);
690
+ const exists = await fs7.pathExists(destPath);
785
691
  if (exists && !opts.refill) {
786
692
  if (dryRun) log(printable + ": exists (project-owned, skipping)");
787
693
  return null;
@@ -789,11 +695,11 @@ async function writeWorkflowConfig(root, dryRun, opts = {}) {
789
695
  const ctx = await readJson(hausPath(root, "context-map.json")) ?? {
790
696
  ...FALLBACK_CONTEXT,
791
697
  root,
792
- repoName: path9.basename(root)
698
+ repoName: path8.basename(root)
793
699
  };
794
700
  const values = await deriveWorkflowConfig(root, ctx);
795
701
  if (exists) {
796
- const current = await fs8.readFile(destPath, "utf8");
702
+ const current = await fs7.readFile(destPath, "utf8");
797
703
  const refilled = refillContent(current, values);
798
704
  if (refilled === current) {
799
705
  if (dryRun) log(printable + ": no blank fields to refill");
@@ -815,7 +721,7 @@ async function writeWorkflowConfig(root, dryRun, opts = {}) {
815
721
  }
816
722
 
817
723
  // src/claude/write-workflow.ts
818
- import fs9 from "fs-extra";
724
+ import fs8 from "fs-extra";
819
725
 
820
726
  // src/claude/managed-template.ts
821
727
  function normaliseLF(content2) {
@@ -829,10 +735,10 @@ function parseHausManagedHeader(line2) {
829
735
  }
830
736
 
831
737
  // src/claude/write-workflow.ts
832
- var STABLE_ID2 = "template.workflow";
833
- var SCHEMA_VERSION2 = "1";
738
+ var STABLE_ID = "template.workflow";
739
+ var SCHEMA_VERSION = "1";
834
740
  function makeWorkflowHeader(pkgVersion, contentHash) {
835
- return `<!-- HAUS-MANAGED id=${STABLE_ID2} v=${SCHEMA_VERSION2} source=@haus-tech/haus-workflow@${pkgVersion} hash=${contentHash} -->`;
741
+ return `<!-- HAUS-MANAGED id=${STABLE_ID} v=${SCHEMA_VERSION} source=@haus-tech/haus-workflow@${pkgVersion} hash=${contentHash} -->`;
836
742
  }
837
743
  async function writeWorkflow(root, pkgVersion, dryRun) {
838
744
  const templateContent = await readWorkflowTemplate({ dryRun });
@@ -848,16 +754,16 @@ async function writeWorkflow(root, pkgVersion, dryRun) {
848
754
  ${templateContent}`;
849
755
  const destPath = hausPath(root, "WORKFLOW.md");
850
756
  const printable = displayPath(root, destPath);
851
- if (await fs9.pathExists(destPath)) {
852
- const existing = await fs9.readFile(destPath, "utf8");
757
+ if (await fs8.pathExists(destPath)) {
758
+ const existing = await fs8.readFile(destPath, "utf8");
853
759
  const firstLine = existing.split("\n")[0] ?? "";
854
760
  const parsed = parseHausManagedHeader(firstLine);
855
761
  if (!parsed) {
856
762
  warn(`${printable}: no HAUS-MANAGED header \u2014 file appears user-owned, skipping`);
857
763
  return null;
858
764
  }
859
- if (parsed.id !== STABLE_ID2) {
860
- warn(`${printable}: HAUS-MANAGED id mismatch (expected ${STABLE_ID2}) \u2014 skipping`);
765
+ if (parsed.id !== STABLE_ID) {
766
+ warn(`${printable}: HAUS-MANAGED id mismatch (expected ${STABLE_ID}) \u2014 skipping`);
861
767
  return null;
862
768
  }
863
769
  const existingContent = existing.slice(firstLine.length + 1);
@@ -871,7 +777,7 @@ ${templateContent}`;
871
777
  }
872
778
  }
873
779
  if (dryRun) {
874
- const prev = await fs9.pathExists(destPath) ? await fs9.readFile(destPath, "utf8") : "";
780
+ const prev = await fs8.pathExists(destPath) ? await fs8.readFile(destPath, "utf8") : "";
875
781
  if (!prev) {
876
782
  log(createUnifiedDiff(printable, "", next));
877
783
  } else {
@@ -898,7 +804,7 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
898
804
  estimatedTokenReductionPct: 0
899
805
  };
900
806
  const pkgRoot = packageRoot();
901
- const hausVersion = (await readJson(path10.join(pkgRoot, "package.json")))?.version ?? "0.0.0";
807
+ const hausVersion = (await readJson(path9.join(pkgRoot, "package.json")))?.version ?? "0.0.0";
902
808
  const coreFiles = [
903
809
  claudePath(root, "settings.json"),
904
810
  claudePath(root, "rules", "haus.md"),
@@ -911,10 +817,8 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
911
817
  const workflowConfigPath = await writeWorkflowConfig(root, dryRun, {
912
818
  refill: opts.refillConfig
913
819
  });
914
- const projectFactsPath = await writeProjectFacts(root, hausVersion, dryRun);
915
820
  const p6Files = [
916
821
  rootClaudeMdPath,
917
- projectFactsPath,
918
822
  ...workflowPath ? [workflowPath] : [],
919
823
  ...workflowConfigPath ? [workflowConfigPath] : []
920
824
  ];
@@ -928,7 +832,7 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
928
832
  await writeManagedJson(root, claudePath(root, "settings.json"), hookSettings, dryRun);
929
833
  if (!dryRun) await assertPostApplySettingsMatchCanonical(root, hookSettings);
930
834
  const configPath = hausPath(root, "config.json");
931
- if (!await fs10.pathExists(configPath)) {
835
+ if (!await fs9.pathExists(configPath)) {
932
836
  await writeManagedJson(root, configPath, DEFAULT_HOOKS_CONFIG, dryRun);
933
837
  }
934
838
  await writeManagedText(
@@ -956,12 +860,12 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
956
860
  dryRun
957
861
  );
958
862
  const fixtureManifestPath = process.env["HAUS_FIXTURE_CATALOG"];
959
- const manifestPath = fixtureManifestPath ?? path10.join(pkgRoot, "library", "catalog", "manifest.json");
960
- const manifestDir = path10.dirname(manifestPath);
863
+ const manifestPath = fixtureManifestPath ?? path9.join(pkgRoot, "library", "catalog", "manifest.json");
864
+ const manifestDir = path9.dirname(manifestPath);
961
865
  const manifest = await readJson(manifestPath) ?? { items: [] };
962
866
  const manifestById = new Map((manifest.items ?? []).map((item) => [item.id, item]));
963
867
  const cacheManifest = await readJson(
964
- path10.join(CACHE_DIR, "manifest.json")
868
+ path9.join(CACHE_DIR, "manifest.json")
965
869
  );
966
870
  const cacheManifestById = new Map((cacheManifest?.items ?? []).map((item) => [item.id, item]));
967
871
  const installedPathsByItem = /* @__PURE__ */ new Map();
@@ -983,23 +887,23 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
983
887
  }
984
888
  }
985
889
  const cachedItem = cacheManifestById.get(item.id);
986
- const cachePath = cachedItem?.path ? path10.join(CACHE_DIR, cachedItem.path) : null;
987
- const sourcePath = cachePath && await fs10.pathExists(cachePath) ? cachePath : path10.join(manifestDir, manifestItem.path);
890
+ const cachePath = cachedItem?.path ? path9.join(CACHE_DIR, cachedItem.path) : null;
891
+ const sourcePath = cachePath && await fs9.pathExists(cachePath) ? cachePath : path9.join(manifestDir, manifestItem.path);
988
892
  const target = item.type === "agent" ? "agents" : item.type === "template" ? "templates" : "skills";
989
- const destination = claudePath(root, target, path10.basename(sourcePath));
990
- if (await fs10.pathExists(sourcePath)) {
893
+ const destination = claudePath(root, target, path9.basename(sourcePath));
894
+ if (await fs9.pathExists(sourcePath)) {
991
895
  if (dryRun) {
992
- const exists = await fs10.pathExists(destination);
896
+ const exists = await fs9.pathExists(destination);
993
897
  log(
994
898
  `${displayPath(root, destination)}: ${exists ? "would overwrite" : "would create"} (${item.id})`
995
899
  );
996
900
  } else {
997
- await fs10.ensureDir(path10.dirname(destination));
998
- await fs10.copy(sourcePath, destination, { overwrite: true, errorOnExist: false });
901
+ await fs9.ensureDir(path9.dirname(destination));
902
+ await fs9.copy(sourcePath, destination, { overwrite: true, errorOnExist: false });
999
903
  }
1000
904
  files.push(destination);
1001
905
  const current = installedPathsByItem.get(item.id) ?? [];
1002
- installedPathsByItem.set(item.id, [...current, path10.relative(root, destination)]);
906
+ installedPathsByItem.set(item.id, [...current, path9.relative(root, destination)]);
1003
907
  installedIds.add(item.id);
1004
908
  } else {
1005
909
  warn(
@@ -1016,7 +920,7 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
1016
920
  id: r.id,
1017
921
  type: r.type,
1018
922
  reason: r.reason,
1019
- confidenceLevel: r.confidenceLevel
923
+ selectionMode: r.selectionMode
1020
924
  })),
1021
925
  false
1022
926
  );
@@ -1050,7 +954,7 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
1050
954
  return [...new Set(files)];
1051
955
  }
1052
956
  async function writeManagedText(root, filePath, nextText, dryRun) {
1053
- const prev = await fs10.pathExists(filePath) ? await fs10.readFile(filePath, "utf8") : "";
957
+ const prev = await fs9.pathExists(filePath) ? await fs9.readFile(filePath, "utf8") : "";
1054
958
  const printable = displayPath(root, filePath);
1055
959
  if (dryRun) {
1056
960
  if (!prev) {
@@ -1077,7 +981,7 @@ async function writeManagedJson(root, filePath, value, dryRun) {
1077
981
 
1078
982
  // src/commands/apply.ts
1079
983
  async function cacheHasItems() {
1080
- const data = await readJson(path11.join(CACHE_DIR, "manifest.json"));
984
+ const data = await readJson(path10.join(CACHE_DIR, "manifest.json"));
1081
985
  return Array.isArray(data?.items) && data.items.length > 0;
1082
986
  }
1083
987
  async function runApply(options) {
@@ -1104,7 +1008,7 @@ async function runApply(options) {
1104
1008
  } else {
1105
1009
  const items = rec.recommended;
1106
1010
  const choices = items.map((item) => ({
1107
- name: `${item.id} [${item.confidenceLevel}] \u2014 ${item.reason}`,
1011
+ name: `${item.id} [${item.selectionMode}] \u2014 ${item.reason}`,
1108
1012
  value: item.id,
1109
1013
  checked: true
1110
1014
  }));
@@ -1147,8 +1051,8 @@ async function runApply(options) {
1147
1051
 
1148
1052
  // src/catalog/load-catalog.ts
1149
1053
  import os3 from "os";
1150
- import path12 from "path";
1151
- var CACHE_MANIFEST = path12.join(os3.homedir(), CATALOG_CACHE_SUBDIR, "manifest.json");
1054
+ import path11 from "path";
1055
+ var CACHE_MANIFEST = path11.join(os3.homedir(), CATALOG_CACHE_SUBDIR, "manifest.json");
1152
1056
  async function loadCatalog(root) {
1153
1057
  const envPath = process.env["HAUS_FIXTURE_CATALOG"];
1154
1058
  if (envPath) {
@@ -1157,10 +1061,10 @@ async function loadCatalog(root) {
1157
1061
  }
1158
1062
  const cacheData = await readJson(CACHE_MANIFEST);
1159
1063
  if (cacheData?.items?.length) return cacheData.items;
1160
- const localManifest = path12.join(root, "library/catalog/manifest.json");
1064
+ const localManifest = path11.join(root, "library/catalog/manifest.json");
1161
1065
  const localData = await readJson(localManifest);
1162
1066
  if (localData?.items?.length) return localData.items;
1163
- const packageManifest = path12.join(packageRoot(), "library/catalog/manifest.json");
1067
+ const packageManifest = path11.join(packageRoot(), "library/catalog/manifest.json");
1164
1068
  const data = await readJson(packageManifest);
1165
1069
  return data?.items ?? [];
1166
1070
  }
@@ -1200,7 +1104,7 @@ async function runCatalogAudit() {
1200
1104
  }
1201
1105
 
1202
1106
  // src/commands/config.ts
1203
- import path13 from "path";
1107
+ import path12 from "path";
1204
1108
  var CONFIG_PATH2 = ".haus-workflow/config.json";
1205
1109
  var HOOK_ALIASES = {
1206
1110
  "hook.context": "context"
@@ -1213,7 +1117,7 @@ async function runConfig(key, action) {
1213
1117
  );
1214
1118
  }
1215
1119
  const root = process.cwd();
1216
- const configPath = path13.join(root, CONFIG_PATH2);
1120
+ const configPath = path12.join(root, CONFIG_PATH2);
1217
1121
  const existing = await readJson(configPath);
1218
1122
  const cfg = existing ?? structuredClone(DEFAULT_HOOKS_CONFIG);
1219
1123
  cfg.hooks ??= {};
@@ -1234,27 +1138,15 @@ function normalizeRecommendation(input2) {
1234
1138
  const normalizedReasons = item.reasons?.map((reason) => ({
1235
1139
  code: reason.code ?? "legacy-reason",
1236
1140
  message: reason.message ?? item.reason ?? "legacy recommendation reason",
1237
- weight: reason.weight ?? 0,
1238
1141
  ...reason.signal ? { signal: reason.signal } : {}
1239
- })) ?? [
1240
- { code: "legacy-reason", message: item.reason ?? "legacy recommendation reason", weight: 0 }
1241
- ];
1242
- const confidence = item.confidence ?? 0;
1142
+ })) ?? [{ code: "legacy-reason", message: item.reason ?? "legacy recommendation reason" }];
1243
1143
  return {
1244
1144
  id: item.id,
1245
1145
  type: item.type ?? "skill",
1246
1146
  reason: item.reason ?? normalizedReasons.map((reason) => reason.message).join(", "),
1247
1147
  reasons: normalizedReasons,
1248
- confidence,
1249
- confidenceLevel: item.confidenceLevel ?? (confidence >= 0.75 ? "high" : confidence >= 0.4 ? "medium" : "low"),
1250
1148
  selectionMode: item.selectionMode ?? "matched",
1251
1149
  install: item.install ?? true,
1252
- score: item.score ?? 0,
1253
- scoreBreakdown: {
1254
- bonuses: normalizedReasons,
1255
- penalties: [],
1256
- finalScore: item.score ?? 0
1257
- },
1258
1150
  tags: item.tags,
1259
1151
  ecosystem: item.ecosystem,
1260
1152
  tokenEstimate: item.tokenEstimate
@@ -1266,15 +1158,8 @@ function normalizeRecommendation(input2) {
1266
1158
  skipReasons: item.skipReasons?.map((reason) => ({
1267
1159
  code: reason.code ?? "legacy-skip-reason",
1268
1160
  message: reason.message ?? item.reason ?? "legacy skipped reason",
1269
- penalty: reason.penalty ?? 0,
1270
1161
  ...reason.signal ? { signal: reason.signal } : {}
1271
- })) ?? [
1272
- {
1273
- code: "legacy-skip-reason",
1274
- message: item.reason ?? "legacy skipped reason",
1275
- penalty: 0
1276
- }
1277
- ]
1162
+ })) ?? [{ code: "legacy-skip-reason", message: item.reason ?? "legacy skipped reason" }]
1278
1163
  }));
1279
1164
  return {
1280
1165
  mode: input2.mode === "guided" ? "guided" : "fast",
@@ -1294,8 +1179,6 @@ function buildRecommendationExplanation(recommendation) {
1294
1179
  return {
1295
1180
  selected: recommendation.recommended.map((item) => ({
1296
1181
  id: item.id,
1297
- confidence: item.confidence,
1298
- confidenceLevel: item.confidenceLevel,
1299
1182
  selectionMode: item.selectionMode,
1300
1183
  reasons: item.reasons.map((reason) => reason.message)
1301
1184
  })),
@@ -1305,7 +1188,6 @@ function buildRecommendationExplanation(recommendation) {
1305
1188
  reasonDetails: item.skipReasons.map((reason) => ({
1306
1189
  code: reason.code,
1307
1190
  message: reason.message,
1308
- penalty: reason.penalty,
1309
1191
  ...reason.signal ? { signal: reason.signal } : {}
1310
1192
  }))
1311
1193
  })),
@@ -1532,6 +1414,13 @@ function computeRuleIntents(rule) {
1532
1414
  }
1533
1415
 
1534
1416
  // src/recommender/rule-selection.ts
1417
+ function evidenceCount(rule) {
1418
+ return rule.reasons.filter((r) => r.code !== "default-baseline").length;
1419
+ }
1420
+ function isRoleOnly(rule) {
1421
+ const codes = rule.reasons.map((r) => r.code).filter((c) => c !== "default-baseline");
1422
+ return codes.length > 0 && codes.every((c) => c === "repo-role-match");
1423
+ }
1535
1424
  var DEFAULT_CONTEXT_TOKEN_BUDGET = 12e3;
1536
1425
  function pickTaskRelevantRules(recommendation, task, taskIntents = /* @__PURE__ */ new Set(), opts = {}) {
1537
1426
  const recommended = recommendation?.recommended ?? [];
@@ -1549,7 +1438,7 @@ function applyTokenBudget(rules, budget) {
1549
1438
  used += r.tokenEstimate ?? 0;
1550
1439
  }
1551
1440
  }
1552
- const matched = rules.filter((r) => r.selectionMode !== "baseline").sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
1441
+ const matched = rules.filter((r) => r.selectionMode !== "baseline").sort((a, b) => evidenceCount(b) - evidenceCount(a) || a.id.localeCompare(b.id));
1553
1442
  for (const r of matched) {
1554
1443
  const est = r.tokenEstimate ?? 0;
1555
1444
  if (used + est <= budget) {
@@ -1587,20 +1476,20 @@ function selectRules(recommended, task, taskIntents) {
1587
1476
  });
1588
1477
  if (tokenMatches.length > 0) return tokenMatches;
1589
1478
  const taskWantsTesting = taskIntents.has("testing");
1590
- const cappedMediumOrHigh = recommended.filter((rule) => {
1479
+ const capped = recommended.filter((rule) => {
1591
1480
  if (rule.selectionMode === "baseline") return false;
1592
- if (rule.confidenceLevel === "low") return false;
1481
+ if (isRoleOnly(rule)) return false;
1593
1482
  if (taskWantsTesting) return true;
1594
1483
  const ruleIntents = computeRuleIntents(rule);
1595
1484
  const isTestingOnly = ruleIntents.size > 0 && [...ruleIntents].every((i) => i === "testing");
1596
1485
  return !isTestingOnly;
1597
1486
  });
1598
- return cappedMediumOrHigh.slice(0, 8);
1487
+ return capped.slice(0, 8);
1599
1488
  }
1600
1489
 
1601
1490
  // src/scanner/scan-project.ts
1602
1491
  import { readFile as readFile2 } from "fs/promises";
1603
- import path17 from "path";
1492
+ import path16 from "path";
1604
1493
 
1605
1494
  // src/utils/audit-checks.ts
1606
1495
  function isRecord(v) {
@@ -1627,8 +1516,8 @@ function compareVersions(a, b) {
1627
1516
  }
1628
1517
 
1629
1518
  // src/scanner/detect-package-manager.ts
1630
- import path14 from "path";
1631
- import fs11 from "fs-extra";
1519
+ import path13 from "path";
1520
+ import fs10 from "fs-extra";
1632
1521
  function detectPackageManager(root, packageManagerField) {
1633
1522
  const field = String(packageManagerField ?? "").trim();
1634
1523
  if (field.startsWith("yarn@")) {
@@ -1646,9 +1535,9 @@ function detectPackageManager(root, packageManagerField) {
1646
1535
  if (satisfiesVersion(version, ">=9")) return "npm";
1647
1536
  return "unknown";
1648
1537
  }
1649
- if (fs11.existsSync(path14.join(root, "yarn.lock"))) return "yarn";
1650
- if (fs11.existsSync(path14.join(root, "pnpm-lock.yaml"))) return "pnpm";
1651
- if (fs11.existsSync(path14.join(root, "package-lock.json"))) return "npm";
1538
+ if (fs10.existsSync(path13.join(root, "yarn.lock"))) return "yarn";
1539
+ if (fs10.existsSync(path13.join(root, "pnpm-lock.yaml"))) return "pnpm";
1540
+ if (fs10.existsSync(path13.join(root, "package-lock.json"))) return "npm";
1652
1541
  return "unknown";
1653
1542
  }
1654
1543
 
@@ -1821,7 +1710,7 @@ function runDetection(ctx, rules = STACK_RULES) {
1821
1710
  }
1822
1711
 
1823
1712
  // src/scanner/detection.ts
1824
- import path15 from "path";
1713
+ import path14 from "path";
1825
1714
  var UNSUPPORTED_MARKERS = {
1826
1715
  "requirements.txt": "python",
1827
1716
  "pyproject.toml": "python",
@@ -1875,14 +1764,14 @@ function finalizeRoles(registryRoles, deps, files) {
1875
1764
  function collectUnsupportedSignals(files) {
1876
1765
  return [
1877
1766
  ...new Set(
1878
- files.map((f) => UNSUPPORTED_MARKERS[path15.basename(f)]).filter((s) => Boolean(s))
1767
+ files.map((f) => UNSUPPORTED_MARKERS[path14.basename(f)]).filter((s) => Boolean(s))
1879
1768
  )
1880
1769
  ].sort();
1881
1770
  }
1882
1771
 
1883
1772
  // src/scanner/render.ts
1884
1773
  import { readFile } from "fs/promises";
1885
- import path16 from "path";
1774
+ import path15 from "path";
1886
1775
 
1887
1776
  // src/scanner/role-labels.ts
1888
1777
  var ROLE_LABELS = {
@@ -1948,7 +1837,7 @@ async function buildContentBlob(root, files) {
1948
1837
  const batch = await Promise.all(
1949
1838
  slice.slice(i, i + CHUNK).map(async (rel) => {
1950
1839
  try {
1951
- return await readFile(path16.join(root, rel), "utf8");
1840
+ return await readFile(path15.join(root, rel), "utf8");
1952
1841
  } catch {
1953
1842
  return "";
1954
1843
  }
@@ -1958,11 +1847,6 @@ async function buildContentBlob(root, files) {
1958
1847
  }
1959
1848
  return parts.join("\n");
1960
1849
  }
1961
- function computeConfidence(roles, stacks) {
1962
- const stackCount = Object.values(stacks).reduce((sum, arr) => sum + arr.length, 0);
1963
- if (roles.length === 0) return 0.15;
1964
- return Math.min(0.99, Number((0.4 + roles.length * 0.08 + stackCount * 0.02).toFixed(2)));
1965
- }
1966
1850
  function renderSummary(context) {
1967
1851
  return `# Repo summary
1968
1852
 
@@ -2022,8 +1906,8 @@ var SAFE_FILES = [
2022
1906
  "Gemfile"
2023
1907
  ];
2024
1908
  async function scanProject(root, mode = "fast") {
2025
- const pkg = await readJson(path17.join(root, "package.json"));
2026
- const composer = await readJson(path17.join(root, "composer.json"));
1909
+ const pkg = await readJson(path16.join(root, "package.json"));
1910
+ const composer = await readJson(path16.join(root, "composer.json"));
2027
1911
  const files = await listFiles(root, SAFE_FILES);
2028
1912
  const safeFiles = files.filter((f) => !blocked(f));
2029
1913
  const deps = dependencySet(pkg, composer);
@@ -2057,10 +1941,9 @@ async function scanProject(root, mode = "fast") {
2057
1941
  mode,
2058
1942
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2059
1943
  root,
2060
- repoName: String(pkg?.name ?? path17.basename(root)),
1944
+ repoName: String(pkg?.name ?? path16.basename(root)),
2061
1945
  packageManager,
2062
1946
  repoRoles: roles,
2063
- confidence: computeConfidence(roles, stacks),
2064
1947
  detectedStacks: stacks,
2065
1948
  dependencies: deps,
2066
1949
  securityRisks,
@@ -2076,7 +1959,7 @@ async function scanProject(root, mode = "fast") {
2076
1959
  const scanHashes = Object.fromEntries(
2077
1960
  await Promise.all(
2078
1961
  safeFiles.map(
2079
- async (f) => [f, hashText(await readFile2(path17.join(root, f), "utf8"))]
1962
+ async (f) => [f, hashText(await readFile2(path16.join(root, f), "utf8"))]
2080
1963
  )
2081
1964
  )
2082
1965
  );
@@ -2106,9 +1989,6 @@ async function runContext(options) {
2106
1989
  const summary = await readText(hausPath(root, "repo-summary.md")) ?? "";
2107
1990
  const recommendationRaw = await readJson(hausPath(root, "recommendation.json"));
2108
1991
  const recommendation = recommendationRaw ? normalizeRecommendation(recommendationRaw) : void 0;
2109
- const rawBreakdownById = new Map(
2110
- (recommendationRaw?.recommended ?? []).map((item) => [item.id, item.scoreBreakdown])
2111
- );
2112
1992
  const taskIntents = options.task ? classifyTaskIntents(options.task) : /* @__PURE__ */ new Set();
2113
1993
  const selected = pickTaskRelevantRules(recommendation, options.task, taskIntents, {
2114
1994
  tokenBudget: DEFAULT_CONTEXT_TOKEN_BUDGET
@@ -2119,10 +1999,9 @@ async function runContext(options) {
2119
1999
  roles: context.repoRoles,
2120
2000
  selectedRules: selected.map((x) => ({
2121
2001
  id: x.id,
2122
- confidenceLevel: x.confidenceLevel,
2123
2002
  selectionMode: x.selectionMode,
2124
2003
  reasons: x.reasons.map((reason) => reason.message),
2125
- ...options.verbose ? { scoreBreakdown: rawBreakdownById.get(x.id) } : {}
2004
+ ...options.verbose ? { signals: x.reasons.map((r) => r.signal).filter(Boolean) } : {}
2126
2005
  })),
2127
2006
  skippedCount: recommendation?.skippedRules ?? 0,
2128
2007
  estimatedTokenReductionPct: recommendation?.estimatedTokenReductionPct ?? 0
@@ -2143,15 +2022,8 @@ async function runContext(options) {
2143
2022
  ...payload.selectedRules.flatMap((rule) => {
2144
2023
  const reasonLine = `- ${rule.id}: ${rule.reasons.join(", ")}`;
2145
2024
  if (!options.verbose) return [reasonLine];
2146
- const breakdown = rawBreakdownById.get(rule.id);
2147
- if (!breakdown) return [reasonLine];
2148
- const bonuses = (breakdown.bonuses ?? []).map(
2149
- (b) => ` + ${b.code}(+${b.weight})${b.signal ? ` [${b.signal}]` : ""}`
2150
- );
2151
- const penalties = (breakdown.penalties ?? []).map(
2152
- (p) => ` - ${p.code}(${p.penalty})${p.signal ? ` [${p.signal}]` : ""}`
2153
- );
2154
- return [reasonLine, ...bonuses, ...penalties];
2025
+ const signals = (rule.signals ?? []).map((s) => ` \u2022 ${s}`);
2026
+ return [reasonLine, ...signals];
2155
2027
  }),
2156
2028
  summary
2157
2029
  ];
@@ -2160,8 +2032,8 @@ async function runContext(options) {
2160
2032
  }
2161
2033
 
2162
2034
  // src/commands/doctor.ts
2163
- import path18 from "path";
2164
- import fs12 from "fs-extra";
2035
+ import path17 from "path";
2036
+ import fs11 from "fs-extra";
2165
2037
 
2166
2038
  // src/update/npm-version.ts
2167
2039
  var NPM_PACKAGE_NAME = "@haus-tech/haus-workflow";
@@ -2241,7 +2113,7 @@ async function runDoctor(options) {
2241
2113
  const enabled = await isHookEnabled(root, key);
2242
2114
  ok(`- HOOK ${key}: ${enabled ? "enabled" : "disabled (default)"}`);
2243
2115
  }
2244
- const rootClaudeMdPath = path18.join(root, "CLAUDE.md");
2116
+ const rootClaudeMdPath = path17.join(root, "CLAUDE.md");
2245
2117
  const rootClaudeMdContent = await readText(rootClaudeMdPath);
2246
2118
  if (!rootClaudeMdContent) {
2247
2119
  flag(
@@ -2269,7 +2141,7 @@ async function runDoctor(options) {
2269
2141
  const block = rootClaudeMdContent.slice(beginIdx, endIdx + BLOCK_END.length);
2270
2142
  const importTargets = [...block.matchAll(/@\.haus-workflow\/(\S+)/g)].map((m) => m[1]);
2271
2143
  for (const target of importTargets) {
2272
- if (!await fs12.pathExists(hausPath(root, target))) {
2144
+ if (!await fs11.pathExists(hausPath(root, target))) {
2273
2145
  flag(
2274
2146
  `- CLAUDE.md import: @.haus-workflow/${target} does not resolve (run \`haus apply --write\`)`,
2275
2147
  `A file CLAUDE.md links to (${target}) is missing, so part of the guidance won't load`,
@@ -2280,7 +2152,7 @@ async function runDoctor(options) {
2280
2152
  }
2281
2153
  }
2282
2154
  const workflowPath = hausPath(root, "WORKFLOW.md");
2283
- const workflowExists = await fs12.pathExists(workflowPath);
2155
+ const workflowExists = await fs11.pathExists(workflowPath);
2284
2156
  if (!workflowExists) {
2285
2157
  flag(
2286
2158
  "- .haus-workflow/WORKFLOW.md: missing (run `haus apply --write`)",
@@ -2294,15 +2166,15 @@ async function runDoctor(options) {
2294
2166
  ok("- .haus-workflow/WORKFLOW.md: OK (user-owned)");
2295
2167
  } else {
2296
2168
  const storedHashMatch = firstLine.match(/hash=(sha256-[a-f0-9]+)/);
2297
- const cachePath = path18.join(CACHE_DIR, "templates/agentic-workflow-standard.md");
2298
- const bundledPath = path18.join(
2169
+ const cachePath = path17.join(CACHE_DIR, "templates/agentic-workflow-standard.md");
2170
+ const bundledPath = path17.join(
2299
2171
  packageRoot(),
2300
2172
  "library",
2301
2173
  "global",
2302
2174
  "templates",
2303
2175
  "agentic-workflow-standard.md"
2304
2176
  );
2305
- const templatePath = await fs12.pathExists(cachePath) ? cachePath : bundledPath;
2177
+ const templatePath = await fs11.pathExists(cachePath) ? cachePath : bundledPath;
2306
2178
  const templateContent = await readText(templatePath);
2307
2179
  if (storedHashMatch && templateContent) {
2308
2180
  const currentHash = hashText(normaliseLF(templateContent));
@@ -2321,7 +2193,7 @@ async function runDoctor(options) {
2321
2193
  }
2322
2194
  }
2323
2195
  const workflowConfigPath = hausPath(root, "workflow-config.md");
2324
- const workflowConfigExists = await fs12.pathExists(workflowConfigPath);
2196
+ const workflowConfigExists = await fs11.pathExists(workflowConfigPath);
2325
2197
  if (!workflowConfigExists) {
2326
2198
  flag(
2327
2199
  "- .haus-workflow/workflow-config.md: missing (run `haus apply --write`)",
@@ -2329,7 +2201,7 @@ async function runDoctor(options) {
2329
2201
  "haus apply --write"
2330
2202
  );
2331
2203
  } else {
2332
- const cfg = await fs12.readFile(workflowConfigPath, "utf8");
2204
+ const cfg = await fs11.readFile(workflowConfigPath, "utf8");
2333
2205
  const unfilled = cfg.split("\n").filter((l) => l.includes("<!-- fill in")).length;
2334
2206
  if (unfilled > 0) {
2335
2207
  flag(
@@ -2341,23 +2213,6 @@ async function runDoctor(options) {
2341
2213
  ok("- .haus-workflow/workflow-config.md: OK (project-owned)");
2342
2214
  }
2343
2215
  }
2344
- const projectMdPath = hausPath(root, "project.md");
2345
- const projectMdExists = await fs12.pathExists(projectMdPath);
2346
- if (!projectMdExists) {
2347
- flag(
2348
- "- .haus-workflow/project.md: missing (run `haus apply --write`)",
2349
- "The project facts file is missing",
2350
- "haus apply --write"
2351
- );
2352
- } else {
2353
- const projectMdContent = await readText(projectMdPath);
2354
- const hasHeader = projectMdContent?.split("\n")[0]?.includes("HAUS-MANAGED") ?? false;
2355
- if (!hasHeader) {
2356
- ok("- .haus-workflow/project.md: no HAUS-MANAGED header (user-owned)");
2357
- } else {
2358
- ok("- .haus-workflow/project.md: OK");
2359
- }
2360
- }
2361
2216
  const cacheAgeMs = await getCacheManifestAge();
2362
2217
  if (cacheAgeMs === null) {
2363
2218
  flag(
@@ -2377,7 +2232,7 @@ async function runDoctor(options) {
2377
2232
  ok(`- CATALOG CACHE: OK (${cacheAgeDays}d old)`);
2378
2233
  }
2379
2234
  }
2380
- const pkgJson = await readJson(path18.join(packageRoot(), "package.json"));
2235
+ const pkgJson = await readJson(path17.join(packageRoot(), "package.json"));
2381
2236
  const currentVersion = pkgJson?.version ?? "0.0.0";
2382
2237
  const npmStatus = await fetchNpmVersionStatus(currentVersion);
2383
2238
  if (npmStatus.updateAvailable && npmStatus.latest !== null) {
@@ -2425,7 +2280,6 @@ function formatRecommendationHuman(rec) {
2425
2280
  if (rec.recommended.length === 0) lines.push(" (none)");
2426
2281
  for (const item of rec.recommended) {
2427
2282
  lines.push(`- ${item.id}`);
2428
- lines.push(` confidence: ${item.confidenceLevel} (${item.confidence.toFixed(2)})`);
2429
2283
  lines.push(` selection: ${item.selectionMode}`);
2430
2284
  lines.push(" why:");
2431
2285
  for (const reason of item.reasons) lines.push(` - ${formatReasonWithSignal(reason)}`);
@@ -2519,51 +2373,46 @@ async function runGuard(kind, _options) {
2519
2373
  }
2520
2374
 
2521
2375
  // src/commands/init.ts
2522
- import path19 from "path";
2523
- import fs13 from "fs-extra";
2376
+ import path18 from "path";
2377
+ import fs12 from "fs-extra";
2524
2378
 
2525
- // src/recommender/ecosystem.ts
2526
- var ECOSYSTEM_GROUPS = {
2527
- laravel: ["laravel-app", "laravel-nova-app"],
2528
- wordpress: ["wordpress-site", "wordpress-bedrock-site", "wordpress-vanilla-site"],
2529
- vendure: ["vendure-app", "vendure-plugin"],
2530
- nestjs: ["nestjs-api"],
2531
- nextjs: ["next-app"],
2532
- react: ["react-app", "next-app", "design-system"],
2533
- vue: ["vue-app"],
2534
- dotnet: ["dotnet-service"],
2535
- nx: ["nx-monorepo"],
2536
- turbo: ["turbo-monorepo"]
2537
- };
2538
- var ECOSYSTEM_PRIMARY_BACKENDS = /* @__PURE__ */ new Set([
2539
- "laravel",
2540
- "wordpress",
2541
- "vendure",
2542
- "nestjs",
2543
- "dotnet"
2544
- ]);
2545
- var ECOSYSTEM_COMPATIBLE_BACKENDS = {
2546
- vendure: /* @__PURE__ */ new Set(["vendure", "nestjs"]),
2547
- nestjs: /* @__PURE__ */ new Set(["nestjs"]),
2548
- laravel: /* @__PURE__ */ new Set(["laravel"]),
2549
- wordpress: /* @__PURE__ */ new Set(["wordpress"]),
2550
- dotnet: /* @__PURE__ */ new Set(["dotnet"])
2551
- };
2552
- function inferRepoEcosystems(roles) {
2553
- const ecosystems = /* @__PURE__ */ new Set();
2554
- for (const [eco, roleList] of Object.entries(ECOSYSTEM_GROUPS)) {
2555
- if (roleList.some((r) => roles.includes(r))) ecosystems.add(eco);
2379
+ // src/utils/exec.ts
2380
+ import { execa } from "execa";
2381
+ async function runCommand(command, args = [], options = {}) {
2382
+ try {
2383
+ const result = await execa(command, args, {
2384
+ reject: false,
2385
+ // non-zero exits are returned, not thrown
2386
+ ...options
2387
+ });
2388
+ return {
2389
+ command,
2390
+ args,
2391
+ stdout: String(result.stdout ?? ""),
2392
+ stderr: String(result.stderr ?? ""),
2393
+ exitCode: result.exitCode ?? 0
2394
+ };
2395
+ } catch (error2) {
2396
+ const message = error2 instanceof Error ? error2.message : String(error2);
2397
+ throw new Error(`Failed to run command: ${command} ${args.join(" ")} (${message})`);
2556
2398
  }
2557
- return [...ecosystems];
2558
2399
  }
2559
- function pickDominantBackend(ecosystems) {
2560
- for (const eco of ecosystems) {
2561
- if (ECOSYSTEM_PRIMARY_BACKENDS.has(eco)) return eco;
2562
- }
2563
- return void 0;
2400
+ async function runGit(args, options = {}) {
2401
+ return runCommand("git", args, options);
2564
2402
  }
2565
- function isBackendEcosystem(eco) {
2566
- return ECOSYSTEM_PRIMARY_BACKENDS.has(eco);
2403
+
2404
+ // src/recommender/git-signal.ts
2405
+ async function readChangedFiles(root) {
2406
+ if (process.env.HAUS_DISABLE_GIT_SIGNALS === "1") return [];
2407
+ try {
2408
+ const result = await runGit(["diff", "--name-only"], { cwd: root });
2409
+ if (result.exitCode !== 0) {
2410
+ return [];
2411
+ }
2412
+ return result.stdout.split("\n").map((x) => x.trim()).filter(Boolean).sort();
2413
+ } catch {
2414
+ return [];
2415
+ }
2567
2416
  }
2568
2417
 
2569
2418
  // src/recommender/policies.ts
@@ -2633,63 +2482,6 @@ function mergeRecommendationWarnings(context) {
2633
2482
  return [.../* @__PURE__ */ new Set([...statusLines, ...context.warnings, ...riskLines])];
2634
2483
  }
2635
2484
 
2636
- // src/utils/exec.ts
2637
- import { execa } from "execa";
2638
- async function runCommand(command, args = [], options = {}) {
2639
- try {
2640
- const result = await execa(command, args, {
2641
- reject: false,
2642
- // non-zero exits are returned, not thrown
2643
- ...options
2644
- });
2645
- return {
2646
- command,
2647
- args,
2648
- stdout: String(result.stdout ?? ""),
2649
- stderr: String(result.stderr ?? ""),
2650
- exitCode: result.exitCode ?? 0
2651
- };
2652
- } catch (error2) {
2653
- const message = error2 instanceof Error ? error2.message : String(error2);
2654
- throw new Error(`Failed to run command: ${command} ${args.join(" ")} (${message})`);
2655
- }
2656
- }
2657
- async function runGit(args, options = {}) {
2658
- return runCommand("git", args, options);
2659
- }
2660
-
2661
- // src/recommender/scoring.ts
2662
- function computeConfidenceLevel(args) {
2663
- const { isDefaultBaseline, reasons, hasEcosystemConflict, score } = args;
2664
- const positiveCodes = new Set(reasons.map((r) => r.code));
2665
- positiveCodes.delete("default-baseline");
2666
- const distinctSignals = positiveCodes.size;
2667
- const strongCount = (positiveCodes.has("repo-role-match") ? 1 : 0) + (positiveCodes.has("stack-match") ? 1 : 0) + (positiveCodes.has("requires-any-match") ? 1 : 0);
2668
- if (hasEcosystemConflict) return "low";
2669
- if (isDefaultBaseline && distinctSignals === 0) return "medium";
2670
- if (strongCount >= 2 && score >= 70) return "high";
2671
- if (strongCount >= 1 && distinctSignals >= 2 && score >= 50) return "medium";
2672
- if (distinctSignals === 1) return "low";
2673
- return distinctSignals >= 2 ? "medium" : "low";
2674
- }
2675
- function confidenceLevelToNumber(level, score) {
2676
- const base = level === "high" ? 0.85 : level === "medium" ? 0.6 : 0.3;
2677
- const bonus = Math.min(0.1, Math.max(0, score - 40) / 1e3);
2678
- return Number(Math.min(0.99, base + bonus).toFixed(2));
2679
- }
2680
- async function readChangedFiles(root) {
2681
- if (process.env.HAUS_DISABLE_GIT_SIGNALS === "1") return [];
2682
- try {
2683
- const result = await runGit(["diff", "--name-only"], { cwd: root });
2684
- if (result.exitCode !== 0) {
2685
- return [];
2686
- }
2687
- return result.stdout.split("\n").map((x) => x.trim()).filter(Boolean).sort();
2688
- } catch {
2689
- return [];
2690
- }
2691
- }
2692
-
2693
2485
  // src/recommender/recommend.ts
2694
2486
  async function recommend(root, context) {
2695
2487
  const items = await loadCatalog(root);
@@ -2697,249 +2489,147 @@ async function recommend(root, context) {
2697
2489
  const sources = await readJson(
2698
2490
  hausPath(root, "sources-report.json")
2699
2491
  ) ?? {};
2700
- const stackSet = buildStackSet(context);
2701
- const depSet = new Set(context.dependencies.map((d) => d.toLowerCase()));
2702
- const roleSet = new Set(context.repoRoles.map((r) => r.toLowerCase()));
2703
- const repoEcosystems = inferRepoEcosystems(context.repoRoles);
2704
- const dominantBackendEcosystem = pickDominantBackend(repoEcosystems);
2492
+ const deep = await readJson(hausPath(root, "deep-context.json")) ?? {};
2493
+ const scannerStacks = buildStackSet(context);
2494
+ const scannerRoles = new Set(context.repoRoles.map((r) => r.toLowerCase()));
2495
+ const scannerDeps = new Set(context.dependencies.map((d) => d.toLowerCase()));
2496
+ const toStrings = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
2497
+ const deepStackValues = deep.stacks && typeof deep.stacks === "object" && !Array.isArray(deep.stacks) ? Object.values(deep.stacks).flatMap(toStrings) : [];
2498
+ const deepRoles = new Set(toStrings(deep.roles).map((r) => r.toLowerCase()));
2499
+ const deepStacks = new Set(
2500
+ [...toStrings(deep.roles), ...deepStackValues, ...toStrings(deep.patterns)].map(
2501
+ (x) => x.toLowerCase()
2502
+ )
2503
+ );
2504
+ const roleSet = /* @__PURE__ */ new Set([...scannerRoles, ...deepRoles]);
2505
+ const stackSet = /* @__PURE__ */ new Set([...scannerStacks, ...deepStacks]);
2506
+ const depSet = scannerDeps;
2705
2507
  const recommended = [];
2706
2508
  const skipped = [];
2707
2509
  const goals = Object.values(setupAnswers).join(" ").toLowerCase();
2708
2510
  const sourceTrust = new Map((sources.items ?? []).map((x) => [x.id, x.status ?? "candidate"]));
2709
2511
  const changedFiles = await readChangedFiles(root);
2710
- const securityRiskCount = context.securityRisks?.length ?? 0;
2512
+ const skip = (id, code, message, signal) => {
2513
+ skipped.push({ id, reason: message, skipReasons: [{ code, message, signal }] });
2514
+ };
2515
+ const roleSignal = (name) => scannerRoles.has(name.toLowerCase()) ? `role:${name}` : `deep:role:${name}`;
2516
+ const stackSignal = (name) => scannerStacks.has(name.toLowerCase()) ? `tag:${name}` : `deep:tag:${name}`;
2711
2517
  for (const item of items) {
2712
2518
  const itemSearchText = `${item.id} ${item.tags.join(" ")}`.toLowerCase();
2713
2519
  if (UNSUPPORTED.some((x) => itemSearchText.includes(x))) {
2714
- skipped.push({
2715
- id: item.id,
2716
- reason: "Unsupported stack policy",
2717
- skipReasons: [
2718
- {
2719
- code: "unsupported-policy",
2720
- message: "Unsupported stack policy",
2721
- penalty: 100
2722
- }
2723
- ]
2724
- });
2520
+ skip(item.id, "unsupported-policy", "Unsupported stack policy");
2725
2521
  continue;
2726
2522
  }
2727
2523
  if (item.source === "curated") {
2728
2524
  const rs = item.reviewStatus;
2729
2525
  if (!rs || rs !== "approved") {
2730
- skipped.push({
2731
- id: item.id,
2732
- reason: `Curated item not approved (reviewStatus=${rs ?? "unset"})`,
2733
- skipReasons: [
2734
- {
2735
- code: "curated-not-approved",
2736
- message: `Curated item requires reviewStatus:approved (got ${rs ?? "unset"})`,
2737
- penalty: 100,
2738
- signal: `reviewStatus:${rs ?? "unset"}`
2739
- }
2740
- ]
2741
- });
2526
+ skip(
2527
+ item.id,
2528
+ "curated-not-approved",
2529
+ `Curated item requires reviewStatus:approved (got ${rs ?? "unset"})`,
2530
+ `reviewStatus:${rs ?? "unset"}`
2531
+ );
2742
2532
  continue;
2743
2533
  }
2744
2534
  if (item.riskLevel === "blocked") {
2745
- skipped.push({
2746
- id: item.id,
2747
- reason: "Curated item risk level is blocked",
2748
- skipReasons: [
2749
- {
2750
- code: "curated-risk-blocked",
2751
- message: "Curated item riskLevel is blocked",
2752
- penalty: 100,
2753
- signal: "riskLevel:blocked"
2754
- }
2755
- ]
2756
- });
2535
+ skip(
2536
+ item.id,
2537
+ "curated-risk-blocked",
2538
+ "Curated item riskLevel is blocked",
2539
+ "riskLevel:blocked"
2540
+ );
2757
2541
  continue;
2758
2542
  }
2759
2543
  }
2544
+ if (SENSITIVE_ITEM_KEYWORDS.some((x) => itemSearchText.includes(x))) {
2545
+ skip(item.id, "sensitive-policy", "Sensitive content policy block");
2546
+ continue;
2547
+ }
2548
+ const trust = sourceTrust.get(item.source);
2549
+ if (trust === "candidate" || trust === "rejected") {
2550
+ skip(item.id, "source-trust", "Source trust policy block", `trust:${trust}`);
2551
+ continue;
2552
+ }
2553
+ if (item.source && item.source !== "haus" && trust !== "approved") {
2554
+ skip(item.id, "source-approval", "Source not approved", `source:${item.source}`);
2555
+ continue;
2556
+ }
2557
+ if (item.id === "haus.nx21-monorepo-patterns" && !roleSet.has("nx-monorepo")) {
2558
+ skip(
2559
+ item.id,
2560
+ "required-role-missing",
2561
+ "Required role missing: nx-monorepo",
2562
+ "role:nx-monorepo"
2563
+ );
2564
+ continue;
2565
+ }
2566
+ if (item.id === "haus.turbo-monorepo-patterns" && !roleSet.has("turbo-monorepo")) {
2567
+ skip(
2568
+ item.id,
2569
+ "required-role-missing",
2570
+ "Required role missing: turbo-monorepo",
2571
+ "role:turbo-monorepo"
2572
+ );
2573
+ continue;
2574
+ }
2760
2575
  const isDefaultBaseline = item.default === true;
2761
2576
  const reasons = [];
2762
- const skipReasons = [];
2763
- let score = 0;
2764
- const pushReason = (code, message, weight, signal) => {
2765
- score += weight;
2766
- reasons.push({ code, message, weight, signal });
2767
- };
2768
- const pushSkipReason = (code, message, penalty, signal) => {
2769
- score -= penalty;
2770
- skipReasons.push({ code, message, penalty, signal });
2771
- };
2772
- if (isDefaultBaseline) {
2773
- pushReason("default-baseline", "catalog default baseline", 25, "policy:default");
2774
- }
2577
+ const push = (code, message, signal) => reasons.push({ code, message, signal });
2578
+ if (isDefaultBaseline) push("default-baseline", "catalog default baseline", "policy:default");
2775
2579
  const roleMatch = item.repoRoles.find((r) => roleSet.has(r.toLowerCase()));
2776
- if (roleMatch) {
2777
- pushReason("repo-role-match", "repo role match", 40, `role:${roleMatch}`);
2778
- }
2580
+ if (roleMatch) push("repo-role-match", "repo role match", roleSignal(roleMatch));
2779
2581
  const tagMatch = item.tags.find((t) => stackSet.has(t.toLowerCase()));
2780
- if (tagMatch) {
2781
- pushReason("stack-match", "stack/dependency match", 30, `tag:${tagMatch}`);
2782
- }
2582
+ if (tagMatch) push("stack-match", "stack/dependency match", stackSignal(tagMatch));
2783
2583
  const goalMatch = item.tags.find(
2784
2584
  (t) => goals.includes(t) || goals.includes(t.replace(/-/g, " "))
2785
2585
  );
2786
- if (goalMatch) {
2787
- pushReason("goal-match", "guided goal match", 15, `goal:${goalMatch}`);
2788
- }
2586
+ if (goalMatch) push("goal-match", "guided goal match", `goal:${goalMatch}`);
2789
2587
  if (item.tags.includes(context.packageManager) || item.tags.includes(`${context.packageManager}4`) || item.tags.includes(`${context.packageManager}89`)) {
2790
- pushReason(
2588
+ push(
2791
2589
  "package-manager-match",
2792
2590
  "package manager match",
2793
- 10,
2794
2591
  `packageManager:${context.packageManager}`
2795
2592
  );
2796
2593
  }
2797
2594
  const configSignal = item.tags.find(
2798
2595
  (t) => context.warnings.join(" ").toLowerCase().includes(t.toLowerCase())
2799
2596
  );
2800
- if (configSignal) {
2801
- pushReason("config-signal-match", "config signal match", 20, `warning:${configSignal}`);
2802
- }
2597
+ if (configSignal) push("config-signal-match", "config signal match", `warning:${configSignal}`);
2803
2598
  const changedMatch = changedFiles.find((f) => f.includes(item.id.split(".").pop() ?? ""));
2804
- if (changedMatch) {
2805
- pushReason("changed-file-match", "changed file match", 10, `changedFile:${changedMatch}`);
2806
- }
2807
- if (item.id === "haus.nx21-monorepo-patterns" && !roleSet.has("nx-monorepo")) {
2808
- skipped.push({
2809
- id: item.id,
2810
- reason: "Required role missing: nx-monorepo",
2811
- skipReasons: [
2812
- {
2813
- code: "required-role-missing",
2814
- message: "Required role missing: nx-monorepo",
2815
- penalty: 100,
2816
- signal: "role:nx-monorepo"
2817
- }
2818
- ]
2819
- });
2820
- continue;
2821
- }
2822
- if (item.id === "haus.turbo-monorepo-patterns" && !roleSet.has("turbo-monorepo")) {
2823
- skipped.push({
2824
- id: item.id,
2825
- reason: "Required role missing: turbo-monorepo",
2826
- skipReasons: [
2827
- {
2828
- code: "required-role-missing",
2829
- message: "Required role missing: turbo-monorepo",
2830
- penalty: 100,
2831
- signal: "role:turbo-monorepo"
2832
- }
2833
- ]
2834
- });
2835
- continue;
2836
- }
2599
+ if (changedMatch)
2600
+ push("changed-file-match", "changed file match", `changedFile:${changedMatch}`);
2837
2601
  const requiresAny = item.requiresAny ?? [];
2838
2602
  if (requiresAny.length > 0) {
2839
- const satisfied = matchRequiresAny(requiresAny, {
2840
- stackSet,
2841
- depSet,
2842
- roleSet
2843
- });
2603
+ const satisfied = matchRequiresAny(requiresAny, { stackSet, depSet, roleSet });
2844
2604
  if (!satisfied.matched) {
2845
2605
  const description = describeRequiresAny(requiresAny);
2846
- skipped.push({
2847
- id: item.id,
2848
- reason: `requiresAny unsatisfied: needs ${description}`,
2849
- skipReasons: [
2850
- {
2851
- code: "requires-any-unsatisfied",
2852
- message: `requiresAny unsatisfied: needs ${description}`,
2853
- penalty: 100,
2854
- signal: description
2855
- }
2856
- ]
2857
- });
2606
+ skip(
2607
+ item.id,
2608
+ "requires-any-unsatisfied",
2609
+ `requiresAny unsatisfied: needs ${description}`,
2610
+ description
2611
+ );
2858
2612
  continue;
2859
2613
  }
2860
2614
  if (!reasons.some((r) => r.code === "stack-match")) {
2861
- pushReason("requires-any-match", "requires-any signal match", 25, satisfied.signal);
2862
- }
2863
- }
2864
- if (item.ecosystem && dominantBackendEcosystem && isBackendEcosystem(item.ecosystem)) {
2865
- const compat = ECOSYSTEM_COMPATIBLE_BACKENDS[dominantBackendEcosystem] ?? /* @__PURE__ */ new Set([dominantBackendEcosystem]);
2866
- if (!compat.has(item.ecosystem)) {
2867
- pushSkipReason(
2868
- "ecosystem-conflict",
2869
- `ecosystem conflict: rule ecosystem=${item.ecosystem} but repo dominant backend=${dominantBackendEcosystem}`,
2870
- 40,
2871
- `ecosystem:${item.ecosystem}->${dominantBackendEcosystem}`
2872
- );
2615
+ push("requires-any-match", "requires-any signal match", satisfied.signal);
2873
2616
  }
2874
2617
  }
2875
- if (SENSITIVE_ITEM_KEYWORDS.some((x) => itemSearchText.includes(x))) {
2876
- pushSkipReason("sensitive-policy", "Sensitive content policy block", 100);
2877
- }
2878
- const trust = sourceTrust.get(item.source);
2879
- if (trust === "candidate" || trust === "rejected") {
2880
- pushSkipReason("source-trust", "Source trust policy block", 100);
2881
- }
2882
- if (item.source && item.source !== "haus" && trust !== "approved") {
2883
- pushSkipReason("source-approval", "Source not approved", 100);
2884
- }
2885
- if (securityRiskCount > 0 && !isDefaultBaseline && (item.tags.includes("security") || item.id.includes("security"))) {
2886
- pushSkipReason(
2887
- "security-risk-penalty",
2888
- "Security-tagged item penalized by active risk signals",
2889
- 20
2890
- );
2891
- }
2892
- const positiveReasonCodes = new Set(
2893
- reasons.map((r) => r.code).filter((c) => c !== "default-baseline")
2894
- );
2895
- const hasRoleSignal = positiveReasonCodes.has("repo-role-match");
2896
- const hasDepOrStackSignal = positiveReasonCodes.has("stack-match") || positiveReasonCodes.has("requires-any-match");
2897
- if (hasRoleSignal && !hasDepOrStackSignal && !isDefaultBaseline && requiresAny.length === 0) {
2898
- pushSkipReason(
2899
- "role-only-bleed-guard",
2900
- "role match without dep/stack signal (role-only bleed)",
2901
- 25,
2902
- roleMatch ? `role:${roleMatch}` : void 0
2903
- );
2904
- }
2905
- const minScore = isDefaultBaseline ? 1 : 40;
2906
- if (score >= minScore) {
2907
- const confidenceLevel = computeConfidenceLevel({
2908
- isDefaultBaseline,
2909
- reasons,
2910
- hasEcosystemConflict: skipReasons.some((s) => s.code === "ecosystem-conflict"),
2911
- score
2912
- });
2913
- const confidence = confidenceLevelToNumber(confidenceLevel, score);
2618
+ const hasEvidence = reasons.some((r) => r.code !== "default-baseline");
2619
+ if (isDefaultBaseline || hasEvidence) {
2914
2620
  recommended.push({
2915
2621
  id: item.id,
2916
2622
  type: item.type,
2917
- reason: reasons.length ? reasons.map((x) => x.message).join(", ") : `score=${score}`,
2623
+ reason: reasons.length ? reasons.map((x) => x.message).join(", ") : "eligible",
2918
2624
  reasons,
2919
- confidence,
2920
- confidenceLevel,
2921
- selectionMode: isDefaultBaseline && reasons.every((r) => r.code === "default-baseline") ? "baseline" : "matched",
2625
+ selectionMode: isDefaultBaseline && !hasEvidence ? "baseline" : "matched",
2922
2626
  install: true,
2923
- score,
2924
- scoreBreakdown: {
2925
- bonuses: reasons,
2926
- penalties: skipReasons,
2927
- finalScore: score
2928
- },
2929
2627
  tags: item.tags,
2930
2628
  ecosystem: item.ecosystem,
2931
2629
  tokenEstimate: item.tokenEstimate
2932
2630
  });
2933
2631
  } else {
2934
- if (skipReasons.length === 0) {
2935
- skipReasons.push({
2936
- code: "no-role-stack-match",
2937
- message: "No role/stack match",
2938
- penalty: 0
2939
- });
2940
- }
2941
- const primary = skipReasons[0];
2942
- skipped.push({ id: item.id, reason: primary.message, skipReasons });
2632
+ skip(item.id, "no-role-stack-match", "No role/stack match");
2943
2633
  }
2944
2634
  }
2945
2635
  recommended.sort((a, b) => a.id.localeCompare(b.id));
@@ -3086,8 +2776,8 @@ async function runSetupProject(options) {
3086
2776
  // src/commands/init.ts
3087
2777
  async function runInit(options) {
3088
2778
  const root = process.cwd();
3089
- const hausDir = path19.join(root, ".haus-workflow");
3090
- const alreadyInit = await fs13.pathExists(hausDir);
2779
+ const hausDir = path18.join(root, ".haus-workflow");
2780
+ const alreadyInit = await fs12.pathExists(hausDir);
3091
2781
  if (alreadyInit) {
3092
2782
  log("Haus AI already initialized in this project.");
3093
2783
  log("Run `haus setup-project` to reconfigure.");
@@ -3099,8 +2789,8 @@ async function runInit(options) {
3099
2789
 
3100
2790
  // src/install/apply.ts
3101
2791
  import crypto2 from "crypto";
3102
- import path22 from "path";
3103
- import fs15 from "fs-extra";
2792
+ import path21 from "path";
2793
+ import fs14 from "fs-extra";
3104
2794
 
3105
2795
  // src/install/allow-rules.ts
3106
2796
  var ALLOWED_SUBCOMMANDS = [
@@ -3147,13 +2837,13 @@ ${content2}`;
3147
2837
 
3148
2838
  // src/install/manifest.ts
3149
2839
  import os4 from "os";
3150
- import path20 from "path";
2840
+ import path19 from "path";
3151
2841
  var MANIFEST_SCHEMA = "haus-install-manifest/1";
3152
2842
  function globalClaudeDir() {
3153
- return path20.join(os4.homedir(), ".claude");
2843
+ return path19.join(os4.homedir(), ".claude");
3154
2844
  }
3155
2845
  function hausManifestPath() {
3156
- return path20.join(globalClaudeDir(), "haus", "install-manifest.json");
2846
+ return path19.join(globalClaudeDir(), "haus", "install-manifest.json");
3157
2847
  }
3158
2848
  async function readManifest() {
3159
2849
  return readJson(hausManifestPath());
@@ -3172,10 +2862,10 @@ function buildManifest(source, files, hooks) {
3172
2862
  }
3173
2863
 
3174
2864
  // src/install/settings-merge.ts
3175
- import path21 from "path";
3176
- import fs14 from "fs-extra";
2865
+ import path20 from "path";
2866
+ import fs13 from "fs-extra";
3177
2867
  function settingsJsonPath() {
3178
- return path21.join(globalClaudeDir(), "settings.json");
2868
+ return path20.join(globalClaudeDir(), "settings.json");
3179
2869
  }
3180
2870
  async function readSettings() {
3181
2871
  const parsed = await readJson(settingsJsonPath());
@@ -3316,7 +3006,7 @@ function stripHausHooks(settings) {
3316
3006
  async function loadHooksFragment(fragmentPath) {
3317
3007
  let raw;
3318
3008
  try {
3319
- raw = await fs14.readJson(fragmentPath);
3009
+ raw = await fs13.readJson(fragmentPath);
3320
3010
  } catch {
3321
3011
  return [];
3322
3012
  }
@@ -3325,46 +3015,46 @@ async function loadHooksFragment(fragmentPath) {
3325
3015
  }
3326
3016
 
3327
3017
  // src/install/apply.ts
3328
- var SCHEMA_VERSION3 = "1";
3018
+ var SCHEMA_VERSION2 = "1";
3329
3019
  function hashContent(content2) {
3330
3020
  return `sha256-${crypto2.createHash("sha256").update(content2).digest("hex")}`;
3331
3021
  }
3332
3022
  function sourceVersion() {
3333
3023
  try {
3334
- const pkgPath = path22.join(packageRoot(), "package.json");
3335
- const pkg = JSON.parse(fs15.readFileSync(pkgPath, "utf8"));
3024
+ const pkgPath = path21.join(packageRoot(), "package.json");
3025
+ const pkg = JSON.parse(fs14.readFileSync(pkgPath, "utf8"));
3336
3026
  return `${pkg.name ?? "haus"}@${pkg.version ?? "0.0.0"}`;
3337
3027
  } catch {
3338
3028
  return "haus@0.0.0";
3339
3029
  }
3340
3030
  }
3341
3031
  function globalSrcDir() {
3342
- return path22.join(packageRoot(), "library", "global");
3032
+ return path21.join(packageRoot(), "library", "global");
3343
3033
  }
3344
3034
  function collectSourceFiles(srcDir, claudeDir) {
3345
3035
  const entries = [];
3346
- const skillsDir = path22.join(srcDir, "skills");
3347
- if (fs15.pathExistsSync(skillsDir)) {
3348
- for (const skillName of fs15.readdirSync(skillsDir)) {
3349
- const skillFile = path22.join(skillsDir, skillName, "SKILL.md");
3350
- if (fs15.pathExistsSync(skillFile)) {
3036
+ const skillsDir = path21.join(srcDir, "skills");
3037
+ if (fs14.pathExistsSync(skillsDir)) {
3038
+ for (const skillName of fs14.readdirSync(skillsDir)) {
3039
+ const skillFile = path21.join(skillsDir, skillName, "SKILL.md");
3040
+ if (fs14.pathExistsSync(skillFile)) {
3351
3041
  entries.push({
3352
3042
  stableId: `skill.${skillName}`,
3353
- srcRelPath: path22.join("library", "global", "skills", skillName, "SKILL.md"),
3354
- destPath: path22.join(claudeDir, "skills", skillName, "SKILL.md")
3043
+ srcRelPath: path21.join("library", "global", "skills", skillName, "SKILL.md"),
3044
+ destPath: path21.join(claudeDir, "skills", skillName, "SKILL.md")
3355
3045
  });
3356
3046
  }
3357
3047
  }
3358
3048
  }
3359
- const commandsDir = path22.join(srcDir, "commands");
3360
- if (fs15.pathExistsSync(commandsDir)) {
3361
- for (const fileName of fs15.readdirSync(commandsDir)) {
3049
+ const commandsDir = path21.join(srcDir, "commands");
3050
+ if (fs14.pathExistsSync(commandsDir)) {
3051
+ for (const fileName of fs14.readdirSync(commandsDir)) {
3362
3052
  if (!fileName.endsWith(".md")) continue;
3363
3053
  const commandName = fileName.slice(0, -".md".length);
3364
3054
  entries.push({
3365
3055
  stableId: `command.${commandName}`,
3366
- srcRelPath: path22.join("library", "global", "commands", fileName),
3367
- destPath: path22.join(claudeDir, "commands", fileName)
3056
+ srcRelPath: path21.join("library", "global", "commands", fileName),
3057
+ destPath: path21.join(claudeDir, "commands", fileName)
3368
3058
  });
3369
3059
  }
3370
3060
  }
@@ -3388,7 +3078,7 @@ async function applyInstall(options = {}) {
3388
3078
  };
3389
3079
  const manifestFiles = [];
3390
3080
  for (const entry of sourceFiles) {
3391
- const srcPath = path22.join(packageRoot(), entry.srcRelPath);
3081
+ const srcPath = path21.join(packageRoot(), entry.srcRelPath);
3392
3082
  const rawContent = await readText(srcPath);
3393
3083
  if (rawContent === void 0) {
3394
3084
  warn(`Source file not found: ${entry.srcRelPath}`);
@@ -3396,7 +3086,7 @@ async function applyInstall(options = {}) {
3396
3086
  }
3397
3087
  const stamped = stampMarkdown(rawContent, {
3398
3088
  stableId: entry.stableId,
3399
- schemaVersion: SCHEMA_VERSION3,
3089
+ schemaVersion: SCHEMA_VERSION2,
3400
3090
  source
3401
3091
  });
3402
3092
  const newHash = hashContent(stamped);
@@ -3408,7 +3098,7 @@ async function applyInstall(options = {}) {
3408
3098
  }
3409
3099
  continue;
3410
3100
  }
3411
- const destExists = fs15.pathExistsSync(entry.destPath);
3101
+ const destExists = fs14.pathExistsSync(entry.destPath);
3412
3102
  if (destExists) {
3413
3103
  const currentContent = await readText(entry.destPath);
3414
3104
  if (currentContent !== void 0) {
@@ -3441,10 +3131,10 @@ async function applyInstall(options = {}) {
3441
3131
  destPath: entry.destPath,
3442
3132
  srcRelPath: entry.srcRelPath,
3443
3133
  hash: newHash,
3444
- schemaVersion: SCHEMA_VERSION3
3134
+ schemaVersion: SCHEMA_VERSION2
3445
3135
  });
3446
3136
  }
3447
- const fragmentPath = path22.join(srcDir, "settings-fragments", "hooks.json");
3137
+ const fragmentPath = path21.join(srcDir, "settings-fragments", "hooks.json");
3448
3138
  const fragments = await loadHooksFragment(fragmentPath);
3449
3139
  const settings = await readSettings();
3450
3140
  const { settings: hookSettings, addedIds } = mergeHooks(settings, fragments);
@@ -3455,13 +3145,13 @@ async function applyInstall(options = {}) {
3455
3145
  const currentDestPaths = new Set(sourceFiles.map((f) => f.destPath));
3456
3146
  for (const entry of existingManifest.files) {
3457
3147
  if (currentDestPaths.has(entry.destPath)) continue;
3458
- if (!fs15.pathExistsSync(entry.destPath)) continue;
3148
+ if (!fs14.pathExistsSync(entry.destPath)) continue;
3459
3149
  const content2 = await readText(entry.destPath);
3460
3150
  if (!content2) continue;
3461
3151
  const hasHeader = parseMarkdownHeader(content2) !== void 0;
3462
3152
  const currentHash = hashContent(content2);
3463
3153
  if (hasHeader && currentHash === entry.hash) {
3464
- if (!dryRun) await fs15.remove(entry.destPath);
3154
+ if (!dryRun) await fs14.remove(entry.destPath);
3465
3155
  result.deleted.push(entry.destPath);
3466
3156
  } else {
3467
3157
  warn(`Orphaned file ${entry.destPath} was user-modified \u2014 leaving in place`);
@@ -3584,20 +3274,20 @@ async function runScan(options) {
3584
3274
  }
3585
3275
 
3586
3276
  // src/commands/undo.ts
3587
- import path23 from "path";
3588
- import fs16 from "fs-extra";
3277
+ import path22 from "path";
3278
+ import fs15 from "fs-extra";
3589
3279
  var CLAUDE_DIR = ".claude";
3590
3280
  async function runUndo(options) {
3591
3281
  const root = process.cwd();
3592
- const targets = [path23.join(root, CLAUDE_DIR), path23.join(root, HAUS_DIR)];
3593
- const existing = targets.filter((p) => fs16.existsSync(p));
3282
+ const targets = [path22.join(root, CLAUDE_DIR), path22.join(root, HAUS_DIR)];
3283
+ const existing = targets.filter((p) => fs15.existsSync(p));
3594
3284
  if (existing.length === 0) {
3595
3285
  log("Nothing to remove: no .claude/ or .haus-workflow/ in this directory.");
3596
3286
  return;
3597
3287
  }
3598
3288
  if (!options.yes) {
3599
3289
  const ok = await confirm(
3600
- `Remove ${existing.map((p) => path23.relative(root, p)).join(" and ")}? This cannot be undone.`
3290
+ `Remove ${existing.map((p) => path22.relative(root, p)).join(" and ")}? This cannot be undone.`
3601
3291
  );
3602
3292
  if (!ok) {
3603
3293
  log("Cancelled.");
@@ -3605,15 +3295,15 @@ async function runUndo(options) {
3605
3295
  }
3606
3296
  }
3607
3297
  for (const p of existing) {
3608
- await fs16.remove(p);
3609
- log(`Removed ${path23.relative(root, p)}`);
3298
+ await fs15.remove(p);
3299
+ log(`Removed ${path22.relative(root, p)}`);
3610
3300
  }
3611
3301
  }
3612
3302
 
3613
3303
  // src/install/uninstall.ts
3614
3304
  import crypto3 from "crypto";
3615
- import path24 from "path";
3616
- import fs17 from "fs-extra";
3305
+ import path23 from "path";
3306
+ import fs16 from "fs-extra";
3617
3307
  async function runUninstall(options = {}) {
3618
3308
  const { force = false } = options;
3619
3309
  const manifest = await readManifest();
@@ -3623,7 +3313,7 @@ async function runUninstall(options = {}) {
3623
3313
  return result;
3624
3314
  }
3625
3315
  for (const entry of manifest.files) {
3626
- const exists = fs17.pathExistsSync(entry.destPath);
3316
+ const exists = fs16.pathExistsSync(entry.destPath);
3627
3317
  if (!exists) continue;
3628
3318
  const content2 = await readText(entry.destPath);
3629
3319
  if (content2 === void 0) continue;
@@ -3641,22 +3331,22 @@ async function runUninstall(options = {}) {
3641
3331
  result.skipped.push(entry.destPath);
3642
3332
  continue;
3643
3333
  }
3644
- await fs17.remove(entry.destPath);
3645
- await pruneEmptyDir(path24.dirname(entry.destPath));
3334
+ await fs16.remove(entry.destPath);
3335
+ await pruneEmptyDir(path23.dirname(entry.destPath));
3646
3336
  result.deleted.push(entry.destPath);
3647
3337
  }
3648
3338
  const settings = await readSettings();
3649
3339
  const stripped = stripHausHooks(stripHausAllow(stripHausDeny(settings)));
3650
3340
  await writeSettings(stripped);
3651
3341
  result.hooksStripped = true;
3652
- const hausDir = path24.join(globalClaudeDir(), "haus");
3342
+ const hausDir = path23.join(globalClaudeDir(), "haus");
3653
3343
  const manifestPath = hausManifestPath();
3654
- if (fs17.pathExistsSync(manifestPath)) {
3655
- await fs17.remove(manifestPath);
3344
+ if (fs16.pathExistsSync(manifestPath)) {
3345
+ await fs16.remove(manifestPath);
3656
3346
  }
3657
- if (fs17.pathExistsSync(hausDir)) {
3658
- const remaining = await fs17.readdir(hausDir);
3659
- if (remaining.length === 0) await fs17.remove(hausDir);
3347
+ if (fs16.pathExistsSync(hausDir)) {
3348
+ const remaining = await fs16.readdir(hausDir);
3349
+ if (remaining.length === 0) await fs16.remove(hausDir);
3660
3350
  }
3661
3351
  return result;
3662
3352
  }
@@ -3675,8 +3365,8 @@ function printUninstallResult(result) {
3675
3365
  }
3676
3366
  async function pruneEmptyDir(dir) {
3677
3367
  try {
3678
- const entries = await fs17.readdir(dir);
3679
- if (entries.length === 0) await fs17.remove(dir);
3368
+ const entries = await fs16.readdir(dir);
3369
+ if (entries.length === 0) await fs16.remove(dir);
3680
3370
  } catch {
3681
3371
  }
3682
3372
  }
@@ -3694,7 +3384,7 @@ async function runUninstallCommand(options) {
3694
3384
  }
3695
3385
 
3696
3386
  // src/commands/update.ts
3697
- import path26 from "path";
3387
+ import path25 from "path";
3698
3388
 
3699
3389
  // src/update/diff-generated-files.ts
3700
3390
  function diffGeneratedFiles() {
@@ -3721,7 +3411,7 @@ function summarizeLockDiff(before, after) {
3721
3411
 
3722
3412
  // src/update/lockfile.ts
3723
3413
  import { mkdir, readFile as readFile3, copyFile } from "fs/promises";
3724
- import path25 from "path";
3414
+ import path24 from "path";
3725
3415
  async function checkLock(root) {
3726
3416
  const lock = await readJson(hausPath(root, "haus.lock.json")) ?? [];
3727
3417
  const hasValidVersions = lock.every(
@@ -3742,7 +3432,7 @@ async function applyLock(root) {
3742
3432
  try {
3743
3433
  const backupDir = hausPath(root, "backups");
3744
3434
  await mkdir(backupDir, { recursive: true });
3745
- await copyFile(lockPath, path25.join(backupDir, `haus.lock.${Date.now()}.json`));
3435
+ await copyFile(lockPath, path24.join(backupDir, `haus.lock.${Date.now()}.json`));
3746
3436
  } catch {
3747
3437
  }
3748
3438
  const enriched = await Promise.all(
@@ -3764,7 +3454,7 @@ function diffLock(before, after) {
3764
3454
  }
3765
3455
  async function hasLocalOverrides(root) {
3766
3456
  try {
3767
- await readFile3(path25.join(root, ".claude", "settings.json"), "utf8");
3457
+ await readFile3(path24.join(root, ".claude", "settings.json"), "utf8");
3768
3458
  return true;
3769
3459
  } catch {
3770
3460
  return false;
@@ -3776,7 +3466,7 @@ var NPM_PACKAGE_NAME2 = "@haus-tech/haus-workflow";
3776
3466
  async function runUpdate(options) {
3777
3467
  const root = process.cwd();
3778
3468
  if (options.check) {
3779
- const pkgJson2 = await readJson(path26.join(packageRoot(), "package.json"));
3469
+ const pkgJson2 = await readJson(path25.join(packageRoot(), "package.json"));
3780
3470
  const currentVersion2 = pkgJson2?.version ?? "0.0.0";
3781
3471
  const [status, npmVersion, latestCatalogTag] = await Promise.all([
3782
3472
  checkLock(root),
@@ -3803,7 +3493,7 @@ async function runUpdate(options) {
3803
3493
  if (!status.ok) process.exitCode = 1;
3804
3494
  return;
3805
3495
  }
3806
- const pkgJson = await readJson(path26.join(packageRoot(), "package.json"));
3496
+ const pkgJson = await readJson(path25.join(packageRoot(), "package.json"));
3807
3497
  const currentVersion = pkgJson?.version ?? "0.0.0";
3808
3498
  const npmStatus = await fetchNpmVersionStatus(currentVersion);
3809
3499
  if (npmStatus.updateAvailable && npmStatus.latest !== null) {
@@ -3833,8 +3523,8 @@ async function runUpdate(options) {
3833
3523
  }
3834
3524
 
3835
3525
  // src/commands/validate-catalog.ts
3836
- import fs18 from "fs";
3837
- import path27 from "path";
3526
+ import fs17 from "fs";
3527
+ import path26 from "path";
3838
3528
 
3839
3529
  // library/catalog/validation-rules.json
3840
3530
  var validation_rules_default = {
@@ -4083,23 +3773,23 @@ function auditShippedFiles(manifestDir, items) {
4083
3773
  const failures = [];
4084
3774
  for (const item of items) {
4085
3775
  if (!item.path) continue;
4086
- const absPath = path27.join(manifestDir, item.path);
3776
+ const absPath = path26.join(manifestDir, item.path);
4087
3777
  if (item.type === "skill") {
4088
- const skillMd = path27.join(absPath, "SKILL.md");
4089
- if (!fs18.existsSync(skillMd)) {
4090
- failures.push(`${item.id}: missing ${path27.relative(manifestDir, skillMd)}`);
3778
+ const skillMd = path26.join(absPath, "SKILL.md");
3779
+ if (!fs17.existsSync(skillMd)) {
3780
+ failures.push(`${item.id}: missing ${path26.relative(manifestDir, skillMd)}`);
4091
3781
  continue;
4092
3782
  }
4093
- const text = fs18.readFileSync(skillMd, "utf8");
3783
+ const text = fs17.readFileSync(skillMd, "utf8");
4094
3784
  for (const section of REQUIRED_SKILL_SECTIONS) {
4095
3785
  if (!text.includes(section)) failures.push(`${item.id}: SKILL.md missing ${section}`);
4096
3786
  }
4097
3787
  } else if (item.type === "agent") {
4098
- if (!fs18.existsSync(absPath)) {
3788
+ if (!fs17.existsSync(absPath)) {
4099
3789
  failures.push(`${item.id}: missing agent file ${item.path}`);
4100
3790
  continue;
4101
3791
  }
4102
- const text = fs18.readFileSync(absPath, "utf8");
3792
+ const text = fs17.readFileSync(absPath, "utf8");
4103
3793
  if (!text.startsWith("---")) failures.push(`${item.id}: agent file missing YAML frontmatter`);
4104
3794
  for (const section of REQUIRED_AGENT_SECTIONS) {
4105
3795
  if (!text.includes(section)) failures.push(`${item.id}: agent file missing ${section}`);
@@ -4110,7 +3800,7 @@ function auditShippedFiles(manifestDir, items) {
4110
3800
  failures.push(`${item.id}: agent file contains disallowed phrase "${phrase}"`);
4111
3801
  }
4112
3802
  } else if (item.type === "template") {
4113
- if (!fs18.existsSync(absPath)) {
3803
+ if (!fs17.existsSync(absPath)) {
4114
3804
  failures.push(`${item.id}: missing template file ${item.path}`);
4115
3805
  }
4116
3806
  }
@@ -4121,11 +3811,11 @@ function auditMarkdownContent(manifestDir) {
4121
3811
  const failures = [];
4122
3812
  const dirs = ["skills", "agents"];
4123
3813
  for (const dir of dirs) {
4124
- const abs = path27.join(manifestDir, dir);
4125
- if (!fs18.existsSync(abs)) continue;
3814
+ const abs = path26.join(manifestDir, dir);
3815
+ if (!fs17.existsSync(abs)) continue;
4126
3816
  walkMd(abs, (file) => {
4127
- const text = fs18.readFileSync(file, "utf8");
4128
- const rel = path27.relative(manifestDir, file);
3817
+ const text = fs17.readFileSync(file, "utf8");
3818
+ const rel = path26.relative(manifestDir, file);
4129
3819
  const lines = text.split(/\r?\n/);
4130
3820
  for (let i = 0; i < lines.length; i++) {
4131
3821
  const line2 = lines[i] ?? "";
@@ -4144,8 +3834,8 @@ function auditMarkdownContent(manifestDir) {
4144
3834
  return failures;
4145
3835
  }
4146
3836
  function walkMd(dir, fn) {
4147
- for (const entry of fs18.readdirSync(dir, { withFileTypes: true })) {
4148
- const full = path27.join(dir, entry.name);
3837
+ for (const entry of fs17.readdirSync(dir, { withFileTypes: true })) {
3838
+ const full = path26.join(dir, entry.name);
4149
3839
  if (entry.isDirectory()) walkMd(full, fn);
4150
3840
  else if (entry.name.endsWith(".md")) fn(full);
4151
3841
  }
@@ -4156,8 +3846,8 @@ async function runValidateCatalog(manifestPath) {
4156
3846
  process.exitCode = 1;
4157
3847
  return;
4158
3848
  }
4159
- const abs = path27.resolve(process.cwd(), manifestPath);
4160
- const manifestDir = path27.dirname(abs);
3849
+ const abs = path26.resolve(process.cwd(), manifestPath);
3850
+ const manifestDir = path26.dirname(abs);
4161
3851
  const data = await readJson(abs);
4162
3852
  if (!data?.items) {
4163
3853
  error(`Could not read catalog manifest at ${abs}`);
@@ -4186,7 +3876,7 @@ async function runValidateCatalog(manifestPath) {
4186
3876
  }
4187
3877
 
4188
3878
  // src/commands/workspace.ts
4189
- import path28 from "path";
3879
+ import path27 from "path";
4190
3880
  import YAML from "yaml";
4191
3881
  async function runWorkspace(action) {
4192
3882
  if (action === "init") {
@@ -4219,7 +3909,7 @@ relationships: []
4219
3909
  const summaries = [];
4220
3910
  const ownership = {};
4221
3911
  for (const repo of repos) {
4222
- const repoRoot = path28.resolve(process.cwd(), repo.path);
3912
+ const repoRoot = path27.resolve(process.cwd(), repo.path);
4223
3913
  const result = await scanProject(repoRoot, "fast");
4224
3914
  summaries.push({
4225
3915
  name: repo.name,
@@ -4255,7 +3945,7 @@ ${summaries.map(
4255
3945
  // src/cli.ts
4256
3946
  function cliVersion() {
4257
3947
  try {
4258
- const pkgPath = path29.join(packageRoot(), "package.json");
3948
+ const pkgPath = path28.join(packageRoot(), "package.json");
4259
3949
  const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
4260
3950
  return pkg.version ?? "0.0.0";
4261
3951
  } catch {
@@ -4265,7 +3955,7 @@ function cliVersion() {
4265
3955
  var program = new Command();
4266
3956
  function validateRuntimeNodeVersion() {
4267
3957
  try {
4268
- const pkgPath = path29.join(packageRoot(), "package.json");
3958
+ const pkgPath = path28.join(packageRoot(), "package.json");
4269
3959
  const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
4270
3960
  const requiredRange = pkg.engines?.node;
4271
3961
  if (requiredRange && !satisfiesVersion(process.version, requiredRange)) {