@haus-tech/haus-workflow 4.1.0 → 4.3.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,29 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.3.0](https://github.com/WeAreHausTech/haus-workflow/compare/v4.2.0...v4.3.0) (2026-08-24)
4
+
5
+ ### Features
6
+
7
+ - **doctor:** advisory hint when project docs were never generated ([#341](https://github.com/WeAreHausTech/haus-workflow/issues/341)) ([711ca0f](https://github.com/WeAreHausTech/haus-workflow/commit/711ca0fa5d2ed27e0bf05601c2e7a556ce542733))
8
+ - **install:** allow all non-destructive subcommands ([#338](https://github.com/WeAreHausTech/haus-workflow/issues/338)) ([47d4636](https://github.com/WeAreHausTech/haus-workflow/commit/47d463689ca9e82faaf1f3eed74e1a308a47890f))
9
+ - **new:** add react-router stack; project:new writes docs, sets structure, stays private ([#345](https://github.com/WeAreHausTech/haus-workflow/issues/345)) ([2a45cd9](https://github.com/WeAreHausTech/haus-workflow/commit/2a45cd9b44aa35ecf38aaa623cd7ce02fdff62d8)), closes [#339](https://github.com/WeAreHausTech/haus-workflow/issues/339) [#340](https://github.com/WeAreHausTech/haus-workflow/issues/340) [#342](https://github.com/WeAreHausTech/haus-workflow/issues/342) [#343](https://github.com/WeAreHausTech/haus-workflow/issues/343) [#339](https://github.com/WeAreHausTech/haus-workflow/issues/339) [#340](https://github.com/WeAreHausTech/haus-workflow/issues/340) [#342](https://github.com/WeAreHausTech/haus-workflow/issues/342) [#343](https://github.com/WeAreHausTech/haus-workflow/issues/343)
10
+ - **scanner:** detect python and go repos as backend stack tokens ([#344](https://github.com/WeAreHausTech/haus-workflow/issues/344)) ([6a062d9](https://github.com/WeAreHausTech/haus-workflow/commit/6a062d989408634810a7a7007718c981960a2814))
11
+ - **skill:** update the request-pipeline skills to the current working versions ([#334](https://github.com/WeAreHausTech/haus-workflow/issues/334)) ([7e2c556](https://github.com/WeAreHausTech/haus-workflow/commit/7e2c556be36e333b4577814daf7f09ac63cc023f))
12
+
13
+ ### Bug Fixes
14
+
15
+ - **request-classify:** redefine B2 around what the change requires ([#335](https://github.com/WeAreHausTech/haus-workflow/issues/335)) ([5ef8418](https://github.com/WeAreHausTech/haus-workflow/commit/5ef84184273f151e93fb1daee7a60bd0f0a80b7c))
16
+
17
+ ## [4.2.0](https://github.com/WeAreHausTech/haus-workflow/compare/v4.1.0...v4.2.0) (2026-08-21)
18
+
19
+ ### Features
20
+
21
+ - **install:** bundle request-pipeline and slack-cowork global skills ([#332](https://github.com/WeAreHausTech/haus-workflow/issues/332)) ([1978b5d](https://github.com/WeAreHausTech/haus-workflow/commit/1978b5d2cb5098a8c87e9db7148633ebef5b4c33))
22
+
23
+ ### Bug Fixes
24
+
25
+ - haus new empty-cache flag and setupOk signal ([#333](https://github.com/WeAreHausTech/haus-workflow/issues/333)) ([5a9004a](https://github.com/WeAreHausTech/haus-workflow/commit/5a9004ae23d7189d5645dc7e830e3b4cbb420203)), closes [#329](https://github.com/WeAreHausTech/haus-workflow/issues/329) [#330](https://github.com/WeAreHausTech/haus-workflow/issues/330)
26
+
3
27
  ## [4.1.0](https://github.com/WeAreHausTech/haus-workflow/compare/v4.0.1...v4.1.0) (2026-08-21)
4
28
 
5
29
  ### Features
package/README.md CHANGED
@@ -138,13 +138,16 @@ waiting on timeouts (catalog sync, npm version check, workflow-template fetch, l
138
138
  reference fetch, global-plugin install; the read-only local plugin probe still runs — see
139
139
  [docs/cli.md](docs/cli.md) → `--offline` for why). It never changes what gets installed.
140
140
 
141
- The honest-shortfall reporting covers `haus apply`, `haus init`, and `haus workspace
142
- setup` (issue #325 extended `apply`'s original ADR-0060 gate to the other two): a run
143
- that cannot install everything it wanted reports `K of N catalog items installed` and
144
- exits 1 if it installed none, unless `--allow-empty-cache` opts into writing core files
145
- anyway (still exits 1 having installed nothing). `workspace setup` reports this per
146
- member repo — a repo that installed zero catalog items is recorded `failed`, not `ok`.
147
- See [docs/cli.md](docs/cli.md) for each command's exact flags and behaviour.
141
+ The honest-shortfall reporting covers `haus apply`, `haus init`, `haus new`, and
142
+ `haus workspace setup` (issue #325 extended `apply`'s original ADR-0060 gate to `init`
143
+ and `workspace setup`; issue #329 threaded the same `--allow-empty-cache` flag through
144
+ `haus new`): a run that cannot install everything it wanted reports `K of N catalog
145
+ items installed` and exits 1 if it installed none, unless `--allow-empty-cache` opts
146
+ into writing core files anyway (still exits 1 having installed nothing). `workspace
147
+ setup` reports this per member repo — a repo that installed zero catalog items, hit
148
+ the zero-signal guard, or was refused for an unsupported catalog format is recorded
149
+ `failed`, not `ok` (issue #330). See [docs/cli.md](docs/cli.md) for each command's
150
+ exact flags and behaviour.
148
151
 
149
152
  ## Internal docs
150
153
 
package/dist/cli.js CHANGED
@@ -1225,10 +1225,6 @@ var init_validation_rules = __esm({
1225
1225
  "library/catalog/validation-rules.json"() {
1226
1226
  validation_rules_default = {
1227
1227
  forbiddenTags: [
1228
- "python",
1229
- "django",
1230
- "go",
1231
- "golang",
1232
1228
  "rust",
1233
1229
  "rustlang",
1234
1230
  "java",
@@ -1278,6 +1274,8 @@ var init_validation_rules = __esm({
1278
1274
  "typescript",
1279
1275
  "php",
1280
1276
  "csharp",
1277
+ "python",
1278
+ "golang",
1281
1279
  "vendure",
1282
1280
  "vendure3",
1283
1281
  "nestjs",
@@ -2380,9 +2378,6 @@ function isRecord(v) {
2380
2378
 
2381
2379
  // src/scanner/detection.ts
2382
2380
  var UNSUPPORTED_MARKERS = {
2383
- "requirements.txt": "python",
2384
- "pyproject.toml": "python",
2385
- "go.mod": "go",
2386
2381
  "Cargo.toml": "rust",
2387
2382
  "pom.xml": "java",
2388
2383
  "build.gradle": "java",
@@ -2818,7 +2813,55 @@ async function writeManagedJson(root, filePath, value, dryRun, quiet = false) {
2818
2813
  }
2819
2814
 
2820
2815
  // src/install/allow-rules.ts
2821
- var ALLOWED_SUBCOMMANDS = ["init", "apply", "doctor", "scan", "recommend"];
2816
+ var ALLOWED_SUBCOMMANDS = [
2817
+ // Setup / health / recommendation flow
2818
+ "init",
2819
+ "apply",
2820
+ "doctor",
2821
+ "scan",
2822
+ "recommend",
2823
+ "explain-recommendation",
2824
+ "fetch-refs",
2825
+ "rescan",
2826
+ // Project creation (create-only; refuses existing targets)
2827
+ "clone",
2828
+ "new",
2829
+ // Global install / update (writes ~/.claude + plugin pins, never deletes user content)
2830
+ "update",
2831
+ "install",
2832
+ "configs",
2833
+ // Read-only reporting and validation
2834
+ "ci-gate",
2835
+ "catalog-audit",
2836
+ "validate-catalog",
2837
+ "backups list",
2838
+ "guard file-access",
2839
+ "guard bash",
2840
+ // Catalog cache maintenance (staged + validated, never deletes user content)
2841
+ "catalog bundle export",
2842
+ "catalog bundle import",
2843
+ // Decision gate (read-only; `suggest` prints a draft, it does not write one)
2844
+ "decisions check",
2845
+ "decisions suggest",
2846
+ "decisions next-number",
2847
+ "decisions guard",
2848
+ "decisions validate",
2849
+ "adr check",
2850
+ "adr suggest",
2851
+ "adr next-number",
2852
+ "adr guard",
2853
+ "adr validate",
2854
+ // Workspace (planning previews, scans, setup merge; no deleters)
2855
+ "workspace discover",
2856
+ "workspace scan",
2857
+ "workspace setup",
2858
+ "workspace doctor",
2859
+ "workspace link-context",
2860
+ "workspace worktree add",
2861
+ "workspace worktree hydrate",
2862
+ "workspace worktree list",
2863
+ "workspace worktree doctor"
2864
+ ];
2822
2865
  function buildAllowRules() {
2823
2866
  return [...new Set(ALLOWED_SUBCOMMANDS.map((sub) => `Bash(haus ${sub}:*)`))];
2824
2867
  }
@@ -7587,7 +7630,23 @@ var STACK_RULES = [
7587
7630
  stack: ["auth", "saml2"],
7588
7631
  any: [dep("24slides/laravel-saml2"), dep("aacotroneo/laravel-saml2")]
7589
7632
  },
7590
- { stack: ["auth", "next-auth"], any: [dep("next-auth"), dep("@auth/core")] }
7633
+ { stack: ["auth", "next-auth"], any: [dep("next-auth"), dep("@auth/core")] },
7634
+ // Python — broad language signal so python-tooling catalog items gated on
7635
+ // { stack: 'python' } are recommendable on any Python project (same rationale as
7636
+ // the 'php' rule above: composer.json presence). Appended at the end so existing
7637
+ // bucket insertion order is untouched.
7638
+ {
7639
+ stack: ["backend", "python"],
7640
+ any: [
7641
+ fileEndsWith("pyproject.toml"),
7642
+ fileEndsWith("requirements.txt"),
7643
+ fileEndsWith("setup.py"),
7644
+ fileEquals("Pipfile")
7645
+ ]
7646
+ },
7647
+ // Go — broad language signal, same rationale: catalog items gated on
7648
+ // { stack: 'go' } become recommendable on any Go module.
7649
+ { stack: ["backend", "go"], any: [fileEndsWith("go.mod")] }
7591
7650
  ];
7592
7651
  function runDetection(ctx, rules = STACK_RULES) {
7593
7652
  const roles = [];
@@ -7707,11 +7766,17 @@ var SAFE_FILES = [
7707
7766
  "**/*.sln",
7708
7767
  "docker-compose.*",
7709
7768
  "Dockerfile",
7710
- // Unsupported-ecosystem markers — matched by PRESENCE only (never content-read; none
7711
- // match the content-blob extensions). Drive detectionStatus / unsupportedSignals.
7769
+ // Python/Go detection signals — matched by PRESENCE only (never content-read; none
7770
+ // match the content-blob extensions). Drive the `python`/`go` stack rules in
7771
+ // detection-registry.ts; requirements.txt / pyproject.toml / go.mod also still feed
7772
+ // detectionStatus / unsupportedSignals (UNSUPPORTED_MARKERS in detection.ts).
7712
7773
  "requirements.txt",
7713
7774
  "pyproject.toml",
7775
+ "setup.py",
7776
+ "Pipfile",
7714
7777
  "go.mod",
7778
+ // Unsupported-ecosystem markers — matched by PRESENCE only (never content-read; none
7779
+ // match the content-blob extensions). Drive detectionStatus / unsupportedSignals.
7715
7780
  "Cargo.toml",
7716
7781
  "pom.xml",
7717
7782
  "build.gradle",
@@ -8032,6 +8097,20 @@ async function runDoctor(options) {
8032
8097
  ok("- .haus-workflow/workflow-config.md: OK (project-owned)");
8033
8098
  }
8034
8099
  }
8100
+ const docsSkillInstalled = await fs37.pathExists(
8101
+ path45.join(root, ".claude", "skills", "writing-documentation", "SKILL.md")
8102
+ );
8103
+ if (docsSkillInstalled) {
8104
+ if (await fs37.pathExists(path45.join(root, "docs", "SUMMARY.md"))) {
8105
+ ok("- PROJECT DOCS: OK (docs/SUMMARY.md present)");
8106
+ } else {
8107
+ suggest(
8108
+ "- PROJECT DOCS: docs/SUMMARY.md missing \u2014 run the writing-documentation skill (`/docs` in Claude Code) to generate the project docs",
8109
+ "The project docs index (docs/SUMMARY.md) has not been generated yet",
8110
+ "run `/docs` in Claude Code (the writing-documentation skill)"
8111
+ );
8112
+ }
8113
+ }
8035
8114
  if (workflowExists) {
8036
8115
  const prettierIgnore = await readText(path45.join(root, ".prettierignore")) ?? "";
8037
8116
  const lines = prettierIgnore.split("\n");
@@ -10585,8 +10664,10 @@ async function runSetupCore(root, opts) {
10585
10664
  hooksOk: false,
10586
10665
  written: [],
10587
10666
  // Pre-existing zero-signal guard, not the #325 catalog-install honesty check —
10588
- // deliberately left out of scope for `catalogInstallOk` (see its doc comment).
10589
- catalogInstallOk: true
10667
+ // `catalogInstallOk` stays true; `setupOk` is false so workspace setup cannot
10668
+ // record this repo as ok (issue #330).
10669
+ catalogInstallOk: true,
10670
+ setupOk: false
10590
10671
  };
10591
10672
  }
10592
10673
  await writeJson(hausPath(root, "recommendation.json"), recommendation);
@@ -10616,7 +10697,8 @@ async function runSetupCore(root, opts) {
10616
10697
  warnings: warningLines,
10617
10698
  hooksOk,
10618
10699
  written: [],
10619
- catalogInstallOk: true
10700
+ catalogInstallOk: true,
10701
+ setupOk: true
10620
10702
  };
10621
10703
  const envelope = (hooksResult) => ({
10622
10704
  scan: scanResult,
@@ -10645,7 +10727,7 @@ async function runSetupCore(root, opts) {
10645
10727
  error(sync.formatRefusal);
10646
10728
  process.exitCode = 1;
10647
10729
  emitJsonEnvelope(json, quiet, envelope(hooks));
10648
- return baseResult;
10730
+ return { ...baseResult, setupOk: false };
10649
10731
  }
10650
10732
  if (sync.newItems.length > 0) {
10651
10733
  say(`Catalog cache populated: ${sync.newItems.length} new item(s).`);
@@ -10680,7 +10762,7 @@ async function runSetupCore(root, opts) {
10680
10762
  error(message2);
10681
10763
  process.exitCode = 1;
10682
10764
  emitJsonEnvelope(json, quiet, envelope(hooks));
10683
- return { ...baseResult, catalogInstallOk: false };
10765
+ return { ...baseResult, catalogInstallOk: false, setupOk: false };
10684
10766
  }
10685
10767
  } else {
10686
10768
  warn(
@@ -10727,7 +10809,13 @@ async function runSetupCore(root, opts) {
10727
10809
  say(`- HOOKS OK: ${hooksAfter.message}`);
10728
10810
  }
10729
10811
  emitJsonEnvelope(json, quiet, envelope(hooksAfter));
10730
- return { ...baseResult, hooksOk: hooksOkAfter, written: files, catalogInstallOk };
10812
+ return {
10813
+ ...baseResult,
10814
+ hooksOk: hooksOkAfter,
10815
+ written: files,
10816
+ catalogInstallOk,
10817
+ setupOk: catalogInstallOk
10818
+ };
10731
10819
  }
10732
10820
 
10733
10821
  // src/commands/init.ts
@@ -10963,6 +11051,29 @@ var STACKS = [
10963
11051
  overlayConfigIds: ["haus.eslint-config", "haus.prettier-config"],
10964
11052
  expectedRole: "react-app"
10965
11053
  },
11054
+ {
11055
+ id: "react-router",
11056
+ title: "React Router app (framework mode, TypeScript)",
11057
+ scaffold: {
11058
+ package: "create-react-router",
11059
+ majorPin: 8,
11060
+ // --yes skips all option prompts; --no-git-init because the git step here
11061
+ // owns init/branch/commit; --no-agent-skills because haus installs its own
11062
+ // context assets. Default template ships react-router + @react-router/node,
11063
+ // which is exactly the detection registry's react-router-app rule.
11064
+ args: [
11065
+ "{name}",
11066
+ "--no-install",
11067
+ "--no-git-init",
11068
+ "--no-agent-skills",
11069
+ "--package-manager",
11070
+ "{pm}",
11071
+ "--yes"
11072
+ ]
11073
+ },
11074
+ overlayConfigIds: ["haus.eslint-config", "haus.prettier-config"],
11075
+ expectedRole: "react-router-app"
11076
+ },
10966
11077
  {
10967
11078
  id: "nest",
10968
11079
  title: "NestJS API",
@@ -11153,13 +11264,15 @@ async function createProject(opts, deps = defaultDeps) {
11153
11264
  phase = "init";
11154
11265
  if (!opts.json) log("\u25B6 init: haus setup pipeline");
11155
11266
  const exitCodeBeforeSetup = process.exitCode;
11267
+ let setupResult;
11156
11268
  try {
11157
- await deps.setup(projectPath, {
11269
+ setupResult = await deps.setup(projectPath, {
11158
11270
  json: false,
11159
11271
  apply: true,
11160
11272
  dryRun: false,
11161
11273
  quiet: opts.json,
11162
- confirm: opts.yes ? async () => true : () => confirm("Approve and write Claude files now?")
11274
+ confirm: opts.yes ? async () => true : () => confirm("Approve and write Claude files now?"),
11275
+ allowEmptyCache: opts.allowEmptyCache
11163
11276
  });
11164
11277
  } catch (err) {
11165
11278
  steps.push({
@@ -11169,15 +11282,26 @@ async function createProject(opts, deps = defaultDeps) {
11169
11282
  });
11170
11283
  return { ok: false, projectPath, repoUrl, installHint, steps };
11171
11284
  }
11285
+ const catalogInstallRefused = setupResult?.catalogInstallOk === false;
11286
+ const retryCmd = catalogInstallRefused ? `cd ${opts.name} && haus init --allow-empty-cache` : `cd ${opts.name} && haus init`;
11172
11287
  if (process.exitCode === 1 && exitCodeBeforeSetup !== 1) {
11173
- steps.push({
11174
- step: "init",
11175
- status: "failed",
11176
- detail: `haus init reported failure. Project and repo are intact \u2014 cd ${opts.name} && haus init to retry.`
11177
- });
11178
- return { ok: false, projectPath, repoUrl, installHint, steps };
11288
+ if (opts.allowEmptyCache && catalogInstallRefused) {
11289
+ steps.push({
11290
+ step: "init",
11291
+ status: "done",
11292
+ detail: "core files written; catalog items skipped (--allow-empty-cache)"
11293
+ });
11294
+ } else {
11295
+ steps.push({
11296
+ step: "init",
11297
+ status: "failed",
11298
+ detail: `haus init reported failure. Project and repo are intact \u2014 ${retryCmd} to retry.`
11299
+ });
11300
+ return { ok: false, projectPath, repoUrl, installHint, steps };
11301
+ }
11302
+ } else {
11303
+ steps.push({ step: "init", status: "done" });
11179
11304
  }
11180
- steps.push({ step: "init", status: "done" });
11181
11305
  phase = "overlay";
11182
11306
  if (!opts.json) log("\u25B6 overlay: haus config items");
11183
11307
  let overlay;
@@ -11264,7 +11388,7 @@ async function createProject(opts, deps = defaultDeps) {
11264
11388
  }
11265
11389
  if (!opts.json) log(`\u2713 Created ${projectPath}${repoUrl ? ` \u2192 ${repoUrl}` : ""}
11266
11390
  ${installHint}`);
11267
- return { ok: true, projectPath, repoUrl, installHint, steps };
11391
+ return { ok: process.exitCode !== 1, projectPath, repoUrl, installHint, steps };
11268
11392
  }
11269
11393
 
11270
11394
  // src/commands/new.ts
@@ -11297,7 +11421,8 @@ function resolveNewOptions(stackArg, nameArg, flags, parentDir) {
11297
11421
  pm,
11298
11422
  yes: flags.yes === true,
11299
11423
  dryRun,
11300
- json: flags.json === true
11424
+ json: flags.json === true,
11425
+ allowEmptyCache: flags.allowEmptyCache === true
11301
11426
  }
11302
11427
  };
11303
11428
  }
@@ -14232,8 +14357,8 @@ async function runWorkspaceSetup(workspaceRoot, options = {}) {
14232
14357
  error(`Setup failed for ${repo.name}: ${message2}`);
14233
14358
  continue;
14234
14359
  }
14235
- if (!res.catalogInstallOk) {
14236
- const message2 = "No catalog items were installed for this repo (zero installable content) \u2014 see the per-repo output above, or re-run with --allow-empty-cache.";
14360
+ if (!res.setupOk) {
14361
+ const message2 = !res.catalogInstallOk ? "No catalog items were installed for this repo (zero installable content) \u2014 see the per-repo output above, or re-run with --allow-empty-cache." : "Setup wrote nothing for this repo \u2014 see the per-repo output above.";
14237
14362
  statuses.push({
14238
14363
  name: repo.name,
14239
14364
  path: repo.path,
@@ -14769,7 +14894,10 @@ program.command("init").description("Full setup: scan, recommend, and optionally
14769
14894
  "--allow-empty-cache",
14770
14895
  "Proceed when no catalog item has installable content: write core files and skip the catalog items. Still exits 1 if none installed (ADR-0060)"
14771
14896
  ).action(runInit);
14772
- program.command("new").description("Create a new project from an allowed Haus stack, push it to GitHub, and run init").argument("[stack]", "Stack id (omit to pick interactively; see --list)").argument("[name]", "Project directory and repo name").option("--list", "Print the allowed stacks and exit").option("--org <org>", "GitHub org to create the repo in (default: personal account)").option("--public", "Create the GitHub repo public").option("--private", "Create the GitHub repo private (default)").option("--no-github", "Skip GitHub repo creation and push").option("--pm <pm>", "Package manager: yarn | npm | pnpm", "yarn").option("-y, --yes", "Skip confirmations (non-interactive)").option("--dry-run", "Print the full plan without executing anything").option("--json", "Emit a machine-readable result document").action(runNew);
14897
+ program.command("new").description("Create a new project from an allowed Haus stack, push it to GitHub, and run init").argument("[stack]", "Stack id (omit to pick interactively; see --list)").argument("[name]", "Project directory and repo name").option("--list", "Print the allowed stacks and exit").option("--org <org>", "GitHub org to create the repo in (default: personal account)").option("--public", "Create the GitHub repo public").option("--private", "Create the GitHub repo private (default)").option("--no-github", "Skip GitHub repo creation and push").option("--pm <pm>", "Package manager: yarn | npm | pnpm", "yarn").option("-y, --yes", "Skip confirmations (non-interactive)").option("--dry-run", "Print the full plan without executing anything").option("--json", "Emit a machine-readable result document").option(
14898
+ "--allow-empty-cache",
14899
+ "Proceed when no catalog item has installable content: write core files and skip the catalog items. Still exits 1 if none installed (ADR-0060)"
14900
+ ).action(runNew);
14773
14901
  program.command("clone").description("Clone a single git repository by URL").argument("<url>", "Git URL to clone").argument("[dir]", "Target directory (default: repo name derived from the URL)").option("--dry-run", "Print what would happen without cloning").action(
14774
14902
  (url, dir, opts) => runClone(url, { dir, dryRun: opts.dryRun })
14775
14903
  );