@haus-tech/haus-workflow 1.6.0 → 1.7.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,14 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.7.0](https://github.com/WeAreHausTech/haus-workflow/compare/v1.6.0...v1.7.0) (2026-08-13)
4
+
5
+ ### Features
6
+
7
+ - **cli:** --dry-run on destructive commands; doctor states what --force discards ([#283](https://github.com/WeAreHausTech/haus-workflow/issues/283)) ([35dfe55](https://github.com/WeAreHausTech/haus-workflow/commit/35dfe553e8282011d365492a3168fee7aee819b4)), closes [#237](https://github.com/WeAreHausTech/haus-workflow/issues/237) [#248](https://github.com/WeAreHausTech/haus-workflow/issues/248)
8
+ - **cli:** add --json to workspace worktree and backups subcommands ([#280](https://github.com/WeAreHausTech/haus-workflow/issues/280)) ([bf1f7b2](https://github.com/WeAreHausTech/haus-workflow/commit/bf1f7b219432ee7cf2dfe6a70e8ae74ab8963b46)), closes [#235](https://github.com/WeAreHausTech/haus-workflow/issues/235) [#236](https://github.com/WeAreHausTech/haus-workflow/issues/236)
9
+ - **cli:** doctor --json and explain-recommendation hint in recommend output ([#281](https://github.com/WeAreHausTech/haus-workflow/issues/281)) ([12fa8ff](https://github.com/WeAreHausTech/haus-workflow/commit/12fa8ff3723a07b735529cfdb32e193302a90bc2)), closes [#234](https://github.com/WeAreHausTech/haus-workflow/issues/234) [#234](https://github.com/WeAreHausTech/haus-workflow/issues/234) [#247](https://github.com/WeAreHausTech/haus-workflow/issues/247)
10
+ - **cli:** upgrade visibility — release notes on update, lingering deprecated-item detection in doctor ([#284](https://github.com/WeAreHausTech/haus-workflow/issues/284)) ([e8d0c04](https://github.com/WeAreHausTech/haus-workflow/commit/e8d0c04b2e7d457cc0e4ea20f18a55fd4d8ee394)), closes [#242](https://github.com/WeAreHausTech/haus-workflow/issues/242) [#251](https://github.com/WeAreHausTech/haus-workflow/issues/251)
11
+
3
12
  ## [1.6.0](https://github.com/WeAreHausTech/haus-workflow/compare/v1.5.8...v1.6.0) (2026-08-13)
4
13
 
5
14
  ### Features
package/dist/cli.js CHANGED
@@ -4644,14 +4644,24 @@ function formatAge(mtimeMs, now) {
4644
4644
  if (hours < 48) return `${hours}h ago`;
4645
4645
  return `${Math.round(hours / 24)}d ago`;
4646
4646
  }
4647
- async function runList(root) {
4647
+ async function runList(root, json) {
4648
4648
  const entries = await listBackups(root);
4649
+ const newestFirst = [...entries].reverse();
4650
+ if (json) {
4651
+ log(
4652
+ JSON.stringify(
4653
+ newestFirst.map(({ id, kind, mtimeMs, absPath }) => ({ id, kind, mtimeMs, absPath })),
4654
+ null,
4655
+ 2
4656
+ )
4657
+ );
4658
+ return;
4659
+ }
4649
4660
  if (entries.length === 0) {
4650
4661
  log("No backups found under .haus-workflow/backups/.");
4651
4662
  return;
4652
4663
  }
4653
4664
  const now = Date.now();
4654
- const newestFirst = [...entries].reverse();
4655
4665
  for (const entry of newestFirst) {
4656
4666
  log(`${entry.id} [${entry.kind}] ${formatAge(entry.mtimeMs, now)}`);
4657
4667
  }
@@ -4685,13 +4695,13 @@ async function lstatIfRegularFile(abs) {
4685
4695
  throw err;
4686
4696
  }
4687
4697
  }
4688
- async function restoreLockBackup(root, entry, yes) {
4698
+ async function restoreLockBackup(root, entry, yes, json, dryRun) {
4689
4699
  const target = hausPath(root, "haus.lock.json");
4690
4700
  if (await isUnsafeWriteTarget(root, target)) {
4691
4701
  error(
4692
4702
  `Refusing to restore: ${target} (or a parent directory) is a symlink or other non-regular file \u2014 never written through.`
4693
4703
  );
4694
- return false;
4704
+ return { ok: false, restored: 0 };
4695
4705
  }
4696
4706
  if (await fs22.pathExists(target)) {
4697
4707
  const targetStat = await fs22.stat(target);
@@ -4701,22 +4711,30 @@ async function restoreLockBackup(root, entry, yes) {
4701
4711
  );
4702
4712
  }
4703
4713
  }
4714
+ if (dryRun) {
4715
+ if (!json) {
4716
+ log(
4717
+ `[dry-run] Would restore .haus-workflow/haus.lock.json from ${entry.id}. Nothing was changed.`
4718
+ );
4719
+ }
4720
+ return { ok: true, restored: 0, dryRun: true, wouldRestore: [".haus-workflow/haus.lock.json"] };
4721
+ }
4704
4722
  if (!yes) {
4705
4723
  const ok = await confirm(
4706
4724
  `Restore .haus-workflow/haus.lock.json from backup ${entry.id}? This overwrites the current lockfile.`
4707
4725
  );
4708
4726
  if (!ok) {
4709
- log("Cancelled.");
4710
- return false;
4727
+ if (!json) log("Cancelled.");
4728
+ return { ok: false, restored: 0 };
4711
4729
  }
4712
4730
  }
4713
4731
  if (!await lstatIfRegularFile(entry.absPath) || await isUnsafeWriteTarget(root, target)) {
4714
4732
  error(`Refusing to restore: ${entry.id} or ${target} changed since being checked.`);
4715
- return false;
4733
+ return { ok: false, restored: 0 };
4716
4734
  }
4717
4735
  await fs22.copy(entry.absPath, target, { overwrite: true });
4718
- log(`Restored .haus-workflow/haus.lock.json from ${entry.id}.`);
4719
- return true;
4736
+ if (!json) log(`Restored .haus-workflow/haus.lock.json from ${entry.id}.`);
4737
+ return { ok: true, restored: 1 };
4720
4738
  }
4721
4739
  async function collectFilesRecursive(dir) {
4722
4740
  const files = [];
@@ -4743,7 +4761,7 @@ async function collectFilesRecursive(dir) {
4743
4761
  }
4744
4762
  return { files, skippedLinks, skippedOther };
4745
4763
  }
4746
- async function restoreDirBackup(root, entry, yes) {
4764
+ async function restoreDirBackup(root, entry, yes, json, dryRun) {
4747
4765
  const { files, skippedLinks, skippedOther } = await collectFilesRecursive(entry.absPath);
4748
4766
  if (skippedLinks.length > 0) {
4749
4767
  warn(
@@ -4756,8 +4774,8 @@ async function restoreDirBackup(root, entry, yes) {
4756
4774
  );
4757
4775
  }
4758
4776
  if (files.length === 0) {
4759
- log(`Backup ${entry.id} contains no files.`);
4760
- return false;
4777
+ if (!json) log(`Backup ${entry.id} contains no files.`);
4778
+ return { ok: false, restored: 0 };
4761
4779
  }
4762
4780
  const safeFiles = [];
4763
4781
  const unsafeTargets = [];
@@ -4773,8 +4791,8 @@ async function restoreDirBackup(root, entry, yes) {
4773
4791
  );
4774
4792
  }
4775
4793
  if (safeFiles.length === 0) {
4776
- log(`Backup ${entry.id} has no restorable targets.`);
4777
- return false;
4794
+ if (!json) log(`Backup ${entry.id} has no restorable targets.`);
4795
+ return { ok: false, restored: 0 };
4778
4796
  }
4779
4797
  const stale = [];
4780
4798
  const vanished = [];
@@ -4805,8 +4823,8 @@ async function restoreDirBackup(root, entry, yes) {
4805
4823
  );
4806
4824
  }
4807
4825
  if (toRestore.length === 0) {
4808
- log(`Backup ${entry.id} has no restorable files left after re-validation.`);
4809
- return false;
4826
+ if (!json) log(`Backup ${entry.id} has no restorable files left after re-validation.`);
4827
+ return { ok: false, restored: 0 };
4810
4828
  }
4811
4829
  if (stale.length > 0) {
4812
4830
  const detail = stale.map(
@@ -4816,13 +4834,22 @@ async function restoreDirBackup(root, entry, yes) {
4816
4834
  `${stale.length} file(s) at the restore destination are newer than backup ${entry.id} and will be overwritten: ${detail}`
4817
4835
  );
4818
4836
  }
4837
+ if (dryRun) {
4838
+ const rels = toRestore.map((abs) => path26.relative(entry.absPath, abs));
4839
+ if (!json) {
4840
+ log(`[dry-run] Would restore ${rels.length} file(s) from ${entry.id}:`);
4841
+ for (const rel of rels) log(` ${rel}`);
4842
+ log("[dry-run] Nothing was changed.");
4843
+ }
4844
+ return { ok: true, restored: 0, dryRun: true, wouldRestore: rels };
4845
+ }
4819
4846
  if (!yes) {
4820
4847
  const ok = await confirm(
4821
4848
  `Restore ${toRestore.length} file(s) from backup ${entry.id} to their original paths?`
4822
4849
  );
4823
4850
  if (!ok) {
4824
- log("Cancelled.");
4825
- return false;
4851
+ if (!json) log("Cancelled.");
4852
+ return { ok: false, restored: 0 };
4826
4853
  }
4827
4854
  }
4828
4855
  let restoredCount = 0;
@@ -4843,23 +4870,20 @@ async function restoreDirBackup(root, entry, yes) {
4843
4870
  `Skipped ${skippedAtWrite.length} file(s) that changed since being checked, right before restoring: ${skippedAtWrite.join(", ")}`
4844
4871
  );
4845
4872
  }
4846
- log(`Restored ${restoredCount} file(s) from ${entry.id}.`);
4847
- return restoredCount > 0;
4873
+ if (!json) log(`Restored ${restoredCount} file(s) from ${entry.id}.`);
4874
+ return { ok: restoredCount > 0, restored: restoredCount };
4848
4875
  }
4849
- async function runRestore(root, id, yes) {
4876
+ async function runRestore(root, id, yes, json, dryRun) {
4850
4877
  const entries = await listBackups(root);
4851
4878
  const entry = entries.find((e) => e.id === id);
4852
4879
  if (!entry) {
4853
4880
  error(`No backup found with id "${id}". Run \`haus backups list\` to see available backups.`);
4854
- return false;
4881
+ return { ok: false, restored: 0 };
4855
4882
  }
4856
- return entry.kind === "lock" ? restoreLockBackup(root, entry, yes) : restoreDirBackup(root, entry, yes);
4883
+ return entry.kind === "lock" ? restoreLockBackup(root, entry, yes, json, dryRun) : restoreDirBackup(root, entry, yes, json, dryRun);
4857
4884
  }
4858
4885
  async function runPrune(root, opts) {
4859
- if (opts.olderThanDays === void 0 && opts.keep === void 0) {
4860
- error("Specify --older-than <days> or --keep <n> \u2014 refusing to prune with no bound.");
4861
- return false;
4862
- }
4886
+ const json = Boolean(opts.json);
4863
4887
  const entries = await listBackups(root);
4864
4888
  const now = Date.now();
4865
4889
  const toRemove = /* @__PURE__ */ new Set();
@@ -4872,11 +4896,32 @@ async function runPrune(root, opts) {
4872
4896
  if (excess > 0) for (const entry of entries.slice(0, excess)) toRemove.add(entry.id);
4873
4897
  }
4874
4898
  if (toRemove.size === 0) {
4875
- log("Nothing to prune.");
4876
- return true;
4899
+ if (!json) log("Nothing to prune.");
4900
+ return {
4901
+ ok: true,
4902
+ removed: [],
4903
+ failed: [],
4904
+ kept: entries.length,
4905
+ ...opts.dryRun ? { dryRun: true, wouldRemove: [] } : {}
4906
+ };
4877
4907
  }
4878
4908
  const removeList = entries.filter((e) => toRemove.has(e.id));
4879
4909
  const keepCount = entries.length - removeList.length;
4910
+ if (opts.dryRun) {
4911
+ if (!json) {
4912
+ log(`[dry-run] Would remove ${removeList.length} backup(s), keeping ${keepCount}:`);
4913
+ for (const e of removeList) log(` ${e.id}`);
4914
+ log("[dry-run] Nothing was changed.");
4915
+ }
4916
+ return {
4917
+ ok: true,
4918
+ removed: [],
4919
+ failed: [],
4920
+ kept: keepCount,
4921
+ dryRun: true,
4922
+ wouldRemove: removeList.map((e) => e.id)
4923
+ };
4924
+ }
4880
4925
  if (!opts.yes) {
4881
4926
  const ok = await confirm(
4882
4927
  `Remove ${removeList.length} backup(s)?
@@ -4884,8 +4929,8 @@ async function runPrune(root, opts) {
4884
4929
  Keeping ${keepCount}.`
4885
4930
  );
4886
4931
  if (!ok) {
4887
- log("Cancelled.");
4888
- return false;
4932
+ if (!json) log("Cancelled.");
4933
+ return { ok: false, removed: [], failed: [], kept: entries.length, cancelled: true };
4889
4934
  }
4890
4935
  }
4891
4936
  const removed = [];
@@ -4898,37 +4943,52 @@ Keeping ${keepCount}.`
4898
4943
  failed.push({ id: entry.id, message: err instanceof Error ? err.message : String(err) });
4899
4944
  }
4900
4945
  }
4901
- log(`Removed ${removed.length} backup(s), kept ${entries.length - removed.length}.`);
4946
+ if (!json) log(`Removed ${removed.length} backup(s), kept ${entries.length - removed.length}.`);
4902
4947
  if (failed.length > 0) {
4903
4948
  warn(
4904
4949
  `Failed to remove ${failed.length} backup(s): ${failed.map((f) => `${f.id} (${f.message})`).join(", ")}`
4905
4950
  );
4906
- return false;
4907
4951
  }
4908
- return true;
4952
+ return {
4953
+ ok: failed.length === 0,
4954
+ removed,
4955
+ failed,
4956
+ kept: entries.length - removed.length
4957
+ };
4909
4958
  }
4910
4959
  async function runBackups(action, opts = {}) {
4911
4960
  const root = opts.root ?? process.cwd();
4961
+ const json = Boolean(opts.json);
4912
4962
  if (action === "list") {
4913
- await runList(root);
4963
+ await runList(root, json);
4914
4964
  return;
4915
4965
  }
4916
4966
  if (action === "restore") {
4917
4967
  if (!opts.id) {
4968
+ if (json) log(JSON.stringify({ ok: false, error: "Usage: haus backups restore <id>" }));
4918
4969
  error("Usage: haus backups restore <id>");
4919
4970
  process.exitCode = 1;
4920
4971
  return;
4921
4972
  }
4922
- if (!await runRestore(root, opts.id, Boolean(opts.yes))) process.exitCode = 1;
4973
+ const result2 = await runRestore(root, opts.id, Boolean(opts.yes), json, Boolean(opts.dryRun));
4974
+ if (json) log(JSON.stringify({ id: opts.id, ...result2 }, null, 2));
4975
+ if (!result2.ok) process.exitCode = 1;
4976
+ return;
4977
+ }
4978
+ if (opts.olderThan === void 0 && opts.keep === void 0) {
4979
+ const message = "Specify --older-than <days> or --keep <n> \u2014 refusing to prune with no bound.";
4980
+ if (json) log(JSON.stringify({ ok: false, error: message }));
4981
+ error(message);
4982
+ process.exitCode = 1;
4923
4983
  return;
4924
4984
  }
4925
4985
  let olderThanDays;
4926
4986
  if (opts.olderThan !== void 0) {
4927
4987
  const n = Number(opts.olderThan);
4928
4988
  if (!Number.isFinite(n) || n < 0) {
4929
- error(
4930
- `Invalid --older-than value "${opts.olderThan}" \u2014 expected a non-negative number of days.`
4931
- );
4989
+ const message = `Invalid --older-than value "${opts.olderThan}" \u2014 expected a non-negative number of days.`;
4990
+ if (json) log(JSON.stringify({ ok: false, error: message }));
4991
+ error(message);
4932
4992
  process.exitCode = 1;
4933
4993
  return;
4934
4994
  }
@@ -4938,13 +4998,23 @@ async function runBackups(action, opts = {}) {
4938
4998
  if (opts.keep !== void 0) {
4939
4999
  const n = Number(opts.keep);
4940
5000
  if (!Number.isFinite(n) || n < 0 || !Number.isInteger(n)) {
4941
- error(`Invalid --keep value "${opts.keep}" \u2014 expected a non-negative integer.`);
5001
+ const message = `Invalid --keep value "${opts.keep}" \u2014 expected a non-negative integer.`;
5002
+ if (json) log(JSON.stringify({ ok: false, error: message }));
5003
+ error(message);
4942
5004
  process.exitCode = 1;
4943
5005
  return;
4944
5006
  }
4945
5007
  keep = n;
4946
5008
  }
4947
- if (!await runPrune(root, { olderThanDays, keep, yes: Boolean(opts.yes) })) process.exitCode = 1;
5009
+ const result = await runPrune(root, {
5010
+ olderThanDays,
5011
+ keep,
5012
+ yes: Boolean(opts.yes),
5013
+ json,
5014
+ dryRun: Boolean(opts.dryRun)
5015
+ });
5016
+ if (json) log(JSON.stringify(result, null, 2));
5017
+ if (!result.ok) process.exitCode = 1;
4948
5018
  }
4949
5019
 
4950
5020
  // src/commands/catalog-audit.ts
@@ -6194,12 +6264,19 @@ async function runDoctor(options) {
6194
6264
  const root = process.cwd();
6195
6265
  if (options?.hooks) {
6196
6266
  const hooks2 = await verifyProjectSettingsHooksContract(root);
6197
- if (hooks2.skipped) {
6198
- error(`Haus doctor --hooks: ${hooks2.message}`);
6199
- process.exitCode = 1;
6267
+ const hooksOk = !hooks2.skipped && hooks2.ok;
6268
+ if (options.json) {
6269
+ log(
6270
+ JSON.stringify(
6271
+ { ok: hooksOk, skipped: Boolean(hooks2.skipped), message: hooks2.message },
6272
+ null,
6273
+ 2
6274
+ )
6275
+ );
6276
+ if (!hooksOk) process.exitCode = 1;
6200
6277
  return;
6201
6278
  }
6202
- if (!hooks2.ok) {
6279
+ if (!hooksOk) {
6203
6280
  error(`Haus doctor --hooks: ${hooks2.message}`);
6204
6281
  process.exitCode = 1;
6205
6282
  return;
@@ -6339,10 +6416,17 @@ async function runDoctor(options) {
6339
6416
  const templateContent = await readText(templatePath);
6340
6417
  const verdict = checkManagedTamper(bodyContent, storedHashMatch?.[1], templateContent ?? null);
6341
6418
  if (verdict.status === "tampered" || verdict.status === "legacy-diverged") {
6419
+ let diffNote = "";
6420
+ if (templateContent != null) {
6421
+ const summary = summarizeDiff(
6422
+ createUnifiedDiff(".haus-workflow/WORKFLOW.md", bodyContent, templateContent)
6423
+ );
6424
+ diffNote = ` \u2014 restoring would discard ${summary.deletions} local line(s) and add ${summary.additions}`;
6425
+ }
6342
6426
  flag(
6343
- "- .haus-workflow/WORKFLOW.md: modified locally (run `haus apply --write --force` to restore)",
6344
- "The workflow standard file was edited after haus wrote it",
6345
- "haus apply --write --force"
6427
+ `- .haus-workflow/WORKFLOW.md: modified locally${diffNote} (preview: \`haus apply --dry-run\`; restore: \`haus apply --write --force\`)`,
6428
+ `The workflow standard file was edited after haus wrote it${diffNote}`,
6429
+ "preview with `haus apply --dry-run`, then `haus apply --write --force` to restore"
6346
6430
  );
6347
6431
  } else if (verdict.status === "stale") {
6348
6432
  suggest(
@@ -6524,6 +6608,40 @@ async function runDoctor(options) {
6524
6608
  }
6525
6609
  }
6526
6610
  }
6611
+ {
6612
+ const lockRows = await readJson(
6613
+ hausPath(root, "haus.lock.json")
6614
+ );
6615
+ if (lockRows && lockRows.length > 0) {
6616
+ const catalogItems = await loadCatalog(root);
6617
+ const byId = new Map(catalogItems.map((item) => [item.id, item]));
6618
+ const activePinIds = activePins.map((pin) => pin.id);
6619
+ const lingering = [];
6620
+ for (const row of lockRows) {
6621
+ if (typeof row.id !== "string") continue;
6622
+ if (isSupersededByPlugin(row.id, activePinIds)) continue;
6623
+ const manifestItem = byId.get(row.id);
6624
+ if (manifestItem && manifestItem.reviewStatus !== "deprecated") continue;
6625
+ let onDisk = false;
6626
+ for (const rel of row.paths ?? []) {
6627
+ const abs = resolveContainedPath(root, rel);
6628
+ if (abs !== null && await fs30.pathExists(abs)) {
6629
+ onDisk = true;
6630
+ break;
6631
+ }
6632
+ }
6633
+ if (onDisk) lingering.push(row.id);
6634
+ }
6635
+ if (lingering.length > 0) {
6636
+ const idList = `${lingering.slice(0, 3).join(", ")}${lingering.length > 3 ? ", \u2026" : ""}`;
6637
+ suggest(
6638
+ `- CATALOG ITEMS: ${lingering.length} installed item(s) are removed or deprecated in the current catalog (${idList}) \u2014 pre-0.30.0 CLIs never pruned these`,
6639
+ `${lingering.length} installed item(s) no longer exist in the catalog (removed or deprecated)`,
6640
+ "haus update (prunes them when unmodified; see docs/runbook.md for locally-edited copies)"
6641
+ );
6642
+ }
6643
+ }
6644
+ }
6527
6645
  const pkgJson = await readJson(path40.join(packageRoot(), "package.json"));
6528
6646
  const currentVersion = pkgJson?.version ?? "0.0.0";
6529
6647
  const npmStatus = await fetchNpmVersionStatus(currentVersion);
@@ -6539,6 +6657,10 @@ async function runDoctor(options) {
6539
6657
  ok(`- CLI: ${currentVersion} (version check unavailable)`);
6540
6658
  }
6541
6659
  if (attention.length > 0) process.exitCode = 1;
6660
+ if (options?.json) {
6661
+ log(JSON.stringify({ ok: attention.length === 0, attention, suggestions, detail }, null, 2));
6662
+ return;
6663
+ }
6542
6664
  if (attention.length === 0) {
6543
6665
  log("\u2705 Your project is set up and healthy.");
6544
6666
  } else {
@@ -6968,6 +7090,31 @@ async function hasLocalOverrides(root) {
6968
7090
  }
6969
7091
  }
6970
7092
 
7093
+ // src/update/release-notes.ts
7094
+ var RELEASES_API_BASE = "https://api.github.com/repos/WeAreHausTech/haus-workflow/releases/tags";
7095
+ var MAX_NOTE_LINES = 40;
7096
+ async function fetchReleaseNotes(version) {
7097
+ try {
7098
+ const headers = await getGithubApiHeaders();
7099
+ const res = await fetch(`${RELEASES_API_BASE}/v${version}`, {
7100
+ headers,
7101
+ // Same bound npm-version.ts uses — best-effort must also mean bounded-time.
7102
+ signal: AbortSignal.timeout(8e3)
7103
+ });
7104
+ if (!res.ok) return null;
7105
+ const release = await res.json();
7106
+ if (typeof release.body !== "string" || release.body.trim().length === 0) return null;
7107
+ const lines = release.body.trim().split("\n");
7108
+ if (lines.length <= MAX_NOTE_LINES) return lines.join("\n");
7109
+ return [
7110
+ ...lines.slice(0, MAX_NOTE_LINES),
7111
+ `\u2026 (${lines.length - MAX_NOTE_LINES} more lines)`
7112
+ ].join("\n");
7113
+ } catch {
7114
+ return null;
7115
+ }
7116
+ }
7117
+
6971
7118
  // src/commands/update.ts
6972
7119
  var NPM_PACKAGE_NAME2 = "@haus-tech/haus-workflow";
6973
7120
  async function runUpdate(options) {
@@ -7032,6 +7179,13 @@ async function runUpdate(options) {
7032
7179
  if (npmStatus.updateAvailable && npmStatus.latest !== null) {
7033
7180
  log(`npm update available: ${currentVersion} \u2192 ${npmStatus.latest}`);
7034
7181
  log(`Run: npm install -g ${NPM_PACKAGE_NAME2}`);
7182
+ const notes = await fetchReleaseNotes(npmStatus.latest);
7183
+ if (notes !== null) {
7184
+ log(`
7185
+ What changed in ${npmStatus.latest}:`);
7186
+ log(notes);
7187
+ log("");
7188
+ }
7035
7189
  } else if (npmStatus.latest !== null) {
7036
7190
  log(`npm package up to date: ${currentVersion}`);
7037
7191
  }
@@ -8118,13 +8272,6 @@ function applyCoInstallSuppression(recommended, skipped) {
8118
8272
  message: "Skip TDD superpower when a stack testing skill is selected",
8119
8273
  signal: "co-install:stack-testing"
8120
8274
  },
8121
- {
8122
- suppress: "haus.superpowers-specifying-gates",
8123
- whenAnyRecommended: ["haus.superpowers-checking-gates"],
8124
- code: "co-install-gate-workflow",
8125
- message: "Skip specifying-gates when checking-gates is selected",
8126
- signal: "co-install:checking-gates"
8127
- },
8128
8275
  {
8129
8276
  suppress: "haus.ecc-redis-patterns",
8130
8277
  whenAnyRecommended: ["haus.redis-redis-connections"],
@@ -8687,6 +8834,7 @@ async function runRecommend(options) {
8687
8834
  );
8688
8835
  }
8689
8836
  for (const w of result.warnings) log(`- WARN: ${w}`);
8837
+ log("Run `haus explain-recommendation` for per-item reasons and skip explanations.");
8690
8838
  }
8691
8839
 
8692
8840
  // src/commands/refresh.ts
@@ -8996,6 +9144,12 @@ async function runUndo(options) {
8996
9144
  `(preserving ${lockPreserved.length} locally-modified catalog item file(s) \u2014 not shown above)`
8997
9145
  );
8998
9146
  }
9147
+ if (options.dryRun) {
9148
+ log(`[dry-run] Would remove:
9149
+ ${summaryParts.join("\n ")}`);
9150
+ log("[dry-run] User-owned .claude/ files would be preserved. Nothing was changed.");
9151
+ return;
9152
+ }
8999
9153
  if (!options.yes) {
9000
9154
  const ok = await confirm(
9001
9155
  `Remove haus-managed files?
@@ -9121,6 +9275,21 @@ function printUninstallResult(result) {
9121
9275
  // src/commands/uninstall.ts
9122
9276
  async function runUninstallCommand(options) {
9123
9277
  try {
9278
+ if (options.dryRun) {
9279
+ const manifest = await readManifest();
9280
+ if (!manifest) {
9281
+ log("[dry-run] No install manifest found \u2014 nothing would be removed.");
9282
+ return;
9283
+ }
9284
+ if (manifest.files.length > 0) {
9285
+ log(`[dry-run] Would remove ${manifest.files.length} haus-managed file(s) from ~/.claude/:`);
9286
+ for (const f of manifest.files) log(` ${f.destPath}`);
9287
+ }
9288
+ log(
9289
+ "[dry-run] Would strip haus hook entries from ~/.claude/settings.json and remove the install manifest. Nothing was changed."
9290
+ );
9291
+ return;
9292
+ }
9124
9293
  if (!options.yes) {
9125
9294
  const manifest = await readManifest();
9126
9295
  if (manifest) {
@@ -10338,6 +10507,9 @@ async function runRemove(opts) {
10338
10507
  }
10339
10508
 
10340
10509
  // src/commands/workspace/worktree.ts
10510
+ function emitJson(result) {
10511
+ log(JSON.stringify(result, null, 2));
10512
+ }
10341
10513
  function describeInstall(hydration) {
10342
10514
  if (hydration.install.plan.manager === "none") return "no install step (no recognized lockfile)";
10343
10515
  if (!hydration.install.ran)
@@ -10346,7 +10518,8 @@ function describeInstall(hydration) {
10346
10518
  }
10347
10519
  async function handleAdd(opts) {
10348
10520
  if (!opts.slug) {
10349
- error("Usage: haus workspace worktree add <slug>");
10521
+ if (opts.json) emitJson({ ok: false, error: "Usage: haus workspace worktree add <slug>" });
10522
+ else error("Usage: haus workspace worktree add <slug>");
10350
10523
  process.exitCode = 1;
10351
10524
  return;
10352
10525
  }
@@ -10359,10 +10532,16 @@ async function handleAdd(opts) {
10359
10532
  };
10360
10533
  const result = await runAdd(addOpts);
10361
10534
  if ("error" in result) {
10362
- error(result.error);
10535
+ if (opts.json) emitJson(result);
10536
+ else error(result.error);
10363
10537
  process.exitCode = 1;
10364
10538
  return;
10365
10539
  }
10540
+ if (opts.json) {
10541
+ emitJson(result);
10542
+ if (!result.dryRun && !result.ok) process.exitCode = 1;
10543
+ return;
10544
+ }
10366
10545
  if (result.dryRun) {
10367
10546
  log(
10368
10547
  `[dry-run] Would create the workspace worktree at ${result.worktreePath} on branch "${result.branch}" (${result.workspaceWorktree.branchAction}).`
@@ -10402,10 +10581,16 @@ async function handleHydrate(opts) {
10402
10581
  };
10403
10582
  const result = await runHydrateWorktree(hydrateOpts);
10404
10583
  if (!result.ok && "error" in result) {
10405
- error(result.error);
10584
+ if (opts.json) emitJson(result);
10585
+ else error(result.error);
10406
10586
  process.exitCode = 1;
10407
10587
  return;
10408
10588
  }
10589
+ if (opts.json) {
10590
+ emitJson(result);
10591
+ if (!result.dryRun && !result.ok) process.exitCode = 1;
10592
+ return;
10593
+ }
10409
10594
  if (result.dryRun) {
10410
10595
  log(`[dry-run] Would hydrate "${result.slug}":`);
10411
10596
  for (const r of result.results) {
@@ -10427,7 +10612,8 @@ async function handleHydrate(opts) {
10427
10612
  }
10428
10613
  async function handleRemove(opts) {
10429
10614
  if (!opts.slug) {
10430
- error("Usage: haus workspace worktree remove <slug>");
10615
+ if (opts.json) emitJson({ ok: false, error: "Usage: haus workspace worktree remove <slug>" });
10616
+ else error("Usage: haus workspace worktree remove <slug>");
10431
10617
  process.exitCode = 1;
10432
10618
  return;
10433
10619
  }
@@ -10437,6 +10623,11 @@ async function handleRemove(opts) {
10437
10623
  dryRun: Boolean(opts.dryRun)
10438
10624
  };
10439
10625
  const result = await runRemove(removeOpts);
10626
+ if (opts.json) {
10627
+ emitJson(result);
10628
+ if (!result.ok || "removed" in result && result.failed.length > 0) process.exitCode = 1;
10629
+ return;
10630
+ }
10440
10631
  if (!result.ok && "error" in result) {
10441
10632
  error(result.error);
10442
10633
  process.exitCode = 1;
@@ -10461,8 +10652,13 @@ async function handleRemove(opts) {
10461
10652
  }
10462
10653
  }
10463
10654
  }
10464
- async function handleList() {
10655
+ async function handleList(opts) {
10465
10656
  const result = await runList2();
10657
+ if (opts.json) {
10658
+ emitJson(result);
10659
+ if (!result.ok) process.exitCode = 1;
10660
+ return;
10661
+ }
10466
10662
  if (!result.ok) {
10467
10663
  error(result.error);
10468
10664
  process.exitCode = 1;
@@ -10533,7 +10729,7 @@ async function runWorktree(action, opts = {}) {
10533
10729
  case "remove":
10534
10730
  return handleRemove(opts);
10535
10731
  case "list":
10536
- return handleList();
10732
+ return handleList(opts);
10537
10733
  case "doctor":
10538
10734
  return handleDoctor(opts);
10539
10735
  }
@@ -11361,6 +11557,52 @@ async function runWorkspaceUndo(workspaceRoot, options = {}) {
11361
11557
  if (config.repos.length === 0) {
11362
11558
  log(`No repos configured in ${WORKSPACE_FILE}.`);
11363
11559
  }
11560
+ if (options.dryRun) {
11561
+ for (const repo of config.repos) {
11562
+ const repoRoot = path71.resolve(workspaceRoot, repo.path);
11563
+ log(`
11564
+ \u2192 ${repo.name} (${repo.path})`);
11565
+ try {
11566
+ if (!existsSync9(repoRoot) || !statSync2(repoRoot).isDirectory()) {
11567
+ throw new Error(`Repo path is not a directory: ${repo.path}`);
11568
+ }
11569
+ await runUndo({ dryRun: true, root: repoRoot });
11570
+ } catch (err) {
11571
+ warn(
11572
+ `[dry-run] Undo preview failed for ${repo.name}: ${err instanceof Error ? err.message : String(err)}`
11573
+ );
11574
+ }
11575
+ }
11576
+ const linkedContext2 = await readLinkedContext(workspaceRoot);
11577
+ for (const entry of linkedContext2) {
11578
+ const abs = resolveContainedPath(workspaceRoot, entry.path);
11579
+ if (abs === null) {
11580
+ warn(`Refusing to remove linked context path outside the workspace root: ${entry.path}`);
11581
+ continue;
11582
+ }
11583
+ if (await fs48.pathExists(abs)) {
11584
+ log(`[dry-run] Would remove ${entry.path} (linked ${entry.type} from ${entry.repo})`);
11585
+ }
11586
+ }
11587
+ const workspaceArtifacts = [
11588
+ ...WORKSPACE_AGGREGATE_FILES.map((rel) => hausPath(workspaceRoot, rel)),
11589
+ manifestPath(workspaceRoot),
11590
+ hausPath(workspaceRoot, "WORKSPACE.md")
11591
+ ];
11592
+ for (const abs of workspaceArtifacts) {
11593
+ if (await fs48.pathExists(abs)) {
11594
+ log(`[dry-run] Would remove ${path71.relative(workspaceRoot, abs)}`);
11595
+ }
11596
+ }
11597
+ if (!await fs48.pathExists(hausPath(workspaceRoot, "WORKSPACE.md"))) {
11598
+ const claudeMd = await readText(path71.join(workspaceRoot, "CLAUDE.md"));
11599
+ if (claudeMd?.includes(WORKSPACE_BLOCK_BEGIN) || claudeMd && hasLegacyWorkspaceBlock(claudeMd)) {
11600
+ log("[dry-run] Would remove the workspace haus import block from CLAUDE.md");
11601
+ }
11602
+ }
11603
+ log(`[dry-run] ${WORKSPACE_FILE} would be left in place. Nothing was changed.`);
11604
+ return;
11605
+ }
11364
11606
  if (!options.yes) {
11365
11607
  const ok = await confirm(
11366
11608
  `Undo haus setup for ${config.repos.length} repo(s) in this workspace, plus workspace-root artifacts?`
@@ -11560,7 +11802,7 @@ async function runWorkspace(action, options = {}) {
11560
11802
  await runWorkspaceDoctor(workspaceRoot, { json: options.json });
11561
11803
  return;
11562
11804
  case "undo":
11563
- await runWorkspaceUndo(workspaceRoot, { yes: options.yes });
11805
+ await runWorkspaceUndo(workspaceRoot, { yes: options.yes, dryRun: options.dryRun });
11564
11806
  return;
11565
11807
  case "link-context":
11566
11808
  await runWorkspaceLinkContext(workspaceRoot, { write: options.write, json: options.json });
@@ -11608,7 +11850,7 @@ program.command("setup-project").description("Fast full setup: scan, recommend,
11608
11850
  ).action(runSetupProject);
11609
11851
  program.command("doctor").description(
11610
11852
  "Validate project setup: hooks contract, managed files, cache freshness, CLI version"
11611
- ).option("--hooks", "Verify .claude/settings.json matches the hook contract").action(runDoctor);
11853
+ ).option("--hooks", "Verify .claude/settings.json matches the hook contract").option("--json", "Emit all findings as one JSON document on stdout instead of human output").action(runDoctor);
11612
11854
  program.command("fetch-refs").description("Fetch and cache llms.txt content from catalog item references").option("--all", "Fetch for all catalog items (default)").option("--id <id>", "Fetch for a single catalog item by id").option("--json", "Emit machine-readable JSON summary to stdout").action(runFetchRefs);
11613
11855
  program.command("apply").description("Write recommended catalog items and core Claude files to .claude/").option("--dry-run").option("--write").option("--select", "Interactively select catalog items before applying").option(
11614
11856
  "--ids <ids...>",
@@ -11623,12 +11865,20 @@ program.command("apply").description("Write recommended catalog items and core C
11623
11865
  "--prune",
11624
11866
  "Remove lock-tracked catalog items no longer in the current recommendation (hash-gated, backed up first)"
11625
11867
  ).action(runApply);
11626
- program.command("undo").description("Remove haus-managed project files; preserves user-owned .claude/ content").option("-y, --yes", "Skip confirmation").action(runUndo);
11868
+ program.command("undo").description("Remove haus-managed project files; preserves user-owned .claude/ content").option("-y, --yes", "Skip confirmation").option("--dry-run", "Print what would be removed without deleting anything").action(runUndo);
11627
11869
  var backups = program.command("backups").description("List, restore, or prune backups under .haus-workflow/backups/");
11628
- backups.command("list").description("List backups under .haus-workflow/backups/").action(() => runBackups("list"));
11629
- backups.command("restore <id>").description("Restore a specific backup by id (see `haus backups list`)").option("-y, --yes", "Skip confirmation").action((id, opts) => runBackups("restore", { id, yes: opts.yes }));
11630
- backups.command("prune").description("Delete old backups by age and/or count (requires --older-than or --keep)").option("--older-than <days>", "Remove backups older than N days").option("--keep <n>", "Keep only the N most recent backups").option("-y, --yes", "Skip confirmation").action(
11631
- (opts) => runBackups("prune", { olderThan: opts.olderThan, keep: opts.keep, yes: opts.yes })
11870
+ backups.command("list").description("List backups under .haus-workflow/backups/").option("--json", "Emit the backup entries as JSON (newest first)").action((opts) => runBackups("list", { json: opts.json }));
11871
+ backups.command("restore <id>").description("Restore a specific backup by id (see `haus backups list`)").option("-y, --yes", "Skip confirmation").option("--json", "Emit a machine-readable result (JSON-only stdout; warnings on stderr)").option("--dry-run", "Print what would be restored without writing anything").action(
11872
+ (id, opts) => runBackups("restore", { id, yes: opts.yes, json: opts.json, dryRun: opts.dryRun })
11873
+ );
11874
+ backups.command("prune").description("Delete old backups by age and/or count (requires --older-than or --keep)").option("--older-than <days>", "Remove backups older than N days").option("--keep <n>", "Keep only the N most recent backups").option("-y, --yes", "Skip confirmation").option("--json", "Emit a machine-readable result (JSON-only stdout; warnings on stderr)").option("--dry-run", "Print what would be pruned without deleting anything").action(
11875
+ (opts) => runBackups("prune", {
11876
+ olderThan: opts.olderThan,
11877
+ dryRun: opts.dryRun,
11878
+ keep: opts.keep,
11879
+ yes: opts.yes,
11880
+ json: opts.json
11881
+ })
11632
11882
  );
11633
11883
  program.command("explain-recommendation").description("Print a per-item breakdown of the current recommendation, with skip reasons").option("--json").action(runExplainRecommendation);
11634
11884
  program.command("init").description("First-run setup; delegates to setup-project if .haus-workflow/ does not exist").option("--json").action(runInit);
@@ -11646,7 +11896,7 @@ program.command("install").description("Seed ~/.claude/ with Haus-managed skills
11646
11896
  program.command("scaffold [ids...]").description(
11647
11897
  "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."
11648
11898
  ).option("--force", "Overwrite existing files").option("--dry-run", "Preview what would be written without changing anything").option("--root <path>", "Project root (defaults to cwd)", process.cwd()).action(runScaffold);
11649
- program.command("uninstall").description("Remove Haus-managed skills, agents, and hook entries from ~/.claude/").option("--force").option("-y, --yes", "Skip confirmation").action(runUninstallCommand);
11899
+ program.command("uninstall").description("Remove Haus-managed skills, agents, and hook entries from ~/.claude/").option("--force").option("-y, --yes", "Skip confirmation").option("--dry-run", "Print what would be removed without deleting anything").action(runUninstallCommand);
11650
11900
  var guard = program.command("guard").description("PreToolUse hook guards: block dangerous bash commands and sensitive file access");
11651
11901
  guard.command("file-access").description("Block reads/edits of sensitive paths (run by the Claude Code hook)").option("--from-hook").action((opts) => runGuard("file-access", opts));
11652
11902
  guard.command("bash").description("Block dangerous shell commands (run by the Claude Code hook)").option("--from-hook").action((opts) => runGuard("bash", opts));
@@ -11668,7 +11918,7 @@ workspace.command("discover").description("Auto-find member repos and write/merg
11668
11918
  workspace.command("scan").description("Aggregate a cross-repo summary from a fast scan of each repo").option("--json", "Output the written artifact paths as JSON").action((opts) => runWorkspace("scan", opts));
11669
11919
  workspace.command("setup").description("Per-repo setup loop + workspace layer + manifest").option("--write", "Apply changes (default previews only)").option("--dry-run", "Preview changes without writing").option("--json", "Emit machine-readable per-repo output").option("--continue-on-error", "Keep going past a failed repo (default fail-fast)").option("--only <names>", "Restrict to comma-separated repo names").action((opts) => runWorkspace("setup", opts));
11670
11920
  workspace.command("doctor").description("Report workspace drift against the manifest").option("--json", "Output the manifest and drift array as JSON").action((opts) => runWorkspace("doctor", opts));
11671
- workspace.command("undo").description("Revert haus setup for every configured repo, plus workspace-root artifacts").option("-y, --yes", "Skip confirmation").action((opts) => runWorkspace("undo", opts));
11921
+ workspace.command("undo").description("Revert haus setup for every configured repo, plus workspace-root artifacts").option("-y, --yes", "Skip confirmation").option("--dry-run", "Print what would be removed without deleting anything").action((opts) => runWorkspace("undo", opts));
11672
11922
  workspace.command("link-context").description(
11673
11923
  "Copy each member repo's skill/agent/command into the workspace root's .claude/, prefixed to avoid collisions"
11674
11924
  ).option("--write", "Persist copies + manifest + gitignore (default previews only)").option("--json", "Output the link result as JSON").action((opts) => runWorkspace("link-context", opts));
@@ -11677,29 +11927,31 @@ var worktree = workspace.command("worktree").description(
11677
11927
  );
11678
11928
  worktree.command("add <slug>").description(
11679
11929
  "Create a workspace worktree plus one member worktree per repo, on a mirrored branch"
11680
- ).option("--branch <name>", "Branch to create/checkout (default: the slug)").option("--only <repos>", "Restrict to comma- or space-separated member ids/folders").option("--no-hydrate", "Skip hydration (CoW clone + install-reconciliation)").option("--dry-run", "Preview without writing or running anything").action(
11930
+ ).option("--branch <name>", "Branch to create/checkout (default: the slug)").option("--only <repos>", "Restrict to comma- or space-separated member ids/folders").option("--no-hydrate", "Skip hydration (CoW clone + install-reconciliation)").option("--dry-run", "Preview without writing or running anything").option("--json", "Emit the structured add result as JSON").action(
11681
11931
  (slug, opts) => runWorktree("add", {
11682
11932
  slug,
11683
11933
  branch: opts.branch,
11684
11934
  only: opts.only,
11685
11935
  hydrate: opts.hydrate,
11686
- dryRun: opts.dryRun
11936
+ dryRun: opts.dryRun,
11937
+ json: opts.json
11687
11938
  })
11688
11939
  );
11689
11940
  worktree.command("hydrate").description("Re-run CoW clone + install-reconciliation for the current workspace worktree").option("--only <repos>", "Restrict to comma- or space-separated member ids/folders").option("--force", "Re-clone hydration targets even if already present").option(
11690
11941
  "--materialize",
11691
11942
  "Also `git worktree add` any member missing entirely (e.g. a worktree created by plain `git worktree add` outside haus) before hydrating it"
11692
- ).option("--dry-run", "Preview without writing or running anything").action(
11943
+ ).option("--dry-run", "Preview without writing or running anything").option("--json", "Emit the structured hydrate result as JSON").action(
11693
11944
  (opts) => runWorktree("hydrate", {
11694
11945
  only: opts.only,
11695
11946
  force: opts.force,
11696
11947
  materialize: opts.materialize,
11697
- dryRun: opts.dryRun
11948
+ dryRun: opts.dryRun,
11949
+ json: opts.json
11698
11950
  })
11699
11951
  );
11700
- worktree.command("list").description("List materialized workspace worktrees and their member status").action(() => runWorktree("list"));
11701
- worktree.command("remove <slug>").description("Remove a workspace worktree and its member worktrees").option("--force", "Bypass the uncommitted/unpushed-work refusal").option("--dry-run", "Preview without removing anything").action(
11702
- (slug, opts) => runWorktree("remove", { slug, force: opts.force, dryRun: opts.dryRun })
11952
+ worktree.command("list").description("List materialized workspace worktrees and their member status").option("--json", "Emit the structured list result as JSON").action((opts) => runWorktree("list", { json: opts.json }));
11953
+ worktree.command("remove <slug>").description("Remove a workspace worktree and its member worktrees").option("--force", "Bypass the uncommitted/unpushed-work refusal").option("--dry-run", "Preview without removing anything").option("--json", "Emit the structured remove result as JSON").action(
11954
+ (slug, opts) => runWorktree("remove", { slug, force: opts.force, dryRun: opts.dryRun, json: opts.json })
11703
11955
  );
11704
11956
  worktree.command("doctor").description("Fast, side-effect-free health check for the current workspace worktree").option("--from-hook", "Emit for the SessionStart hook \u2014 always exits 0").action((opts) => runWorktree("doctor", { fromHook: opts.fromHook }));
11705
11957
  program.parseAsync(process.argv).catch((err) => {
@@ -1,5 +1,14 @@
1
1
  {
2
- "version": "4.4.1",
2
+ "version": "5.0.0",
3
+ "globalPlugins": [
4
+ {
5
+ "id": "superpowers",
6
+ "plugin": "superpowers-extended-cc",
7
+ "marketplace": "superpowers-extended-cc-marketplace",
8
+ "url": "https://github.com/pcvelz/superpowers.git",
9
+ "ref": "v6.4.0"
10
+ }
11
+ ],
3
12
  "items": [
4
13
  {
5
14
  "id": "haus.agentic-workflow-standard",
@@ -1539,7 +1548,7 @@
1539
1548
  },
1540
1549
  {
1541
1550
  "id": "haus.package-manager-yarn4-pnpm89",
1542
- "version": "1.0.1",
1551
+ "version": "1.0.2",
1543
1552
  "source": "haus",
1544
1553
  "type": "skill",
1545
1554
  "path": "skills/haus-owned/package-managers/package-manager-yarn4-pnpm89",
@@ -2102,406 +2111,6 @@
2102
2111
  }
2103
2112
  ]
2104
2113
  },
2105
- {
2106
- "id": "haus.superpowers-brainstorming",
2107
- "version": "1.0.3",
2108
- "source": "curated",
2109
- "type": "skill",
2110
- "path": "skills/superpowers/brainstorming",
2111
- "title": "Superpowers Brainstorming",
2112
- "purpose": "You MUST use this before any creative work - creating features, building components, adding functionality, or modifying behavior. Explores user intent, requirements and design before implementation.",
2113
- "whenToUse": "You MUST use this before any creative work - creating features, building components, adding functionality, or modifying behavior. Explores user intent, requirements and design before implementation.",
2114
- "whenNotToUse": "Do not use for mechanical edits, bug fixes with known cause, or read-only questions.",
2115
- "tags": ["workflow", "core-skill"],
2116
- "repoRoles": [],
2117
- "tokenEstimate": 2887,
2118
- "installMode": "copy-selected",
2119
- "default": true,
2120
- "reviewStatus": "approved",
2121
- "riskLevel": "low",
2122
- "useMode": "copy",
2123
- "license": "MIT",
2124
- "licenseConfidence": "high",
2125
- "originSourceId": "superpowers-pcvelz",
2126
- "originUrl": "https://github.com/pcvelz/superpowers/tree/e748382ff059bcc0219dae8cc1059ef7f3a938d5/skills/brainstorming",
2127
- "ecosystem": "superpowers",
2128
- "pinnedRef": "e748382ff059bcc0219dae8cc1059ef7f3a938d5"
2129
- },
2130
- {
2131
- "id": "haus.superpowers-checking-gates",
2132
- "version": "1.0.1",
2133
- "source": "curated",
2134
- "type": "skill",
2135
- "path": "skills/superpowers/checking-gates",
2136
- "title": "Superpowers Checking Gates",
2137
- "purpose": "Use when picking up a user-gate task OR when a hook demands re-validation. Runs the \"do I know HOW?\" self-check; if the HOW is clear, executes the verification and posts evidence; if not, hands off to specifying-gates. Kept deliberately separate from executing-plans so that without the opt-in hook, the main flow stays untouched.",
2138
- "whenToUse": "Use when picking up a user-gate task OR when a hook demands re-validation. Runs the \"do I know HOW?\" self-check; if the HOW is clear, executes the verification and posts evidence; if not, hands off to specifying-gates. Kept deliberately separate from executing-plans so that without the opt-in hook, the main flow stays untouched.",
2139
- "whenNotToUse": "Do not use when the user-gate hook is not registered or the task is not user-gated.",
2140
- "tags": ["workflow", "workflow-skill"],
2141
- "repoRoles": [],
2142
- "tokenEstimate": 1228,
2143
- "installMode": "copy-selected",
2144
- "default": true,
2145
- "reviewStatus": "approved",
2146
- "riskLevel": "low",
2147
- "useMode": "copy",
2148
- "license": "MIT",
2149
- "licenseConfidence": "high",
2150
- "originSourceId": "superpowers-pcvelz",
2151
- "originUrl": "https://github.com/pcvelz/superpowers/tree/e748382ff059bcc0219dae8cc1059ef7f3a938d5/skills/checking-gates",
2152
- "ecosystem": "superpowers",
2153
- "pinnedRef": "e748382ff059bcc0219dae8cc1059ef7f3a938d5"
2154
- },
2155
- {
2156
- "id": "haus.superpowers-dispatching-parallel-agents",
2157
- "version": "1.0.1",
2158
- "source": "curated",
2159
- "type": "skill",
2160
- "path": "skills/superpowers/dispatching-parallel-agents",
2161
- "title": "Superpowers Dispatching Parallel Agents",
2162
- "purpose": "Use when facing 2+ independent tasks that can be worked on without shared state or sequential dependencies",
2163
- "whenToUse": "Use when facing 2+ independent tasks that can be worked on without shared state or sequential dependencies",
2164
- "whenNotToUse": "Do not use when tasks share state or must run sequentially.",
2165
- "tags": ["workflow", "workflow-skill"],
2166
- "repoRoles": [],
2167
- "tokenEstimate": 1793,
2168
- "installMode": "copy-selected",
2169
- "default": true,
2170
- "reviewStatus": "approved",
2171
- "riskLevel": "low",
2172
- "useMode": "copy",
2173
- "license": "MIT",
2174
- "licenseConfidence": "high",
2175
- "originSourceId": "superpowers-pcvelz",
2176
- "originUrl": "https://github.com/pcvelz/superpowers/tree/e748382ff059bcc0219dae8cc1059ef7f3a938d5/skills/dispatching-parallel-agents",
2177
- "ecosystem": "superpowers",
2178
- "pinnedRef": "e748382ff059bcc0219dae8cc1059ef7f3a938d5"
2179
- },
2180
- {
2181
- "id": "haus.superpowers-executing-plans",
2182
- "version": "1.0.2",
2183
- "source": "curated",
2184
- "type": "skill",
2185
- "path": "skills/superpowers/executing-plans",
2186
- "title": "Superpowers Executing Plans",
2187
- "purpose": "Use when you have a written implementation plan to execute in a separate session with review checkpoints",
2188
- "whenToUse": "Use when you have a written implementation plan to execute in a separate session with review checkpoints",
2189
- "whenNotToUse": "Do not use before a written plan exists or for single-step trivial changes.",
2190
- "tags": ["workflow", "core-skill"],
2191
- "repoRoles": [],
2192
- "tokenEstimate": 1601,
2193
- "installMode": "copy-selected",
2194
- "default": true,
2195
- "reviewStatus": "approved",
2196
- "riskLevel": "low",
2197
- "useMode": "copy",
2198
- "license": "MIT",
2199
- "licenseConfidence": "high",
2200
- "originSourceId": "superpowers-pcvelz",
2201
- "originUrl": "https://github.com/pcvelz/superpowers/tree/fc6d376756b605f35c399b1b00a16bb27ec55171/skills/executing-plans",
2202
- "ecosystem": "superpowers",
2203
- "pinnedRef": "fc6d376756b605f35c399b1b00a16bb27ec55171"
2204
- },
2205
- {
2206
- "id": "haus.superpowers-finishing-a-development-branch",
2207
- "version": "1.0.3",
2208
- "source": "curated",
2209
- "type": "skill",
2210
- "path": "skills/superpowers/finishing-a-development-branch",
2211
- "title": "Superpowers Finishing A Development Branch",
2212
- "purpose": "Use when implementation is complete, all tests pass, and you need to decide how to integrate the work",
2213
- "whenToUse": "Use when implementation is complete, all tests pass, and you need to decide how to integrate the work",
2214
- "whenNotToUse": "Do not use while implementation or verification is still incomplete.",
2215
- "tags": ["workflow", "quality"],
2216
- "repoRoles": [],
2217
- "tokenEstimate": 1756,
2218
- "installMode": "copy-selected",
2219
- "default": true,
2220
- "reviewStatus": "approved",
2221
- "riskLevel": "low",
2222
- "useMode": "copy",
2223
- "license": "MIT",
2224
- "licenseConfidence": "high",
2225
- "originSourceId": "superpowers-pcvelz",
2226
- "originUrl": "https://github.com/pcvelz/superpowers/tree/e748382ff059bcc0219dae8cc1059ef7f3a938d5/skills/finishing-a-development-branch",
2227
- "ecosystem": "superpowers",
2228
- "pinnedRef": "e748382ff059bcc0219dae8cc1059ef7f3a938d5"
2229
- },
2230
- {
2231
- "id": "haus.superpowers-receiving-code-review",
2232
- "version": "1.0.3",
2233
- "source": "curated",
2234
- "type": "skill",
2235
- "path": "skills/superpowers/receiving-code-review",
2236
- "title": "Superpowers Receiving Code Review",
2237
- "purpose": "Use when receiving code review feedback, before implementing suggestions, especially if feedback seems unclear or technically questionable - requires technical rigor and verification, not performative agreement or blind implementation",
2238
- "whenToUse": "Use when receiving code review feedback, before implementing suggestions, especially if feedback seems unclear or technically questionable - requires technical rigor and verification, not performative agreement or blind implementation",
2239
- "whenNotToUse": "Do not use to blindly apply feedback without technical verification.",
2240
- "tags": ["review", "review-skill"],
2241
- "repoRoles": [],
2242
- "tokenEstimate": 1551,
2243
- "installMode": "copy-selected",
2244
- "default": true,
2245
- "reviewStatus": "approved",
2246
- "riskLevel": "low",
2247
- "useMode": "copy",
2248
- "license": "MIT",
2249
- "licenseConfidence": "high",
2250
- "originSourceId": "superpowers-pcvelz",
2251
- "originUrl": "https://github.com/pcvelz/superpowers/tree/e748382ff059bcc0219dae8cc1059ef7f3a938d5/skills/receiving-code-review",
2252
- "ecosystem": "superpowers",
2253
- "pinnedRef": "e748382ff059bcc0219dae8cc1059ef7f3a938d5"
2254
- },
2255
- {
2256
- "id": "haus.superpowers-requesting-code-review",
2257
- "version": "1.0.3",
2258
- "source": "curated",
2259
- "type": "skill",
2260
- "path": "skills/superpowers/requesting-code-review",
2261
- "title": "Superpowers Requesting Code Review",
2262
- "purpose": "Use when completing tasks, implementing major features, or before merging to verify work meets requirements",
2263
- "whenToUse": "Use when completing tasks, implementing major features, or before merging to verify work meets requirements",
2264
- "whenNotToUse": "Do not use for trivial one-line fixes with no review surface.",
2265
- "tags": ["review", "review-skill"],
2266
- "repoRoles": [],
2267
- "tokenEstimate": 739,
2268
- "installMode": "copy-selected",
2269
- "default": true,
2270
- "reviewStatus": "approved",
2271
- "riskLevel": "low",
2272
- "useMode": "copy",
2273
- "license": "MIT",
2274
- "licenseConfidence": "high",
2275
- "originSourceId": "superpowers-pcvelz",
2276
- "originUrl": "https://github.com/pcvelz/superpowers/tree/e748382ff059bcc0219dae8cc1059ef7f3a938d5/skills/requesting-code-review",
2277
- "ecosystem": "superpowers",
2278
- "pinnedRef": "e748382ff059bcc0219dae8cc1059ef7f3a938d5"
2279
- },
2280
- {
2281
- "id": "haus.superpowers-specifying-gates",
2282
- "version": "1.0.3",
2283
- "source": "curated",
2284
- "type": "skill",
2285
- "path": "skills/superpowers/specifying-gates",
2286
- "title": "Superpowers Specifying Gates",
2287
- "purpose": "Use when a user-gate task has requiresUserSpecification=true OR the agent's \"do I know HOW?\" self-check returns no. Locks down verification mechanics through a short AskUserQuestion sequence and writes the answers back into the task's metadata. Does NOT run the verification itself.",
2288
- "whenToUse": "Use when a user-gate task has requiresUserSpecification=true OR the agent's \"do I know HOW?\" self-check returns no. Locks down verification mechanics through a short AskUserQuestion sequence and writes the answers back into the task's metadata. Does NOT run the verification itself.",
2289
- "whenNotToUse": "Do not use when verification mechanics are already specified or hook is off.",
2290
- "tags": ["workflow", "workflow-skill"],
2291
- "repoRoles": [],
2292
- "tokenEstimate": 1785,
2293
- "installMode": "copy-selected",
2294
- "default": true,
2295
- "reviewStatus": "approved",
2296
- "riskLevel": "low",
2297
- "useMode": "copy",
2298
- "license": "MIT",
2299
- "licenseConfidence": "high",
2300
- "originSourceId": "superpowers-pcvelz",
2301
- "originUrl": "https://github.com/pcvelz/superpowers/tree/e748382ff059bcc0219dae8cc1059ef7f3a938d5/skills/specifying-gates",
2302
- "ecosystem": "superpowers",
2303
- "pinnedRef": "e748382ff059bcc0219dae8cc1059ef7f3a938d5"
2304
- },
2305
- {
2306
- "id": "haus.superpowers-subagent-driven-development",
2307
- "version": "1.0.4",
2308
- "source": "curated",
2309
- "type": "skill",
2310
- "path": "skills/superpowers/subagent-driven-development",
2311
- "title": "Superpowers Subagent Driven Development",
2312
- "purpose": "Use when executing implementation plans with independent tasks in the current session",
2313
- "whenToUse": "Use when executing implementation plans with independent tasks in the current session",
2314
- "whenNotToUse": "Do not use for tightly coupled sequential work or when subagents are unavailable.",
2315
- "tags": ["workflow", "workflow-skill"],
2316
- "repoRoles": [],
2317
- "tokenEstimate": 8024,
2318
- "installMode": "copy-selected",
2319
- "default": true,
2320
- "reviewStatus": "approved",
2321
- "riskLevel": "low",
2322
- "useMode": "copy",
2323
- "license": "MIT",
2324
- "licenseConfidence": "high",
2325
- "originSourceId": "superpowers-pcvelz",
2326
- "originUrl": "https://github.com/pcvelz/superpowers/tree/e748382ff059bcc0219dae8cc1059ef7f3a938d5/skills/subagent-driven-development",
2327
- "ecosystem": "superpowers",
2328
- "pinnedRef": "e748382ff059bcc0219dae8cc1059ef7f3a938d5"
2329
- },
2330
- {
2331
- "id": "haus.superpowers-systematic-debugging",
2332
- "version": "1.0.1",
2333
- "source": "curated",
2334
- "type": "skill",
2335
- "path": "skills/superpowers/systematic-debugging",
2336
- "title": "Superpowers Systematic Debugging",
2337
- "purpose": "Use when encountering any bug, test failure, or unexpected behavior, before proposing fixes",
2338
- "whenToUse": "Use when encountering any bug, test failure, or unexpected behavior, before proposing fixes",
2339
- "whenNotToUse": "Do not use to guess fixes without reproducing or investigating the failure.",
2340
- "tags": ["workflow", "quality"],
2341
- "repoRoles": [],
2342
- "tokenEstimate": 2373,
2343
- "installMode": "copy-selected",
2344
- "default": true,
2345
- "reviewStatus": "approved",
2346
- "riskLevel": "low",
2347
- "useMode": "copy",
2348
- "license": "MIT",
2349
- "licenseConfidence": "high",
2350
- "originSourceId": "superpowers-pcvelz",
2351
- "originUrl": "https://github.com/pcvelz/superpowers/tree/e748382ff059bcc0219dae8cc1059ef7f3a938d5/skills/systematic-debugging",
2352
- "ecosystem": "superpowers",
2353
- "pinnedRef": "e748382ff059bcc0219dae8cc1059ef7f3a938d5"
2354
- },
2355
- {
2356
- "id": "haus.superpowers-test-driven-development",
2357
- "version": "1.0.3",
2358
- "source": "curated",
2359
- "type": "skill",
2360
- "path": "skills/superpowers/test-driven-development",
2361
- "title": "Superpowers Test Driven Development",
2362
- "purpose": "Use when implementing any feature or bugfix, before writing implementation code",
2363
- "whenToUse": "Use when implementing any feature or bugfix, before writing implementation code",
2364
- "whenNotToUse": "Do not use for exploratory spikes, docs-only changes, or throwaway prototypes.",
2365
- "tags": ["testing", "workflow"],
2366
- "repoRoles": [],
2367
- "tokenEstimate": 2254,
2368
- "installMode": "copy-selected",
2369
- "default": true,
2370
- "reviewStatus": "approved",
2371
- "riskLevel": "low",
2372
- "useMode": "copy",
2373
- "license": "MIT",
2374
- "licenseConfidence": "high",
2375
- "originSourceId": "superpowers-pcvelz",
2376
- "originUrl": "https://github.com/pcvelz/superpowers/tree/e748382ff059bcc0219dae8cc1059ef7f3a938d5/skills/test-driven-development",
2377
- "ecosystem": "superpowers",
2378
- "pinnedRef": "e748382ff059bcc0219dae8cc1059ef7f3a938d5"
2379
- },
2380
- {
2381
- "id": "haus.superpowers-using-git-worktrees",
2382
- "version": "1.0.4",
2383
- "source": "curated",
2384
- "type": "skill",
2385
- "path": "skills/superpowers/using-git-worktrees",
2386
- "title": "Superpowers Using Git Worktrees",
2387
- "purpose": "Use when starting feature work that needs isolation from current workspace or before executing implementation plans - ensures an isolated workspace exists via native tools or git worktree fallback",
2388
- "whenToUse": "Use when starting feature work that needs isolation from current workspace or before executing implementation plans - ensures an isolated workspace exists via native tools or git worktree fallback",
2389
- "whenNotToUse": "Do not use when current workspace isolation is already sufficient.",
2390
- "tags": ["workflow", "workflow-skill"],
2391
- "repoRoles": [],
2392
- "tokenEstimate": 1848,
2393
- "installMode": "copy-selected",
2394
- "default": true,
2395
- "reviewStatus": "approved",
2396
- "riskLevel": "low",
2397
- "useMode": "copy",
2398
- "license": "MIT",
2399
- "licenseConfidence": "high",
2400
- "originSourceId": "superpowers-pcvelz",
2401
- "originUrl": "https://github.com/pcvelz/superpowers/tree/e748382ff059bcc0219dae8cc1059ef7f3a938d5/skills/using-git-worktrees",
2402
- "ecosystem": "superpowers",
2403
- "pinnedRef": "e748382ff059bcc0219dae8cc1059ef7f3a938d5"
2404
- },
2405
- {
2406
- "id": "haus.superpowers-using-superpowers",
2407
- "version": "1.0.2",
2408
- "source": "curated",
2409
- "type": "skill",
2410
- "path": "skills/superpowers/using-superpowers",
2411
- "title": "Superpowers Using Superpowers",
2412
- "purpose": "Use when starting any conversation - establishes how to find and use skills, requiring Skill tool invocation before ANY response including clarifying questions",
2413
- "whenToUse": "Use when starting any conversation - establishes how to find and use skills, requiring Skill tool invocation before ANY response including clarifying questions",
2414
- "whenNotToUse": "Do not skip when any skill might apply — this establishes the skill workflow.",
2415
- "tags": ["workflow", "core-skill"],
2416
- "repoRoles": [],
2417
- "tokenEstimate": 1185,
2418
- "installMode": "copy-selected",
2419
- "default": true,
2420
- "reviewStatus": "approved",
2421
- "riskLevel": "low",
2422
- "useMode": "copy",
2423
- "license": "MIT",
2424
- "licenseConfidence": "high",
2425
- "originSourceId": "superpowers-pcvelz",
2426
- "originUrl": "https://github.com/pcvelz/superpowers/tree/e748382ff059bcc0219dae8cc1059ef7f3a938d5/skills/using-superpowers",
2427
- "ecosystem": "superpowers",
2428
- "pinnedRef": "e748382ff059bcc0219dae8cc1059ef7f3a938d5"
2429
- },
2430
- {
2431
- "id": "haus.superpowers-verification-before-completion",
2432
- "version": "1.0.1",
2433
- "source": "curated",
2434
- "type": "skill",
2435
- "path": "skills/superpowers/verification-before-completion",
2436
- "title": "Superpowers Verification Before Completion",
2437
- "purpose": "Use when about to claim work is complete, fixed, or passing, before committing or creating PRs - requires running verification commands and confirming output before making any success claims; evidence before assertions always",
2438
- "whenToUse": "Use when about to claim work is complete, fixed, or passing, before committing or creating PRs - requires running verification commands and confirming output before making any success claims; evidence before assertions always",
2439
- "whenNotToUse": "Do not use to claim success before running the relevant verification commands.",
2440
- "tags": ["quality", "workflow"],
2441
- "repoRoles": [],
2442
- "tokenEstimate": 912,
2443
- "installMode": "copy-selected",
2444
- "default": true,
2445
- "reviewStatus": "approved",
2446
- "riskLevel": "low",
2447
- "useMode": "copy",
2448
- "license": "MIT",
2449
- "licenseConfidence": "high",
2450
- "originSourceId": "superpowers-pcvelz",
2451
- "originUrl": "https://github.com/pcvelz/superpowers/tree/e748382ff059bcc0219dae8cc1059ef7f3a938d5/skills/verification-before-completion",
2452
- "ecosystem": "superpowers",
2453
- "pinnedRef": "e748382ff059bcc0219dae8cc1059ef7f3a938d5"
2454
- },
2455
- {
2456
- "id": "haus.superpowers-writing-plans",
2457
- "version": "1.0.3",
2458
- "source": "curated",
2459
- "type": "skill",
2460
- "path": "skills/superpowers/writing-plans",
2461
- "title": "Superpowers Writing Plans",
2462
- "purpose": "Use when you have a spec or requirements for a multi-step task, before touching code",
2463
- "whenToUse": "Use when you have a spec or requirements for a multi-step task, before touching code",
2464
- "whenNotToUse": "Do not use for single-step tasks that need no multi-step plan.",
2465
- "tags": ["workflow", "core-skill"],
2466
- "repoRoles": [],
2467
- "tokenEstimate": 5850,
2468
- "installMode": "copy-selected",
2469
- "default": true,
2470
- "reviewStatus": "approved",
2471
- "riskLevel": "low",
2472
- "useMode": "copy",
2473
- "license": "MIT",
2474
- "licenseConfidence": "high",
2475
- "originSourceId": "superpowers-pcvelz",
2476
- "originUrl": "https://github.com/pcvelz/superpowers/tree/fc6d376756b605f35c399b1b00a16bb27ec55171/skills/writing-plans",
2477
- "ecosystem": "superpowers",
2478
- "pinnedRef": "fc6d376756b605f35c399b1b00a16bb27ec55171"
2479
- },
2480
- {
2481
- "id": "haus.superpowers-writing-skills",
2482
- "version": "1.0.3",
2483
- "source": "curated",
2484
- "type": "skill",
2485
- "path": "skills/superpowers/writing-skills",
2486
- "title": "Superpowers Writing Skills",
2487
- "purpose": "Use when creating new skills, editing existing skills, or verifying skills work before deployment",
2488
- "whenToUse": "Use when creating new skills, editing existing skills, or verifying skills work before deployment",
2489
- "whenNotToUse": "Do not use for routine code changes unrelated to skill authoring.",
2490
- "tags": ["workflow", "workflow-skill"],
2491
- "repoRoles": [],
2492
- "tokenEstimate": 5555,
2493
- "installMode": "copy-selected",
2494
- "default": true,
2495
- "reviewStatus": "approved",
2496
- "riskLevel": "low",
2497
- "useMode": "copy",
2498
- "license": "MIT",
2499
- "licenseConfidence": "high",
2500
- "originSourceId": "superpowers-pcvelz",
2501
- "originUrl": "https://github.com/pcvelz/superpowers/tree/e748382ff059bcc0219dae8cc1059ef7f3a938d5/skills/writing-skills",
2502
- "ecosystem": "superpowers",
2503
- "pinnedRef": "e748382ff059bcc0219dae8cc1059ef7f3a938d5"
2504
- },
2505
2114
  {
2506
2115
  "id": "haus.tanstack-query-router-patterns",
2507
2116
  "version": "1.0.2",
@@ -3346,7 +2955,7 @@
3346
2955
  "purpose": "Write modern, high-performance C# code using records, pattern matching, value objects, async/await, Span<T>/Memory<T>, and best-practice API design patterns. Emphasizes functional-style programming with C# 12+ features.",
3347
2956
  "whenToUse": "Write modern, high-performance C# code using records, pattern matching, value objects, async/await, Span<T>/Memory<T>, and best-practice API design patterns. Emphasizes functional-style programming with C# 12+ features.",
3348
2957
  "whenNotToUse": "Do not use when a more specific skill or command applies.",
3349
- "tags": ["dotnet", "csharp", "backend"],
2958
+ "tags": ["dotnet", "csharp"],
3350
2959
  "repoRoles": [],
3351
2960
  "tokenEstimate": 2664,
3352
2961
  "installMode": "copy-selected",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haus-tech/haus-workflow",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "Haus AI workflow CLI for Claude Code.",
5
5
  "type": "module",
6
6
  "bin": {