@haus-tech/haus-workflow 0.32.0 → 1.0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,165 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.0](https://github.com/WeAreHausTech/haus-workflow/compare/v0.32.1...v1.0.0) (2026-07-02)
4
+
5
+ ### ⚠ BREAKING CHANGES
6
+
7
+ - **workflow:** the standalone /haus-setup, /haus-clone,
8
+ /haus-cloneandsetup, /haus-doctor, and /haus-fix slash commands no longer
9
+ exist. Use /haus-workflow <task> instead (e.g. /haus-workflow project:init,
10
+ /haus-workflow project:doctor). Existing global installs self-clean the
11
+ old files on the next `haus update`/`haus install` via the installer's
12
+ existing orphan-pruning.
13
+
14
+ - docs(decisions): add ADR-0011 for the haus-workflow command consolidation
15
+
16
+ Satisfies the decisions-gate CI check for the breaking-change command
17
+ removal + skill-install mechanism change in the prior commit.
18
+
19
+ - fix(workflow): clarify the Step 1 menu is two sequential AskUserQuestion calls
20
+
21
+ Copilot review flagged ambiguous wording ("split into two grouped
22
+ questions in one call") that could read as cramming both question
23
+ blocks into a single AskUserQuestion invocation. AskUserQuestion is
24
+ not guaranteed multi-question across every environment this skill
25
+ runs in, so make the instruction unambiguous: ask Question 1, read
26
+ the answer, then ask Question 2 as a second, separate call.
27
+
28
+ - feat(workflow): upgrade project:refresh to a full non-destructive sync
29
+
30
+ Previously project:refresh just ran `haus apply --write` (reapply from
31
+ the already-computed recommendation, no package/catalog update, no
32
+ rescan, no docs refresh). Upgrade it to: run `haus update` first (npm
33
+ package + catalog + global install), then re-run the project:init
34
+ pipeline in place (rescan, redo the deep docs read, re-recommend,
35
+ apply) — nothing removed first, contrasting with project:reinit which
36
+ wipes everything before re-running the same pipeline.
37
+
38
+ Moves the root-CLAUDE.md-imports check from a now-orphaned SKILL.md
39
+ section into references/init.md's own apply step, since project:init,
40
+ project:reinit, and project:refresh all funnel through it.
41
+
42
+ - feat(workflow): full project:refresh sync + SessionStart staleness hook
43
+
44
+ project:refresh now runs `haus update` (npm package + catalog) first,
45
+ then re-runs the project:init pipeline in place (rescan, redo docs,
46
+ re-recommend, apply) instead of just `haus apply --write`. The `update`
47
+ task's own post-run steps cascade into the same pipeline when run
48
+ inside an already-set-up project, so its "also refreshes this project"
49
+ scope note is now literally true.
50
+
51
+ Adds a new SessionStart hook (`haus.update-check`, in both
52
+ PROJECT_HOOK_FRAGMENTS and CANONICAL_HOOKS) running `haus update
53
+ --from-hook`: silent when the project is up to date, emits a
54
+ SessionStart hookSpecificOutput note nudging `/haus-workflow
55
+ project:refresh` when the npm package, catalog, or lockfile has
56
+ drifted. Never blocks session start on a network failure.
57
+
58
+ Fixes found by adversarial review before landing:
59
+
60
+ - fetchNpmVersionStatus had no test-mode gate, so the new --from-hook
61
+ tests hit the real npm registry; added HAUS_SKIP_NPM_CHECK (mirrors
62
+ HAUS_CATALOG_REMOTE_BASE's test-only-when-explicit pattern) rather
63
+ than gating on bare HAUS_TEST_MODE, which the whole test run already
64
+ sets and would have broken tests/npm-version.test.js's fetch mocks.
65
+ - checkLock() now runs before the npm/catalog network calls in the
66
+ hook check, so a non-haus directory costs nothing.
67
+ - matcher parity between the two hardcoded hook-fragment lists.
68
+ - project:refresh's step 1 now explicitly inherits the "After `haus
69
+ update`" reactions (version bump, WORKFLOW.md nudge, catalog
70
+ summary), which it was silently skipping despite running the same
71
+ command.
72
+ - runbook entry: doctor --hooks going red right after an upgrade,
73
+ before the next apply, is expected (same as any new required rule)
74
+ — documented with the fix (re-apply once).
75
+
76
+ yarn verify: 627/627 tests green.
77
+
78
+ - fix(workflow): drop expensive lock-hash check from the SessionStart hook, add help to the menu
79
+
80
+ Copilot review, two fixes:
81
+
82
+ - runFromHookCheck() called checkLock(), which hashes the content of
83
+ every installed managed path on every single SessionStart. Replaced
84
+ with a new readLockSummary() (count + catalogRef only, no hashing) —
85
+ the hook only ever needed those two cheap facts. Drops the
86
+ lock-drift reason from the hook's output entirely; doctor/project:fix
87
+ remain the tools for catching local file tamper, which is a
88
+ different concern than "is this project behind the installed
89
+ package/catalog" (what this hook actually checks).
90
+ - The Step 1 no-arg menu didn't offer `help`, even though it's a real
91
+ task in the alias table. Added as Question 2 option 4 (still within
92
+ the 4-option cap).
93
+
94
+ Test changes: replaced the lock-drift `--from-hook` test (no longer
95
+ applicable — the hook no longer looks at hashes) with a "healthy
96
+ project with a real lock stays silent" integration test, plus a new
97
+ direct unit test for the "npm behind" JSON-emission path (mocked
98
+ fetch, same style as tests/npm-version.test.js) — there's no way to
99
+ force fetchLatestCatalogTag/fetchNpmVersionStatus to report "behind"
100
+ via env vars alone, so this exercises the actual behind branch and its
101
+ JSON shape deterministically instead of only ever testing silence.
102
+
103
+ yarn verify: 628/628 tests green.
104
+
105
+ - fix(test): use exact hostname match instead of substring for npm registry mock
106
+
107
+ CodeQL flagged String(url).includes('registry.npmjs.org') as an
108
+ incomplete URL substring sanitization (a host like
109
+ evil.com/registry.npmjs.org or registry.npmjs.org.evil.com would also
110
+ match). Not exploitable here (url comes from our own hardcoded fetch
111
+ call, not untrusted input), but the check should still be precise —
112
+ parse the URL and compare new URL(url).hostname exactly.
113
+
114
+ - fix(workflow): stop false-positive catalog-behind nudges, fix docs
115
+
116
+ Copilot review, four fixes:
117
+
118
+ - docs/runbook.md: a backtick code span was split across a line wrap
119
+ ("`/haus-workflow\nproject:refresh`"), rendering broken. Rewrapped.
120
+ - docs/cli.md: --from-hook's description said it checks npm/catalog/
121
+ lock; it never inspected lock content (no hashing) and only reads
122
+ the lock to confirm this is a haus project and get its catalogRef.
123
+ Corrected the wording.
124
+ - readLockSummary() only ever read catalogRef from lock item 0. An
125
+ older/mixed lock could have it missing there while later items carry
126
+ it. Now searches all items for the first defined catalogRef.
127
+ - runFromHookCheck() fell back to 'main' when catalogRef was absent,
128
+ which then almost always compared "behind" against any real release
129
+ tag — a false-positive nudge, and a wrong claim ("installed from
130
+ main") when the ref was actually unknown. Now skips the catalog
131
+ comparison entirely when catalogRef is null.
132
+
133
+ New tests: readLockSummary's cross-item catalogRef fallback (and that
134
+ it never hashes file content, unlike checkLock), plus a regression
135
+ test proving no false "behind" nudge fires when catalogRef is missing
136
+ even if the catalog has newer tags (mocked GitHub tags API).
137
+
138
+ yarn verify: 633/633 tests green.
139
+
140
+ - fix(docs): avoid a prettier-reflowed broken inline code span in cli.md
141
+
142
+ The previous commit's own pre-commit prettier pass reflowed the
143
+ --from-hook bullet and split a `/haus-workflow project:refresh`
144
+ code span across the wrap (the same bug class Copilot flagged in
145
+ runbook.md moments earlier) — verified by re-running prettier on
146
+ docs/cli.md, docs/runbook.md, and SKILL.md directly; all three are
147
+ now stable ("unchanged") under formatting.
148
+
149
+ ### Features
150
+
151
+ - **workflow:** consolidate haus-\* commands into /haus-workflow skill ([#161](https://github.com/WeAreHausTech/haus-workflow/issues/161)) ([e5c34f7](https://github.com/WeAreHausTech/haus-workflow/commit/e5c34f73f723520041d54523f6cf384d890d6c99))
152
+
153
+ ### Bug Fixes
154
+
155
+ - **apply:** scope llms.txt cache to items actually installed ([#162](https://github.com/WeAreHausTech/haus-workflow/issues/162)) ([f331f08](https://github.com/WeAreHausTech/haus-workflow/commit/f331f08a5c2e58bff2d8beda062f30515661685e))
156
+
157
+ ## [0.32.1](https://github.com/WeAreHausTech/haus-workflow/compare/v0.32.0...v0.32.1) (2026-06-30)
158
+
159
+ ### Bug Fixes
160
+
161
+ - **security:** audit hardening — fail-closed, recommender gates, CI hardening ([#157](https://github.com/WeAreHausTech/haus-workflow/issues/157)) ([e545194](https://github.com/WeAreHausTech/haus-workflow/commit/e545194fd3d3eb553bc2955a7ca17574bd98be71))
162
+
3
163
  ## [0.32.0](https://github.com/WeAreHausTech/haus-workflow/compare/v0.31.1...v0.32.0) (2026-06-24)
4
164
 
5
165
  ### Features
package/dist/cli.js CHANGED
@@ -650,48 +650,103 @@ function parseManifest(json) {
650
650
  }
651
651
 
652
652
  // src/catalog/remote-catalog.ts
653
+ function isTestMode() {
654
+ return process.env["HAUS_TEST_MODE"] === "1" || process.env["NODE_ENV"] === "test";
655
+ }
653
656
  function getCacheDir() {
654
657
  return process.env["HAUS_CATALOG_CACHE_DIR_OVERRIDE"] ?? path3.join(os2.homedir(), CATALOG_CACHE_SUBDIR);
655
658
  }
656
659
  var cachedCatalogRef;
657
660
  var cachedBlobPaths;
661
+ function getBundledCatalogRef() {
662
+ try {
663
+ const manifestPath2 = path3.join(packageRoot(), "library/catalog/manifest.json");
664
+ const raw = fs2.readFileSync(manifestPath2, "utf8");
665
+ const data = JSON.parse(raw);
666
+ if (typeof data.version === "string" && data.version) {
667
+ return data.version.startsWith("v") ? data.version : `v${data.version}`;
668
+ }
669
+ } catch {
670
+ }
671
+ return void 0;
672
+ }
658
673
  function getResolvedCatalogRef() {
659
- return cachedCatalogRef ?? process.env["HAUS_CATALOG_REF"] ?? "main";
674
+ const resolved = cachedCatalogRef ?? process.env["HAUS_CATALOG_REF"] ?? getBundledCatalogRef();
675
+ if (!resolved) {
676
+ warn(
677
+ "Could not determine catalog ref from cache, env, or bundled snapshot \u2014 falling back to main (moving target)."
678
+ );
679
+ return "main";
680
+ }
681
+ return resolved;
660
682
  }
661
683
  function isCatalogRefResolved() {
662
684
  return cachedCatalogRef !== void 0 || process.env["HAUS_CATALOG_REF"] !== void 0;
663
685
  }
664
686
  async function resolveCatalogRef(opts) {
665
687
  const env = opts?.env ?? process.env;
666
- if (env["HAUS_CATALOG_REF"]) return env["HAUS_CATALOG_REF"];
688
+ if (env["HAUS_CATALOG_REF"]) {
689
+ if (env["HAUS_CATALOG_REF"] === "main") {
690
+ warn(
691
+ "HAUS_CATALOG_REF=main is set \u2014 fetching from the moving main branch. Pin to a release tag for reproducible installs."
692
+ );
693
+ }
694
+ return env["HAUS_CATALOG_REF"];
695
+ }
667
696
  const fetchLatest = opts?.fetchLatestTag ?? fetchLatestCatalogTag;
668
697
  const tag = await fetchLatest();
669
- return tag ?? "main";
698
+ if (tag !== null) return tag;
699
+ const fallback = opts?.fallbackRef;
700
+ if (fallback) {
701
+ warn(
702
+ `Tag resolution failed \u2014 using cached ref ${fallback}. To use latest, retry or set HAUS_CATALOG_REF explicitly.`
703
+ );
704
+ return fallback;
705
+ }
706
+ const bundled = getBundledCatalogRef();
707
+ if (bundled) {
708
+ warn(
709
+ `Tag resolution failed \u2014 using bundled snapshot ref ${bundled}. To use latest, retry or set HAUS_CATALOG_REF explicitly.`
710
+ );
711
+ return bundled;
712
+ }
713
+ warn(
714
+ "Tag resolution failed and no fallback ref is available. Set HAUS_CATALOG_REF explicitly to avoid fetching from main."
715
+ );
716
+ return "main";
670
717
  }
671
718
  async function remoteBase() {
672
- if (process.env["HAUS_CATALOG_REMOTE_BASE"]) {
719
+ if (isTestMode() && process.env["HAUS_CATALOG_REMOTE_BASE"]) {
673
720
  return process.env["HAUS_CATALOG_REMOTE_BASE"];
674
721
  }
675
722
  if (cachedCatalogRef === void 0) {
676
- cachedCatalogRef = await resolveCatalogRef();
723
+ cachedCatalogRef = await resolveCatalogRef({ fallbackRef: getBundledCatalogRef() });
677
724
  }
678
725
  return `${CATALOG_REPO_URL}/${cachedCatalogRef}`;
679
726
  }
680
727
  async function fetchText(url) {
681
728
  try {
682
729
  const res = await fetch(url, { signal: AbortSignal.timeout(1e4) });
683
- if (!res.ok) return null;
730
+ if (!res.ok) {
731
+ warn(`Catalog fetch HTTP ${res.status}: ${url}`);
732
+ return null;
733
+ }
684
734
  return await res.text();
685
- } catch {
735
+ } catch (e) {
736
+ warn(`Catalog fetch failed (${e instanceof Error ? e.constructor.name : String(e)}): ${url}`);
686
737
  return null;
687
738
  }
688
739
  }
689
740
  async function fetchBytes(url) {
690
741
  try {
691
742
  const res = await fetch(url, { signal: AbortSignal.timeout(1e4) });
692
- if (!res.ok) return null;
743
+ if (!res.ok) {
744
+ warn(`Catalog fetch HTTP ${res.status}: ${url}`);
745
+ return null;
746
+ }
693
747
  return Buffer.from(await res.arrayBuffer());
694
- } catch {
748
+ } catch (e) {
749
+ warn(`Catalog fetch failed (${e instanceof Error ? e.constructor.name : String(e)}): ${url}`);
695
750
  return null;
696
751
  }
697
752
  }
@@ -828,7 +883,7 @@ async function fetchGitHubRecursiveBlobPaths(ref) {
828
883
  }
829
884
  async function fetchCatalogBlobPaths(_base) {
830
885
  if (cachedBlobPaths) return cachedBlobPaths;
831
- if (process.env["HAUS_CATALOG_REMOTE_BASE"]) return null;
886
+ if (isTestMode() && process.env["HAUS_CATALOG_REMOTE_BASE"]) return null;
832
887
  const ref = getResolvedCatalogRef();
833
888
  const paths = await fetchGitHubRecursiveBlobPaths(ref);
834
889
  if (paths) cachedBlobPaths = paths;
@@ -838,7 +893,7 @@ async function listFilesUnderCatalogPrefix(prefix, base) {
838
893
  const normalized = prefix.replace(/\\/g, "/").replace(/\/+$/, "");
839
894
  const prefixSlash = `${normalized}/`;
840
895
  let relFiles;
841
- if (process.env["HAUS_CATALOG_REMOTE_BASE"]) {
896
+ if (isTestMode() && process.env["HAUS_CATALOG_REMOTE_BASE"]) {
842
897
  relFiles = await listMockPrefixFiles(base, normalized);
843
898
  } else {
844
899
  const blobs = await fetchCatalogBlobPaths(base);
@@ -985,7 +1040,7 @@ async function syncConfigItem(item, base) {
985
1040
  if (relFiles && relFiles.length > 0) {
986
1041
  try {
987
1042
  return await syncDirectoryFromPrefix(item, item.path, dest, base, {
988
- validateMarkdown: false,
1043
+ validateMarkdown: true,
989
1044
  requireSkillMd: false,
990
1045
  relFiles
991
1046
  });
@@ -1125,7 +1180,7 @@ function compareSemver(a, b) {
1125
1180
  return 0;
1126
1181
  }
1127
1182
  async function fetchLatestCatalogTag() {
1128
- if (process.env["HAUS_CATALOG_REMOTE_BASE"]) return null;
1183
+ if (isTestMode() && process.env["HAUS_CATALOG_REMOTE_BASE"]) return null;
1129
1184
  try {
1130
1185
  const res = await fetch(CATALOG_TAGS_API_URL, {
1131
1186
  signal: AbortSignal.timeout(5e3),
@@ -1756,6 +1811,13 @@ var PROJECT_HOOK_FRAGMENTS = [
1756
1811
  event: "Stop",
1757
1812
  matcher: "*",
1758
1813
  command: "haus decisions suggest --from-hook"
1814
+ },
1815
+ {
1816
+ id: "haus.update-check",
1817
+ gate: "keep",
1818
+ event: "SessionStart",
1819
+ matcher: "*",
1820
+ command: "haus update --from-hook"
1759
1821
  }
1760
1822
  ];
1761
1823
  async function readProjectSettings(root) {
@@ -1855,6 +1917,12 @@ var CANONICAL_HOOKS = {
1855
1917
  matcher: "*",
1856
1918
  hooks: [{ type: "command", command: "haus decisions suggest --from-hook" }]
1857
1919
  }
1920
+ ],
1921
+ SessionStart: [
1922
+ {
1923
+ matcher: "*",
1924
+ hooks: [{ type: "command", command: "haus update --from-hook" }]
1925
+ }
1858
1926
  ]
1859
1927
  }
1860
1928
  };
@@ -2290,15 +2358,32 @@ ${templateContent}`;
2290
2358
  return null;
2291
2359
  }
2292
2360
  const existingContent = existing.slice(firstLine.length + 1);
2293
- if (!parsed.hash && !force) {
2294
- warn(
2295
- `${printable}: managed header missing hash \u2014 cannot verify tamper status, skipping. Use --force to overwrite.`
2296
- );
2297
- return null;
2298
- }
2299
- if (parsed.hash && hashText(normaliseLF(existingContent)) !== parsed.hash && !force) {
2300
- warn(`${printable}: content modified by user \u2014 skipping. Use --force to overwrite.`);
2301
- return null;
2361
+ const bodyMatchesTemplate = hashText(normaliseLF(existingContent)) === contentHash;
2362
+ if (parsed.hash) {
2363
+ const bodyIntact = hashText(normaliseLF(existingContent)) === parsed.hash;
2364
+ if (!bodyIntact && !force) {
2365
+ warn(`${printable}: content modified by user \u2014 skipping. Use --force to overwrite.`);
2366
+ return null;
2367
+ }
2368
+ } else {
2369
+ if (!bodyMatchesTemplate && !force) {
2370
+ const migratedHeader = makeWorkflowHeader(
2371
+ pkgVersion,
2372
+ hashText(normaliseLF(existingContent))
2373
+ );
2374
+ const migrated = `${migratedHeader}
2375
+ ${existingContent}`;
2376
+ if (hasTextChanged(existing, migrated)) {
2377
+ if (dryRun) {
2378
+ log(`${printable}: migrating legacy hash header (body preserved)`);
2379
+ } else {
2380
+ await writeText(destPath, migrated);
2381
+ }
2382
+ } else if (dryRun) {
2383
+ log(`${printable}: unchanged`);
2384
+ }
2385
+ return destPath;
2386
+ }
2302
2387
  }
2303
2388
  if (!hasTextChanged(existing, next)) {
2304
2389
  if (dryRun) log(`${printable}: unchanged`);
@@ -2342,11 +2427,7 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
2342
2427
  };
2343
2428
  const pkgRoot = packageRoot();
2344
2429
  const hausVersion2 = (await readJson(path14.join(pkgRoot, "package.json")))?.version ?? "0.0.0";
2345
- const coreFiles = [
2346
- claudePath(root, "settings.json"),
2347
- claudePath(root, "rules", "haus.md"),
2348
- claudePath(root, "commands", "haus-doctor.md")
2349
- ];
2430
+ const coreFiles = [claudePath(root, "settings.json"), claudePath(root, "rules", "haus.md")];
2350
2431
  const rootClaudeMdPath = await writeRootClaudeMd(root, dryRun);
2351
2432
  const decisionsSeedPath = await writeDecisionsSeed(root, dryRun);
2352
2433
  const workflowPath = await writeWorkflow(root, hausVersion2, dryRun, opts.force);
@@ -2369,12 +2450,6 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
2369
2450
  await applyProjectSettingsMerge(root);
2370
2451
  await assertPostApplySettingsHausContract(root);
2371
2452
  }
2372
- await writeManagedText(
2373
- root,
2374
- claudePath(root, "commands", "haus-doctor.md"),
2375
- "Run `haus doctor`.",
2376
- dryRun
2377
- );
2378
2453
  const legacyReviewPath = claudePath(root, "commands", "haus-review.md");
2379
2454
  if (await fs14.pathExists(legacyReviewPath)) {
2380
2455
  const content2 = await fs14.readFile(legacyReviewPath, "utf8");
@@ -2389,6 +2464,20 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
2389
2464
  }
2390
2465
  }
2391
2466
  }
