@haus-tech/haus-workflow 0.10.0 → 0.10.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/CHANGELOG.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.10.1](https://github.com/WeAreHausTech/haus-workflow/compare/v0.10.0...v0.10.1) (2026-05-29)
4
+
3
5
  ## [0.10.0](https://github.com/WeAreHausTech/haus-workflow/compare/v0.9.0...v0.10.0) (2026-05-29)
4
6
 
5
7
  ### Features
package/README.md CHANGED
@@ -1,8 +1,6 @@
1
- # haus
1
+ # Haus Workflow
2
2
 
3
- CLI that scans a project, recommends AI context assets for the stack, and writes controlled outputs into `.claude/` and `.haus-workflow/`.
4
-
5
- > **Internal Haus tool.**
3
+ > Internal Haus tool. Open-source but unsupported for external use. No external issues, PRs, or roadmap commitments accepted.
6
4
 
7
5
  ---
8
6
 
package/dist/cli.js CHANGED
@@ -70,8 +70,12 @@ async function syncRemoteCatalog() {
70
70
  return { newItems: [], unchanged: 0, failed: [] };
71
71
  }
72
72
  await fs.ensureDir(CACHE_DIR);
73
- await fs.writeFile(path.join(CACHE_DIR, "manifest.json"), `${JSON.stringify({ items }, null, 2)}
74
- `, "utf8");
73
+ await fs.writeFile(
74
+ path.join(CACHE_DIR, "manifest.json"),
75
+ `${JSON.stringify({ items }, null, 2)}
76
+ `,
77
+ "utf8"
78
+ );
75
79
  const newItems = [];
76
80
  let unchanged = 0;
77
81
  const failed = [];
@@ -374,10 +378,14 @@ import fs4 from "fs-extra";
374
378
  async function assertPostApplySettingsMatchCanonical(root, canonical) {
375
379
  const written = await readJson(claudePath(root, "settings.json"));
376
380
  if (written == null || typeof written !== "object") {
377
- throw new Error("haus: post-apply self-check failed: .claude/settings.json missing or unreadable");
381
+ throw new Error(
382
+ "haus: post-apply self-check failed: .claude/settings.json missing or unreadable"
383
+ );
378
384
  }
379
385
  if (!isDeepStrictEqual(canonical, written)) {
380
- throw new Error("haus: post-apply self-check failed: .claude/settings.json does not match canonical hook contract");
386
+ throw new Error(
387
+ "haus: post-apply self-check failed: .claude/settings.json does not match canonical hook contract"
388
+ );
381
389
  }
382
390
  }
383
391
  async function verifyProjectSettingsHooksContract(root) {
@@ -561,7 +569,11 @@ import fs7 from "fs-extra";
561
569
  var STABLE_ID2 = "template.way-of-work";
562
570
  var SCHEMA_VERSION2 = "1";
563
571
  var TEMPLATE_REL = "library/global/templates/haus-way-of-work.md";
564
- var CATALOG_CACHE_TEMPLATE = path8.join(os3.homedir(), CATALOG_CACHE_SUBDIR, "templates/haus-way-of-work.md");
572
+ var CATALOG_CACHE_TEMPLATE = path8.join(
573
+ os3.homedir(),
574
+ CATALOG_CACHE_SUBDIR,
575
+ "templates/haus-way-of-work.md"
576
+ );
565
577
  function makeWayOfWorkHeader(pkgVersion, contentHash) {
566
578
  return `<!-- HAUS-MANAGED id=${STABLE_ID2} v=${SCHEMA_VERSION2} source=@haus-tech/haus-workflow@${pkgVersion} hash=${contentHash} -->`;
567
579
  }
@@ -648,7 +660,12 @@ async function writeClaudeFiles(root, dryRun, selectedIds) {
648
660
  const wayOfWorkPath = await writeWayOfWork(root, hausVersion, dryRun);
649
661
  const projectFactsPath = await writeProjectFacts(root, hausVersion, dryRun);
650
662
  const p6Files = [rootClaudeMdPath, projectFactsPath, ...wayOfWorkPath ? [wayOfWorkPath] : []];
651
- const files = dryRun ? [...coreFiles, ...p6Files] : [...coreFiles, ...p6Files, hausPath(root, "selected-context.json"), hausPath(root, "haus.lock.json")];
663
+ const files = dryRun ? [...coreFiles, ...p6Files] : [
664
+ ...coreFiles,
665
+ ...p6Files,
666
+ hausPath(root, "selected-context.json"),
667
+ hausPath(root, "haus.lock.json")
668
+ ];
652
669
  const hookSettings = await loadClaudeHooksSettings();
653
670
  await writeManagedJson(root, claudePath(root, "settings.json"), hookSettings, dryRun);
654
671
  if (!dryRun) await assertPostApplySettingsMatchCanonical(root, hookSettings);
@@ -656,7 +673,12 @@ async function writeClaudeFiles(root, dryRun, selectedIds) {
656
673
  if (!await fs8.pathExists(configPath)) {
657
674
  await writeManagedJson(root, configPath, DEFAULT_HOOKS_CONFIG, dryRun);
658
675
  }
659
- await writeManagedText(root, claudePath(root, "commands", "haus-doctor.md"), "Run `haus doctor`.", dryRun);
676
+ await writeManagedText(
677
+ root,
678
+ claudePath(root, "commands", "haus-doctor.md"),
679
+ "Run `haus doctor`.",
680
+ dryRun
681
+ );
660
682
  await writeManagedText(
661
683
  root,
662
684
  claudePath(root, "commands", "haus-review.md"),
@@ -680,7 +702,9 @@ async function writeClaudeFiles(root, dryRun, selectedIds) {
680
702
  const manifestDir = path9.dirname(manifestPath);
681
703
  const manifest = await readJson(manifestPath) ?? { items: [] };
682
704
  const manifestById = new Map((manifest.items ?? []).map((item) => [item.id, item]));
683
- const cacheManifest = await readJson(path9.join(CACHE_DIR, "manifest.json"));
705
+ const cacheManifest = await readJson(
706
+ path9.join(CACHE_DIR, "manifest.json")
707
+ );
684
708
  const cacheManifestById = new Map((cacheManifest?.items ?? []).map((item) => [item.id, item]));
685
709
  const installedPathsByItem = /* @__PURE__ */ new Map();
686
710
  const installedIds = /* @__PURE__ */ new Set();
@@ -708,7 +732,9 @@ async function writeClaudeFiles(root, dryRun, selectedIds) {
708
732
  if (await fs8.pathExists(sourcePath)) {
709
733
  if (dryRun) {
710
734
  const exists = await fs8.pathExists(destination);
711
- log(`${displayPath(root, destination)}: ${exists ? "would overwrite" : "would create"} (${item.id})`);
735
+ log(
736
+ `${displayPath(root, destination)}: ${exists ? "would overwrite" : "would create"} (${item.id})`
737
+ );
712
738
  } else {
713
739
  await fs8.ensureDir(path9.dirname(destination));
714
740
  await fs8.copy(sourcePath, destination, { overwrite: true, errorOnExist: false });
@@ -718,7 +744,9 @@ async function writeClaudeFiles(root, dryRun, selectedIds) {
718
744
  installedPathsByItem.set(item.id, [...current, path9.relative(root, destination)]);
719
745
  installedIds.add(item.id);
720
746
  } else {
721
- warn(`Skipping ${item.id}: source not found at ${sourcePath} \u2014 run \`haus update\` to populate catalog cache`);
747
+ warn(
748
+ `Skipping ${item.id}: source not found at ${sourcePath} \u2014 run \`haus update\` to populate catalog cache`
749
+ );
722
750
  }
723
751
  }
724
752
  if (dryRun) return [...new Set(files)];
@@ -726,7 +754,12 @@ async function writeClaudeFiles(root, dryRun, selectedIds) {
726
754
  await writeManagedJson(
727
755
  root,
728
756
  hausPath(root, "selected-context.json"),
729
- installedItems.map((r) => ({ id: r.id, type: r.type, reason: r.reason, confidenceLevel: r.confidenceLevel })),
757
+ installedItems.map((r) => ({
758
+ id: r.id,
759
+ type: r.type,
760
+ reason: r.reason,
761
+ confidenceLevel: r.confidenceLevel
762
+ })),
730
763
  false
731
764
  );
732
765
  const lock = await Promise.all(
@@ -831,7 +864,9 @@ async function runApply(options) {
831
864
  const catalogItemCount = selectedIds !== void 0 ? selectedIds.length : rec?.recommended.length ?? 0;
832
865
  if (catalogItemCount > 0 && !await cacheHasItems()) {
833
866
  if (isDryRun) {
834
- warn("Catalog cache is empty \u2014 `haus apply --write` will skip catalog items. Run `haus update` first.");
867
+ warn(
868
+ "Catalog cache is empty \u2014 `haus apply --write` will skip catalog items. Run `haus update` first."
869
+ );
835
870
  } else {
836
871
  error(
837
872
  "Catalog cache is empty \u2014 cannot install catalog items. Run `haus update` first, or pass --allow-empty-cache to apply core files only."
@@ -893,7 +928,8 @@ async function runCatalogAudit() {
893
928
  const failures = [];
894
929
  for (const item of items) {
895
930
  const text = `${item.id} ${item.tags.join(" ")}`.toLowerCase();
896
- for (const word of FORBIDDEN) if (text.includes(word)) failures.push(`${item.id} has unsupported tag ${word}`);
931
+ for (const word of FORBIDDEN)
932
+ if (text.includes(word)) failures.push(`${item.id} has unsupported tag ${word}`);
897
933
  }
898
934
  if (failures.length) {
899
935
  failures.forEach((f) => error(f));
@@ -913,7 +949,9 @@ var HOOK_ALIASES = {
913
949
  async function runConfig(key, action) {
914
950
  const hookKey = HOOK_ALIASES[key];
915
951
  if (!hookKey) {
916
- throw new Error(`Unknown config key "${key}". Valid keys: ${Object.keys(HOOK_ALIASES).join(", ")}`);
952
+ throw new Error(
953
+ `Unknown config key "${key}". Valid keys: ${Object.keys(HOOK_ALIASES).join(", ")}`
954
+ );
917
955
  }
918
956
  const root = process.cwd();
919
957
  const configPath = path12.join(root, CONFIG_PATH2);
@@ -939,7 +977,9 @@ function normalizeRecommendation(input2) {
939
977
  message: reason.message ?? item.reason ?? "legacy recommendation reason",
940
978
  weight: reason.weight ?? 0,
941
979
  ...reason.signal ? { signal: reason.signal } : {}
942
- })) ?? [{ code: "legacy-reason", message: item.reason ?? "legacy recommendation reason", weight: 0 }];
980
+ })) ?? [
981
+ { code: "legacy-reason", message: item.reason ?? "legacy recommendation reason", weight: 0 }
982
+ ];
943
983
  const confidence = item.confidence ?? 0;
944
984
  return {
945
985
  id: item.id,
@@ -984,7 +1024,10 @@ function normalizeRecommendation(input2) {
984
1024
  estimatedContextTokens: input2.estimatedContextTokens ?? recommended.length * 320,
985
1025
  selectedRules: input2.selectedRules ?? recommended.length,
986
1026
  skippedRules: input2.skippedRules ?? skipped.length,
987
- estimatedTokenReductionPct: input2.estimatedTokenReductionPct ?? Math.max(0, Math.round(skipped.length / Math.max(recommended.length + skipped.length, 1) * 100))
1027
+ estimatedTokenReductionPct: input2.estimatedTokenReductionPct ?? Math.max(
1028
+ 0,
1029
+ Math.round(skipped.length / Math.max(recommended.length + skipped.length, 1) * 100)
1030
+ )
988
1031
  };
989
1032
  }
990
1033
  function buildRecommendationExplanation(recommendation) {
@@ -1396,11 +1439,13 @@ async function scanProject(root, mode = "fast") {
1396
1439
  if (nodeEngine && !satisfiesVersion(process.version, nodeEngine)) {
1397
1440
  warnings.push(`Current Node ${process.version} does not satisfy package engine ${nodeEngine}`);
1398
1441
  }
1399
- if (safeFiles.some((f) => f.includes("docker-compose"))) crossRepoHints.push("Containerized services detected");
1442
+ if (safeFiles.some((f) => f.includes("docker-compose")))
1443
+ crossRepoHints.push("Containerized services detected");
1400
1444
  if (safeFiles.some((f) => f.includes("turbo.json") || f.includes("nx.json")))
1401
1445
  crossRepoHints.push("Monorepo orchestration detected");
1402
1446
  if (!safeFiles.some((f) => f.endsWith(".env.example"))) securityRisks.push("Missing env template");
1403
- if (safeFiles.some((f) => f.includes("wp-content/uploads"))) securityRisks.push("Uploads directory present");
1447
+ if (safeFiles.some((f) => f.includes("wp-content/uploads")))
1448
+ securityRisks.push("Uploads directory present");
1404
1449
  const context = {
1405
1450
  mode,
1406
1451
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -1420,7 +1465,11 @@ async function scanProject(root, mode = "fast") {
1420
1465
  composer: isRecord(composer?.require) ? Object.keys(composer.require) : []
1421
1466
  };
1422
1467
  const scanHashes = Object.fromEntries(
1423
- await Promise.all(safeFiles.map(async (f) => [f, hashText(await readFile(path14.join(root, f), "utf8"))]))
1468
+ await Promise.all(
1469
+ safeFiles.map(
1470
+ async (f) => [f, hashText(await readFile(path14.join(root, f), "utf8"))]
1471
+ )
1472
+ )
1424
1473
  );
1425
1474
  const repoSummary = renderSummary(context);
1426
1475
  await writeJson(hausPath(root, "context-map.json"), context);
@@ -1446,9 +1495,11 @@ function detectRoles(deps, files) {
1446
1495
  if (deps.includes("next") || files.some((f) => f.includes("next.config."))) roles.add("next-app");
1447
1496
  if (deps.includes("react")) roles.add("react-app");
1448
1497
  if (deps.includes("vite") || files.some((f) => f.includes("vite.config."))) roles.add("vite-app");
1449
- if (deps.includes("react-router") && deps.includes("@react-router/node")) roles.add("react-router-app");
1498
+ if (deps.includes("react-router") && deps.includes("@react-router/node"))
1499
+ roles.add("react-router-app");
1450
1500
  if (deps.includes("sanity")) roles.add("sanity-studio");
1451
- if (deps.includes("@strapi/strapi") || deps.some((d) => d.startsWith("@strapi/"))) roles.add("strapi-app");
1501
+ if (deps.includes("@strapi/strapi") || deps.some((d) => d.startsWith("@strapi/")))
1502
+ roles.add("strapi-app");
1452
1503
  if (deps.includes("expo")) roles.add("expo-app");
1453
1504
  if (deps.includes("@vendure/core")) roles.add("vendure-app");
1454
1505
  if (deps.some((d) => d.startsWith("@haus/vendure-")) || files.some((f) => f.includes("vendure-config")))
@@ -1457,7 +1508,8 @@ function detectRoles(deps, files) {
1457
1508
  if (deps.includes("graphql") || deps.includes("@nestjs/graphql")) roles.add("graphql-api");
1458
1509
  if (files.some((f) => f.endsWith("nx.json"))) roles.add("nx-monorepo");
1459
1510
  if (files.some((f) => f.endsWith("turbo.json"))) roles.add("turbo-monorepo");
1460
- if (files.some((f) => f.endsWith("artisan")) || deps.includes("laravel/framework")) roles.add("laravel-app");
1511
+ if (files.some((f) => f.endsWith("artisan")) || deps.includes("laravel/framework"))
1512
+ roles.add("laravel-app");
1461
1513
  if (deps.includes("laravel/nova")) roles.add("laravel-nova-app");
1462
1514
  const hasWpConfig = files.some((f) => f.endsWith("wp-config.php"));
1463
1515
  const hasBedrockLayout = files.some((f) => f.includes("web/app")) || deps.includes("roots/wordpress");
@@ -1493,7 +1545,8 @@ async function detectStacks(root, deps, files, packageManager) {
1493
1545
  if (deps.includes("react")) add("frontend", "react19");
1494
1546
  if (deps.includes("vue")) add("frontend", "vue");
1495
1547
  if (deps.includes("vite")) add("frontend", "vite8");
1496
- if (deps.includes("react-router") && deps.includes("@react-router/node")) add("frontend", "react-router-v7");
1548
+ if (deps.includes("react-router") && deps.includes("@react-router/node"))
1549
+ add("frontend", "react-router-v7");
1497
1550
  if (deps.includes("tailwindcss") || files.some((f) => f.includes("tailwind.config."))) {
1498
1551
  add("frontend", "tailwindcss");
1499
1552
  }
@@ -1512,8 +1565,10 @@ async function detectStacks(root, deps, files, packageManager) {
1512
1565
  if (deps.includes("react-native")) add("frontend", "react-native");
1513
1566
  if (deps.includes("i18next") || deps.includes("react-i18next")) add("tooling", "i18next");
1514
1567
  if (deps.includes("bullmq")) add("tooling", "bullmq");
1515
- if (files.some((f) => f === "Dockerfile" || f.startsWith("docker-compose"))) add("tooling", "docker");
1516
- if (deps.includes("pm2") || files.some((f) => f.includes("ecosystem.config"))) add("tooling", "pm2");
1568
+ if (files.some((f) => f === "Dockerfile" || f.startsWith("docker-compose")))
1569
+ add("tooling", "docker");
1570
+ if (deps.includes("pm2") || files.some((f) => f.includes("ecosystem.config")))
1571
+ add("tooling", "pm2");
1517
1572
  if (deps.some((d) => d.startsWith("@sentry/"))) add("tooling", "sentry");
1518
1573
  if (deps.includes("deployer/deployer")) add("tooling", "deployer-php");
1519
1574
  if (!deps.includes("prettier")) add("tooling", "missing-prettier");
@@ -1530,10 +1585,13 @@ async function detectStacks(root, deps, files, packageManager) {
1530
1585
  if (await hasNeedle(root, files, "NestFactory")) add("backend", "nestjs");
1531
1586
  if (await hasNeedle(root, files, "@VendurePlugin")) add("backend", "vendure3");
1532
1587
  if (deps.includes("graphql") || deps.includes("@nestjs/graphql")) add("backend", "graphql");
1533
- if (files.some((f) => f.endsWith(".graphql") || f.endsWith("schema.graphql"))) add("backend", "graphql");
1588
+ if (files.some((f) => f.endsWith(".graphql") || f.endsWith("schema.graphql")))
1589
+ add("backend", "graphql");
1534
1590
  if (deps.includes("laravel/framework")) add("backend", "laravel");
1535
- if (files.some((f) => f.includes("app/Providers/") || f.includes("routes/"))) add("backend", "laravel");
1536
- if (files.some((f) => f.endsWith("wp-config.php")) || deps.includes("roots/wordpress")) add("backend", "wordpress");
1591
+ if (files.some((f) => f.includes("app/Providers/") || f.includes("routes/")))
1592
+ add("backend", "laravel");
1593
+ if (files.some((f) => f.endsWith("wp-config.php")) || deps.includes("roots/wordpress"))
1594
+ add("backend", "wordpress");
1537
1595
  if (deps.includes("wpackagist-plugin/elementor") || deps.includes("wearehaus/elementor-pro") || deps.includes("wpackagist-theme/hello-elementor")) {
1538
1596
  add("backend", "elementor");
1539
1597
  }
@@ -1676,9 +1734,12 @@ import fs10 from "fs-extra";
1676
1734
  var NPM_PACKAGE_NAME = "@haus-tech/haus-workflow";
1677
1735
  async function fetchNpmVersionStatus(currentVersion) {
1678
1736
  try {
1679
- const res = await fetch(`https://registry.npmjs.org/${encodeURIComponent(NPM_PACKAGE_NAME)}/latest`, {
1680
- signal: AbortSignal.timeout(8e3)
1681
- });
1737
+ const res = await fetch(
1738
+ `https://registry.npmjs.org/${encodeURIComponent(NPM_PACKAGE_NAME)}/latest`,
1739
+ {
1740
+ signal: AbortSignal.timeout(8e3)
1741
+ }
1742
+ );
1682
1743
  if (!res.ok) return { current: currentVersion, latest: null, updateAvailable: false };
1683
1744
  const data = await res.json();
1684
1745
  const latest = data?.version;
@@ -1757,12 +1818,20 @@ async function runDoctor(options) {
1757
1818
  warn("- .haus-workflow/haus-way-of-work.md: no HAUS-MANAGED header (user-owned)");
1758
1819
  } else {
1759
1820
  const storedHashMatch = firstLine.match(/hash=(sha256-[a-f0-9]+)/);
1760
- const templatePath = path15.join(packageRoot(), "library", "global", "templates", "haus-way-of-work.md");
1821
+ const templatePath = path15.join(
1822
+ packageRoot(),
1823
+ "library",
1824
+ "global",
1825
+ "templates",
1826
+ "haus-way-of-work.md"
1827
+ );
1761
1828
  const templateContent = await readText(templatePath);
1762
1829
  if (storedHashMatch && templateContent) {
1763
1830
  const currentHash = hashText(templateContent);
1764
1831
  if (storedHashMatch[1] !== currentHash) {
1765
- warn("- .haus-workflow/haus-way-of-work.md: stale (template updated \u2014 run `haus apply --write`)");
1832
+ warn(
1833
+ "- .haus-workflow/haus-way-of-work.md: stale (template updated \u2014 run `haus apply --write`)"
1834
+ );
1766
1835
  } else {
1767
1836
  log("- .haus-workflow/haus-way-of-work.md: OK");
1768
1837
  }
@@ -1799,7 +1868,9 @@ async function runDoctor(options) {
1799
1868
  const currentVersion = pkgJson?.version ?? "0.0.0";
1800
1869
  const npmStatus = await fetchNpmVersionStatus(currentVersion);
1801
1870
  if (npmStatus.updateAvailable && npmStatus.latest !== null) {
1802
- warn(`- CLI UPDATE: ${currentVersion} \u2192 ${npmStatus.latest} available (run: npm install -g ${NPM_PACKAGE_NAME})`);
1871
+ warn(
1872
+ `- CLI UPDATE: ${currentVersion} \u2192 ${npmStatus.latest} available (run: npm install -g ${NPM_PACKAGE_NAME})`
1873
+ );
1803
1874
  process.exitCode = 1;
1804
1875
  } else if (npmStatus.latest !== null) {
1805
1876
  log(`- CLI: ${currentVersion} (up to date)`);
@@ -2030,7 +2101,9 @@ var ECOSYSTEM_COMPATIBLE_BACKENDS = {
2030
2101
  async function recommend(root, context) {
2031
2102
  const items = await loadCatalog(root);
2032
2103
  const setupAnswers = await readJson(hausPath(root, "setup-answers.json")) ?? {};
2033
- const sources = await readJson(hausPath(root, "sources-report.json")) ?? {};
2104
+ const sources = await readJson(
2105
+ hausPath(root, "sources-report.json")
2106
+ ) ?? {};
2034
2107
  const stackSet = buildStackSet(context);
2035
2108
  const depSet = new Set(context.dependencies.map((d) => d.toLowerCase()));
2036
2109
  const roleSet = new Set(context.repoRoles.map((r) => r.toLowerCase()));
@@ -2114,14 +2187,23 @@ async function recommend(root, context) {
2114
2187
  if (tagMatch) {
2115
2188
  pushReason("stack-match", "stack/dependency match", 30, `tag:${tagMatch}`);
2116
2189
  }
2117
- const goalMatch = item.tags.find((t) => goals.includes(t) || goals.includes(t.replace(/-/g, " ")));
2190
+ const goalMatch = item.tags.find(
2191
+ (t) => goals.includes(t) || goals.includes(t.replace(/-/g, " "))
2192
+ );
2118
2193
  if (goalMatch) {
2119
2194
  pushReason("goal-match", "guided goal match", 15, `goal:${goalMatch}`);
2120
2195
  }
2121
2196
  if (item.tags.includes(context.packageManager) || item.tags.includes(`${context.packageManager}4`) || item.tags.includes(`${context.packageManager}89`)) {
2122
- pushReason("package-manager-match", "package manager match", 10, `packageManager:${context.packageManager}`);
2197
+ pushReason(
2198
+ "package-manager-match",
2199
+ "package manager match",
2200
+ 10,
2201
+ `packageManager:${context.packageManager}`
2202
+ );
2123
2203
  }
2124
- const configSignal = item.tags.find((t) => context.warnings.join(" ").toLowerCase().includes(t.toLowerCase()));
2204
+ const configSignal = item.tags.find(
2205
+ (t) => context.warnings.join(" ").toLowerCase().includes(t.toLowerCase())
2206
+ );
2125
2207
  if (configSignal) {
2126
2208
  pushReason("config-signal-match", "config signal match", 20, `warning:${configSignal}`);
2127
2209
  }
@@ -2208,9 +2290,15 @@ async function recommend(root, context) {
2208
2290
  pushSkipReason("source-approval", "Source not approved", 100);
2209
2291
  }
2210
2292
  if (securityRiskCount > 0 && !isDefaultBaseline && (item.tags.includes("security") || item.id.includes("security"))) {
2211
- pushSkipReason("security-risk-penalty", "Security-tagged item penalized by active risk signals", 20);
2293
+ pushSkipReason(
2294
+ "security-risk-penalty",
2295
+ "Security-tagged item penalized by active risk signals",
2296
+ 20
2297
+ );
2212
2298
  }
2213
- const positiveReasonCodes = new Set(reasons.map((r) => r.code).filter((c) => c !== "default-baseline"));
2299
+ const positiveReasonCodes = new Set(
2300
+ reasons.map((r) => r.code).filter((c) => c !== "default-baseline")
2301
+ );
2214
2302
  const hasRoleSignal = positiveReasonCodes.has("repo-role-match");
2215
2303
  const hasDepOrStackSignal = positiveReasonCodes.has("stack-match") || positiveReasonCodes.has("requires-any-match");
2216
2304
  if (hasRoleSignal && !hasDepOrStackSignal && !isDefaultBaseline && requiresAny.length === 0) {
@@ -2281,7 +2369,11 @@ async function recommend(root, context) {
2281
2369
  };
2282
2370
  }
2283
2371
  function buildStackSet(context) {
2284
- return new Set([...context.repoRoles, ...Object.values(context.detectedStacks).flat()].map((x) => x.toLowerCase()));
2372
+ return new Set(
2373
+ [...context.repoRoles, ...Object.values(context.detectedStacks).flat()].map(
2374
+ (x) => x.toLowerCase()
2375
+ )
2376
+ );
2285
2377
  }
2286
2378
  function inferRepoEcosystems(roles) {
2287
2379
  const ecosystems = /* @__PURE__ */ new Set();
@@ -2768,7 +2860,10 @@ async function applyInstall(options = {}) {
2768
2860
  }
2769
2861
  if (!dryRun && !check) {
2770
2862
  await writeSettings(mergedSettings);
2771
- const manifest = buildManifest(source, manifestFiles, [...existingManifest?.hooks ?? [], ...addedIds]);
2863
+ const manifest = buildManifest(source, manifestFiles, [
2864
+ ...existingManifest?.hooks ?? [],
2865
+ ...addedIds
2866
+ ]);
2772
2867
  await writeManifest(manifest);
2773
2868
  }
2774
2869
  return result;
@@ -2812,7 +2907,9 @@ async function runInstall(options) {
2812
2907
  process.exitCode = 1;
2813
2908
  } else if (!options.check && !options.dryRun) {
2814
2909
  const total = result.created.length + result.updated.length;
2815
- log(`haus install complete (${total} file(s) written, ${result.hookIds.length} hook(s) added)`);
2910
+ log(
2911
+ `haus install complete (${total} file(s) written, ${result.hookIds.length} hook(s) added)`
2912
+ );
2816
2913
  }
2817
2914
  } catch (err) {
2818
2915
  error(`haus install failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -2821,7 +2918,12 @@ async function runInstall(options) {
2821
2918
  }
2822
2919
 
2823
2920
  // src/memory/memory-store.ts
2824
- var FILES = ["project-learnings.md", "decisions.md", "recurring-issues.md", "client-context.md"];
2921
+ var FILES = [
2922
+ "project-learnings.md",
2923
+ "decisions.md",
2924
+ "recurring-issues.md",
2925
+ "client-context.md"
2926
+ ];
2825
2927
  async function ensureMemory(root) {
2826
2928
  await Promise.all(
2827
2929
  FILES.map(async (name) => {
@@ -2878,7 +2980,10 @@ async function runMemory(subcommand, options) {
2878
2980
  return;
2879
2981
  }
2880
2982
  const compact = `Task: ${options.task ?? "n/a"}
2881
- ${text}`.slice(0, options.fromHook ? 1200 : 4e3);
2983
+ ${text}`.slice(
2984
+ 0,
2985
+ options.fromHook ? 1200 : 4e3
2986
+ );
2882
2987
  log(compact);
2883
2988
  return;
2884
2989
  }
@@ -2979,7 +3084,9 @@ async function runUninstall(options = {}) {
2979
3084
  }
2980
3085
  const currentHash = `sha256-${crypto3.createHash("sha256").update(content).digest("hex")}`;
2981
3086
  if (currentHash !== entry.hash && !force) {
2982
- warn(`Skipping user-edited haus file (hash mismatch): ${entry.destPath} \u2014 use --force to delete`);
3087
+ warn(
3088
+ `Skipping user-edited haus file (hash mismatch): ${entry.destPath} \u2014 use --force to delete`
3089
+ );
2983
3090
  result.skipped.push(entry.destPath);
2984
3091
  continue;
2985
3092
  }
@@ -3066,7 +3173,9 @@ import { mkdir, readFile as readFile2, copyFile } from "fs/promises";
3066
3173
  import path22 from "path";
3067
3174
  async function checkLock(root) {
3068
3175
  const lock = await readJson(hausPath(root, "haus.lock.json")) ?? [];
3069
- const hasValidVersions = lock.every((item) => !item.version || normalizeVersion(item.version) !== null);
3176
+ const hasValidVersions = lock.every(
3177
+ (item) => !item.version || normalizeVersion(item.version) !== null
3178
+ );
3070
3179
  const catalogRef = lock[0]?.catalogRef ?? null;
3071
3180
  return { ok: lock.length > 0 && hasValidVersions, count: lock.length, catalogRef };
3072
3181
  }
@@ -3179,7 +3288,9 @@ import path25 from "path";
3179
3288
  // src/catalog/allowed-stacks.ts
3180
3289
  import path24 from "path";
3181
3290
  async function readAllowedStacks(root) {
3182
- const data = await readJson(path24.join(root, "library", "catalog", "allowed-stacks.json"));
3291
+ const data = await readJson(
3292
+ path24.join(root, "library", "catalog", "allowed-stacks.json")
3293
+ );
3183
3294
  return data?.stacks ?? [];
3184
3295
  }
3185
3296
 
@@ -3204,7 +3315,12 @@ var FORBIDDEN_TAGS = [
3204
3315
  var BANNED_AGENT_PHRASES = ["autonomous", "swarm", "delegate", "orchestrat", "marketplace"];
3205
3316
  var REQUIRED_SKILL_SECTIONS = ["## Use when", "## Do not use when"];
3206
3317
  var REQUIRED_AGENT_SECTIONS = ["## Use when", "## Do not use when", "## Verification"];
3207
- var RISKY_INSTALL_PATTERNS = [/\bnpx\s+-y\b/i, /\bnpx\s+--yes\b/i, /\byarn\s+dlx\b/i, /\bpnpm\s+dlx\b/i];
3318
+ var RISKY_INSTALL_PATTERNS = [
3319
+ /\bnpx\s+-y\b/i,
3320
+ /\bnpx\s+--yes\b/i,
3321
+ /\byarn\s+dlx\b/i,
3322
+ /\bpnpm\s+dlx\b/i
3323
+ ];
3208
3324
  var ALLOWED_NPX_PATTERN = /\bnpx\s+tsx\b/i;
3209
3325
  var ANY_NPX_PATTERN = /\bnpx\s+\S+/i;
3210
3326
  var HTTP_URL_PATTERN = /^http:\/\//i;
@@ -3301,7 +3417,8 @@ function auditShippedFiles(manifestDir, items) {
3301
3417
  }
3302
3418
  const lower = text.toLowerCase();
3303
3419
  for (const phrase of BANNED_AGENT_PHRASES) {
3304
- if (lower.includes(phrase)) failures.push(`${item.id}: agent file contains disallowed phrase "${phrase}"`);
3420
+ if (lower.includes(phrase))
3421
+ failures.push(`${item.id}: agent file contains disallowed phrase "${phrase}"`);
3305
3422
  }
3306
3423
  } else if (item.type === "template") {
3307
3424
  if (!fs16.existsSync(absPath)) {
@@ -3374,7 +3491,13 @@ async function runValidateCatalog(manifestPath) {
3374
3491
  }
3375
3492
  }
3376
3493
  }
3377
- const allFailures = [...structureFailures, ...stackFailures, ...fileFailures, ...contentFailures, ...tagFailures];
3494
+ const allFailures = [
3495
+ ...structureFailures,
3496
+ ...stackFailures,
3497
+ ...fileFailures,
3498
+ ...contentFailures,
3499
+ ...tagFailures
3500
+ ];
3378
3501
  if (allFailures.length) {
3379
3502
  allFailures.forEach((f) => error(f));
3380
3503
  process.exitCode = 1;
@@ -3481,7 +3604,10 @@ program.command("scan").option("--json").action(runScan);
3481
3604
  program.command("recommend").option("--json").action(runRecommend);
3482
3605
  program.command("setup-project").option("--guided").option("--fast").option("--json").action(runSetupProject);
3483
3606
  program.command("doctor").option("--hooks", "Verify .claude/settings.json matches the hook contract").action(runDoctor);
3484
- program.command("apply").option("--dry-run").option("--write").option("--select", "Interactively select catalog items before applying").option("--allow-empty-cache", "Apply core files only when catalog cache is empty (skip catalog items without error)").action(runApply);
3607
+ program.command("apply").option("--dry-run").option("--write").option("--select", "Interactively select catalog items before applying").option(
3608
+ "--allow-empty-cache",
3609
+ "Apply core files only when catalog cache is empty (skip catalog items without error)"
3610
+ ).action(runApply);
3485
3611
  program.command("undo").option("-y, --yes", "Skip confirmation").action(runUndo);
3486
3612
  program.command("explain-recommendation").option("--json").action(runExplainRecommendation);
3487
3613
  program.command("context").option("--task <task>").option("--from-hook").option("--json").option("--verbose").action(runContext);
@@ -407,14 +407,14 @@
407
407
  },
408
408
  {
409
409
  "id": "haus.prettier-setup",
410
- "version": "1.0.0",
410
+ "version": "1.1.0",
411
411
  "source": "haus",
412
412
  "type": "skill",
413
413
  "path": "skills/prettier-setup",
414
414
  "title": "Haus Prettier setup",
415
- "purpose": "Install `@haus-tech/prettier-config` and wire `.prettierrc` to the shared config.",
416
- "whenToUse": "Use when Prettier is missing from a haus repo or when migrating an ad-hoc Prettier config to the shared package.",
417
- "whenNotToUse": "Do not use when Prettier is already configured against `@haus-tech/prettier-config`.",
415
+ "purpose": "Install `@haus-tech/tech-config` and wire `.prettierrc` to the shared prettier config via the `/prettier` subpath export.",
416
+ "whenToUse": "Use when Prettier is missing from a haus repo, when migrating an ad-hoc config, or when migrating from the deprecated `@haus-tech/prettier-config`.",
417
+ "whenNotToUse": "Do not use when Prettier is already configured against `@haus-tech/tech-config/prettier`.",
418
418
  "references": [
419
419
  "references/conventions.md",
420
420
  "references/scope.md",
@@ -438,7 +438,7 @@
438
438
  },
439
439
  {
440
440
  "id": "haus.eslint-setup",
441
- "version": "1.0.0",
441
+ "version": "1.1.0",
442
442
  "source": "haus",
443
443
  "type": "skill",
444
444
  "path": "skills/eslint-setup",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haus-tech/haus-workflow",
3
- "version": "0.10.0",
3
+ "version": "0.10.1",
4
4
  "description": "Haus AI workflow CLI for Claude Code.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -30,8 +30,6 @@
30
30
  "format:write": "prettier --write src scripts",
31
31
  "typecheck": "tsc --noEmit",
32
32
  "typecheck:scripts": "tsc --noEmit --project tsconfig.scripts.json",
33
- "cleanup:status": "tsx scripts/cleanup-status.ts",
34
- "bench:hooks": "tsx scripts/bench-hooks.ts",
35
33
  "pack:local": "yarn pack",
36
34
  "publish:dry": "npm pack --dry-run",
37
35
  "publish:public": "yarn npm publish --access public",