@haus-tech/haus-workflow 0.32.1 → 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,159 @@
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
+
3
157
  ## [0.32.1](https://github.com/WeAreHausTech/haus-workflow/compare/v0.32.0...v0.32.1) (2026-06-30)
4
158
 
5
159
  ### Bug Fixes
package/dist/cli.js CHANGED
@@ -1811,6 +1811,13 @@ var PROJECT_HOOK_FRAGMENTS = [
1811
1811
  event: "Stop",
1812
1812
  matcher: "*",
1813
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"
1814
1821
  }
1815
1822
  ];
1816
1823
  async function readProjectSettings(root) {
@@ -1910,6 +1917,12 @@ var CANONICAL_HOOKS = {
1910
1917
  matcher: "*",
1911
1918
  hooks: [{ type: "command", command: "haus decisions suggest --from-hook" }]
1912
1919
  }
1920
+ ],
1921
+ SessionStart: [
1922
+ {
1923
+ matcher: "*",
1924
+ hooks: [{ type: "command", command: "haus update --from-hook" }]
1925
+ }
1913
1926
  ]
1914
1927
  }
1915
1928
  };
@@ -2414,11 +2427,7 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
2414
2427
  };
2415
2428
  const pkgRoot = packageRoot();
2416
2429
  const hausVersion2 = (await readJson(path14.join(pkgRoot, "package.json")))?.version ?? "0.0.0";
2417
- const coreFiles = [
2418
- claudePath(root, "settings.json"),
2419
- claudePath(root, "rules", "haus.md"),
2420
- claudePath(root, "commands", "haus-doctor.md")
2421
- ];
2430
+ const coreFiles = [claudePath(root, "settings.json"), claudePath(root, "rules", "haus.md")];
2422
2431
  const rootClaudeMdPath = await writeRootClaudeMd(root, dryRun);
2423
2432
  const decisionsSeedPath = await writeDecisionsSeed(root, dryRun);
2424
2433
  const workflowPath = await writeWorkflow(root, hausVersion2, dryRun, opts.force);
@@ -2441,12 +2450,6 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
2441
2450
  await applyProjectSettingsMerge(root);
2442
2451
  await assertPostApplySettingsHausContract(root);
2443
2452
  }
2444
- await writeManagedText(
2445
- root,
2446
- claudePath(root, "commands", "haus-doctor.md"),
2447
- "Run `haus doctor`.",
2448
- dryRun
2449
- );
2450
2453
  const legacyReviewPath = claudePath(root, "commands", "haus-review.md");
2451
2454
  if (await fs14.pathExists(legacyReviewPath)) {
2452
2455
  const content2 = await fs14.readFile(legacyReviewPath, "utf8");
@@ -2461,6 +2464,20 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
2461
2464
  }
2462
2465
  }