2467
+ const legacyDoctorPath = claudePath(root, "commands", "haus-doctor.md");
2468
+ if (await fs14.pathExists(legacyDoctorPath)) {
2469
+ const content2 = await fs14.readFile(legacyDoctorPath, "utf8");
2470
+ const stub = "Run `haus doctor`.";
2471
+ if (content2 === stub || content2 === `${stub}
2472
+ ` || content2 === `${stub}\r
2473
+ `) {
2474
+ if (dryRun) {
2475
+ say(`[dry-run] would remove stale ${displayPath(root, legacyDoctorPath)}`);
2476
+ } else {
2477
+ await fs14.remove(legacyDoctorPath);
2478
+ }
2479
+ }
2480
+ }
2392
2481
  await writeManagedText(
2393
2482
  root,
2394
2483
  claudePath(root, "rules", "haus.md"),
@@ -2407,7 +2496,7 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
2407
2496
  "results in plain language \u2014 never make them use a terminal or read JSON.",
2408
2497
  "- Set up / configure / fix / check \u2192 `haus setup-project`, `haus apply --write`, `haus doctor`",
2409
2498
  "- Update package + catalog \u2192 `haus update`",
2410
- "- The `/haus-workflow`, `/haus-setup`, `/haus-doctor`, and `/haus-fix` commands do the same.",
2499
+ "- `/haus-workflow <task>` does the same conversationally (e.g. `init`, `fix`, `doctor`, `reinit`).",
2411
2500
  ""
2412
2501
  ].join("\n"),
