@haus-tech/haus-workflow 0.12.0 → 0.13.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 path30 from "path";
5
+ import path28 from "path";
6
6
  import { Command } from "commander";
7
7
 
8
8
  // src/commands/apply.ts
9
- import path12 from "path";
9
+ import path10 from "path";
10
10
  import checkbox from "@inquirer/checkbox";
11
11
 
12
12
  // src/catalog/remote-catalog.ts
@@ -53,6 +53,18 @@ async function fetchRemoteManifest() {
53
53
  return null;
54
54
  }
55
55
  }
56
+ var WORKFLOW_TEMPLATE_REL = "templates/agentic-workflow-standard.md";
57
+ async function readWorkflowTemplate(opts = {}) {
58
+ const dest = path.join(CACHE_DIR, WORKFLOW_TEMPLATE_REL);
59
+ if (await fs.pathExists(dest)) return fs.readFile(dest, "utf8");
60
+ const text = await fetchText(`${REMOTE_BASE}/${WORKFLOW_TEMPLATE_REL}`);
61
+ if (text === null) return null;
62
+ if (!opts.dryRun) {
63
+ await fs.ensureDir(path.dirname(dest));
64
+ await fs.writeFile(dest, text, "utf8");
65
+ }
66
+ return text;
67
+ }
56
68
  function isSafeCatalogPath(itemPath) {
57
69
  if (!itemPath || path.isAbsolute(itemPath) || itemPath.includes("\\")) return false;
58
70
  const normalized = path.normalize(itemPath);
@@ -80,7 +92,8 @@ async function syncRemoteCatalog() {
80
92
  let unchanged = 0;
81
93
  const failed = [];
82
94
  for (const item of items) {
83
- if (item.type !== "skill" && item.type !== "agent" || !item.path) continue;
95
+ if (item.type !== "skill" && item.type !== "agent" && item.type !== "template" || !item.path)
96
+ continue;
84
97
  if (!isSafeCatalogPath(item.path)) {
85
98
  warn(`Skipping ${item.id}: invalid path "${item.path}"`);
86
99
  failed.push(item.id);
@@ -158,8 +171,8 @@ async function getCacheManifestAge() {
158
171
  }
159
172
 
160
173
  // src/claude/write-claude-files.ts
161
- import path11 from "path";
162
- import fs10 from "fs-extra";
174
+ import path9 from "path";
175
+ import fs9 from "fs-extra";
163
176
 
164
177
  // src/update/hash-installed.ts
165
178
  import path3 from "path";
@@ -403,8 +416,8 @@ function buildDenyRules() {
403
416
  for (const command of DANGEROUS_COMMANDS) {
404
417
  rules.push(`Bash(${command}:*)`);
405
418
  }
406
- for (const path31 of SENSITIVE_PATHS) {
407
- const pattern = SENSITIVE_DIRS.has(path31) ? `${path31}/**` : path31;
419
+ for (const path29 of SENSITIVE_PATHS) {
420
+ const pattern = SENSITIVE_DIRS.has(path29) ? `${path29}/**` : path29;
408
421
  for (const tool of FILE_TOOLS) {
409
422
  rules.push(`${tool}(${pattern})`);
410
423
  }
@@ -502,109 +515,13 @@ async function verifyProjectSettingsHooksContract(root) {
502
515
  return { ok: true, message: "settings.json matches canonical hook contract." };
503
516
  }
504
517
 
505
- // src/claude/write-project-facts.ts
518
+ // src/claude/write-root-claude-md.ts
506
519
  import path6 from "path";
507
520
  import fs5 from "fs-extra";
508
- var STABLE_ID = "generated.project-facts";
509
- var SCHEMA_VERSION = "1";
510
- function makeHeader(pkgVersion) {
511
- return `<!-- HAUS-MANAGED id=${STABLE_ID} v=${SCHEMA_VERSION} source=@haus-tech/haus-workflow@${pkgVersion} -->`;
512
- }
513
- function renderProjectFacts(ctx, rec, pkgVersion) {
514
- const header = makeHeader(pkgVersion);
515
- const stackEntries = Object.entries(ctx.detectedStacks ?? {});
516
- const stackLines = stackEntries.length > 0 ? stackEntries.map(([stack, files]) => {
517
- const f = files;
518
- return `- **${stack}**: ${f.slice(0, 3).join(", ")}${f.length > 3 ? ", \u2026" : ""}`;
519
- }).join("\n") : "- none detected";
520
- const roles = ctx.repoRoles?.length > 0 ? ctx.repoRoles.join(", ") : "unknown";
521
- const recLines = rec.recommended.length > 0 ? rec.recommended.map((r) => `- \`${r.id}\` (${r.type}) \u2014 ${r.reason}`).join("\n") : "- none";
522
- const warningLines = [...ctx.warnings ?? [], ...rec.warnings ?? []];
523
- const warnSection = warningLines.length > 0 ? warningLines.map((w) => `- ${w}`).join("\n") : "- none";
524
- const repoName = ctx.repoName ?? path6.basename(ctx.root ?? "unknown");
525
- return `${header}
526
-
527
- # What haus found in this project
528
-
529
- > This is a plain summary of your project that haus wrote automatically, so Claude
530
- > always has the basics to hand. haus rewrites it on every \`haus apply\`, so don't
531
- > edit it by hand \u2014 your changes would be replaced next time.
532
-
533
- **Repo:** ${repoName}
534
- **Package manager:** ${ctx.packageManager ?? "unknown"}
535
- **Roles:** ${roles}
536
-
537
- ## Detected stacks
538
-
539
- ${stackLines}
540
-
541
- ## Recommended context
542
-
543
- ${recLines}
544
-
545
- ## Warnings
546
-
547
- ${warnSection}
548
- `;
549
- }
550
- async function writeProjectFacts(root, pkgVersion, dryRun) {
551
- const ctx = await readJson(hausPath(root, "context-map.json")) ?? {
552
- mode: "fast",
553
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
554
- root,
555
- repoName: path6.basename(root),
556
- packageManager: "unknown",
557
- repoRoles: [],
558
- confidence: 0,
559
- detectedStacks: {},
560
- dependencies: [],
561
- securityRisks: [],
562
- crossRepoHints: [],
563
- warnings: [],
564
- detectionStatus: "unknown",
565
- unsupportedSignals: []
566
- };
567
- const rec = await readJson(hausPath(root, "recommendation.json")) ?? {
568
- mode: "fast",
569
- recommended: [],
570
- skipped: [],
571
- warnings: [],
572
- estimatedContextTokens: 0,
573
- selectedRules: 0,
574
- skippedRules: 0,
575
- estimatedTokenReductionPct: 0
576
- };
577
- const destPath = hausPath(root, "project.md");
578
- const printable = displayPath(root, destPath);
579
- const next = renderProjectFacts(ctx, rec, pkgVersion);
580
- const prev = await fs5.pathExists(destPath) ? await fs5.readFile(destPath, "utf8") : "";
581
- if (dryRun) {
582
- if (!prev) {
583
- log(createUnifiedDiff(printable, "", next));
584
- } else if (hasTextChanged(prev, next)) {
585
- log(createUnifiedDiff(printable, prev, next));
586
- } else {
587
- log(`${printable}: unchanged`);
588
- }
589
- return destPath;
590
- }
591
- if (hasTextChanged(prev, next) && prev.length > 0) {
592
- const diffText = createUnifiedDiff(printable, prev, next);
593
- const summary = summarizeDiff(diffText);
594
- log(`Overwriting ${printable} (diff +${summary.additions} -${summary.deletions})`);
595
- }
596
- await writeText(destPath, next);
597
- return destPath;
598
- }
599
-
600
- // src/claude/write-root-claude-md.ts
601
- import path7 from "path";
602
- import fs6 from "fs-extra";
603
521
  var BLOCK_BEGIN = "<!-- HAUS:BEGIN haus-imports v=1 -->";
604
522
  var BLOCK_END = "<!-- HAUS:END haus-imports -->";
605
523
  var IMPORT_CONTENT = `@.haus-workflow/WORKFLOW.md
606
- @.haus-workflow/workflow-config.md
607
- @.haus-workflow/project.md`;
524
+ @.haus-workflow/workflow-config.md`;
608
525
  function buildImportBlock() {
609
526
  return `${BLOCK_BEGIN}
610
527
  ${IMPORT_CONTENT}
@@ -629,9 +546,9 @@ ${block}
629
546
  `;
630
547
  }
631
548
  async function writeRootClaudeMd(root, dryRun) {
632
- const filePath = path7.join(root, "CLAUDE.md");
549
+ const filePath = path6.join(root, "CLAUDE.md");
633
550
  const block = buildImportBlock();
634
- const prev = await fs6.pathExists(filePath) ? await fs6.readFile(filePath, "utf8") : "";
551
+ const prev = await fs5.pathExists(filePath) ? await fs5.readFile(filePath, "utf8") : "";
635
552
  const next = injectHausBlock(prev, block);
636
553
  const printable = displayPath(root, filePath);
637
554
  if (dryRun) {
@@ -654,22 +571,12 @@ async function writeRootClaudeMd(root, dryRun) {
654
571
  }
655
572
 
656
573
  // src/claude/write-workflow-config.ts
657
- import path9 from "path";
658
- import fs8 from "fs-extra";
659
-
660
- // src/claude/derive-workflow-config.ts
661
574
  import path8 from "path";
662
575
  import fs7 from "fs-extra";
663
- var VALIDATION_LIBS = [
664
- "zod",
665
- "valibot",
666
- "yup",
667
- "joi",
668
- "@hapi/joi",
669
- "class-validator",
670
- "superstruct",
671
- "ajv"
672
- ];
576
+
577
+ // src/claude/derive-workflow-config.ts
578
+ import path7 from "path";
579
+ import fs6 from "fs-extra";
673
580
  function binCmd(pm, bin, args) {
674
581
  const tail = args ? ` ${args}` : "";
675
582
  if (pm === "yarn") return `yarn ${bin}${tail}`;
@@ -678,7 +585,7 @@ function binCmd(pm, bin, args) {
678
585
  }
679
586
  async function deriveWorkflowConfig(root, ctx) {
680
587
  const pm = ctx.packageManager === "unknown" ? "npm" : ctx.packageManager;
681
- const pkg = await readJson(path8.join(root, "package.json"));
588
+ const pkg = await readJson(path7.join(root, "package.json"));
682
589
  const scripts = pkg?.scripts ?? {};
683
590
  const deps = new Set(ctx.dependencies);
684
591
  const stacks = Object.values(ctx.detectedStacks ?? {}).flat();
@@ -688,22 +595,13 @@ async function deriveWorkflowConfig(root, ctx) {
688
595
  return null;
689
596
  };
690
597
  const hasDep = (name) => deps.has(name);
691
- const exists = (rel) => fs7.pathExistsSync(path8.join(root, rel));
692
- const hasTypeScript = hasDep("typescript") || exists("tsconfig.json");
693
- const hasEslint = hasDep("eslint");
694
- const hasPrettier = hasDep("prettier");
598
+ const exists = (rel) => fs6.pathExistsSync(path7.join(root, rel));
695
599
  const hasPlaywright = hasDep("@playwright/test") || stacks.includes("playwright");
696
600
  const hasCypress = hasDep("cypress");
697
601
  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;
698
602
  return {
699
603
  test: script("test") ?? `${pm} test`,
700
604
  testE2E: firstScript("test:e2e", "e2e", "test:integration") ?? (hasPlaywright ? binCmd(pm, "playwright", "test") : null) ?? (hasCypress ? binCmd(pm, "cypress", "run") : null),
701
- typecheck: firstScript("typecheck", "type-check", "tsc") ?? (hasTypeScript ? binCmd(pm, "tsc", "--noEmit") : null),
702
- lint: script("lint") ?? (hasEslint ? binCmd(pm, "eslint", ".") : null),
703
- lintFix: firstScript("lint:fix", "lint-fix") ?? (scripts.lint ? `${pm} run lint -- --fix` : hasEslint ? binCmd(pm, "eslint", ". --fix") : null),
704
- formatCheck: firstScript("format:check", "format-check", "prettier:check") ?? (hasPrettier ? binCmd(pm, "prettier", "--check .") : null),
705
- securityAudit: `${pm} audit`,
706
- validationLibrary: VALIDATION_LIBS.find((lib) => deps.has(lib)) ?? null,
707
605
  preCommitTool,
708
606
  specPath: exists("docs/SPEC.md") ? "docs/SPEC.md" : null,
709
607
  designPath: exists("docs/DESIGN.md") ? "docs/DESIGN.md" : null,
@@ -718,13 +616,12 @@ function fields(v) {
718
616
  { prefix: "- Design: ", value: v.designPath, hint: "path, e.g. docs/DESIGN.md" },
719
617
  { prefix: "- UX flows: ", value: v.uxPath, hint: "path, e.g. docs/UX.md" },
720
618
  { prefix: "- Test (unit + integration): ", value: v.test, hint: "command", code: true },
721
- { prefix: "- Test (E2E): ", value: v.testE2E, hint: "command, e.g. playwright test", code: true },
722
- { prefix: "- Type check: ", value: v.typecheck, hint: "command, e.g. tsc --noEmit", code: true },
723
- { prefix: "- Lint: ", value: v.lint, hint: "command, e.g. eslint .", code: true },
724
- { prefix: "- Lint fix: ", value: v.lintFix, hint: "command, e.g. eslint . --fix", code: true },
725
- { prefix: "- Format check: ", value: v.formatCheck, hint: "command, e.g. prettier --check .", code: true },
726
- { prefix: "- Security audit: ", value: v.securityAudit, hint: "command", code: true },
727
- { prefix: "- Library: ", value: v.validationLibrary, hint: "e.g. zod, yup, joi" },
619
+ {
620
+ prefix: "- Test (E2E): ",
621
+ value: v.testE2E,
622
+ hint: "command, e.g. playwright test",
623
+ code: true
624
+ },
728
625
  { prefix: "- Tool: ", value: v.preCommitTool, hint: "e.g. lefthook, husky" }
729
626
  ];
730
627
  }
@@ -738,7 +635,7 @@ function line(f) {
738
635
  function buildWorkflowConfig(v) {
739
636
  const f = fields(v);
740
637
  const byPrefix = (p) => line(f.find((x) => x.prefix === p));
741
- 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";
638
+ 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";
742
639
  }
743
640
  function refillContent(existing, v) {
744
641
  const f = fields(v);
@@ -756,7 +653,6 @@ var FALLBACK_CONTEXT = {
756
653
  repoName: "",
757
654
  packageManager: "unknown",
758
655
  repoRoles: [],
759
- confidence: 0,
760
656
  detectedStacks: {},
761
657
  dependencies: [],
762
658
  securityRisks: [],
@@ -768,7 +664,7 @@ var FALLBACK_CONTEXT = {
768
664
  async function writeWorkflowConfig(root, dryRun, opts = {}) {
769
665
  const destPath = hausPath(root, "workflow-config.md");
770
666
  const printable = displayPath(root, destPath);
771
- const exists = await fs8.pathExists(destPath);
667
+ const exists = await fs7.pathExists(destPath);
772
668
  if (exists && !opts.refill) {
773
669
  if (dryRun) log(printable + ": exists (project-owned, skipping)");
774
670
  return null;
@@ -776,11 +672,11 @@ async function writeWorkflowConfig(root, dryRun, opts = {}) {
776
672
  const ctx = await readJson(hausPath(root, "context-map.json")) ?? {
777
673
  ...FALLBACK_CONTEXT,
778
674
  root,
779
- repoName: path9.basename(root)
675
+ repoName: path8.basename(root)
780
676
  };
781
677
  const values = await deriveWorkflowConfig(root, ctx);
782
678
  if (exists) {
783
- const current = await fs8.readFile(destPath, "utf8");
679
+ const current = await fs7.readFile(destPath, "utf8");
784
680
  const refilled = refillContent(current, values);
785
681
  if (refilled === current) {
786
682
  if (dryRun) log(printable + ": no blank fields to refill");
@@ -802,8 +698,7 @@ async function writeWorkflowConfig(root, dryRun, opts = {}) {
802
698
  }
803
699
 
804
700
  // src/claude/write-workflow.ts
805
- import path10 from "path";
806
- import fs9 from "fs-extra";
701
+ import fs8 from "fs-extra";
807
702
 
808
703
  // src/claude/managed-template.ts
809
704
  function normaliseLF(content2) {
@@ -817,35 +712,35 @@ function parseHausManagedHeader(line2) {
817
712
  }
818
713
 
819
714
  // src/claude/write-workflow.ts
820
- var STABLE_ID2 = "template.workflow";
821
- var SCHEMA_VERSION2 = "1";
822
- var CATALOG_CACHE_TEMPLATE = path10.join(CACHE_DIR, "templates/agentic-workflow-standard.md");
715
+ var STABLE_ID = "template.workflow";
716
+ var SCHEMA_VERSION = "1";
823
717
  function makeWorkflowHeader(pkgVersion, contentHash) {
824
- return `<!-- HAUS-MANAGED id=${STABLE_ID2} v=${SCHEMA_VERSION2} source=@haus-tech/haus-workflow@${pkgVersion} hash=${contentHash} -->`;
718
+ return `<!-- HAUS-MANAGED id=${STABLE_ID} v=${SCHEMA_VERSION} source=@haus-tech/haus-workflow@${pkgVersion} hash=${contentHash} -->`;
825
719
  }
826
720
  async function writeWorkflow(root, pkgVersion, dryRun) {
827
- if (!await fs9.pathExists(CATALOG_CACHE_TEMPLATE)) {
828
- warn(`Workflow template not found \u2014 run \`haus update\` to fetch from catalog`);
721
+ const templateContent = await readWorkflowTemplate({ dryRun });
722
+ if (templateContent === null) {
723
+ warn(
724
+ `Workflow template could not be fetched from the catalog \u2014 check your network, then re-run \`haus apply --write\` (or \`haus update\`)`
725
+ );
829
726
  return null;
830
727
  }
831
- const templatePath = CATALOG_CACHE_TEMPLATE;
832
- const templateContent = await fs9.readFile(templatePath, "utf8");
833
728
  const contentHash = hashText(normaliseLF(templateContent));
834
729
  const header = makeWorkflowHeader(pkgVersion, contentHash);
835
730
  const next = `${header}
836
731
  ${templateContent}`;
837
732
  const destPath = hausPath(root, "WORKFLOW.md");
838
733
  const printable = displayPath(root, destPath);
839
- if (await fs9.pathExists(destPath)) {
840
- const existing = await fs9.readFile(destPath, "utf8");
734
+ if (await fs8.pathExists(destPath)) {
735
+ const existing = await fs8.readFile(destPath, "utf8");
841
736
  const firstLine = existing.split("\n")[0] ?? "";
842
737
  const parsed = parseHausManagedHeader(firstLine);
843
738
  if (!parsed) {
844
739
  warn(`${printable}: no HAUS-MANAGED header \u2014 file appears user-owned, skipping`);
845
740
  return null;
846
741
  }
847
- if (parsed.id !== STABLE_ID2) {
848
- warn(`${printable}: HAUS-MANAGED id mismatch (expected ${STABLE_ID2}) \u2014 skipping`);
742
+ if (parsed.id !== STABLE_ID) {
743
+ warn(`${printable}: HAUS-MANAGED id mismatch (expected ${STABLE_ID}) \u2014 skipping`);
849
744
  return null;
850
745
  }
851
746
  const existingContent = existing.slice(firstLine.length + 1);
@@ -859,7 +754,7 @@ ${templateContent}`;
859
754
  }
860
755
  }
861
756
  if (dryRun) {
862
- const prev = await fs9.pathExists(destPath) ? await fs9.readFile(destPath, "utf8") : "";
757
+ const prev = await fs8.pathExists(destPath) ? await fs8.readFile(destPath, "utf8") : "";
863
758
  if (!prev) {
864
759
  log(createUnifiedDiff(printable, "", next));
865
760
  } else {
@@ -886,7 +781,7 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
886
781
  estimatedTokenReductionPct: 0
887
782
  };
888
783
  const pkgRoot = packageRoot();
889
- const hausVersion = (await readJson(path11.join(pkgRoot, "package.json")))?.version ?? "0.0.0";
784
+ const hausVersion = (await readJson(path9.join(pkgRoot, "package.json")))?.version ?? "0.0.0";
890
785
  const coreFiles = [
891
786
  claudePath(root, "settings.json"),
892
787
  claudePath(root, "rules", "haus.md"),
@@ -899,10 +794,8 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
899
794
  const workflowConfigPath = await writeWorkflowConfig(root, dryRun, {
900
795
  refill: opts.refillConfig
901
796
  });
902
- const projectFactsPath = await writeProjectFacts(root, hausVersion, dryRun);
903
797
  const p6Files = [
904
798
  rootClaudeMdPath,
905
- projectFactsPath,
906
799
  ...workflowPath ? [workflowPath] : [],
907
800
  ...workflowConfigPath ? [workflowConfigPath] : []
908
801
  ];
@@ -916,7 +809,7 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
916
809
  await writeManagedJson(root, claudePath(root, "settings.json"), hookSettings, dryRun);
917
810
  if (!dryRun) await assertPostApplySettingsMatchCanonical(root, hookSettings);
918
811
  const configPath = hausPath(root, "config.json");
919
- if (!await fs10.pathExists(configPath)) {
812
+ if (!await fs9.pathExists(configPath)) {
920
813
  await writeManagedJson(root, configPath, DEFAULT_HOOKS_CONFIG, dryRun);
921
814
  }
922
815
  await writeManagedText(
@@ -944,12 +837,12 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
944
837
  dryRun
945
838
  );
946
839
  const fixtureManifestPath = process.env["HAUS_FIXTURE_CATALOG"];
947
- const manifestPath = fixtureManifestPath ?? path11.join(pkgRoot, "library", "catalog", "manifest.json");
948
- const manifestDir = path11.dirname(manifestPath);
840
+ const manifestPath = fixtureManifestPath ?? path9.join(pkgRoot, "library", "catalog", "manifest.json");
841
+ const manifestDir = path9.dirname(manifestPath);
949
842
  const manifest = await readJson(manifestPath) ?? { items: [] };
950
843
  const manifestById = new Map((manifest.items ?? []).map((item) => [item.id, item]));
951
844
  const cacheManifest = await readJson(
952
- path11.join(CACHE_DIR, "manifest.json")
845
+ path9.join(CACHE_DIR, "manifest.json")
953
846
  );
954
847
  const cacheManifestById = new Map((cacheManifest?.items ?? []).map((item) => [item.id, item]));
955
848
  const installedPathsByItem = /* @__PURE__ */ new Map();
@@ -971,23 +864,23 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
971
864
  }
972
865
  }
973
866
  const cachedItem = cacheManifestById.get(item.id);
974
- const cachePath = cachedItem?.path ? path11.join(CACHE_DIR, cachedItem.path) : null;
975
- const sourcePath = cachePath && await fs10.pathExists(cachePath) ? cachePath : path11.join(manifestDir, manifestItem.path);
867
+ const cachePath = cachedItem?.path ? path9.join(CACHE_DIR, cachedItem.path) : null;
868
+ const sourcePath = cachePath && await fs9.pathExists(cachePath) ? cachePath : path9.join(manifestDir, manifestItem.path);
976
869
  const target = item.type === "agent" ? "agents" : item.type === "template" ? "templates" : "skills";
977
- const destination = claudePath(root, target, path11.basename(sourcePath));
978
- if (await fs10.pathExists(sourcePath)) {
870
+ const destination = claudePath(root, target, path9.basename(sourcePath));
871
+ if (await fs9.pathExists(sourcePath)) {
979
872
  if (dryRun) {
980
- const exists = await fs10.pathExists(destination);
873
+ const exists = await fs9.pathExists(destination);
981
874
  log(
982
875
  `${displayPath(root, destination)}: ${exists ? "would overwrite" : "would create"} (${item.id})`
983
876
  );
984
877
  } else {
985
- await fs10.ensureDir(path11.dirname(destination));
986
- await fs10.copy(sourcePath, destination, { overwrite: true, errorOnExist: false });
878
+ await fs9.ensureDir(path9.dirname(destination));
879
+ await fs9.copy(sourcePath, destination, { overwrite: true, errorOnExist: false });
987
880
  }
988
881
  files.push(destination);
989
882
  const current = installedPathsByItem.get(item.id) ?? [];
990
- installedPathsByItem.set(item.id, [...current, path11.relative(root, destination)]);
883
+ installedPathsByItem.set(item.id, [...current, path9.relative(root, destination)]);
991
884
  installedIds.add(item.id);
992
885
  } else {
993
886
  warn(
@@ -1004,7 +897,7 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
1004
897
  id: r.id,
1005
898
  type: r.type,
1006
899
  reason: r.reason,
1007
- confidenceLevel: r.confidenceLevel
900
+ selectionMode: r.selectionMode
1008
901
  })),
1009
902
  false
1010
903
  );
@@ -1038,7 +931,7 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
1038
931
  return [...new Set(files)];
1039
932
  }
1040
933
  async function writeManagedText(root, filePath, nextText, dryRun) {
1041
- const prev = await fs10.pathExists(filePath) ? await fs10.readFile(filePath, "utf8") : "";
934
+ const prev = await fs9.pathExists(filePath) ? await fs9.readFile(filePath, "utf8") : "";
1042
935
  const printable = displayPath(root, filePath);
1043
936
  if (dryRun) {
1044
937
  if (!prev) {
@@ -1065,7 +958,7 @@ async function writeManagedJson(root, filePath, value, dryRun) {
1065
958
 
1066
959
  // src/commands/apply.ts
1067
960
  async function cacheHasItems() {
1068
- const data = await readJson(path12.join(CACHE_DIR, "manifest.json"));
961
+ const data = await readJson(path10.join(CACHE_DIR, "manifest.json"));
1069
962
  return Array.isArray(data?.items) && data.items.length > 0;
1070
963
  }
1071
964
  async function runApply(options) {
@@ -1092,7 +985,7 @@ async function runApply(options) {
1092
985
  } else {
1093
986
  const items = rec.recommended;
1094
987
  const choices = items.map((item) => ({
1095
- name: `${item.id} [${item.confidenceLevel}] \u2014 ${item.reason}`,
988
+ name: `${item.id} [${item.selectionMode}] \u2014 ${item.reason}`,
1096
989
  value: item.id,
1097
990
  checked: true
1098
991
  }));
@@ -1135,8 +1028,8 @@ async function runApply(options) {
1135
1028
 
1136
1029
  // src/catalog/load-catalog.ts
1137
1030
  import os3 from "os";
1138
- import path13 from "path";
1139
- var CACHE_MANIFEST = path13.join(os3.homedir(), CATALOG_CACHE_SUBDIR, "manifest.json");
1031
+ import path11 from "path";
1032
+ var CACHE_MANIFEST = path11.join(os3.homedir(), CATALOG_CACHE_SUBDIR, "manifest.json");
1140
1033
  async function loadCatalog(root) {
1141
1034
  const envPath = process.env["HAUS_FIXTURE_CATALOG"];
1142
1035
  if (envPath) {
@@ -1145,10 +1038,10 @@ async function loadCatalog(root) {
1145
1038
  }
1146
1039
  const cacheData = await readJson(CACHE_MANIFEST);
1147
1040
  if (cacheData?.items?.length) return cacheData.items;
1148
- const localManifest = path13.join(root, "library/catalog/manifest.json");
1041
+ const localManifest = path11.join(root, "library/catalog/manifest.json");
1149
1042
  const localData = await readJson(localManifest);
1150
1043
  if (localData?.items?.length) return localData.items;
1151
- const packageManifest = path13.join(packageRoot(), "library/catalog/manifest.json");
1044
+ const packageManifest = path11.join(packageRoot(), "library/catalog/manifest.json");
1152
1045
  const data = await readJson(packageManifest);
1153
1046
  return data?.items ?? [];
1154
1047
  }
@@ -1188,7 +1081,7 @@ async function runCatalogAudit() {
1188
1081
  }
1189
1082
 
1190
1083
  // src/commands/config.ts
1191
- import path14 from "path";
1084
+ import path12 from "path";
1192
1085
  var CONFIG_PATH2 = ".haus-workflow/config.json";
1193
1086
  var HOOK_ALIASES = {
1194
1087
  "hook.context": "context"
@@ -1201,7 +1094,7 @@ async function runConfig(key, action) {
1201
1094
  );
1202
1095
  }
1203
1096
  const root = process.cwd();
1204
- const configPath = path14.join(root, CONFIG_PATH2);
1097
+ const configPath = path12.join(root, CONFIG_PATH2);
1205
1098
  const existing = await readJson(configPath);
1206
1099
  const cfg = existing ?? structuredClone(DEFAULT_HOOKS_CONFIG);
1207
1100
  cfg.hooks ??= {};
@@ -1222,27 +1115,15 @@ function normalizeRecommendation(input2) {
1222
1115
  const normalizedReasons = item.reasons?.map((reason) => ({
1223
1116
  code: reason.code ?? "legacy-reason",
1224
1117
  message: reason.message ?? item.reason ?? "legacy recommendation reason",
1225
- weight: reason.weight ?? 0,
1226
1118
  ...reason.signal ? { signal: reason.signal } : {}
1227
- })) ?? [
1228
- { code: "legacy-reason", message: item.reason ?? "legacy recommendation reason", weight: 0 }
1229
- ];
1230
- const confidence = item.confidence ?? 0;
1119
+ })) ?? [{ code: "legacy-reason", message: item.reason ?? "legacy recommendation reason" }];
1231
1120
  return {
1232
1121
  id: item.id,
1233
1122
  type: item.type ?? "skill",
1234
1123
  reason: item.reason ?? normalizedReasons.map((reason) => reason.message).join(", "),
1235
1124
  reasons: normalizedReasons,
1236
- confidence,
1237
- confidenceLevel: item.confidenceLevel ?? (confidence >= 0.75 ? "high" : confidence >= 0.4 ? "medium" : "low"),
1238
1125
  selectionMode: item.selectionMode ?? "matched",
1239
1126
  install: item.install ?? true,
1240
- score: item.score ?? 0,
1241
- scoreBreakdown: {
1242
- bonuses: normalizedReasons,
1243
- penalties: [],
1244
- finalScore: item.score ?? 0
1245
- },
1246
1127
  tags: item.tags,
1247
1128
  ecosystem: item.ecosystem,
1248
1129
  tokenEstimate: item.tokenEstimate
@@ -1254,15 +1135,8 @@ function normalizeRecommendation(input2) {
1254
1135
  skipReasons: item.skipReasons?.map((reason) => ({
1255
1136
  code: reason.code ?? "legacy-skip-reason",
1256
1137
  message: reason.message ?? item.reason ?? "legacy skipped reason",
1257
- penalty: reason.penalty ?? 0,
1258
1138
  ...reason.signal ? { signal: reason.signal } : {}
1259
- })) ?? [
1260
- {
1261
- code: "legacy-skip-reason",
1262
- message: item.reason ?? "legacy skipped reason",
1263
- penalty: 0
1264
- }
1265
- ]
1139
+ })) ?? [{ code: "legacy-skip-reason", message: item.reason ?? "legacy skipped reason" }]
1266
1140
  }));
1267
1141
  return {
1268
1142
  mode: input2.mode === "guided" ? "guided" : "fast",
@@ -1282,8 +1156,6 @@ function buildRecommendationExplanation(recommendation) {
1282
1156
  return {
1283
1157
  selected: recommendation.recommended.map((item) => ({
1284
1158
  id: item.id,
1285
- confidence: item.confidence,
1286
- confidenceLevel: item.confidenceLevel,
1287
1159
  selectionMode: item.selectionMode,
1288
1160
  reasons: item.reasons.map((reason) => reason.message)
1289
1161
  })),
@@ -1293,7 +1165,6 @@ function buildRecommendationExplanation(recommendation) {
1293
1165
  reasonDetails: item.skipReasons.map((reason) => ({
1294
1166
  code: reason.code,
1295
1167
  message: reason.message,
1296
- penalty: reason.penalty,
1297
1168
  ...reason.signal ? { signal: reason.signal } : {}
1298
1169
  }))
1299
1170
  })),
@@ -1520,6 +1391,13 @@ function computeRuleIntents(rule) {
1520
1391
  }
1521
1392
 
1522
1393
  // src/recommender/rule-selection.ts
1394
+ function evidenceCount(rule) {
1395
+ return rule.reasons.filter((r) => r.code !== "default-baseline").length;
1396
+ }
1397
+ function isRoleOnly(rule) {
1398
+ const codes = rule.reasons.map((r) => r.code).filter((c) => c !== "default-baseline");
1399
+ return codes.length > 0 && codes.every((c) => c === "repo-role-match");
1400
+ }
1523
1401
  var DEFAULT_CONTEXT_TOKEN_BUDGET = 12e3;
1524
1402
  function pickTaskRelevantRules(recommendation, task, taskIntents = /* @__PURE__ */ new Set(), opts = {}) {
1525
1403
  const recommended = recommendation?.recommended ?? [];
@@ -1537,7 +1415,7 @@ function applyTokenBudget(rules, budget) {
1537
1415
  used += r.tokenEstimate ?? 0;
1538
1416
  }
1539
1417
  }
1540
- const matched = rules.filter((r) => r.selectionMode !== "baseline").sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
1418
+ const matched = rules.filter((r) => r.selectionMode !== "baseline").sort((a, b) => evidenceCount(b) - evidenceCount(a) || a.id.localeCompare(b.id));
1541
1419
  for (const r of matched) {
1542
1420
  const est = r.tokenEstimate ?? 0;
1543
1421
  if (used + est <= budget) {
@@ -1575,20 +1453,20 @@ function selectRules(recommended, task, taskIntents) {
1575
1453
  });
1576
1454
  if (tokenMatches.length > 0) return tokenMatches;
1577
1455
  const taskWantsTesting = taskIntents.has("testing");
1578
- const cappedMediumOrHigh = recommended.filter((rule) => {
1456
+ const capped = recommended.filter((rule) => {
1579
1457
  if (rule.selectionMode === "baseline") return false;
1580
- if (rule.confidenceLevel === "low") return false;
1458
+ if (isRoleOnly(rule)) return false;
1581
1459
  if (taskWantsTesting) return true;
1582
1460
  const ruleIntents = computeRuleIntents(rule);
1583
1461
  const isTestingOnly = ruleIntents.size > 0 && [...ruleIntents].every((i) => i === "testing");
1584
1462
  return !isTestingOnly;
1585
1463
  });
1586
- return cappedMediumOrHigh.slice(0, 8);
1464
+ return capped.slice(0, 8);
1587
1465
  }
1588
1466
 
1589
1467
  // src/scanner/scan-project.ts
1590
1468
  import { readFile as readFile2 } from "fs/promises";
1591
- import path18 from "path";
1469
+ import path16 from "path";
1592
1470
 
1593
1471
  // src/utils/audit-checks.ts
1594
1472
  function isRecord(v) {
@@ -1615,8 +1493,8 @@ function compareVersions(a, b) {
1615
1493
  }
1616
1494
 
1617
1495
  // src/scanner/detect-package-manager.ts
1618
- import path15 from "path";
1619
- import fs11 from "fs-extra";
1496
+ import path13 from "path";
1497
+ import fs10 from "fs-extra";
1620
1498
  function detectPackageManager(root, packageManagerField) {
1621
1499
  const field = String(packageManagerField ?? "").trim();
1622
1500
  if (field.startsWith("yarn@")) {
@@ -1634,9 +1512,9 @@ function detectPackageManager(root, packageManagerField) {
1634
1512
  if (satisfiesVersion(version, ">=9")) return "npm";
1635
1513
  return "unknown";
1636
1514
  }
1637
- if (fs11.existsSync(path15.join(root, "yarn.lock"))) return "yarn";
1638
- if (fs11.existsSync(path15.join(root, "pnpm-lock.yaml"))) return "pnpm";
1639
- if (fs11.existsSync(path15.join(root, "package-lock.json"))) return "npm";
1515
+ if (fs10.existsSync(path13.join(root, "yarn.lock"))) return "yarn";
1516
+ if (fs10.existsSync(path13.join(root, "pnpm-lock.yaml"))) return "pnpm";
1517
+ if (fs10.existsSync(path13.join(root, "package-lock.json"))) return "npm";
1640
1518
  return "unknown";
1641
1519
  }
1642
1520
 
@@ -1809,7 +1687,7 @@ function runDetection(ctx, rules = STACK_RULES) {
1809
1687
  }
1810
1688
 
1811
1689
  // src/scanner/detection.ts
1812
- import path16 from "path";
1690
+ import path14 from "path";
1813
1691
  var UNSUPPORTED_MARKERS = {
1814
1692
  "requirements.txt": "python",
1815
1693
  "pyproject.toml": "python",
@@ -1863,14 +1741,14 @@ function finalizeRoles(registryRoles, deps, files) {
1863
1741
  function collectUnsupportedSignals(files) {
1864
1742
  return [
1865
1743
  ...new Set(
1866
- files.map((f) => UNSUPPORTED_MARKERS[path16.basename(f)]).filter((s) => Boolean(s))
1744
+ files.map((f) => UNSUPPORTED_MARKERS[path14.basename(f)]).filter((s) => Boolean(s))
1867
1745
  )
1868
1746
  ].sort();
1869
1747
  }
1870
1748
 
1871
1749
  // src/scanner/render.ts
1872
1750
  import { readFile } from "fs/promises";
1873
- import path17 from "path";
1751
+ import path15 from "path";
1874
1752
 
1875
1753
  // src/scanner/role-labels.ts
1876
1754
  var ROLE_LABELS = {
@@ -1936,7 +1814,7 @@ async function buildContentBlob(root, files) {
1936
1814
  const batch = await Promise.all(
1937
1815
  slice.slice(i, i + CHUNK).map(async (rel) => {
1938
1816
  try {
1939
- return await readFile(path17.join(root, rel), "utf8");
1817
+ return await readFile(path15.join(root, rel), "utf8");
1940
1818
  } catch {
1941
1819
  return "";
1942
1820
  }
@@ -1946,11 +1824,6 @@ async function buildContentBlob(root, files) {
1946
1824
  }
1947
1825
  return parts.join("\n");
1948
1826
  }
1949
- function computeConfidence(roles, stacks) {
1950
- const stackCount = Object.values(stacks).reduce((sum, arr) => sum + arr.length, 0);
1951
- if (roles.length === 0) return 0.15;
1952
- return Math.min(0.99, Number((0.4 + roles.length * 0.08 + stackCount * 0.02).toFixed(2)));
1953
- }
1954
1827
  function renderSummary(context) {
1955
1828
  return `# Repo summary
1956
1829
 
@@ -2010,8 +1883,8 @@ var SAFE_FILES = [
2010
1883
  "Gemfile"
2011
1884
  ];
2012
1885
  async function scanProject(root, mode = "fast") {
2013
- const pkg = await readJson(path18.join(root, "package.json"));
2014
- const composer = await readJson(path18.join(root, "composer.json"));
1886
+ const pkg = await readJson(path16.join(root, "package.json"));
1887
+ const composer = await readJson(path16.join(root, "composer.json"));
2015
1888
  const files = await listFiles(root, SAFE_FILES);
2016
1889
  const safeFiles = files.filter((f) => !blocked(f));
2017
1890
  const deps = dependencySet(pkg, composer);
@@ -2045,10 +1918,9 @@ async function scanProject(root, mode = "fast") {
2045
1918
  mode,
2046
1919
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2047
1920
  root,
2048
- repoName: String(pkg?.name ?? path18.basename(root)),
1921
+ repoName: String(pkg?.name ?? path16.basename(root)),
2049
1922
  packageManager,
2050
1923
  repoRoles: roles,
2051
- confidence: computeConfidence(roles, stacks),
2052
1924
  detectedStacks: stacks,
2053
1925
  dependencies: deps,
2054
1926
  securityRisks,
@@ -2064,7 +1936,7 @@ async function scanProject(root, mode = "fast") {
2064
1936
  const scanHashes = Object.fromEntries(
2065
1937
  await Promise.all(
2066
1938
  safeFiles.map(
2067
- async (f) => [f, hashText(await readFile2(path18.join(root, f), "utf8"))]
1939
+ async (f) => [f, hashText(await readFile2(path16.join(root, f), "utf8"))]
2068
1940
  )
2069
1941
  )
2070
1942
  );
@@ -2094,9 +1966,6 @@ async function runContext(options) {
2094
1966
  const summary = await readText(hausPath(root, "repo-summary.md")) ?? "";
2095
1967
  const recommendationRaw = await readJson(hausPath(root, "recommendation.json"));
2096
1968
  const recommendation = recommendationRaw ? normalizeRecommendation(recommendationRaw) : void 0;
2097
- const rawBreakdownById = new Map(
2098
- (recommendationRaw?.recommended ?? []).map((item) => [item.id, item.scoreBreakdown])
2099
- );
2100
1969
  const taskIntents = options.task ? classifyTaskIntents(options.task) : /* @__PURE__ */ new Set();
2101
1970
  const selected = pickTaskRelevantRules(recommendation, options.task, taskIntents, {
2102
1971
  tokenBudget: DEFAULT_CONTEXT_TOKEN_BUDGET
@@ -2107,10 +1976,9 @@ async function runContext(options) {
2107
1976
  roles: context.repoRoles,
2108
1977
  selectedRules: selected.map((x) => ({
2109
1978
  id: x.id,
2110
- confidenceLevel: x.confidenceLevel,
2111
1979
  selectionMode: x.selectionMode,
2112
1980
  reasons: x.reasons.map((reason) => reason.message),
2113
- ...options.verbose ? { scoreBreakdown: rawBreakdownById.get(x.id) } : {}
1981
+ ...options.verbose ? { signals: x.reasons.map((r) => r.signal).filter(Boolean) } : {}
2114
1982
  })),
2115
1983
  skippedCount: recommendation?.skippedRules ?? 0,
2116
1984
  estimatedTokenReductionPct: recommendation?.estimatedTokenReductionPct ?? 0
@@ -2131,15 +1999,8 @@ async function runContext(options) {
2131
1999
  ...payload.selectedRules.flatMap((rule) => {
2132
2000
  const reasonLine = `- ${rule.id}: ${rule.reasons.join(", ")}`;
2133
2001
  if (!options.verbose) return [reasonLine];
2134
- const breakdown = rawBreakdownById.get(rule.id);
2135
- if (!breakdown) return [reasonLine];
2136
- const bonuses = (breakdown.bonuses ?? []).map(
2137
- (b) => ` + ${b.code}(+${b.weight})${b.signal ? ` [${b.signal}]` : ""}`
2138
- );
2139
- const penalties = (breakdown.penalties ?? []).map(
2140
- (p) => ` - ${p.code}(${p.penalty})${p.signal ? ` [${p.signal}]` : ""}`
2141
- );
2142
- return [reasonLine, ...bonuses, ...penalties];
2002
+ const signals = (rule.signals ?? []).map((s) => ` \u2022 ${s}`);
2003
+ return [reasonLine, ...signals];
2143
2004
  }),
2144
2005
  summary
2145
2006
  ];
@@ -2148,8 +2009,8 @@ async function runContext(options) {
2148
2009
  }
2149
2010
 
2150
2011
  // src/commands/doctor.ts
2151
- import path19 from "path";
2152
- import fs12 from "fs-extra";
2012
+ import path17 from "path";
2013
+ import fs11 from "fs-extra";
2153
2014
 
2154
2015
  // src/update/npm-version.ts
2155
2016
  var NPM_PACKAGE_NAME = "@haus-tech/haus-workflow";
@@ -2229,7 +2090,7 @@ async function runDoctor(options) {
2229
2090
  const enabled = await isHookEnabled(root, key);
2230
2091
  ok(`- HOOK ${key}: ${enabled ? "enabled" : "disabled (default)"}`);
2231
2092
  }
2232
- const rootClaudeMdPath = path19.join(root, "CLAUDE.md");
2093
+ const rootClaudeMdPath = path17.join(root, "CLAUDE.md");
2233
2094
  const rootClaudeMdContent = await readText(rootClaudeMdPath);
2234
2095
  if (!rootClaudeMdContent) {
2235
2096
  flag(
@@ -2257,7 +2118,7 @@ async function runDoctor(options) {
2257
2118
  const block = rootClaudeMdContent.slice(beginIdx, endIdx + BLOCK_END.length);
2258
2119
  const importTargets = [...block.matchAll(/@\.haus-workflow\/(\S+)/g)].map((m) => m[1]);
2259
2120
  for (const target of importTargets) {
2260
- if (!await fs12.pathExists(hausPath(root, target))) {
2121
+ if (!await fs11.pathExists(hausPath(root, target))) {
2261
2122
  flag(
2262
2123
  `- CLAUDE.md import: @.haus-workflow/${target} does not resolve (run \`haus apply --write\`)`,
2263
2124
  `A file CLAUDE.md links to (${target}) is missing, so part of the guidance won't load`,
@@ -2268,7 +2129,7 @@ async function runDoctor(options) {
2268
2129
  }
2269
2130
  }
2270
2131
  const workflowPath = hausPath(root, "WORKFLOW.md");
2271
- const workflowExists = await fs12.pathExists(workflowPath);
2132
+ const workflowExists = await fs11.pathExists(workflowPath);
2272
2133
  if (!workflowExists) {
2273
2134
  flag(
2274
2135
  "- .haus-workflow/WORKFLOW.md: missing (run `haus apply --write`)",
@@ -2282,15 +2143,15 @@ async function runDoctor(options) {
2282
2143
  ok("- .haus-workflow/WORKFLOW.md: OK (user-owned)");
2283
2144
  } else {
2284
2145
  const storedHashMatch = firstLine.match(/hash=(sha256-[a-f0-9]+)/);
2285
- const cachePath = path19.join(CACHE_DIR, "templates/agentic-workflow-standard.md");
2286
- const bundledPath = path19.join(
2146
+ const cachePath = path17.join(CACHE_DIR, "templates/agentic-workflow-standard.md");
2147
+ const bundledPath = path17.join(
2287
2148
  packageRoot(),
2288
2149
  "library",
2289
2150
  "global",
2290
2151
  "templates",
2291
2152
  "agentic-workflow-standard.md"
2292
2153
  );
2293
- const templatePath = await fs12.pathExists(cachePath) ? cachePath : bundledPath;
2154
+ const templatePath = await fs11.pathExists(cachePath) ? cachePath : bundledPath;
2294
2155
  const templateContent = await readText(templatePath);
2295
2156
  if (storedHashMatch && templateContent) {
2296
2157
  const currentHash = hashText(normaliseLF(templateContent));
@@ -2309,7 +2170,7 @@ async function runDoctor(options) {
2309
2170
  }
2310
2171
  }
2311
2172
  const workflowConfigPath = hausPath(root, "workflow-config.md");
2312
- const workflowConfigExists = await fs12.pathExists(workflowConfigPath);
2173
+ const workflowConfigExists = await fs11.pathExists(workflowConfigPath);
2313
2174
  if (!workflowConfigExists) {
2314
2175
  flag(
2315
2176
  "- .haus-workflow/workflow-config.md: missing (run `haus apply --write`)",
@@ -2317,7 +2178,7 @@ async function runDoctor(options) {
2317
2178
  "haus apply --write"
2318
2179
  );
2319
2180
  } else {
2320
- const cfg = await fs12.readFile(workflowConfigPath, "utf8");
2181
+ const cfg = await fs11.readFile(workflowConfigPath, "utf8");
2321
2182
  const unfilled = cfg.split("\n").filter((l) => l.includes("<!-- fill in")).length;
2322
2183
  if (unfilled > 0) {
2323
2184
  flag(
@@ -2329,23 +2190,6 @@ async function runDoctor(options) {
2329
2190
  ok("- .haus-workflow/workflow-config.md: OK (project-owned)");
2330
2191
  }
2331
2192
  }
2332
- const projectMdPath = hausPath(root, "project.md");
2333
- const projectMdExists = await fs12.pathExists(projectMdPath);
2334
- if (!projectMdExists) {
2335
- flag(
2336
- "- .haus-workflow/project.md: missing (run `haus apply --write`)",
2337
- "The project facts file is missing",
2338
- "haus apply --write"
2339
- );
2340
- } else {
2341
- const projectMdContent = await readText(projectMdPath);
2342
- const hasHeader = projectMdContent?.split("\n")[0]?.includes("HAUS-MANAGED") ?? false;
2343
- if (!hasHeader) {
2344
- ok("- .haus-workflow/project.md: no HAUS-MANAGED header (user-owned)");
2345
- } else {
2346
- ok("- .haus-workflow/project.md: OK");
2347
- }
2348
- }
2349
2193
  const cacheAgeMs = await getCacheManifestAge();
2350
2194
  if (cacheAgeMs === null) {
2351
2195
  flag(
@@ -2365,7 +2209,7 @@ async function runDoctor(options) {
2365
2209
  ok(`- CATALOG CACHE: OK (${cacheAgeDays}d old)`);
2366
2210
  }
2367
2211
  }
2368
- const pkgJson = await readJson(path19.join(packageRoot(), "package.json"));
2212
+ const pkgJson = await readJson(path17.join(packageRoot(), "package.json"));
2369
2213
  const currentVersion = pkgJson?.version ?? "0.0.0";
2370
2214
  const npmStatus = await fetchNpmVersionStatus(currentVersion);
2371
2215
  if (npmStatus.updateAvailable && npmStatus.latest !== null) {
@@ -2413,7 +2257,6 @@ function formatRecommendationHuman(rec) {
2413
2257
  if (rec.recommended.length === 0) lines.push(" (none)");
2414
2258
  for (const item of rec.recommended) {
2415
2259
  lines.push(`- ${item.id}`);
2416
- lines.push(` confidence: ${item.confidenceLevel} (${item.confidence.toFixed(2)})`);
2417
2260
  lines.push(` selection: ${item.selectionMode}`);
2418
2261
  lines.push(" why:");
2419
2262
  for (const reason of item.reasons) lines.push(` - ${formatReasonWithSignal(reason)}`);
@@ -2507,51 +2350,46 @@ async function runGuard(kind, _options) {
2507
2350
  }
2508
2351
 
2509
2352
  // src/commands/init.ts
2510
- import path20 from "path";
2511
- import fs13 from "fs-extra";
2353
+ import path18 from "path";
2354
+ import fs12 from "fs-extra";
2512
2355
 
2513
- // src/recommender/ecosystem.ts
2514
- var ECOSYSTEM_GROUPS = {
2515
- laravel: ["laravel-app", "laravel-nova-app"],
2516
- wordpress: ["wordpress-site", "wordpress-bedrock-site", "wordpress-vanilla-site"],
2517
- vendure: ["vendure-app", "vendure-plugin"],
2518
- nestjs: ["nestjs-api"],
2519
- nextjs: ["next-app"],
2520
- react: ["react-app", "next-app", "design-system"],
2521
- vue: ["vue-app"],
2522
- dotnet: ["dotnet-service"],
2523
- nx: ["nx-monorepo"],
2524
- turbo: ["turbo-monorepo"]
2525
- };
2526
- var ECOSYSTEM_PRIMARY_BACKENDS = /* @__PURE__ */ new Set([
2527
- "laravel",
2528
- "wordpress",
2529
- "vendure",
2530
- "nestjs",
2531
- "dotnet"
2532
- ]);
2533
- var ECOSYSTEM_COMPATIBLE_BACKENDS = {
2534
- vendure: /* @__PURE__ */ new Set(["vendure", "nestjs"]),
2535
- nestjs: /* @__PURE__ */ new Set(["nestjs"]),
2536
- laravel: /* @__PURE__ */ new Set(["laravel"]),
2537
- wordpress: /* @__PURE__ */ new Set(["wordpress"]),
2538
- dotnet: /* @__PURE__ */ new Set(["dotnet"])
2539
- };
2540
- function inferRepoEcosystems(roles) {
2541
- const ecosystems = /* @__PURE__ */ new Set();
2542
- for (const [eco, roleList] of Object.entries(ECOSYSTEM_GROUPS)) {
2543
- if (roleList.some((r) => roles.includes(r))) ecosystems.add(eco);
2356
+ // src/utils/exec.ts
2357
+ import { execa } from "execa";
2358
+ async function runCommand(command, args = [], options = {}) {
2359
+ try {
2360
+ const result = await execa(command, args, {
2361
+ reject: false,
2362
+ // non-zero exits are returned, not thrown
2363
+ ...options
2364
+ });
2365
+ return {
2366
+ command,
2367
+ args,
2368
+ stdout: String(result.stdout ?? ""),
2369
+ stderr: String(result.stderr ?? ""),
2370
+ exitCode: result.exitCode ?? 0
2371
+ };
2372
+ } catch (error2) {
2373
+ const message = error2 instanceof Error ? error2.message : String(error2);
2374
+ throw new Error(`Failed to run command: ${command} ${args.join(" ")} (${message})`);
2544
2375
  }
2545
- return [...ecosystems];
2546
2376
  }
2547
- function pickDominantBackend(ecosystems) {
2548
- for (const eco of ecosystems) {
2549
- if (ECOSYSTEM_PRIMARY_BACKENDS.has(eco)) return eco;
2550
- }
2551
- return void 0;
2377
+ async function runGit(args, options = {}) {
2378
+ return runCommand("git", args, options);
2552
2379
  }
2553
- function isBackendEcosystem(eco) {
2554
- return ECOSYSTEM_PRIMARY_BACKENDS.has(eco);
2380
+
2381
+ // src/recommender/git-signal.ts
2382
+ async function readChangedFiles(root) {
2383
+ if (process.env.HAUS_DISABLE_GIT_SIGNALS === "1") return [];
2384
+ try {
2385
+ const result = await runGit(["diff", "--name-only"], { cwd: root });
2386
+ if (result.exitCode !== 0) {
2387
+ return [];
2388
+ }
2389
+ return result.stdout.split("\n").map((x) => x.trim()).filter(Boolean).sort();
2390
+ } catch {
2391
+ return [];
2392
+ }
2555
2393
  }
2556
2394
 
2557
2395
  // src/recommender/policies.ts
@@ -2621,63 +2459,6 @@ function mergeRecommendationWarnings(context) {
2621
2459
  return [.../* @__PURE__ */ new Set([...statusLines, ...context.warnings, ...riskLines])];
2622
2460
  }
2623
2461
 
2624
- // src/utils/exec.ts
2625
- import { execa } from "execa";
2626
- async function runCommand(command, args = [], options = {}) {
2627
- try {
2628
- const result = await execa(command, args, {
2629
- reject: false,
2630
- // non-zero exits are returned, not thrown
2631
- ...options
2632
- });
2633
- return {
2634
- command,
2635
- args,
2636
- stdout: String(result.stdout ?? ""),
2637
- stderr: String(result.stderr ?? ""),
2638
- exitCode: result.exitCode ?? 0
2639
- };
2640
- } catch (error2) {
2641
- const message = error2 instanceof Error ? error2.message : String(error2);
2642
- throw new Error(`Failed to run command: ${command} ${args.join(" ")} (${message})`);
2643
- }
2644
- }
2645
- async function runGit(args, options = {}) {
2646
- return runCommand("git", args, options);
2647
- }
2648
-
2649
- // src/recommender/scoring.ts
2650
- function computeConfidenceLevel(args) {
2651
- const { isDefaultBaseline, reasons, hasEcosystemConflict, score } = args;
2652
- const positiveCodes = new Set(reasons.map((r) => r.code));
2653
- positiveCodes.delete("default-baseline");
2654
- const distinctSignals = positiveCodes.size;
2655
- const strongCount = (positiveCodes.has("repo-role-match") ? 1 : 0) + (positiveCodes.has("stack-match") ? 1 : 0) + (positiveCodes.has("requires-any-match") ? 1 : 0);
2656
- if (hasEcosystemConflict) return "low";
2657
- if (isDefaultBaseline && distinctSignals === 0) return "medium";
2658
- if (strongCount >= 2 && score >= 70) return "high";
2659
- if (strongCount >= 1 && distinctSignals >= 2 && score >= 50) return "medium";
2660
- if (distinctSignals === 1) return "low";
2661
- return distinctSignals >= 2 ? "medium" : "low";
2662
- }
2663
- function confidenceLevelToNumber(level, score) {
2664
- const base = level === "high" ? 0.85 : level === "medium" ? 0.6 : 0.3;
2665
- const bonus = Math.min(0.1, Math.max(0, score - 40) / 1e3);
2666
- return Number(Math.min(0.99, base + bonus).toFixed(2));
2667
- }
2668
- async function readChangedFiles(root) {
2669
- if (process.env.HAUS_DISABLE_GIT_SIGNALS === "1") return [];
2670
- try {
2671
- const result = await runGit(["diff", "--name-only"], { cwd: root });
2672
- if (result.exitCode !== 0) {
2673
- return [];
2674
- }
2675
- return result.stdout.split("\n").map((x) => x.trim()).filter(Boolean).sort();
2676
- } catch {
2677
- return [];
2678
- }
2679
- }
2680
-
2681
2462
  // src/recommender/recommend.ts
2682
2463
  async function recommend(root, context) {
2683
2464
  const items = await loadCatalog(root);
@@ -2685,249 +2466,147 @@ async function recommend(root, context) {
2685
2466
  const sources = await readJson(
2686
2467
  hausPath(root, "sources-report.json")
2687
2468
  ) ?? {};
2688
- const stackSet = buildStackSet(context);
2689
- const depSet = new Set(context.dependencies.map((d) => d.toLowerCase()));
2690
- const roleSet = new Set(context.repoRoles.map((r) => r.toLowerCase()));
2691
- const repoEcosystems = inferRepoEcosystems(context.repoRoles);
2692
- const dominantBackendEcosystem = pickDominantBackend(repoEcosystems);
2469
+ const deep = await readJson(hausPath(root, "deep-context.json")) ?? {};
2470
+ const scannerStacks = buildStackSet(context);
2471
+ const scannerRoles = new Set(context.repoRoles.map((r) => r.toLowerCase()));
2472
+ const scannerDeps = new Set(context.dependencies.map((d) => d.toLowerCase()));
2473
+ const toStrings = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
2474
+ const deepStackValues = deep.stacks && typeof deep.stacks === "object" && !Array.isArray(deep.stacks) ? Object.values(deep.stacks).flatMap(toStrings) : [];
2475
+ const deepRoles = new Set(toStrings(deep.roles).map((r) => r.toLowerCase()));
2476
+ const deepStacks = new Set(
2477
+ [...toStrings(deep.roles), ...deepStackValues, ...toStrings(deep.patterns)].map(
2478
+ (x) => x.toLowerCase()
2479
+ )
2480
+ );
2481
+ const roleSet = /* @__PURE__ */ new Set([...scannerRoles, ...deepRoles]);
2482
+ const stackSet = /* @__PURE__ */ new Set([...scannerStacks, ...deepStacks]);
2483
+ const depSet = scannerDeps;
2693
2484
  const recommended = [];
2694
2485
  const skipped = [];
2695
2486
  const goals = Object.values(setupAnswers).join(" ").toLowerCase();
2696
2487
  const sourceTrust = new Map((sources.items ?? []).map((x) => [x.id, x.status ?? "candidate"]));
2697
2488
  const changedFiles = await readChangedFiles(root);
2698
- const securityRiskCount = context.securityRisks?.length ?? 0;
2489
+ const skip = (id, code, message, signal) => {
2490
+ skipped.push({ id, reason: message, skipReasons: [{ code, message, signal }] });
2491
+ };
2492
+ const roleSignal = (name) => scannerRoles.has(name.toLowerCase()) ? `role:${name}` : `deep:role:${name}`;
2493
+ const stackSignal = (name) => scannerStacks.has(name.toLowerCase()) ? `tag:${name}` : `deep:tag:${name}`;
2699
2494
  for (const item of items) {
2700
2495
  const itemSearchText = `${item.id} ${item.tags.join(" ")}`.toLowerCase();
2701
2496
  if (UNSUPPORTED.some((x) => itemSearchText.includes(x))) {
2702
- skipped.push({
2703
- id: item.id,
2704
- reason: "Unsupported stack policy",
2705
- skipReasons: [
2706
- {
2707
- code: "unsupported-policy",
2708
- message: "Unsupported stack policy",
2709
- penalty: 100
2710
- }
2711
- ]
2712
- });
2497
+ skip(item.id, "unsupported-policy", "Unsupported stack policy");
2713
2498
  continue;
2714
2499
  }
2715
2500
  if (item.source === "curated") {
2716
2501
  const rs = item.reviewStatus;
2717
2502
  if (!rs || rs !== "approved") {
2718
- skipped.push({
2719
- id: item.id,
2720
- reason: `Curated item not approved (reviewStatus=${rs ?? "unset"})`,
2721
- skipReasons: [
2722
- {
2723
- code: "curated-not-approved",
2724
- message: `Curated item requires reviewStatus:approved (got ${rs ?? "unset"})`,
2725
- penalty: 100,
2726
- signal: `reviewStatus:${rs ?? "unset"}`
2727
- }
2728
- ]
2729
- });
2503
+ skip(
2504
+ item.id,
2505
+ "curated-not-approved",
2506
+ `Curated item requires reviewStatus:approved (got ${rs ?? "unset"})`,
2507
+ `reviewStatus:${rs ?? "unset"}`
2508
+ );
2730
2509
  continue;
2731
2510
  }
2732
2511
  if (item.riskLevel === "blocked") {
2733
- skipped.push({
2734
- id: item.id,
2735
- reason: "Curated item risk level is blocked",
2736
- skipReasons: [
2737
- {
2738
- code: "curated-risk-blocked",
2739
- message: "Curated item riskLevel is blocked",
2740
- penalty: 100,
2741
- signal: "riskLevel:blocked"
2742
- }
2743
- ]
2744
- });
2512
+ skip(
2513
+ item.id,
2514
+ "curated-risk-blocked",
2515
+ "Curated item riskLevel is blocked",
2516
+ "riskLevel:blocked"
2517
+ );
2745
2518
  continue;
2746
2519
  }
2747
2520
  }
2521
+ if (SENSITIVE_ITEM_KEYWORDS.some((x) => itemSearchText.includes(x))) {
2522
+ skip(item.id, "sensitive-policy", "Sensitive content policy block");
2523
+ continue;
2524
+ }
2525
+ const trust = sourceTrust.get(item.source);
2526
+ if (trust === "candidate" || trust === "rejected") {
2527
+ skip(item.id, "source-trust", "Source trust policy block", `trust:${trust}`);
2528
+ continue;
2529
+ }
2530
+ if (item.source && item.source !== "haus" && trust !== "approved") {
2531
+ skip(item.id, "source-approval", "Source not approved", `source:${item.source}`);
2532
+ continue;
2533
+ }
2534
+ if (item.id === "haus.nx21-monorepo-patterns" && !roleSet.has("nx-monorepo")) {
2535
+ skip(
2536
+ item.id,
2537
+ "required-role-missing",
2538
+ "Required role missing: nx-monorepo",
2539
+ "role:nx-monorepo"
2540
+ );
2541
+ continue;
2542
+ }
2543
+ if (item.id === "haus.turbo-monorepo-patterns" && !roleSet.has("turbo-monorepo")) {
2544
+ skip(
2545
+ item.id,
2546
+ "required-role-missing",
2547
+ "Required role missing: turbo-monorepo",
2548
+ "role:turbo-monorepo"
2549
+ );
2550
+ continue;
2551
+ }
2748
2552
  const isDefaultBaseline = item.default === true;
2749
2553
  const reasons = [];
2750
- const skipReasons = [];
2751
- let score = 0;
2752
- const pushReason = (code, message, weight, signal) => {
2753
- score += weight;
2754
- reasons.push({ code, message, weight, signal });
2755
- };
2756
- const pushSkipReason = (code, message, penalty, signal) => {
2757
- score -= penalty;
2758
- skipReasons.push({ code, message, penalty, signal });
2759
- };
2760
- if (isDefaultBaseline) {
2761
- pushReason("default-baseline", "catalog default baseline", 25, "policy:default");
2762
- }
2554
+ const push = (code, message, signal) => reasons.push({ code, message, signal });
2555
+ if (isDefaultBaseline) push("default-baseline", "catalog default baseline", "policy:default");
2763
2556
  const roleMatch = item.repoRoles.find((r) => roleSet.has(r.toLowerCase()));
2764
- if (roleMatch) {
2765
- pushReason("repo-role-match", "repo role match", 40, `role:${roleMatch}`);
2766
- }
2557
+ if (roleMatch) push("repo-role-match", "repo role match", roleSignal(roleMatch));
2767
2558
  const tagMatch = item.tags.find((t) => stackSet.has(t.toLowerCase()));
2768
- if (tagMatch) {
2769
- pushReason("stack-match", "stack/dependency match", 30, `tag:${tagMatch}`);
2770
- }
2559
+ if (tagMatch) push("stack-match", "stack/dependency match", stackSignal(tagMatch));
2771
2560
  const goalMatch = item.tags.find(
2772
2561
  (t) => goals.includes(t) || goals.includes(t.replace(/-/g, " "))
2773
2562
  );
2774
- if (goalMatch) {
2775
- pushReason("goal-match", "guided goal match", 15, `goal:${goalMatch}`);
2776
- }
2563
+ if (goalMatch) push("goal-match", "guided goal match", `goal:${goalMatch}`);
2777
2564
  if (item.tags.includes(context.packageManager) || item.tags.includes(`${context.packageManager}4`) || item.tags.includes(`${context.packageManager}89`)) {
2778
- pushReason(
2565
+ push(
2779
2566
  "package-manager-match",
2780
2567
  "package manager match",
2781
- 10,
2782
2568
  `packageManager:${context.packageManager}`
2783
2569
  );
2784
2570
  }
2785
2571
  const configSignal = item.tags.find(
2786
2572
  (t) => context.warnings.join(" ").toLowerCase().includes(t.toLowerCase())
2787
2573
  );
2788
- if (configSignal) {
2789
- pushReason("config-signal-match", "config signal match", 20, `warning:${configSignal}`);
2790
- }
2574
+ if (configSignal) push("config-signal-match", "config signal match", `warning:${configSignal}`);
2791
2575
  const changedMatch = changedFiles.find((f) => f.includes(item.id.split(".").pop() ?? ""));
2792
- if (changedMatch) {
2793
- pushReason("changed-file-match", "changed file match", 10, `changedFile:${changedMatch}`);
2794
- }
2795
- if (item.id === "haus.nx21-monorepo-patterns" && !roleSet.has("nx-monorepo")) {
2796
- skipped.push({
2797
- id: item.id,
2798
- reason: "Required role missing: nx-monorepo",
2799
- skipReasons: [
2800
- {
2801
- code: "required-role-missing",
2802
- message: "Required role missing: nx-monorepo",
2803
- penalty: 100,
2804
- signal: "role:nx-monorepo"
2805
- }
2806
- ]
2807
- });
2808
- continue;
2809
- }
2810
- if (item.id === "haus.turbo-monorepo-patterns" && !roleSet.has("turbo-monorepo")) {
2811
- skipped.push({
2812
- id: item.id,
2813
- reason: "Required role missing: turbo-monorepo",
2814
- skipReasons: [
2815
- {
2816
- code: "required-role-missing",
2817
- message: "Required role missing: turbo-monorepo",
2818
- penalty: 100,
2819
- signal: "role:turbo-monorepo"
2820
- }
2821
- ]
2822
- });
2823
- continue;
2824
- }
2576
+ if (changedMatch)
2577
+ push("changed-file-match", "changed file match", `changedFile:${changedMatch}`);
2825
2578
  const requiresAny = item.requiresAny ?? [];
2826
2579
  if (requiresAny.length > 0) {
2827
- const satisfied = matchRequiresAny(requiresAny, {
2828
- stackSet,
2829
- depSet,
2830
- roleSet
2831
- });
2580
+ const satisfied = matchRequiresAny(requiresAny, { stackSet, depSet, roleSet });
2832
2581
  if (!satisfied.matched) {
2833
2582
  const description = describeRequiresAny(requiresAny);
2834
- skipped.push({
2835
- id: item.id,
2836
- reason: `requiresAny unsatisfied: needs ${description}`,
2837
- skipReasons: [
2838
- {
2839
- code: "requires-any-unsatisfied",
2840
- message: `requiresAny unsatisfied: needs ${description}`,
2841
- penalty: 100,
2842
- signal: description
2843
- }
2844
- ]
2845
- });
2583
+ skip(
2584
+ item.id,
2585
+ "requires-any-unsatisfied",
2586
+ `requiresAny unsatisfied: needs ${description}`,
2587
+ description
2588
+ );
2846
2589
  continue;
2847
2590
  }
2848
2591
  if (!reasons.some((r) => r.code === "stack-match")) {
2849
- pushReason("requires-any-match", "requires-any signal match", 25, satisfied.signal);
2592
+ push("requires-any-match", "requires-any signal match", satisfied.signal);
2850
2593
  }
2851
2594
  }
2852
- if (item.ecosystem && dominantBackendEcosystem && isBackendEcosystem(item.ecosystem)) {
2853
- const compat = ECOSYSTEM_COMPATIBLE_BACKENDS[dominantBackendEcosystem] ?? /* @__PURE__ */ new Set([dominantBackendEcosystem]);
2854
- if (!compat.has(item.ecosystem)) {
2855
- pushSkipReason(
2856
- "ecosystem-conflict",
2857
- `ecosystem conflict: rule ecosystem=${item.ecosystem} but repo dominant backend=${dominantBackendEcosystem}`,
2858
- 40,
2859
- `ecosystem:${item.ecosystem}->${dominantBackendEcosystem}`
2860
- );
2861
- }
2862
- }
2863
- if (SENSITIVE_ITEM_KEYWORDS.some((x) => itemSearchText.includes(x))) {
2864
- pushSkipReason("sensitive-policy", "Sensitive content policy block", 100);
2865
- }
2866
- const trust = sourceTrust.get(item.source);
2867
- if (trust === "candidate" || trust === "rejected") {
2868
- pushSkipReason("source-trust", "Source trust policy block", 100);
2869
- }
2870
- if (item.source && item.source !== "haus" && trust !== "approved") {
2871
- pushSkipReason("source-approval", "Source not approved", 100);
2872
- }
2873
- if (securityRiskCount > 0 && !isDefaultBaseline && (item.tags.includes("security") || item.id.includes("security"))) {
2874
- pushSkipReason(
2875
- "security-risk-penalty",
2876
- "Security-tagged item penalized by active risk signals",
2877
- 20
2878
- );
2879
- }
2880
- const positiveReasonCodes = new Set(
2881
- reasons.map((r) => r.code).filter((c) => c !== "default-baseline")
2882
- );
2883
- const hasRoleSignal = positiveReasonCodes.has("repo-role-match");
2884
- const hasDepOrStackSignal = positiveReasonCodes.has("stack-match") || positiveReasonCodes.has("requires-any-match");
2885
- if (hasRoleSignal && !hasDepOrStackSignal && !isDefaultBaseline && requiresAny.length === 0) {
2886
- pushSkipReason(
2887
- "role-only-bleed-guard",
2888
- "role match without dep/stack signal (role-only bleed)",
2889
- 25,
2890
- roleMatch ? `role:${roleMatch}` : void 0
2891
- );
2892
- }
2893
- const minScore = isDefaultBaseline ? 1 : 40;
2894
- if (score >= minScore) {
2895
- const confidenceLevel = computeConfidenceLevel({
2896
- isDefaultBaseline,
2897
- reasons,
2898
- hasEcosystemConflict: skipReasons.some((s) => s.code === "ecosystem-conflict"),
2899
- score
2900
- });
2901
- const confidence = confidenceLevelToNumber(confidenceLevel, score);
2595
+ const hasEvidence = reasons.some((r) => r.code !== "default-baseline");
2596
+ if (isDefaultBaseline || hasEvidence) {
2902
2597
  recommended.push({
2903
2598
  id: item.id,
2904
2599
  type: item.type,
2905
- reason: reasons.length ? reasons.map((x) => x.message).join(", ") : `score=${score}`,
2600
+ reason: reasons.length ? reasons.map((x) => x.message).join(", ") : "eligible",
2906
2601
  reasons,
2907
- confidence,
2908
- confidenceLevel,
2909
- selectionMode: isDefaultBaseline && reasons.every((r) => r.code === "default-baseline") ? "baseline" : "matched",
2602
+ selectionMode: isDefaultBaseline && !hasEvidence ? "baseline" : "matched",
2910
2603
  install: true,
2911
- score,
2912
- scoreBreakdown: {
2913
- bonuses: reasons,
2914
- penalties: skipReasons,
2915
- finalScore: score
2916
- },
2917
2604
  tags: item.tags,
2918
2605
  ecosystem: item.ecosystem,
2919
2606
  tokenEstimate: item.tokenEstimate
2920
2607
  });
2921
2608
  } else {
2922
- if (skipReasons.length === 0) {
2923
- skipReasons.push({
2924
- code: "no-role-stack-match",
2925
- message: "No role/stack match",
2926
- penalty: 0
2927
- });
2928
- }
2929
- const primary = skipReasons[0];
2930
- skipped.push({ id: item.id, reason: primary.message, skipReasons });
2609
+ skip(item.id, "no-role-stack-match", "No role/stack match");
2931
2610
  }
2932
2611
  }
2933
2612
  recommended.sort((a, b) => a.id.localeCompare(b.id));
@@ -3074,8 +2753,8 @@ async function runSetupProject(options) {
3074
2753
  // src/commands/init.ts
3075
2754
  async function runInit(options) {
3076
2755
  const root = process.cwd();
3077
- const hausDir = path20.join(root, ".haus-workflow");
3078
- const alreadyInit = await fs13.pathExists(hausDir);
2756
+ const hausDir = path18.join(root, ".haus-workflow");
2757
+ const alreadyInit = await fs12.pathExists(hausDir);
3079
2758
  if (alreadyInit) {
3080
2759
  log("Haus AI already initialized in this project.");
3081
2760
  log("Run `haus setup-project` to reconfigure.");
@@ -3087,8 +2766,8 @@ async function runInit(options) {
3087
2766
 
3088
2767
  // src/install/apply.ts
3089
2768
  import crypto2 from "crypto";
3090
- import path23 from "path";
3091
- import fs15 from "fs-extra";
2769
+ import path21 from "path";
2770
+ import fs14 from "fs-extra";
3092
2771
 
3093
2772
  // src/install/allow-rules.ts
3094
2773
  var ALLOWED_SUBCOMMANDS = [
@@ -3135,13 +2814,13 @@ ${content2}`;
3135
2814
 
3136
2815
  // src/install/manifest.ts
3137
2816
  import os4 from "os";
3138
- import path21 from "path";
2817
+ import path19 from "path";
3139
2818
  var MANIFEST_SCHEMA = "haus-install-manifest/1";
3140
2819
  function globalClaudeDir() {
3141
- return path21.join(os4.homedir(), ".claude");
2820
+ return path19.join(os4.homedir(), ".claude");
3142
2821
  }
3143
2822
  function hausManifestPath() {
3144
- return path21.join(globalClaudeDir(), "haus", "install-manifest.json");
2823
+ return path19.join(globalClaudeDir(), "haus", "install-manifest.json");
3145
2824
  }
3146
2825
  async function readManifest() {
3147
2826
  return readJson(hausManifestPath());
@@ -3160,10 +2839,10 @@ function buildManifest(source, files, hooks) {
3160
2839
  }
3161
2840
 
3162
2841
  // src/install/settings-merge.ts
3163
- import path22 from "path";
3164
- import fs14 from "fs-extra";
2842
+ import path20 from "path";
2843
+ import fs13 from "fs-extra";
3165
2844
  function settingsJsonPath() {
3166
- return path22.join(globalClaudeDir(), "settings.json");
2845
+ return path20.join(globalClaudeDir(), "settings.json");
3167
2846
  }
3168
2847
  async function readSettings() {
3169
2848
  const parsed = await readJson(settingsJsonPath());
@@ -3304,7 +2983,7 @@ function stripHausHooks(settings) {
3304
2983
  async function loadHooksFragment(fragmentPath) {
3305
2984
  let raw;
3306
2985
  try {
3307
- raw = await fs14.readJson(fragmentPath);
2986
+ raw = await fs13.readJson(fragmentPath);
3308
2987
  } catch {
3309
2988
  return [];
3310
2989
  }
@@ -3313,46 +2992,46 @@ async function loadHooksFragment(fragmentPath) {
3313
2992
  }
3314
2993
 
3315
2994
  // src/install/apply.ts
3316
- var SCHEMA_VERSION3 = "1";
2995
+ var SCHEMA_VERSION2 = "1";
3317
2996
  function hashContent(content2) {
3318
2997
  return `sha256-${crypto2.createHash("sha256").update(content2).digest("hex")}`;
3319
2998
  }
3320
2999
  function sourceVersion() {
3321
3000
  try {
3322
- const pkgPath = path23.join(packageRoot(), "package.json");
3323
- const pkg = JSON.parse(fs15.readFileSync(pkgPath, "utf8"));
3001
+ const pkgPath = path21.join(packageRoot(), "package.json");
3002
+ const pkg = JSON.parse(fs14.readFileSync(pkgPath, "utf8"));
3324
3003
  return `${pkg.name ?? "haus"}@${pkg.version ?? "0.0.0"}`;
3325
3004
  } catch {
3326
3005
  return "haus@0.0.0";
3327
3006
  }
3328
3007
  }
3329
3008
  function globalSrcDir() {
3330
- return path23.join(packageRoot(), "library", "global");
3009
+ return path21.join(packageRoot(), "library", "global");
3331
3010
  }
3332
3011
  function collectSourceFiles(srcDir, claudeDir) {
3333
3012
  const entries = [];
3334
- const skillsDir = path23.join(srcDir, "skills");
3335
- if (fs15.pathExistsSync(skillsDir)) {
3336
- for (const skillName of fs15.readdirSync(skillsDir)) {
3337
- const skillFile = path23.join(skillsDir, skillName, "SKILL.md");
3338
- if (fs15.pathExistsSync(skillFile)) {
3013
+ const skillsDir = path21.join(srcDir, "skills");
3014
+ if (fs14.pathExistsSync(skillsDir)) {
3015
+ for (const skillName of fs14.readdirSync(skillsDir)) {
3016
+ const skillFile = path21.join(skillsDir, skillName, "SKILL.md");
3017
+ if (fs14.pathExistsSync(skillFile)) {
3339
3018
  entries.push({
3340
3019
  stableId: `skill.${skillName}`,
3341
- srcRelPath: path23.join("library", "global", "skills", skillName, "SKILL.md"),
3342
- destPath: path23.join(claudeDir, "skills", skillName, "SKILL.md")
3020
+ srcRelPath: path21.join("library", "global", "skills", skillName, "SKILL.md"),
3021
+ destPath: path21.join(claudeDir, "skills", skillName, "SKILL.md")
3343
3022
  });
3344
3023
  }
3345
3024
  }
3346
3025
  }
3347
- const commandsDir = path23.join(srcDir, "commands");
3348
- if (fs15.pathExistsSync(commandsDir)) {
3349
- for (const fileName of fs15.readdirSync(commandsDir)) {
3026
+ const commandsDir = path21.join(srcDir, "commands");
3027
+ if (fs14.pathExistsSync(commandsDir)) {
3028
+ for (const fileName of fs14.readdirSync(commandsDir)) {
3350
3029
  if (!fileName.endsWith(".md")) continue;
3351
3030
  const commandName = fileName.slice(0, -".md".length);
3352
3031
  entries.push({
3353
3032
  stableId: `command.${commandName}`,
3354
- srcRelPath: path23.join("library", "global", "commands", fileName),
3355
- destPath: path23.join(claudeDir, "commands", fileName)
3033
+ srcRelPath: path21.join("library", "global", "commands", fileName),
3034
+ destPath: path21.join(claudeDir, "commands", fileName)
3356
3035
  });
3357
3036
  }
3358
3037
  }
@@ -3376,7 +3055,7 @@ async function applyInstall(options = {}) {
3376
3055
  };
3377
3056
  const manifestFiles = [];
3378
3057
  for (const entry of sourceFiles) {
3379
- const srcPath = path23.join(packageRoot(), entry.srcRelPath);
3058
+ const srcPath = path21.join(packageRoot(), entry.srcRelPath);
3380
3059
  const rawContent = await readText(srcPath);
3381
3060
  if (rawContent === void 0) {
3382
3061
  warn(`Source file not found: ${entry.srcRelPath}`);
@@ -3384,7 +3063,7 @@ async function applyInstall(options = {}) {
3384
3063
  }
3385
3064
  const stamped = stampMarkdown(rawContent, {
3386
3065
  stableId: entry.stableId,
3387
- schemaVersion: SCHEMA_VERSION3,
3066
+ schemaVersion: SCHEMA_VERSION2,
3388
3067
  source
3389
3068
  });
3390
3069
  const newHash = hashContent(stamped);
@@ -3396,7 +3075,7 @@ async function applyInstall(options = {}) {
3396
3075
  }
3397
3076
  continue;
3398
3077
  }
3399
- const destExists = fs15.pathExistsSync(entry.destPath);
3078
+ const destExists = fs14.pathExistsSync(entry.destPath);
3400
3079
  if (destExists) {
3401
3080
  const currentContent = await readText(entry.destPath);
3402
3081
  if (currentContent !== void 0) {
@@ -3429,10 +3108,10 @@ async function applyInstall(options = {}) {
3429
3108
  destPath: entry.destPath,
3430
3109
  srcRelPath: entry.srcRelPath,
3431
3110
  hash: newHash,
3432
- schemaVersion: SCHEMA_VERSION3
3111
+ schemaVersion: SCHEMA_VERSION2
3433
3112
  });
3434
3113
  }
3435
- const fragmentPath = path23.join(srcDir, "settings-fragments", "hooks.json");
3114
+ const fragmentPath = path21.join(srcDir, "settings-fragments", "hooks.json");
3436
3115
  const fragments = await loadHooksFragment(fragmentPath);
3437
3116
  const settings = await readSettings();
3438
3117
  const { settings: hookSettings, addedIds } = mergeHooks(settings, fragments);
@@ -3443,13 +3122,13 @@ async function applyInstall(options = {}) {
3443
3122
  const currentDestPaths = new Set(sourceFiles.map((f) => f.destPath));
3444
3123
  for (const entry of existingManifest.files) {
3445
3124
  if (currentDestPaths.has(entry.destPath)) continue;
3446
- if (!fs15.pathExistsSync(entry.destPath)) continue;
3125
+ if (!fs14.pathExistsSync(entry.destPath)) continue;
3447
3126
  const content2 = await readText(entry.destPath);
3448
3127
  if (!content2) continue;
3449
3128
  const hasHeader = parseMarkdownHeader(content2) !== void 0;
3450
3129
  const currentHash = hashContent(content2);
3451
3130
  if (hasHeader && currentHash === entry.hash) {
3452
- if (!dryRun) await fs15.remove(entry.destPath);
3131
+ if (!dryRun) await fs14.remove(entry.destPath);
3453
3132
  result.deleted.push(entry.destPath);
3454
3133
  } else {
3455
3134
  warn(`Orphaned file ${entry.destPath} was user-modified \u2014 leaving in place`);
@@ -3572,20 +3251,20 @@ async function runScan(options) {
3572
3251
  }
3573
3252
 
3574
3253
  // src/commands/undo.ts
3575
- import path24 from "path";
3576
- import fs16 from "fs-extra";
3254
+ import path22 from "path";
3255
+ import fs15 from "fs-extra";
3577
3256
  var CLAUDE_DIR = ".claude";
3578
3257
  async function runUndo(options) {
3579
3258
  const root = process.cwd();
3580
- const targets = [path24.join(root, CLAUDE_DIR), path24.join(root, HAUS_DIR)];
3581
- const existing = targets.filter((p) => fs16.existsSync(p));
3259
+ const targets = [path22.join(root, CLAUDE_DIR), path22.join(root, HAUS_DIR)];
3260
+ const existing = targets.filter((p) => fs15.existsSync(p));
3582
3261
  if (existing.length === 0) {
3583
3262
  log("Nothing to remove: no .claude/ or .haus-workflow/ in this directory.");
3584
3263
  return;
3585
3264
  }
3586
3265
  if (!options.yes) {
3587
3266
  const ok = await confirm(
3588
- `Remove ${existing.map((p) => path24.relative(root, p)).join(" and ")}? This cannot be undone.`
3267
+ `Remove ${existing.map((p) => path22.relative(root, p)).join(" and ")}? This cannot be undone.`
3589
3268
  );
3590
3269
  if (!ok) {
3591
3270
  log("Cancelled.");
@@ -3593,15 +3272,15 @@ async function runUndo(options) {
3593
3272
  }
3594
3273
  }
3595
3274
  for (const p of existing) {
3596
- await fs16.remove(p);
3597
- log(`Removed ${path24.relative(root, p)}`);
3275
+ await fs15.remove(p);
3276
+ log(`Removed ${path22.relative(root, p)}`);
3598
3277
  }
3599
3278
  }
3600
3279
 
3601
3280
  // src/install/uninstall.ts
3602
3281
  import crypto3 from "crypto";
3603
- import path25 from "path";
3604
- import fs17 from "fs-extra";
3282
+ import path23 from "path";
3283
+ import fs16 from "fs-extra";
3605
3284
  async function runUninstall(options = {}) {
3606
3285
  const { force = false } = options;
3607
3286
  const manifest = await readManifest();
@@ -3611,7 +3290,7 @@ async function runUninstall(options = {}) {
3611
3290
  return result;
3612
3291
  }
3613
3292
  for (const entry of manifest.files) {
3614
- const exists = fs17.pathExistsSync(entry.destPath);
3293
+ const exists = fs16.pathExistsSync(entry.destPath);
3615
3294
  if (!exists) continue;
3616
3295
  const content2 = await readText(entry.destPath);
3617
3296
  if (content2 === void 0) continue;
@@ -3629,22 +3308,22 @@ async function runUninstall(options = {}) {
3629
3308
  result.skipped.push(entry.destPath);
3630
3309
  continue;
3631
3310
  }
3632
- await fs17.remove(entry.destPath);
3633
- await pruneEmptyDir(path25.dirname(entry.destPath));
3311
+ await fs16.remove(entry.destPath);
3312
+ await pruneEmptyDir(path23.dirname(entry.destPath));
3634
3313
  result.deleted.push(entry.destPath);
3635
3314
  }
3636
3315
  const settings = await readSettings();
3637
3316
  const stripped = stripHausHooks(stripHausAllow(stripHausDeny(settings)));
3638
3317
  await writeSettings(stripped);
3639
3318
  result.hooksStripped = true;
3640
- const hausDir = path25.join(globalClaudeDir(), "haus");
3319
+ const hausDir = path23.join(globalClaudeDir(), "haus");
3641
3320
  const manifestPath = hausManifestPath();
3642
- if (fs17.pathExistsSync(manifestPath)) {
3643
- await fs17.remove(manifestPath);
3321
+ if (fs16.pathExistsSync(manifestPath)) {
3322
+ await fs16.remove(manifestPath);
3644
3323
  }
3645
- if (fs17.pathExistsSync(hausDir)) {
3646
- const remaining = await fs17.readdir(hausDir);
3647
- if (remaining.length === 0) await fs17.remove(hausDir);
3324
+ if (fs16.pathExistsSync(hausDir)) {
3325
+ const remaining = await fs16.readdir(hausDir);
3326
+ if (remaining.length === 0) await fs16.remove(hausDir);
3648
3327
  }
3649
3328
  return result;
3650
3329
  }
@@ -3663,8 +3342,8 @@ function printUninstallResult(result) {
3663
3342
  }
3664
3343
  async function pruneEmptyDir(dir) {
3665
3344
  try {
3666
- const entries = await fs17.readdir(dir);
3667
- if (entries.length === 0) await fs17.remove(dir);
3345
+ const entries = await fs16.readdir(dir);
3346
+ if (entries.length === 0) await fs16.remove(dir);
3668
3347
  } catch {
3669
3348
  }
3670
3349
  }
@@ -3682,7 +3361,7 @@ async function runUninstallCommand(options) {
3682
3361
  }
3683
3362
 
3684
3363
  // src/commands/update.ts
3685
- import path27 from "path";
3364
+ import path25 from "path";
3686
3365
 
3687
3366
  // src/update/diff-generated-files.ts
3688
3367
  function diffGeneratedFiles() {
@@ -3709,7 +3388,7 @@ function summarizeLockDiff(before, after) {
3709
3388
 
3710
3389
  // src/update/lockfile.ts
3711
3390
  import { mkdir, readFile as readFile3, copyFile } from "fs/promises";
3712
- import path26 from "path";
3391
+ import path24 from "path";
3713
3392
  async function checkLock(root) {
3714
3393
  const lock = await readJson(hausPath(root, "haus.lock.json")) ?? [];
3715
3394
  const hasValidVersions = lock.every(
@@ -3730,7 +3409,7 @@ async function applyLock(root) {
3730
3409
  try {
3731
3410
  const backupDir = hausPath(root, "backups");
3732
3411
  await mkdir(backupDir, { recursive: true });
3733
- await copyFile(lockPath, path26.join(backupDir, `haus.lock.${Date.now()}.json`));
3412
+ await copyFile(lockPath, path24.join(backupDir, `haus.lock.${Date.now()}.json`));
3734
3413
  } catch {
3735
3414
  }
3736
3415
  const enriched = await Promise.all(
@@ -3752,7 +3431,7 @@ function diffLock(before, after) {
3752
3431
  }
3753
3432
  async function hasLocalOverrides(root) {
3754
3433
  try {
3755
- await readFile3(path26.join(root, ".claude", "settings.json"), "utf8");
3434
+ await readFile3(path24.join(root, ".claude", "settings.json"), "utf8");
3756
3435
  return true;
3757
3436
  } catch {
3758
3437
  return false;
@@ -3764,7 +3443,7 @@ var NPM_PACKAGE_NAME2 = "@haus-tech/haus-workflow";
3764
3443
  async function runUpdate(options) {
3765
3444
  const root = process.cwd();
3766
3445
  if (options.check) {
3767
- const pkgJson2 = await readJson(path27.join(packageRoot(), "package.json"));
3446
+ const pkgJson2 = await readJson(path25.join(packageRoot(), "package.json"));
3768
3447
  const currentVersion2 = pkgJson2?.version ?? "0.0.0";
3769
3448
  const [status, npmVersion, latestCatalogTag] = await Promise.all([
3770
3449
  checkLock(root),
@@ -3791,7 +3470,7 @@ async function runUpdate(options) {
3791
3470
  if (!status.ok) process.exitCode = 1;
3792
3471
  return;
3793
3472
  }
3794
- const pkgJson = await readJson(path27.join(packageRoot(), "package.json"));
3473
+ const pkgJson = await readJson(path25.join(packageRoot(), "package.json"));
3795
3474
  const currentVersion = pkgJson?.version ?? "0.0.0";
3796
3475
  const npmStatus = await fetchNpmVersionStatus(currentVersion);
3797
3476
  if (npmStatus.updateAvailable && npmStatus.latest !== null) {
@@ -3821,8 +3500,8 @@ async function runUpdate(options) {
3821
3500
  }
3822
3501
 
3823
3502
  // src/commands/validate-catalog.ts
3824
- import fs18 from "fs";
3825
- import path28 from "path";
3503
+ import fs17 from "fs";
3504
+ import path26 from "path";
3826
3505
 
3827
3506
  // library/catalog/validation-rules.json
3828
3507
  var validation_rules_default = {
@@ -4071,23 +3750,23 @@ function auditShippedFiles(manifestDir, items) {
4071
3750
  const failures = [];
4072
3751
  for (const item of items) {
4073
3752
  if (!item.path) continue;
4074
- const absPath = path28.join(manifestDir, item.path);
3753
+ const absPath = path26.join(manifestDir, item.path);
4075
3754
  if (item.type === "skill") {
4076
- const skillMd = path28.join(absPath, "SKILL.md");
4077
- if (!fs18.existsSync(skillMd)) {
4078
- failures.push(`${item.id}: missing ${path28.relative(manifestDir, skillMd)}`);
3755
+ const skillMd = path26.join(absPath, "SKILL.md");
3756
+ if (!fs17.existsSync(skillMd)) {
3757
+ failures.push(`${item.id}: missing ${path26.relative(manifestDir, skillMd)}`);
4079
3758
  continue;
4080
3759
  }
4081
- const text = fs18.readFileSync(skillMd, "utf8");
3760
+ const text = fs17.readFileSync(skillMd, "utf8");
4082
3761
  for (const section of REQUIRED_SKILL_SECTIONS) {
4083
3762
  if (!text.includes(section)) failures.push(`${item.id}: SKILL.md missing ${section}`);
4084
3763
  }
4085
3764
  } else if (item.type === "agent") {
4086
- if (!fs18.existsSync(absPath)) {
3765
+ if (!fs17.existsSync(absPath)) {
4087
3766
  failures.push(`${item.id}: missing agent file ${item.path}`);
4088
3767
  continue;
4089
3768
  }
4090
- const text = fs18.readFileSync(absPath, "utf8");
3769
+ const text = fs17.readFileSync(absPath, "utf8");
4091
3770
  if (!text.startsWith("---")) failures.push(`${item.id}: agent file missing YAML frontmatter`);
4092
3771
  for (const section of REQUIRED_AGENT_SECTIONS) {
4093
3772
  if (!text.includes(section)) failures.push(`${item.id}: agent file missing ${section}`);
@@ -4098,7 +3777,7 @@ function auditShippedFiles(manifestDir, items) {
4098
3777
  failures.push(`${item.id}: agent file contains disallowed phrase "${phrase}"`);
4099
3778
  }
4100
3779
  } else if (item.type === "template") {
4101
- if (!fs18.existsSync(absPath)) {
3780
+ if (!fs17.existsSync(absPath)) {
4102
3781
  failures.push(`${item.id}: missing template file ${item.path}`);
4103
3782
  }
4104
3783
  }
@@ -4109,11 +3788,11 @@ function auditMarkdownContent(manifestDir) {
4109
3788
  const failures = [];
4110
3789
  const dirs = ["skills", "agents"];
4111
3790
  for (const dir of dirs) {
4112
- const abs = path28.join(manifestDir, dir);
4113
- if (!fs18.existsSync(abs)) continue;
3791
+ const abs = path26.join(manifestDir, dir);
3792
+ if (!fs17.existsSync(abs)) continue;
4114
3793
  walkMd(abs, (file) => {
4115
- const text = fs18.readFileSync(file, "utf8");
4116
- const rel = path28.relative(manifestDir, file);
3794
+ const text = fs17.readFileSync(file, "utf8");
3795
+ const rel = path26.relative(manifestDir, file);
4117
3796
  const lines = text.split(/\r?\n/);
4118
3797
  for (let i = 0; i < lines.length; i++) {
4119
3798
  const line2 = lines[i] ?? "";
@@ -4132,8 +3811,8 @@ function auditMarkdownContent(manifestDir) {
4132
3811
  return failures;
4133
3812
  }
4134
3813
  function walkMd(dir, fn) {
4135
- for (const entry of fs18.readdirSync(dir, { withFileTypes: true })) {
4136
- const full = path28.join(dir, entry.name);
3814
+ for (const entry of fs17.readdirSync(dir, { withFileTypes: true })) {
3815
+ const full = path26.join(dir, entry.name);
4137
3816
  if (entry.isDirectory()) walkMd(full, fn);
4138
3817
  else if (entry.name.endsWith(".md")) fn(full);
4139
3818
  }
@@ -4144,8 +3823,8 @@ async function runValidateCatalog(manifestPath) {
4144
3823
  process.exitCode = 1;
4145
3824
  return;
4146
3825
  }
4147
- const abs = path28.resolve(process.cwd(), manifestPath);
4148
- const manifestDir = path28.dirname(abs);
3826
+ const abs = path26.resolve(process.cwd(), manifestPath);
3827
+ const manifestDir = path26.dirname(abs);
4149
3828
  const data = await readJson(abs);
4150
3829
  if (!data?.items) {
4151
3830
  error(`Could not read catalog manifest at ${abs}`);
@@ -4174,7 +3853,7 @@ async function runValidateCatalog(manifestPath) {
4174
3853
  }
4175
3854
 
4176
3855
  // src/commands/workspace.ts
4177
- import path29 from "path";
3856
+ import path27 from "path";
4178
3857
  import YAML from "yaml";
4179
3858
  async function runWorkspace(action) {
4180
3859
  if (action === "init") {
@@ -4207,7 +3886,7 @@ relationships: []
4207
3886
  const summaries = [];
4208
3887
  const ownership = {};
4209
3888
  for (const repo of repos) {
4210
- const repoRoot = path29.resolve(process.cwd(), repo.path);
3889
+ const repoRoot = path27.resolve(process.cwd(), repo.path);
4211
3890
  const result = await scanProject(repoRoot, "fast");
4212
3891
  summaries.push({
4213
3892
  name: repo.name,
@@ -4243,7 +3922,7 @@ ${summaries.map(
4243
3922
  // src/cli.ts
4244
3923
  function cliVersion() {
4245
3924
  try {
4246
- const pkgPath = path30.join(packageRoot(), "package.json");
3925
+ const pkgPath = path28.join(packageRoot(), "package.json");
4247
3926
  const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
4248
3927
  return pkg.version ?? "0.0.0";
4249
3928
  } catch {
@@ -4253,7 +3932,7 @@ function cliVersion() {
4253
3932
  var program = new Command();
4254
3933
  function validateRuntimeNodeVersion() {
4255
3934
  try {
4256
- const pkgPath = path30.join(packageRoot(), "package.json");
3935
+ const pkgPath = path28.join(packageRoot(), "package.json");
4257
3936
  const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
4258
3937
  const requiredRange = pkg.engines?.node;
4259
3938
  if (requiredRange && !satisfiesVersion(process.version, requiredRange)) {