2463
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
+ }
2464
2481
  await writeManagedText(
2465
2482
  root,
2466
2483
  claudePath(root, "rules", "haus.md"),
@@ -2479,7 +2496,7 @@ async function writeClaudeFiles(root, dryRun, selectedIds, opts = {}) {
2479
2496
  "results in plain language \u2014 never make them use a terminal or read JSON.",
2480
2497
  "- Set up / configure / fix / check \u2192 `haus setup-project`, `haus apply --write`, `haus doctor`",
2481
2498
  "- Update package + catalog \u2192 `haus update`",
2482
- "- 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`).",
2483
2500
  ""
2484
2501
  ].join("\n"),
2485
2502
  dryRun
@@ -2781,8 +2798,25 @@ async function fetchSingleRef(url, cacheDir, meta) {
2781
2798
  return { result: "failed" };
2782
2799
  }
2783
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
+ }
2784
2818
  async function fetchRefsForItems(items, cacheDir) {
2785
- const urls = [...new Set(items.flatMap((item) => (item.references ?? []).filter(isLlmsTxtUrl)))];
2819
+ const urls = collectLlmsTxtUrls(items);
2786
2820
  const summary = {
2787
2821
  fetched: 0,
2788
2822
  unchanged: 0,
@@ -2917,14 +2951,22 @@ async function runApply(options) {
2917
2951
  if (!isDryRun) {
2918
2952
  try {
2919
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));
2920
2956
  const allItems = await loadCatalog(root);
2921
- const summary = await fetchRefsForItems(allItems, cacheDir);
2957
+ const installedItems = allItems.filter((item) => installedIds.has(item.id));
2958
+ const summary = await fetchRefsForItems(installedItems, cacheDir);
2922
2959
  if (summary.fetched > 0)
2923
2960
  log(`Fetched ${summary.fetched} llms.txt reference(s) to ${cacheDir}`);
2924
2961
  if (summary.failed > 0)
2925
2962
  warn(
2926
2963
  `Failed to fetch ${summary.failed} llms.txt reference(s): ${summary.failedUrls.join(", ")}`
2927
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
+ );
2928
2970
  } catch (err) {
2929
2971
  warn(
2930
2972
  `Could not fetch llms.txt references: ${err instanceof Error ? err.message : String(err)}`
@@ -3927,7 +3969,13 @@ async function readContextOrScan(root) {
3927
3969
 
3928
3970
  // src/update/npm-version.ts
3929
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
+ }
3930
3975
  async function fetchNpmVersionStatus(currentVersion) {
3976
+ if (isTestMode2() && process.env["HAUS_SKIP_NPM_CHECK"]) {
3977
+ return { current: currentVersion, latest: null, updateAvailable: false };
3978
+ }
3931
3979
  try {
3932
3980
  const res = await fetch(
3933
3981
  `https://registry.npmjs.org/${encodeURIComponent(NPM_PACKAGE_NAME)}/latest`,
@@ -5081,6 +5129,22 @@ function sourceVersion() {
5081
5129
  function globalSrcDir() {
5082
5130
  return path30.join(packageRoot(), "library", "global");
5083
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
+ }
5084
5148
  function collectSourceFiles(srcDir, claudeDir) {
5085
5149
  const entries = [];
5086
5150
  const skillsDir = path30.join(srcDir, "skills");
@@ -5093,6 +5157,7 @@ function collectSourceFiles(srcDir, claudeDir) {
5093
5157
  srcRelPath: path30.join("library", "global", "skills", skillName, "SKILL.md"),
5094
5158
  destPath: path30.join(claudeDir, "skills", skillName, "SKILL.md")
5095
5159
  });
5160
+ entries.push(...collectSkillAuxFiles(skillsDir, claudeDir, skillName));
5096
5161
  }
5097
5162
  }
5098
5163
  }
@@ -5534,7 +5599,7 @@ import path32 from "node:path";
5534
5599
  import fs26 from "fs-extra";
5535
5600
 
5536
5601
  // src/claude/managed-paths.ts
5537
- var PROJECT_MANAGED_CLAUDE_REL = ["rules/haus.md", "commands/haus-doctor.md"];
5602
+ var PROJECT_MANAGED_CLAUDE_REL = ["rules/haus.md"];
5538
5603
  var PROJECT_MANAGED_HAUS_REL = ["haus.lock.json"];
5539
5604
  function coreManagedAbsolutePaths(root) {
5540
5605
  const claude = PROJECT_MANAGED_CLAUDE_REL.map((rel) => claudePath(root, rel));
@@ -5778,6 +5843,11 @@ function summarizeLockDiff(before, after) {
5778
5843
  // src/update/lockfile.ts
5779
5844
  import { mkdir, readFile as readFile2, copyFile } from "node:fs/promises";
5780
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
+ }
5781
5851
  async function checkLock(root) {
5782
5852
  const lock = await readJson(hausPath(root, "haus.lock.json")) ?? [];
5783
5853
  const hasValidVersions = lock.every(
@@ -5841,6 +5911,10 @@ async function hasLocalOverrides(root) {
5841
5911
  var NPM_PACKAGE_NAME2 = "@haus-tech/haus-workflow";
5842
5912
  async function runUpdate(options) {
5843
5913
  const root = process.cwd();
5914
+ if (options.fromHook) {
5915
+ await runFromHookCheck(root);
5916
+ return;
5917
+ }
5844
5918
  if (options.check) {
5845
5919
  const pkgJson2 = await readJson(path35.join(packageRoot(), "package.json"));
5846
5920
  const currentVersion2 = pkgJson2?.version ?? "0.0.0";
@@ -5906,6 +5980,36 @@ async function runUpdate(options) {
5906
5980
  await refreshProjectFiles(root);
5907
5981
  log("Update applied with backup in .haus-workflow/backups/. Run haus doctor.");
5908
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
+ }
5909
6013
  async function refreshProjectFiles(root) {
5910
6014
  log("Refreshing project .claude/ files...");
5911
6015
  try {
@@ -6981,7 +7085,7 @@ program.command("clone").description("Clone a single git repository by URL").arg
6981
7085
  program.command("refresh").action(runRefresh);
6982
7086
  program.command("catalog-audit").action(runCatalogAudit);
6983
7087
  program.command("validate-catalog").argument("[manifest]").action(runValidateCatalog);
6984
- 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);
6985
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);
6986
7090
  program.command("scaffold [ids...]").description(
6987
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,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.1",
3
+ "version": "1.0.0",
4
4
  "description": "Haus AI workflow CLI for Claude Code.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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.