2413
2502
  dryRun
@@ -2491,11 +2580,50 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
2491
2580
  `${displayPath(root, destination)}: ${exists ? "would overwrite" : "would create"} (${item.id})`
2492
2581
  );
2493
2582
  } else if (item.type === "skill") {
2583
+ const skillFiles = (await fs14.readdir(sourcePath, { recursive: true })).filter(
2584
+ (f) => typeof f === "string" && f.endsWith(".md")
2585
+ );
2586
+ let skillValid = true;
2587
+ for (const relFile of skillFiles) {
2588
+ const mdPath = path14.join(sourcePath, relFile);
2589
+ const mdContent = await fs14.readFile(mdPath, "utf8");
2590
+ const mdValidation = validateCatalogItem(
2591
+ {
2592
+ id: manifestItem.id,
2593
+ type: "skill",
2594
+ path: manifestItem.path,
2595
+ source: manifestItem.source
2596
+ },
2597
+ mdContent
2598
+ );
2599
+ if (!mdValidation.ok) {
2600
+ warn(
2601
+ `Skipping ${item.id}: pre-copy validation failed (${relFile}) \u2014 ${mdValidation.reason}`
2602
+ );
2603
+ skillValid = false;
2604
+ break;
2605
+ }
2606
+ }
2607
+ if (!skillValid) continue;
2494
2608
  await installCatalogSkill(sourcePath, destination, {
2495
2609
  originSourceId: manifestItem.originSourceId,
2496
2610
  dryRun: false
2497
2611
  });
2498
2612
  } else {
2613
+ const fileContent = await fs14.readFile(sourcePath, "utf8");
2614
+ const validation = validateCatalogItem(
2615
+ {
2616
+ id: manifestItem.id,
2617
+ type: manifestItem.type,
2618
+ path: manifestItem.path,
2619
+ source: manifestItem.source
2620
+ },
2621
+ fileContent
2622
+ );
2623
+ if (!validation.ok) {
2624
+ warn(`Skipping ${item.id}: pre-copy validation failed \u2014 ${validation.reason}`);
2625
+ continue;
2626
+ }
2499
2627
  await fs14.ensureDir(path14.dirname(destination));
2500
2628
  await fs14.copy(sourcePath, destination, { overwrite: true, errorOnExist: false });
2501
2629
  }
@@ -2670,8 +2798,25 @@ async function fetchSingleRef(url, cacheDir, meta) {
2670
2798
  return { result: "failed" };
2671
2799
  }
2672
2800
  }
