@haus-tech/haus-workflow 1.5.1 → 1.5.2

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,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.5.2](https://github.com/WeAreHausTech/haus-workflow/compare/v1.5.1...v1.5.2) (2026-08-10)
4
+
5
+ ### Bug Fixes
6
+
7
+ - **scanner:** gate version-numbered stack tags on actual pinned major ([#211](https://github.com/WeAreHausTech/haus-workflow/issues/211)) ([ffd68ce](https://github.com/WeAreHausTech/haus-workflow/commit/ffd68ce2ab13d8a68496b107ebf01f09ccfe1fc9))
8
+ - **workspace:** stop link-context self-reference recursion ([#210](https://github.com/WeAreHausTech/haus-workflow/issues/210)) ([79136a0](https://github.com/WeAreHausTech/haus-workflow/commit/79136a0a186cd1d751954368832b258009504e69))
9
+
3
10
  ## [1.5.1](https://github.com/WeAreHausTech/haus-workflow/compare/v1.5.0...v1.5.1) (2026-08-10)
4
11
 
5
12
  ### Bug Fixes
package/dist/cli.js CHANGED
@@ -4713,6 +4713,9 @@ function resolveAbsPath(workspaceRoot, folder, overrides) {
4713
4713
  if (override) return path31.resolve(workspaceRoot, override);
4714
4714
  return path31.resolve(workspaceRoot, folder);
4715
4715
  }
4716
+ function isSelfMember(workspaceRoot, member) {
4717
+ return path31.resolve(member.absPath) === path31.resolve(workspaceRoot);
4718
+ }
4716
4719
  async function readPathOverrides(workspaceRoot) {
4717
4720
  const filePath = path31.join(workspaceRoot, REPOS_LOCAL_FILE);
4718
4721
  const result = await readJsonDetailed(filePath);
@@ -4778,9 +4781,9 @@ async function readMembers(rootInfo) {
4778
4781
  const workspaceRoot = rootInfo.mainRoot;
4779
4782
  const overrides = await readPathOverrides(workspaceRoot);
4780
4783
  const fromYaml = await readFromWorkspaceYaml(workspaceRoot, overrides);
4781
- if (fromYaml) return fromYaml;
4784
+ if (fromYaml) return fromYaml.filter((m) => !isSelfMember(workspaceRoot, m));
4782
4785
  const fromManifest = await readFromManifest(workspaceRoot, overrides);
4783
- if (fromManifest) return fromManifest;
4786
+ if (fromManifest) return fromManifest.filter((m) => !isSelfMember(workspaceRoot, m));
4784
4787
  return [];
4785
4788
  }
4786
4789
 
@@ -4865,6 +4868,15 @@ function compareVersions(a, b) {
4865
4868
  }
4866
4869
  return semver.compare(normalizedA, normalizedB);
4867
4870
  }
4871
+ function resolveMajorVersion(range) {
4872
+ if (!range) return null;
4873
+ try {
4874
+ const min = semver.minVersion(range);
4875
+ return min ? min.major : null;
4876
+ } catch {
4877
+ return null;
4878
+ }
4879
+ }
4868
4880
 
4869
4881
  // src/scanner/detect-package-manager.ts
4870
4882
  import path33 from "node:path";
@@ -4896,6 +4908,11 @@ function detectPackageManager(root, packageManagerField) {
4896
4908
  var dep = (value) => ({ kind: "dep", value });
4897
4909
  var depPrefix = (value) => ({ kind: "depPrefix", value });
4898
4910
  var depAbsent = (value) => ({ kind: "depAbsent", value });
4911
+ var depMajor = (value, major) => ({
4912
+ kind: "depMajor",
4913
+ value,
4914
+ major
4915
+ });
4899
4916
  var fileEndsWith = (value) => ({
4900
4917
  kind: "file",
4901
4918
  value,
@@ -4935,6 +4952,8 @@ function matchSignal(sig, ctx) {
4935
4952
  return false;
4936
4953
  case "depAbsent":
4937
4954
  return !ctx.deps.has(sig.value);
4955
+ case "depMajor":
4956
+ return ctx.deps.has(sig.value) && resolveMajorVersion(ctx.depVersions.get(sig.value) ?? "") === sig.major;
4938
4957
  case "content":
4939
4958
  return ctx.contentBlob.includes(sig.value);
4940
4959
  case "file":
@@ -4986,9 +5005,9 @@ var ROLE_RULES = [
4986
5005
  ];
4987
5006
  var STACK_RULES = [
4988
5007
  { stack: ["frontend", "nextjs"], any: [dep("next")] },
4989
- { stack: ["frontend", "react19"], any: [dep("react")] },
5008
+ { stack: ["frontend", "react19"], any: [depMajor("react", 19)] },
4990
5009
  { stack: ["frontend", "vue"], any: [dep("vue")] },
4991
- { stack: ["frontend", "vite8"], any: [dep("vite")] },
5010
+ { stack: ["frontend", "vite8"], any: [depMajor("vite", 8)] },
4992
5011
  { stack: ["frontend", "react-router-v7"], all: [dep("react-router"), dep("@react-router/node")] },
4993
5012
  {
4994
5013
  stack: ["frontend", "tailwindcss"],
@@ -4998,7 +5017,7 @@ var STACK_RULES = [
4998
5017
  stack: ["frontend", "shadcn"],
4999
5018
  all: [fileEndsWith("components.json"), dep("class-variance-authority")]
5000
5019
  },
5001
- { stack: ["tooling", "typescript5"], any: [dep("typescript")] },
5020
+ { stack: ["tooling", "typescript5"], any: [depMajor("typescript", 5)] },
5002
5021
  { stack: ["backend", "sanity"], any: [dep("sanity"), dep("next-sanity"), dep("@sanity/client")] },
5003
5022
  { stack: ["backend", "strapi"], any: [dep("@strapi/strapi"), depPrefix("@strapi/")] },
5004
5023
  { stack: ["backend", "prisma"], any: [dep("prisma"), dep("@prisma/client")] },
@@ -5023,9 +5042,15 @@ var STACK_RULES = [
5023
5042
  stack: ["databases", "supabase"],
5024
5043
  any: [dep("@supabase/supabase-js"), depPrefix("@supabase/")]
5025
5044
  },
5026
- { stack: ["backend", "vendure3"], any: [dep("@vendure/core")] },
5045
+ { stack: ["backend", "vendure3"], any: [depMajor("@vendure/core", 3)] },
5027
5046
  { stack: ["backend", "nestjs"], any: [dep("@nestjs/core")] },
5028
5047
  { stack: ["backend", "nestjs"], any: [content("NestFactory")] },
5048
+ // Deliberately NOT major-gated: this rule catches vendure *plugin* repos that use
5049
+ // the @VendurePlugin decorator without depending on @vendure/core directly (a
5050
+ // peer/transitive dep, common in a plugin package). depMajor requires the name to
5051
+ // be a direct dependency (see matchSignal's depMajor case), which this repo shape
5052
+ // doesn't satisfy — falling back to content detection is the only signal available,
5053
+ // so it stays presence/content-based rather than gated on a major we can't read.
5029
5054
  { stack: ["backend", "vendure3"], any: [content("@VendurePlugin")] },
5030
5055
  { stack: ["backend", "graphql"], any: [dep("graphql"), dep("@nestjs/graphql")] },
5031
5056
  {
@@ -5127,17 +5152,21 @@ function describeUnknownDetection(unsupportedSignals, isLinkedWorktree) {
5127
5152
  function blocked(rel) {
5128
5153
  return SENSITIVE_PATH_REGEXES.some((x) => x.test(rel));
5129
5154
  }
5130
- function dependencySet(pkg, composer) {
5131
- const depNames = /* @__PURE__ */ new Set();
5155
+ function dependencyVersionMap(pkg, composer) {
5156
+ const versions = /* @__PURE__ */ new Map();
5132
5157
  const pushObj = (obj) => {
5133
5158
  if (!isRecord2(obj)) return;
5134
- for (const key of Object.keys(obj)) depNames.add(key);
5159
+ for (const key of Object.keys(obj)) {
5160
+ if (versions.has(key)) continue;
5161
+ const value = obj[key];
5162
+ versions.set(key, typeof value === "string" ? value : "");
5163
+ }
5135
5164
  };
5136
5165
  pushObj(pkg?.dependencies);
5137
5166
  pushObj(pkg?.devDependencies);
5138
5167
  pushObj(composer?.require);
5139
5168
  pushObj(composer?.["require-dev"]);
5140
- return [...depNames].sort();
5169
+ return versions;
5141
5170
  }
5142
5171
  function finalizeRoles(registryRoles, deps, files) {
5143
5172
  const roles = new Set(registryRoles);
@@ -5269,10 +5298,16 @@ async function scanProject(root) {
5269
5298
  const composer = await readJson(path36.join(root, "composer.json"));
5270
5299
  const files = await listFiles(root, SAFE_FILES);
5271
5300
  const safeFiles = files.filter((f) => !blocked(f));
5272
- const deps = dependencySet(pkg, composer);
5301
+ const depVersions = dependencyVersionMap(pkg, composer);
5302
+ const deps = [...depVersions.keys()].sort();
5273
5303
  const packageManager = detectPackageManager(root, String(pkg?.packageManager ?? ""));
5274
5304
  const contentBlob = await buildContentBlob(root, safeFiles);
5275
- const detection = runDetection({ deps: new Set(deps), files: safeFiles, contentBlob });
5305
+ const detection = runDetection({
5306
+ deps: new Set(deps),
5307
+ depVersions,
5308
+ files: safeFiles,
5309
+ contentBlob
5310
+ });
5276
5311
  const roles = finalizeRoles(detection.roles, deps, safeFiles);
5277
5312
  const stacks = detection.stacks;
5278
5313
  if (packageManager === "yarn") stacks.packageManagers.push("yarn4");
@@ -7330,18 +7365,18 @@ async function findRepoRoots(workspaceRoot, maxDepth = DEFAULT_MAX_DEPTH) {
7330
7365
  return repoRoots;
7331
7366
  }
7332
7367
  async function discoverRepos(workspaceRoot, maxDepth = DEFAULT_MAX_DEPTH) {
7333
- const repoRoots = await findRepoRoots(workspaceRoot, maxDepth);
7368
+ const repoRoots = (await findRepoRoots(workspaceRoot, maxDepth)).filter((dir) => dir !== ".");
7334
7369
  return mapWithConcurrency(repoRoots, async (relDir) => {
7335
7370
  const absDir = path44.resolve(workspaceRoot, relDir);
7336
7371
  const pkg = await readJson(path44.join(absDir, "package.json"));
7337
- const name = typeof pkg?.name === "string" && pkg.name.length > 0 ? pkg.name : path44.basename(relDir === "." ? workspaceRoot : absDir);
7372
+ const name = typeof pkg?.name === "string" && pkg.name.length > 0 ? pkg.name : path44.basename(absDir);
7338
7373
  let role = "auto";
7339
7374
  try {
7340
7375
  const scan = await scanProject(absDir);
7341
7376
  if (scan.repoRoles.length > 0) role = scan.repoRoles.join("+");
7342
7377
  } catch {
7343
7378
  }
7344
- return { name, path: relDir === "." ? "." : relDir, role };
7379
+ return { name, path: relDir, role };
7345
7380
  });
7346
7381
  }
7347
7382
  function mergeWorkspaceConfig(existing, discovered, opts = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haus-tech/haus-workflow",
3
- "version": "1.5.1",
3
+ "version": "1.5.2",
4
4
  "description": "Haus AI workflow CLI for Claude Code.",
5
5
  "type": "module",
6
6
  "bin": {