2801
+ function collectLlmsTxtUrls(items) {
2802
+ return [...new Set(items.flatMap((item) => (item.references ?? []).filter(isLlmsTxtUrl)))];
2803
+ }
2804
+ async function pruneOrphanedRefs(cacheDir, keepUrls) {
2805
+ const meta = await readCacheMeta(cacheDir);
2806
+ let removed = 0;
2807
+ for (const [url, entry] of Object.entries(meta)) {
2808
+ if (keepUrls.has(url)) continue;
2809
+ delete meta[url];
2810
+ removed++;
2811
+ const safeFile = typeof entry.file === "string" && entry.file === path16.basename(entry.file);
2812
+ if (safeFile) await fs16.remove(path16.join(cacheDir, entry.file)).catch(() => {
2813
+ });
2814
+ }
2815
+ if (removed > 0) await writeCacheMeta(cacheDir, meta);
2816
+ return removed;
2817
+ }
2673
2818
  async function fetchRefsForItems(items, cacheDir) {
2674
- const urls = [...new Set(items.flatMap((item) => (item.references ?? []).filter(isLlmsTxtUrl)))];
2819
+ const urls = collectLlmsTxtUrls(items);
2675
2820
  const summary = {
2676
2821
  fetched: 0,
2677
2822
  unchanged: 0,
@@ -2806,14 +2951,22 @@ async function runApply(options) {
2806
2951
  if (!isDryRun) {
2807
2952
  try {
2808
2953
  const cacheDir = hausPath(root, "llms-cache");
2954
+ const lock = await readJson(hausPath(root, "haus.lock.json")) ?? [];
2955
+ const installedIds = new Set(lock.map((entry) => entry.id));
2809
2956
  const allItems = await loadCatalog(root);
2810
- const summary = await fetchRefsForItems(allItems, cacheDir);
2957
+ const installedItems = allItems.filter((item) => installedIds.has(item.id));
2958
+ const summary = await fetchRefsForItems(installedItems, cacheDir);
2811
2959
  if (summary.fetched > 0)
2812
2960
  log(`Fetched ${summary.fetched} llms.txt reference(s) to ${cacheDir}`);
2813
2961
  if (summary.failed > 0)
2814
2962
  warn(
2815
2963
  `Failed to fetch ${summary.failed} llms.txt reference(s): ${summary.failedUrls.join(", ")}`
2816
2964
  );
2965
+ const pruned = await pruneOrphanedRefs(cacheDir, new Set(collectLlmsTxtUrls(installedItems)));
2966
+ if (pruned > 0)
2967
+ log(
2968
+ `Removed ${pruned} orphaned llms.txt cache entr${pruned === 1 ? "y" : "ies"} from ${cacheDir}`
2969
+ );
2817
2970
  } catch (err) {
2818
2971
  warn(
2819
2972
  `Could not fetch llms.txt references: ${err instanceof Error ? err.message : String(err)}`
@@ -3705,7 +3858,7 @@ function buildSourcesReport(items) {
3705
3858
  }
3706
3859
  return {
3707
3860
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
3708
- items: [...statusBySource.entries()].map(([id, status]) => ({ id, status })).sort((a, b) => a.id.localeCompare(b.id))
3861
+ items: [...statusBySource.entries()].map(([source, status]) => ({ source, status })).sort((a, b) => a.source.localeCompare(b.source))
3709
3862
  };
3710
3863
  }
3711
3864
  async function writeSourcesReport(root) {
@@ -3816,7 +3969,13 @@ async function readContextOrScan(root) {
3816
3969
 
3817
3970
  // src/update/npm-version.ts
3818
3971
  var NPM_PACKAGE_NAME = "@haus-tech/haus-workflow";
3972
+ function isTestMode2() {
3973
+ return process.env["HAUS_TEST_MODE"] === "1" || process.env["NODE_ENV"] === "test";
3974
+ }
3819
3975
  async function fetchNpmVersionStatus(currentVersion) {
3976
+ if (isTestMode2() && process.env["HAUS_SKIP_NPM_CHECK"]) {
3977
+ return { current: currentVersion, latest: null, updateAvailable: false };
3978
+ }
3820
3979
  try {
3821
3980
  const res = await fetch(
3822
3981
  `https://registry.npmjs.org/${encodeURIComponent(NPM_PACKAGE_NAME)}/latest`,
@@ -4417,9 +4576,7 @@ function mergeRecommendationWarnings(context) {
4417
4576
  // src/recommender/recommend.ts
4418
4577
  async function recommend(root, context, opts = {}) {
4419
4578
  const items = await loadCatalog(root);
4420
- const sources = await readJson(
4421
- hausPath(root, "sources-report.json")
4422
- ) ?? {};
4579
+ const sources = buildSourcesReport(items);
4423
4580
  const deep = await readJson(hausPath(root, "deep-context.json")) ?? {};
4424
4581
  const scannerStacks = buildStackSet(context);
4425
4582
  const scannerRoles = new Set(context.repoRoles.map((r) => r.toLowerCase()));
@@ -4437,7 +4594,7 @@ async function recommend(root, context, opts = {}) {
4437
4594
  const depSet = scannerDeps;
4438
4595
  const recommended = [];
4439
4596
  const skipped = [];
4440
- const sourceTrust = new Map((sources.items ?? []).map((x) => [x.id, x.status ?? "candidate"]));
4597
+ const sourceTrust = new Map(sources.items.map((x) => [x.source, x.status]));
4441
4598
  const changedFiles = await readChangedFiles(root);
4442
4599
  const skip = (id, code, message, signal) => {
4443
4600
  skipped.push({ id, reason: message, skipReasons: [{ code, message, signal }] });
@@ -4445,8 +4602,13 @@ async function recommend(root, context, opts = {}) {
4445
4602
  const roleSignal = (name) => scannerRoles.has(name.toLowerCase()) ? `role:${name}` : `deep:role:${name}`;
4446
4603
  const stackSignal = (name) => scannerStacks.has(name.toLowerCase()) ? `tag:${name}` : `deep:tag:${name}`;
4447
4604
  for (const item of items) {
4448
- const itemSearchText = `${item.id} ${item.tags.join(" ")}`.toLowerCase();
4449
- if (UNSUPPORTED.some((x) => itemSearchText.includes(x))) {
4605
+ const normSource = typeof item.source === "string" ? item.source.trim() : "";
4606
+ if (!normSource) {
4607
+ skip(item.id, "invalid-source", "Item source is missing or empty");
4608
+ continue;
4609
+ }
4610
+ const itemTags = item.tags.map((t) => t.toLowerCase());
4611
+ if (UNSUPPORTED.some((x) => itemTags.includes(x))) {
4450
4612
  skip(item.id, "unsupported-policy", "Unsupported stack policy");
4451
4613
  continue;
4452
4614
  }
@@ -4454,7 +4616,7 @@ async function recommend(root, context, opts = {}) {
4454
4616
  skip(item.id, "deprecated", "Catalog item is deprecated", "reviewStatus:deprecated");
4455
4617
  continue;
4456
4618
  }
4457
- if (item.source === "curated") {
4619
+ if (normSource === "curated") {
4458
4620
  const rs = item.reviewStatus;
4459
4621
  if (!rs || rs !== "approved") {
4460
4622
  skip(
@@ -4475,17 +4637,19 @@ async function recommend(root, context, opts = {}) {
4475
4637
  continue;
4476
4638
  }
4477
4639
  }
4478
- if (SENSITIVE_ITEM_KEYWORDS.some((x) => itemSearchText.includes(x))) {
4640
+ const sensitiveInId = SENSITIVE_ITEM_KEYWORDS.some((x) => item.id.toLowerCase().includes(x));
4641
+ const sensitiveInTags = SENSITIVE_ITEM_KEYWORDS.some((x) => itemTags.includes(x));
4642
+ if (sensitiveInId || sensitiveInTags) {
4479
4643
  skip(item.id, "sensitive-policy", "Sensitive content policy block");
4480
4644
  continue;
4481
4645
  }
4482
- const trust = sourceTrust.get(item.source);
4646
+ const trust = sourceTrust.get(normSource);
4483
4647
  if (trust === "candidate" || trust === "rejected") {
4484
4648
  skip(item.id, "source-trust", "Source trust policy block", `trust:${trust}`);
4485
4649
  continue;
4486
4650
  }
4487
- if (item.source && item.source !== "haus" && trust !== "approved") {
4488
- skip(item.id, "source-approval", "Source not approved", `source:${item.source}`);
4651
+ if (normSource !== "haus" && trust !== "approved") {
4652
+ skip(item.id, "source-approval", "Source not approved", `source:${normSource}`);
4489
4653
  continue;
4490
4654
  }
4491
4655
  if (item.id === "haus.nx21-monorepo-patterns" && !roleSet.has("nx-monorepo")) {
@@ -4510,15 +4674,6 @@ async function recommend(root, context, opts = {}) {
4510
4674
  skip(item.id, "required-role-missing", "Required role missing: database", "role:database");
4511
4675
  continue;
4512
4676
  }
4513
- if (item.id === "haus.ecc-typescript-reviewer" && (stackSet.has("react") || stackSet.has("react19") || stackSet.has("nextjs") || stackSet.has("next") || roleSet.has("react-app") || roleSet.has("next-app"))) {
4514
- skip(
4515
- item.id,
4516
- "co-install-react-reviewer",
4517
- "Skip typescript-reviewer on React/Next stacks (use react-reviewer)",
4518
- "stack:react|nextjs|role:react-app|next-app"
4519
- );
4520
- continue;
4521
- }
4522
4677
  const isDefaultBaseline = item.default === true;
4523
4678
  const reasons = [];
4524
4679
  const push = (code, message, signal) => reasons.push({ code, message, signal });
@@ -4536,9 +4691,11 @@ async function recommend(root, context, opts = {}) {
4536
4691
  `packageManager:${context.packageManager}`
4537
4692
  );
4538
4693
  }
4539
- const configSignal = item.tags.find(
4540
- (t) => context.warnings.join(" ").toLowerCase().includes(t.toLowerCase())
4541
- );
4694
+ const configSignal = item.tags.find((t) => {
4695
+ const escaped = t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4696
+ const re = new RegExp(`(?:^|\\W)${escaped}(?:\\W|$)`, "i");
4697
+ return context.warnings.some((w) => re.test(w));
4698
+ });
4542
4699
  if (configSignal) push("config-signal-match", "config signal match", `warning:${configSignal}`);
4543
4700
  const idSegment = item.id.split(".").pop() ?? "";
4544
4701
  const changedMatch = idSegment ? changedFiles.find((f) => f.includes(idSegment)) : void 0;
@@ -4671,6 +4828,13 @@ function applyCoInstallSuppression(recommended, skipped) {
4671
4828
  code: "co-install-redis-official",
4672
4829
  message: "Skip ecc-redis-patterns when official redis-connections skill is selected",
4673
4830
  signal: "co-install:redis-connections"
4831
+ },
4832
+ {
4833
+ suppress: "haus.ecc-typescript-reviewer",
4834
+ whenAnyRecommended: ["haus.ecc-react-reviewer"],
4835
+ code: "co-install-react-reviewer",
4836
+ message: "Skip typescript-reviewer on React/Next stacks (use react-reviewer)",
4837
+ signal: "co-install:react-reviewer"
4674
4838
  }
4675
4839
  ];
4676
4840
  for (const rule of rules) {
@@ -4951,7 +5115,7 @@ ${content2}`;
4951
5115
  // src/install/apply.ts
4952
5116
  var SCHEMA_VERSION2 = "1";
4953
5117
  function hashContent(content2) {
4954
- return `sha256-${crypto2.createHash("sha256").update(content2).digest("hex")}`;
5118
+ return `sha256-${crypto2.createHash("sha256").update(normaliseLF(content2)).digest("hex")}`;
4955
5119
  }
4956
5120
  function sourceVersion() {
4957
5121
  try {
@@ -4965,6 +5129,22 @@ function sourceVersion() {
4965
5129
  function globalSrcDir() {
4966
5130
  return path30.join(packageRoot(), "library", "global");
4967
5131
  }
5132
+ function collectSkillAuxFiles(skillsDir, claudeDir, skillName) {
5133
+ const skillDir = path30.join(skillsDir, skillName);
5134
+ const entries = [];
5135
+ for (const rel of fs24.readdirSync(skillDir, { recursive: true })) {
5136
+ if (rel === "SKILL.md") continue;
5137
+ const abs = path30.join(skillDir, rel);
5138
+ if (!fs24.statSync(abs).isFile()) continue;
5139
+ const posixRel = rel.split(path30.sep).join("/");
5140
+ entries.push({
5141
+ stableId: `skill.${skillName}.${posixRel.replace(/\//g, ".")}`,
5142
+ srcRelPath: path30.join("library", "global", "skills", skillName, rel),
5143
+ destPath: path30.join(claudeDir, "skills", skillName, rel)
5144
+ });
5145
+ }
5146
+ return entries;
5147
+ }
4968
5148
  function collectSourceFiles(srcDir, claudeDir) {
4969
5149
  const entries = [];
4970
5150
  const skillsDir = path30.join(srcDir, "skills");
@@ -4977,6 +5157,7 @@ function collectSourceFiles(srcDir, claudeDir) {
4977
5157
  srcRelPath: path30.join("library", "global", "skills", skillName, "SKILL.md"),
4978
5158
  destPath: path30.join(claudeDir, "skills", skillName, "SKILL.md")
4979
5159
  });
5160
+ entries.push(...collectSkillAuxFiles(skillsDir, claudeDir, skillName));
4980
5161
  }
4981
5162
  }
4982
5163
  }
@@ -5418,7 +5599,7 @@ import path32 from "node:path";
5418
5599
  import fs26 from "fs-extra";
5419
5600
 
5420
5601
  // src/claude/managed-paths.ts
5421
- var PROJECT_MANAGED_CLAUDE_REL = ["rules/haus.md", "commands/haus-doctor.md"];
5602
+ var PROJECT_MANAGED_CLAUDE_REL = ["rules/haus.md"];
5422
5603
  var PROJECT_MANAGED_HAUS_REL = ["haus.lock.json"];
5423
5604
  function coreManagedAbsolutePaths(root) {
5424
5605
  const claude = PROJECT_MANAGED_CLAUDE_REL.map((rel) => claudePath(root, rel));
@@ -5662,6 +5843,11 @@ function summarizeLockDiff(before, after) {
5662
5843
  // src/update/lockfile.ts
5663
5844
  import { mkdir, readFile as readFile2, copyFile } from "node:fs/promises";
5664
5845
  import path34 from "node:path";
5846
+ async function readLockSummary(root) {
5847
+ const lock = await readJson(hausPath(root, "haus.lock.json")) ?? [];
5848
+ const catalogRef = lock.find((item) => item.catalogRef)?.catalogRef ?? null;
5849
+ return { count: lock.length, catalogRef };
5850
+ }
5665
5851
  async function checkLock(root) {
5666
5852
  const lock = await readJson(hausPath(root, "haus.lock.json")) ?? [];
5667
5853
  const hasValidVersions = lock.every(
@@ -5725,6 +5911,10 @@ async function hasLocalOverrides(root) {
5725
5911
  var NPM_PACKAGE_NAME2 = "@haus-tech/haus-workflow";
5726
5912
  async function runUpdate(options) {
5727
5913
  const root = process.cwd();
5914
+ if (options.fromHook) {
5915
+ await runFromHookCheck(root);
5916
+ return;
5917
+ }
5728
5918
  if (options.check) {
5729
5919
  const pkgJson2 = await readJson(path35.join(packageRoot(), "package.json"));
5730
5920
  const currentVersion2 = pkgJson2?.version ?? "0.0.0";
@@ -5790,6 +5980,36 @@ async function runUpdate(options) {
5790
5980
  await refreshProjectFiles(root);
5791
5981
  log("Update applied with backup in .haus-workflow/backups/. Run haus doctor.");
5792
5982
  }
5983
+ async function runFromHookCheck(root) {
5984
+ try {
5985
+ const summary = await readLockSummary(root);
5986
+ if (summary.count === 0) return;
5987
+ const pkgJson = await readJson(path35.join(packageRoot(), "package.json"));
5988
+ const currentVersion = pkgJson?.version ?? "0.0.0";
5989
+ const [npmVersion, latestCatalogTag] = await Promise.all([
5990
+ fetchNpmVersionStatus(currentVersion),
5991
+ fetchLatestCatalogTag()
5992
+ ]);
5993
+ const installedRef = summary.catalogRef;
5994
+ const catalogBehind = installedRef !== null && latestCatalogTag !== null && installedRef !== latestCatalogTag;
5995
+ const npmBehind = npmVersion.updateAvailable && npmVersion.latest !== null;
5996
+ if (!npmBehind && !catalogBehind) return;
5997
+ const reasons = [];
5998
+ if (npmBehind) reasons.push(`haus package ${currentVersion} \u2192 ${npmVersion.latest} available`);
5999
+ if (catalogBehind) {
6000
+ reasons.push(`catalog installed from ${installedRef}, latest is ${latestCatalogTag}`);
6001
+ }
6002
+ log(
6003
+ JSON.stringify({
6004
+ hookSpecificOutput: {
6005
+ hookEventName: "SessionStart",
6006
+ additionalContext: `This project's haus setup is behind: ${reasons.join("; ")}. Suggest running \`/haus-workflow project:refresh\` to bring it up to date.`
6007
+ }
6008
+ })
6009
+ );
6010
+ } catch {
6011
+ }
6012
+ }
5793
6013
  async function refreshProjectFiles(root) {
5794
6014
  log("Refreshing project .claude/ files...");
5795
6015
  try {
@@ -5838,7 +6058,6 @@ import path37 from "node:path";
5838
6058
  import fs29 from "node:fs";
5839
6059
  import path36 from "node:path";
5840
6060
  var ITEM_SEMVER_RE = /^\d+\.\d+\.\d+$/;
5841
- var MANIFEST_SEMVER_RE = /^\d+\.\d+\.\d+(?:-[\w.-]+)?(?:\+[\w.-]+)?$/;
5842
6061
  function isSafeCatalogPath2(itemPath) {
5843
6062
  if (!itemPath || path36.isAbsolute(itemPath) || itemPath.includes("\\")) return false;
5844
6063
  const normalized = path36.normalize(itemPath);
@@ -5892,10 +6111,8 @@ function auditManifestStructure(manifestVersion, items) {
5892
6111
  const failures = [];
5893
6112
  const seenIds = /* @__PURE__ */ new Map();
5894
6113
  const seenPaths = /* @__PURE__ */ new Map();
5895
- if (typeof manifestVersion !== "string" || !MANIFEST_SEMVER_RE.test(manifestVersion)) {
5896
- failures.push(
5897
- 'manifest.json: top-level "version" is not valid semver (expected X.Y.Z or X.Y.Z-pre)'
5898
- );
6114
+ if (typeof manifestVersion !== "string" || !ITEM_SEMVER_RE.test(manifestVersion)) {
6115
+ failures.push('manifest.json: top-level "version" is not valid semver (expected X.Y.Z)');
5899
6116
  }
5900
6117
  for (let i = 0; i < items.length; i++) {
5901
6118
  const item = items[i];
@@ -6868,7 +7085,7 @@ program.command("clone").description("Clone a single git repository by URL").arg
6868
7085
  program.command("refresh").action(runRefresh);
6869
7086
  program.command("catalog-audit").action(runCatalogAudit);
6870
7087
  program.command("validate-catalog").argument("[manifest]").action(runValidateCatalog);
6871
- program.command("update").option("--check").action(runUpdate);
7088
+ program.command("update").option("--check").option("--from-hook", "Emit JSON for the Claude Code SessionStart hook (silent if up to date)").action(runUpdate);
6872
7089
  program.command("install").option("--dry-run").option("--force").option("--check", "Exit non-zero if any HAUS-MANAGED file is out of date").option("--postinstall", "Run by the npm postinstall hook; prints a plain-language change notice").action(runInstall);
6873
7090
  program.command("scaffold [ids...]").description(
6874
7091
  "Copy catalog config items (ESLint, Prettier) into the project root. Pass item IDs to scaffold specific items, or omit to scaffold all available config items."
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "3.2.0",
2
+ "version": "3.2.1",
3
3
  "items": [
4
4
  {
5
5
  "id": "haus.agentic-workflow-standard",
@@ -1754,7 +1754,7 @@
1754
1754
  },
1755
1755
  {
1756
1756
  "id": "haus.sentry-sentry-nestjs-sdk",
1757
- "version": "1.0.0",
1757
+ "version": "1.0.1",
1758
1758
  "source": "curated",
1759
1759
  "type": "skill",
1760
1760
  "path": "skills/sentry/sentry-nestjs-sdk",
@@ -1773,8 +1773,8 @@
1773
1773
  "license": "MIT",
1774
1774
  "licenseConfidence": "high",
1775
1775
  "originSourceId": "getsentry-sentry-for-ai",
1776
- "originUrl": "https://github.com/getsentry/sentry-for-ai/blob/9ab3551758913a4144517d4fafb71476a48c64f8/skills/sentry-nestjs-sdk/SKILL.md",
1777
- "pinnedRef": "9ab3551758913a4144517d4fafb71476a48c64f8",
1776
+ "originUrl": "https://github.com/getsentry/sentry-for-ai/blob/cc302403ebf647fd21c3ca736e1ec7ef6e4ed5a3/skills/sentry-nestjs-sdk/SKILL.md",
1777
+ "pinnedRef": "cc302403ebf647fd21c3ca736e1ec7ef6e4ed5a3",
1778
1778
  "ecosystem": "sentry",
1779
1779
  "requiresAny": [
1780
1780
  {
@@ -1787,7 +1787,7 @@
1787
1787
  },
1788
1788
  {
1789
1789
  "id": "haus.sentry-sentry-nextjs-sdk",
1790
- "version": "1.0.1",
1790
+ "version": "1.0.2",
1791
1791
  "source": "curated",
1792
1792
  "type": "skill",
1793
1793
  "path": "skills/sentry/sentry-nextjs-sdk",
@@ -1806,8 +1806,8 @@
1806
1806
  "license": "MIT",
1807
1807
  "licenseConfidence": "high",
1808
1808
  "originSourceId": "getsentry-sentry-for-ai",
1809
- "originUrl": "https://github.com/getsentry/sentry-for-ai/blob/bd84576db74e3e1d13361c7eda3bb303ae017b90/skills/sentry-nextjs-sdk/SKILL.md",
1810
- "pinnedRef": "bd84576db74e3e1d13361c7eda3bb303ae017b90",
1809
+ "originUrl": "https://github.com/getsentry/sentry-for-ai/blob/cc302403ebf647fd21c3ca736e1ec7ef6e4ed5a3/skills/sentry-nextjs-sdk/SKILL.md",
1810
+ "pinnedRef": "cc302403ebf647fd21c3ca736e1ec7ef6e4ed5a3",
1811
1811
  "ecosystem": "sentry",
1812
1812
  "requiresAny": [
1813
1813
  {
@@ -1820,7 +1820,7 @@
1820
1820
  },
1821
1821
  {
1822
1822
  "id": "haus.sentry-sentry-node-sdk",
1823
- "version": "1.0.1",
1823
+ "version": "1.0.2",
1824
1824
  "source": "curated",
1825
1825
  "type": "skill",
1826
1826
  "path": "skills/sentry/sentry-node-sdk",
@@ -1839,8 +1839,8 @@
1839
1839
  "license": "MIT",
1840
1840
  "licenseConfidence": "high",
1841
1841
  "originSourceId": "getsentry-sentry-for-ai",
1842
- "originUrl": "https://github.com/getsentry/sentry-for-ai/blob/bd84576db74e3e1d13361c7eda3bb303ae017b90/skills/sentry-node-sdk/SKILL.md",
1843
- "pinnedRef": "bd84576db74e3e1d13361c7eda3bb303ae017b90",
1842
+ "originUrl": "https://github.com/getsentry/sentry-for-ai/blob/cc302403ebf647fd21c3ca736e1ec7ef6e4ed5a3/skills/sentry-node-sdk/SKILL.md",
1843
+ "pinnedRef": "cc302403ebf647fd21c3ca736e1ec7ef6e4ed5a3",
1844
1844
  "ecosystem": "sentry",
1845
1845
  "requiresAny": [
1846
1846
  {
@@ -1978,7 +1978,7 @@
1978
1978
  },
1979
1979
  {
1980
1980
  "id": "haus.stripe-stripe-best-practices",
1981
- "version": "1.0.2",
1981
+ "version": "1.0.3",
1982
1982
  "source": "curated",
1983
1983
  "type": "skill",
1984
1984
  "path": "skills/stripe/stripe-best-practices",
@@ -1988,7 +1988,7 @@
1988
1988
  "whenNotToUse": "Do not use when a more specific skill or command applies.",
1989
1989
  "tags": ["workflow", "stripe", "payments"],
1990
1990
  "repoRoles": [],
1991
- "tokenEstimate": 7509,
1991
+ "tokenEstimate": 1018,
1992
1992
  "installMode": "copy-selected",
1993
1993
  "default": false,
1994
1994
  "reviewStatus": "approved",
@@ -1997,8 +1997,8 @@
1997
1997
  "license": "MIT",
1998
1998
  "licenseConfidence": "medium",
1999
1999
  "originSourceId": "stripe-ai",
2000
- "originUrl": "https://github.com/stripe/ai/blob/7e7f72eb42e06f79e7bc75f86e9395f4c8232c00/skills/stripe-best-practices/SKILL.md",
2001
- "pinnedRef": "7e7f72eb42e06f79e7bc75f86e9395f4c8232c00",
2000
+ "originUrl": "https://github.com/stripe/ai/blob/f54c9e6c0e8b6cfe4e648745eec68ecab3ddc994/skills/stripe-best-practices/SKILL.md",
2001
+ "pinnedRef": "f54c9e6c0e8b6cfe4e648745eec68ecab3ddc994",
2002
2002
  "ecosystem": "stripe",
2003
2003
  "requiresAny": [
2004
2004
  {
@@ -1,11 +1,13 @@
1
1
  ---
2
2
  name: haus-workflow
3
- description: Haus all-in-one workflow skill. Handles project setup, update, catalog refresh, and CLAUDE.md regeneration. Invoke with a task name or without to get a menu.
3
+ description: Haus all-in-one workflow skill. Handles project setup, re-init, update, catalog refresh, health checks, and CLAUDE.md regeneration. Invoke with a task name or without to get a menu.
4
4
  ---
5
5
 
6
6
  # haus-workflow
7
7
 
8
- All-in-one entry point for the Haus AI workflow.
8
+ All-in-one entry point for the Haus AI workflow. Everything haus-related runs through
9
+ this skill — there are no separate `/haus-setup`, `/haus-clone`, `/haus-cloneandsetup`,
10
+ `/haus-doctor`, or `/haus-fix` commands.
9
11
 
10
12
  ## Invocation
11
13
 
@@ -17,43 +19,56 @@ All-in-one entry point for the Haus AI workflow.
17
19
 
18
20
  Task names use an asymmetric scope convention. The **project:** namespace marks tasks that
19
21
  act on **this repo** (`./.claude`, `./.haus-workflow`) — type `project:` to see them all.
20
- The unprefixed verbs (`update`, `install`, `uninstall`) act on **this machine's
21
- haus install** (`~/.claude`, npm) — they manage the haus tool itself, like `npm install -g`.
22
- The short legacy aliases still work but the names below are canonical.
23
-
24
- | Task name (legacy aliases) | Command | Scope | What it does |
25
- | ----------------------------------------------------------------- | ------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
26
- | `project:init` (`setup`, `init`) | _Setup procedure below_ | project | First-time setup of an **existing** repo: adds AI skills, commands, workflow + project docs |
27
- | `project:clone [name]` (`clone`) | _Clone procedure below_ | project | No name: clone a **workspace**'s repos from `repos.manifest.json`. With a `name`: find & clone one repo by name from GitHub |
28
- | `project:cloneandsetup [name]` (`cloneandsetup`) | _Clone & setup procedure below_ | project | Run `project:clone`, then set up each repo for local dev — deps, databases, cross-repo links, and env — via each repo's `.haus-workflow/localdev.yml` (+ the workspace's order/links/env) |
29
- | `project:add-skills` (`add-skills`, `opt-in`) | _Add-skills procedure below_ | project | Add optional skills, agents, or config (ESLint/Prettier) to an already-set-up project without re-running full setup |
30
- | `project:refresh` (`apply`, `refresh`, `claude-md`, `regenerate`) | `haus apply --write` | project | Re-run setup / refresh `.claude/` context + regenerate root `CLAUDE.md` import block |
31
- | `project:doctor` (`doctor`, `check`) | `haus doctor` | project | Check for install drift |
32
- | `update` (`upgrade`) | `haus update` | global | Update npm package + catalog + `~/.claude/` (also refreshes this project) |
33
- | `install` (`global`) | `haus install` | global | Seed `~/.claude/` with haus-owned files |
34
- | `uninstall` | `haus uninstall` | global | Remove all haus global files from `~/.claude/` |
22
+ The unprefixed verbs (`update`, `install`, `uninstall`, `help`) act on **this machine's
23
+ haus install** (`~/.claude`, npm) or are purely informational — they don't act on this
24
+ repo's files. The short legacy aliases still work but the names below are canonical.
25
+
26
+ | Task name (legacy aliases) | Command | Scope | What it does |
27
+ | ----------------------------------------------------------------- | ------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
28
+ | `project:init` (`setup`, `init`) | _Setup procedure below_ | project | First-time setup of an **existing** repo: adds AI skills, commands, workflow + project docs |
29
+ | `project:reinit` (`reinit`, `re-init`) | _Reinit procedure below_ | project | **Full re-setup.** Removes all haus-managed project files (backed up first, user content preserved), then re-runs `project:init` from scratch |
30
+ | `project:clone [name]` (`clone`) | _Clone procedure below_ | project | No name: clone a **workspace**'s repos from `repos.manifest.json`. With a `name`: find & clone one repo by name from GitHub |
31
+ | `project:cloneandsetup [name]` (`cloneandsetup`) | _Clone & setup procedure below_ | project | Run `project:clone`, then set up each repo for local dev — deps, databases, cross-repo links, and env — via each repo's `.haus-workflow/localdev.yml` (+ the workspace's order/links/env) |
32
+ | `project:add-skills` (`add-skills`, `opt-in`) | _Add-skills procedure below_ | project | Add optional skills, agents, or config (ESLint/Prettier) to an already-set-up project without re-running full setup |
33
+ | `project:refresh` (`apply`, `refresh`, `claude-md`, `regenerate`) | _Refresh procedure below_ | project | **Full non-destructive sync.** Updates the haus package + catalog first, then re-runs `project:init`'s pipeline in place — nothing is removed up front; new catalog items get added, changed ones updated, and anything removed upstream gets pruned by `apply`'s existing orphan-pruning |
34
+ | `project:doctor` (`doctor`, `check`) | `haus doctor` | project | Check for install drift |
35
+ | `project:fix` (`fix`) | _Fix procedure below_ | project | Diagnose and fix install drift: run doctor, apply the suggested fixes, confirm green |
36
+ | `update` (`upgrade`) | `haus update` | global | Update npm package + catalog + `~/.claude/`; if run inside an already-set-up project, fully refreshes it too (same pipeline as `project:refresh`) |
37
+ | `install` (`global`) | `haus install` | global | Seed `~/.claude/` with haus-owned files |
38
+ | `uninstall` | `haus uninstall` | global | Remove all haus global files from `~/.claude/` |
39
+ | `help` (`?`) | _Answer directly below_ | — | Explain what haus-workflow is, list available tasks, point to deeper docs — touches no files |
35
40
 
36
41
  ## Step 1 — Determine the task
37
42
 
38
43
  **If a task argument was passed:** look it up in the alias table above. If no match, tell the user and list valid options. If matched, skip to Step 2.
39
44
 
40
- **If no task was passed:** use `AskUserQuestion` to present this menu:
45
+ **If no task was passed:** the menu has more options than one `AskUserQuestion` question can
46
+ hold (max 4 options per question). Ask two separate questions, one after another — first
47
+ Question 1, read the answer, then ask Question 2 (a second, sequential `AskUserQuestion`
48
+ call; do not try to cram both into a single question):
41
49
 
42
50
  ```
43
- Question: "What would you like to do?"
51
+ Question 1: "What would you like to do?"
44
52
  Options:
45
53
  1. [project] project:init — add haus to an existing project for the first time
46
54
  (AI skills + commands + workflow into a repo you already have, then a deep read to write the CLAUDE.md docs body + docs/)
47
- 2. [project] project:refresh — refresh this project's setup
48
- (haus apply --write — re-runs setup, regenerates CLAUDE.md imports)
49
- 3. [global] update — update haus package + catalog + global files
50
- (haus update — checks npm for new version, fetches catalog, refreshes ~/.claude/)
51
- 4. [project] project:clone [name] — clone repos
55
+ 2. [project] project:refresh — full non-destructive sync with the latest haus
56
+ (updates the haus package + catalog, then re-runs setup in place — nothing removed first)
57
+ 3. [project] project:clone [name] — clone repos
52
58
  (no name: clone a workspace from repos.manifest.json; with a name: find & clone one repo by name from GitHub)
53
- 5. [project] project:cloneandsetup [name] — clone repos, then set them up for local dev
59
+ 4. [project] project:cloneandsetup [name] — clone repos, then set them up for local dev
54
60
  (project:clone, then per-repo deps + databases + cross-repo links + env from localdev.yml)
55
- 6. [project] project:add-skills — add optional skills, agents, or config to this project
61
+
62
+ Question 2: "Anything else?"
63
+ Options:
64
+ 1. [project] project:add-skills — add optional skills, agents, or config to this project
56
65
  (offers opt-in helpers matching your stack that aren't installed yet — no full re-setup)
66
+ 2. [project] project:reinit — full re-setup from scratch
67
+ (removes all haus-managed project files, backed up first, then re-runs project:init)
68
+ 3. [global] update — update haus package + catalog + global files
69
+ (haus update — checks npm for new version, fetches catalog, refreshes ~/.claude/)
70
+ 4. [—] help — explain what haus-workflow is and list all available tasks
71
+ (touches no files — points to docs/cli.md and docs/runbook.md for depth)
57
72
  ```
58
73
 
59
74
  Map the user's selection to the command from the alias table, then continue to Step 2.
@@ -64,28 +79,69 @@ Run the mapped command via Bash. Quote the exact command you are running before
64
79
 
65
80
  **Exception — `project:init` (`setup` / `init`):** this maps to a multi-step procedure, not a single command. Do not run a bare `haus init`. Skip to **Setup (`project:init`)** under Step 3 and follow it.
66
81
 
82
+ **Exception — `project:reinit` (`reinit` / `re-init`):** removes files before re-running setup. Skip to **Reinit (`project:reinit`)** under Step 3 and follow it.
83
+
67
84
  **Exception — `project:clone` (`clone`):** this asks the user a question before running, so it is a short procedure too. Skip to **Clone (`project:clone`)** under Step 3 and follow it.
68
85
 
69
86
  **Exception — `project:cloneandsetup` (`cloneandsetup`):** clone followed by a per-repo setup pass, with confirmations. Skip to **Clone & setup (`project:cloneandsetup`)** under Step 3 and follow it.
70
87
 
71
88
  **Exception — `project:add-skills` (`add-skills` / `opt-in`):** a short scan → recommend → choose → apply procedure. Skip to **Add optional skills (`project:add-skills`)** under Step 3 and follow it.
72
89
 
90
+ **Exception — `project:refresh` (`apply` / `refresh` / `claude-md` / `regenerate`):** updates the haus package first, then re-runs the setup pipeline — not a single command. Skip to **Refresh (`project:refresh`)** under Step 3 and follow it.
91
+
92
+ **Exception — `project:fix` (`fix`):** diagnose-then-fix loop, not a single command. Skip to **Fix (`project:fix`)** under Step 3 and follow it.
93
+
94
+ **Exception — `help` (`?`):** no command runs at all. Skip Step 2 and Step 3 — answer directly from the task alias table above, plus `docs/cli.md` and `docs/runbook.md` for deeper detail on any specific task.
95
+
73
96
  ## Step 3 — Post-run steps
74
97
 
75
98
  After the command completes, follow the relevant post-run steps below.
76
99
 
77
100
  ### Setup (`project:init`)
78
101
 
79
- 1. Open and follow `~/.claude/commands/haus-setup.md` — the installed `haus-setup` command (in some projects also `.claude/commands/haus-setup.md`). Run every step in order. It detects the stack, runs `haus apply --write` (scaffolding, skills, commands, rules, docs skill), writes the **project docs** (`CLAUDE.md` body + `docs/`) and `.haus-workflow/deep-context.json`, runs `haus recommend`, applies the newly-matched helpers, and confirms.
102
+ 1. Open and follow `references/init.md` in this skill's directory. Run every step in order. It detects the stack, runs `haus apply --write` (scaffolding, skills, commands, rules, docs skill), writes the **project docs** (`CLAUDE.md` body + `docs/`) and `.haus-workflow/deep-context.json`, runs `haus recommend`, applies the newly-matched helpers, and confirms.
80
103
  2. Then fill `.haus-workflow/workflow-config.md` — replace every placeholder (`TODO`, `n/a`, empty): test/lint/typecheck/build commands (check `package.json`), docs paths, validation library, pre-commit tool, highest-stakes logic (ask if unclear). Leave none.
81
104
 
105
+ ### Reinit (`project:reinit`)
106
+
107
+ Full re-setup — removes everything haus manages in this project and starts over. Use when
108
+ the project's haus setup is badly out of sync and a plain `project:fix`/`project:refresh`
109
+ isn't enough, or the user explicitly wants a clean slate.
110
+
111
+ 1. **Confirm first, always** — via `AskUserQuestion`. Explain plainly: this removes all
112
+ haus-managed files in this project (commands, skills, rules, workflow docs; `haus undo`
113
+ backs them up first and never touches user-owned `.claude/` content or `.haus-workflow`
114
+ docs you've edited), then re-runs setup from scratch. Offer **Cancel**.
115
+ 2. On confirm, run `haus undo --yes` (already backs up + preserves user content — no need
116
+ to ask a second time).
117
+ 3. Immediately follow **Setup (`project:init`)** above, end to end.
118
+ 4. Report what was removed, backed up (path), and re-added, in plain language.
119
+
120
+ ### Refresh (`project:refresh`)
121
+
122
+ Full non-destructive sync — nothing is removed up front (contrast with `project:reinit`,
123
+ which wipes first). Use for routine "bring this project up to date with the latest haus"
124
+ maintenance: new catalog items get added, changed ones updated, and anything removed
125
+ upstream gets pruned automatically by `apply`'s existing orphan-pruning.
126
+
127
+ 1. Run `haus update`. This bumps the npm package (or reports one's available), syncs the
128
+ catalog cache, refreshes `~/.claude/`, and does a light re-apply of this project's
129
+ already-tracked files. Apply the same reactions as **After `haus update`** below
130
+ (version-bump command, `WORKFLOW.md` update nudge, catalog-changes summary).
131
+ 2. Immediately follow **Setup (`project:init`)** above, end to end. Re-running it is safe
132
+ on an already-set-up project — it re-scans, re-applies, redoes the deep docs read (so
133
+ `CLAUDE.md`/`docs/` catch up with any code drift), and re-recommends against the
134
+ now-current catalog.
135
+ 3. Report what changed in plain language — package/catalog version bumps, docs
136
+ refreshed, helpers added/updated/removed.
137
+
82
138
  ### Clone (`project:clone`)
83
139
 
84
- 1. Open and follow `~/.claude/commands/haus-clone.md` — the installed `haus-clone` command. With a `name` argument it finds and clones one matching repo from GitHub; with no argument it clones a workspace's repos from `repos.manifest.json`. This task only clones — to also install dependencies, use `project:cloneandsetup`.
140
+ 1. Open and follow `references/clone.md` in this skill's directory. With a `name` argument it finds and clones one matching repo from GitHub; with no argument it clones a workspace's repos from `repos.manifest.json`. This task only clones — to also install dependencies, use `project:cloneandsetup`.
85
141
 
86
142
  ### Clone & setup (`project:cloneandsetup`)
87
143
 
88
- 1. Open and follow `~/.claude/commands/haus-cloneandsetup.md` — it runs the full `project:clone` flow, then sets up each cloned repo locally: selects the node version (`nvm install` from `.nvmrc`), enables corepack, installs JS/PHP dependencies, and seeds `.env`, confirming before each phase. It does not start servers.
144
+ 1. Open and follow `references/cloneandsetup.md` in this skill's directory — it runs the full `project:clone` flow, then sets up each cloned repo locally: selects the node version (`nvm install` from `.nvmrc`), enables corepack, installs JS/PHP dependencies, and seeds `.env`, confirming before each phase. It does not start servers.
89
145
 
90
146
  ### Add optional skills (`project:add-skills`)
91
147
 
@@ -112,27 +168,40 @@ choices.
112
168
  5. Confirm with the names added and their combined token estimate, e.g.
113
169
  "Added 2 optional helpers: Code review workflow, TDD workflow (~3k tokens)."
114
170
 
115
- ### After `haus apply --write`
116
-
117
- Verify that the root `CLAUDE.md` imports all three haus files:
171
+ ### Fix (`project:fix`)
118
172
 
119
- ```
120
- @.haus-workflow/WORKFLOW.md
121
- @.haus-workflow/workflow-config.md
122
- @.haus-workflow/project.md
123
- ```
173
+ 1. Run `haus doctor` and read the verdict.
174
+ 2. If the project is already healthy, say so in one line and stop.
175
+ 3. Otherwise, for each item that needs attention, run the exact fix command the
176
+ doctor named (commonly `haus apply --write` or `haus apply --refill-config`).
177
+ 4. Re-run `haus doctor` to confirm the verdict is now green.
178
+ 5. Report what you changed in plain language — what was wrong, what you ran, and
179
+ that it's now resolved.
124
180
 
125
- If any import is missing, add it.
181
+ Only run haus commands and the fixes the doctor suggests. If a fix needs a
182
+ decision you can't make safely, stop and ask in plain language.
126
183
 
127
184
  ### After `haus update`
128
185
 
129
186
  1. If CLI output says a new version is available, run `npm i -g @haus-tech/haus-workflow` and confirm the version bump.
130
187
  2. If `WORKFLOW.md` was updated, remind the user: "WORKFLOW.md updated — review `workflow-config.md` for any new sections that need project-specific values."
131
188
  3. If CLI output shows catalog changes, summarise which templates changed.
132
-
133
- ### After `haus doctor`
134
-
135
- Report findings clearly. For each drift item, suggest the exact fix command.
189
+ 4. **If the current directory has a `.haus-workflow/` folder** (this project was already
190
+ set up by haus), don't stop at the global update — continue immediately into
191
+ **Setup (`project:init`)** above to fully sync this project too (rescan, redo the deep
192
+ docs read, re-recommend, apply). This is what makes `update`'s scope note ("also
193
+ refreshes this project") literally true, not just a light lock-replay. No `.haus-workflow/`
194
+ folder → nothing more to do here, `update` only touched global files.
195
+
196
+ ### After `haus doctor` (`project:doctor`)
197
+
198
+ 1. Read the output. The first line is a plain-language verdict (green = healthy,
199
+ amber = things need attention).
200
+ 2. Relay that verdict to the person in one or two sentences. If anything needs
201
+ attention, list each item as a short sentence plus the exact command that
202
+ fixes it — and offer to run the fix for them (or suggest `project:fix`).
203
+
204
+ Don't show raw command output unless they ask for detail.
136
205
 
137
206
  ### After `haus install` / `haus uninstall`
138
207
 
@@ -1,10 +1,8 @@
1
- ---
2
- description: Clone this project's repositories onto disk (per-repo setup is separate).
3
- ---
1
+ # project:clone procedure
4
2
 
5
3
  Clone repositories for this project. Per-repo setup (install, Docker, `.env`) is a separate step that isn't wired yet — this command only gets repos onto disk.
6
4
 
7
- Cloning a single repo is always `haus clone <url> [dir]`. This command picks _which_ repos to clone and runs that primitive for each. There are two modes, chosen by whether a name was given.
5
+ Cloning a single repo is always `haus clone <url> [dir]`. This procedure picks _which_ repos to clone and runs that primitive for each. There are two modes, chosen by whether a name was given.
8
6
 
9
7
  **Always ask before cloning — never assume.** The user may already have the repos on disk. Do not start cloning until they have confirmed. A missing `repos.local.json` does **not** mean they want a fresh clone; it just means nothing is recorded yet — you must still ask.
10
8
 
@@ -1,6 +1,4 @@
1
- ---
2
- description: Clone a project's repos and set each one up for local development.
3
- ---
1
+ # project:cloneandsetup procedure
4
2
 
5
3
  Clone a project's repos **and** set each one up for local development — node version, dependencies, databases, cross-repo links, and env. This is `project:clone` followed by a per-repo setup pass and a localdev orchestration pass.
6
4
 
@@ -8,7 +6,7 @@ Clone a project's repos **and** set each one up for local development — node v
8
6
 
9
7
  ## Step 1 — Clone
10
8
 
11
- Run the full `project:clone` flow by following `~/.claude/commands/haus-clone.md` end to end (name → one repo; no name → workspace repos from `repos.manifest.json`). Carry the resulting repo list into Step 2.
9
+ Run the full `project:clone` flow by following `references/clone.md` end to end (name → one repo; no name → workspace repos from `repos.manifest.json`). Carry the resulting repo list into Step 2.
12
10
 
13
11
  ## Step 2 — Prerequisite gate (one consolidated check)
14
12
 
@@ -1,6 +1,4 @@
1
- ---
2
- description: Set up this project with haus, conversationally — no terminal needed.
3
- ---
1
+ # project:init procedure
4
2
 
5
3
  Set up this project with haus, conversationally. The person you're helping may
6
4
  not be a developer — never make them open a terminal or read JSON. You run the
@@ -59,11 +57,18 @@ Do this in order:
59
57
 
60
58
  6. **Apply the rest.** Run `haus apply --write` again. It only writes what changed,
61
59
  so this adds the newly-matched helpers from step 4 plus any opt-ins from step 5.
60
+ Then verify the root `CLAUDE.md` imports all three haus files — add any missing:
61
+
62
+ ```
63
+ @.haus-workflow/WORKFLOW.md
64
+ @.haus-workflow/workflow-config.md
65
+ @.haus-workflow/project.md
66
+ ```
62
67
 
63
68
  7. **Confirm.** End with one plain-language line, for example:
64
69
  "✅ Your project is configured — I wrote your project docs, added N guardrails
65
70
  and M coding helpers (K matched after reading your code in depth), plus the J
66
- optional helpers you chose: <name them>. Run `/haus-doctor` any time to
71
+ optional helpers you chose: <name them>. Run `/haus-workflow doctor` any time to
67
72
  re-check." Fill the numbers from the apply output, and name the opt-ins the
68
73
  user picked in step 5 (omit that clause if they chose none).
69
74
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haus-tech/haus-workflow",
3
- "version": "0.32.0",
3
+ "version": "1.0.0",
4
4
  "description": "Haus AI workflow CLI for Claude Code.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -47,13 +47,13 @@
47
47
  "execa": "9.6.1",
48
48
  "fast-glob": "3.3.3",
49
49
  "fs-extra": "11.3.5",
50
- "semver": "7.8.0",
50
+ "semver": "7.8.5",
51
51
  "yaml": "2.9.0"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@release-it/conventional-changelog": "11.0.1",
55
55
  "@types/fs-extra": "11.0.4",
56
- "@types/node": "22.19.17",
56
+ "@types/node": "22.20.0",
57
57
  "@types/semver": "7.7.1",
58
58
  "@typescript-eslint/eslint-plugin": "8.59.3",
59
59
  "@typescript-eslint/parser": "8.59.3",
@@ -1,14 +0,0 @@
1
- ---
2
- description: Check this project's haus setup health and explain it in plain language.
3
- ---
4
-
5
- Check the health of this project's haus setup and explain it in plain language.
6
-
7
- 1. Run `haus doctor`.
8
- 2. Read the output. The first line is a plain-language verdict (green = healthy,
9
- amber = things need attention).
10
- 3. Relay that verdict to the person in one or two sentences. If anything needs
11
- attention, list each item as a short sentence plus the exact command that
12
- fixes it — and offer to run the fix for them (or suggest `/haus-fix`).
13
-
14
- Don't show raw command output unless they ask for detail.
@@ -1,16 +0,0 @@
1
- ---
2
- description: Diagnose and fix this project's haus setup.
3
- ---
4
-
5
- Diagnose and fix this project's haus setup.
6
-
7
- 1. Run `haus doctor` and read the verdict.
8
- 2. If the project is already healthy, say so in one line and stop.
9
- 3. Otherwise, for each item that needs attention, run the exact fix command the
10
- doctor named (commonly `haus apply --write` or `haus apply --refill-config`).
11
- 4. Re-run `haus doctor` to confirm the verdict is now green.
12
- 5. Report what you changed in plain language — what was wrong, what you ran, and
13
- that it's now resolved.
14
-
15
- Only run haus commands and the fixes the doctor suggests. If a fix needs a
16
- decision you can't make safely, stop and ask in plain language.