@wrongstack/plugins 0.236.0 → 0.255.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.
@@ -99,26 +99,15 @@ var plugin = {
99
99
  if (autoIndex && isIndexableFile(fullPath)) {
100
100
  debounceEvent(`index:${fullPath}`, async () => {
101
101
  try {
102
- const { runIndexer } = await import('@wrongstack/tools/codebase-index/index.js');
102
+ const { enqueueReindex } = await import('@wrongstack/tools/codebase-index/index.js');
103
103
  const root = indexProjectRoot || dirPath;
104
- const fakeAppend = async () => {
105
- };
106
- const fakeClose = async () => {
107
- };
108
- const fakeRecordFileChange = () => {
109
- };
110
- const ctx = {
104
+ enqueueReindex({
111
105
  projectRoot: root,
112
- cwd: root,
113
- messages: [],
114
- todos: [],
115
- readFiles: /* @__PURE__ */ new Set(),
116
- fileMtimes: /* @__PURE__ */ new Map(),
117
- session: { id: "fw", append: fakeAppend, close: fakeClose, recordFileChange: fakeRecordFileChange }
118
- };
119
- await runIndexer(ctx, { projectRoot: root, files: [fullPath] });
106
+ files: [fullPath],
107
+ onError: (err) => api.log.warn(`file-watcher: auto-index failed for ${fullPath}: ${err}`)
108
+ });
120
109
  api.metrics.counter("index_file", 1);
121
- api.log.debug(`file-watcher: auto-index triggered for ${fullPath}`);
110
+ api.log.debug(`file-watcher: auto-index scheduled for ${fullPath}`);
122
111
  } catch (err) {
123
112
  api.log.warn(`file-watcher: auto-index failed for ${fullPath}: ${err}`);
124
113
  }
@@ -10,7 +10,8 @@ function runGit(args, cwd) {
10
10
  cwd,
11
11
  stdio: ["pipe", "pipe", "pipe"],
12
12
  timeout: 3e4,
13
- maxBuffer: 10 * 1024 * 1024
13
+ maxBuffer: 10 * 1024 * 1024,
14
+ windowsHide: true
14
15
  }).trim();
15
16
  } catch (err) {
16
17
  const e = err;
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { execFileSync, execSync } from 'child_process';
2
- import { watch, existsSync, readdirSync, readFileSync } from 'fs';
2
+ import { watch, existsSync, readFileSync, writeFileSync, readdirSync } from 'fs';
3
3
  import * as path from 'path';
4
4
  import { isAbsolute, join } from 'path';
5
5
  import { expectDefined, deepMerge as deepMerge$1 } from '@wrongstack/core';
@@ -135,7 +135,7 @@ async function runAutoDoc(input, api) {
135
135
  const results = [];
136
136
  for (const file of input.files) {
137
137
  try {
138
- const { readFileSync: readFileSync2, writeFileSync } = await import('fs');
138
+ const { readFileSync: readFileSync2, writeFileSync: writeFileSync2 } = await import('fs');
139
139
  let content;
140
140
  try {
141
141
  content = readFileSync2(file, "utf-8");
@@ -152,7 +152,7 @@ async function runAutoDoc(input, api) {
152
152
  results.push({ file, entity: entity.name });
153
153
  }
154
154
  if (!input.dryRun && results.length > 0) {
155
- writeFileSync(file, modified, "utf-8");
155
+ writeFileSync2(file, modified, "utf-8");
156
156
  api.log.info(`auto-doc: updated ${file}`);
157
157
  }
158
158
  } catch (err) {
@@ -251,7 +251,8 @@ function runGit(args, cwd) {
251
251
  cwd,
252
252
  stdio: ["pipe", "pipe", "pipe"],
253
253
  timeout: 3e4,
254
- maxBuffer: 10 * 1024 * 1024
254
+ maxBuffer: 10 * 1024 * 1024,
255
+ windowsHide: true
255
256
  }).trim();
256
257
  } catch (err) {
257
258
  const e = err;
@@ -669,7 +670,7 @@ var API_VERSION2 = "^0.1.10";
669
670
  function runShellCheck(files, severity, cwd) {
670
671
  if (!existsSync("shellcheck")) {
671
672
  try {
672
- execSync("shellcheck --version", { encoding: "utf-8", stdio: "ignore" });
673
+ execSync("shellcheck --version", { encoding: "utf-8", stdio: "ignore", windowsHide: true });
673
674
  } catch {
674
675
  throw new Error("shellcheck is not installed. Install via: apt install shellcheck / brew install shellcheck");
675
676
  }
@@ -693,7 +694,8 @@ function runShellCheck(files, severity, cwd) {
693
694
  encoding: "utf-8",
694
695
  cwd,
695
696
  stdio: ["pipe", "pipe", "pipe"],
696
- timeout: 6e4
697
+ timeout: 6e4,
698
+ windowsHide: true
697
699
  });
698
700
  } catch (err) {
699
701
  const e = err;
@@ -1202,26 +1204,15 @@ var plugin5 = {
1202
1204
  if (autoIndex && isIndexableFile(fullPath)) {
1203
1205
  debounceEvent(`index:${fullPath}`, async () => {
1204
1206
  try {
1205
- const { runIndexer } = await import('@wrongstack/tools/codebase-index/index.js');
1207
+ const { enqueueReindex } = await import('@wrongstack/tools/codebase-index/index.js');
1206
1208
  const root = indexProjectRoot || dirPath;
1207
- const fakeAppend = async () => {
1208
- };
1209
- const fakeClose = async () => {
1210
- };
1211
- const fakeRecordFileChange = () => {
1212
- };
1213
- const ctx = {
1209
+ enqueueReindex({
1214
1210
  projectRoot: root,
1215
- cwd: root,
1216
- messages: [],
1217
- todos: [],
1218
- readFiles: /* @__PURE__ */ new Set(),
1219
- fileMtimes: /* @__PURE__ */ new Map(),
1220
- session: { id: "fw", append: fakeAppend, close: fakeClose, recordFileChange: fakeRecordFileChange }
1221
- };
1222
- await runIndexer(ctx, { projectRoot: root, files: [fullPath] });
1211
+ files: [fullPath],
1212
+ onError: (err) => api.log.warn(`file-watcher: auto-index failed for ${fullPath}: ${err}`)
1213
+ });
1223
1214
  api.metrics.counter("index_file", 1);
1224
- api.log.debug(`file-watcher: auto-index triggered for ${fullPath}`);
1215
+ api.log.debug(`file-watcher: auto-index scheduled for ${fullPath}`);
1225
1216
  } catch (err) {
1226
1217
  api.log.warn(`file-watcher: auto-index failed for ${fullPath}: ${err}`);
1227
1218
  }
@@ -2264,8 +2255,8 @@ var plugin9 = {
2264
2255
  if (isAbsolute(outputPath) || outputPath.includes("..")) {
2265
2256
  return { ok: false, error: 'outputPath must be a relative path without ".." components' };
2266
2257
  }
2267
- const { writeFileSync } = await import('fs');
2268
- writeFileSync(outputPath, result, "utf-8");
2258
+ const { writeFileSync: writeFileSync2 } = await import('fs');
2259
+ writeFileSync2(outputPath, result, "utf-8");
2269
2260
  return {
2270
2261
  ok: true,
2271
2262
  outputPath,
@@ -2328,8 +2319,8 @@ var plugin9 = {
2328
2319
  if (isAbsolute(outputPath) || outputPath.includes("..")) {
2329
2320
  return { ok: false, error: 'outputPath must be a relative path without ".." components' };
2330
2321
  }
2331
- const { writeFileSync } = await import('fs');
2332
- writeFileSync(outputPath, result, "utf-8");
2322
+ const { writeFileSync: writeFileSync2 } = await import('fs');
2323
+ writeFileSync2(outputPath, result, "utf-8");
2333
2324
  return {
2334
2325
  ok: true,
2335
2326
  templatePath,
@@ -2430,7 +2421,8 @@ function runGit2(args, cwd) {
2430
2421
  encoding: "utf-8",
2431
2422
  cwd,
2432
2423
  stdio: ["pipe", "pipe", "pipe"],
2433
- timeout: 3e4
2424
+ timeout: 3e4,
2425
+ windowsHide: true
2434
2426
  }).trim();
2435
2427
  } catch (err) {
2436
2428
  const e = err;
@@ -2447,10 +2439,25 @@ function getPackageJson(cwd) {
2447
2439
  return null;
2448
2440
  }
2449
2441
  }
2442
+ function collectManifests(root) {
2443
+ const paths = [];
2444
+ const rootPkg = join(root, "package.json");
2445
+ if (existsSync(rootPkg)) paths.push(rootPkg);
2446
+ for (const group of ["packages", "apps"]) {
2447
+ const groupDir = join(root, group);
2448
+ if (!existsSync(groupDir)) continue;
2449
+ for (const entry of readdirSync(groupDir, { withFileTypes: true })) {
2450
+ if (!entry.isDirectory()) continue;
2451
+ const candidate = join(groupDir, entry.name, "package.json");
2452
+ if (existsSync(candidate)) paths.push(candidate);
2453
+ }
2454
+ }
2455
+ return paths;
2456
+ }
2450
2457
  function parseVersion(v) {
2451
2458
  const m = v.match(/^v?(\d+)\.(\d+)\.(\d+)/);
2452
2459
  if (!m) return [0, 0, 0];
2453
- return [Number.parseInt(expectDefined(m[1])), Number.parseInt(expectDefined(m[2])), Number.parseInt(expectDefined(m[3]))];
2460
+ return [Number.parseInt(expectDefined(m[1]), 10), Number.parseInt(expectDefined(m[2]), 10), Number.parseInt(expectDefined(m[3]), 10)];
2454
2461
  }
2455
2462
  function bumpVersion(version, part) {
2456
2463
  let [major, minor, patch] = parseVersion(version);
@@ -2468,6 +2475,15 @@ function bumpVersion(version, part) {
2468
2475
  }
2469
2476
  return `${major}.${minor}.${patch}`;
2470
2477
  }
2478
+ function parseConventional(subject) {
2479
+ const m = subject.match(/^(\w+)(!)?(?:\(([^)]+)\))?(!)?:\s+(.+)/);
2480
+ return {
2481
+ type: m?.[1] ?? "chore",
2482
+ breaking: !!(m?.[2] ?? m?.[4]),
2483
+ scope: m?.[3],
2484
+ message: m?.[5] ?? subject
2485
+ };
2486
+ }
2471
2487
  function getRecentCommits(sinceTag, cwd) {
2472
2488
  const range = sinceTag ? `${sinceTag}..HEAD` : "-30";
2473
2489
  const output = runGit2(["log", range, "--format=%H %s"], cwd);
@@ -2476,25 +2492,12 @@ function getRecentCommits(sinceTag, cwd) {
2476
2492
  const spaceIdx = line.indexOf(" ");
2477
2493
  const hash = line.slice(0, spaceIdx);
2478
2494
  const message = line.slice(spaceIdx + 1);
2479
- const m = message.match(/^(\w+)(!)?(?:\(([^)]+)\))?:\s(.+)/);
2480
- const type = m?.[1] ?? "chore";
2481
- const breaking = !!m?.[2];
2482
- const scope = m?.[2];
2483
- const msg = m?.[3] ?? message;
2484
- return { hash, type, scope, message: msg, breaking };
2495
+ return { hash, ...parseConventional(message) };
2485
2496
  });
2486
2497
  }
2487
2498
  function determineBump(commits) {
2488
- for (const c of commits) {
2489
- if (c.breaking || c.type === "feat!:" || c.type === "fix!") {
2490
- return "major";
2491
- }
2492
- }
2493
- for (const c of commits) {
2494
- if (c.type === "feat" || c.type === "refactor" && c.scope) {
2495
- return "minor";
2496
- }
2497
- }
2499
+ if (commits.some((c) => c.breaking)) return "major";
2500
+ if (commits.some((c) => c.type === "feat")) return "minor";
2498
2501
  return "patch";
2499
2502
  }
2500
2503
  function generateChangelog(commits) {
@@ -2556,12 +2559,13 @@ var plugin10 = {
2556
2559
  version: "0.1.0",
2557
2560
  description: "Conventional-commit-driven semver version bumps with changelog generation",
2558
2561
  apiVersion: API_VERSION9,
2559
- capabilities: { tools: true },
2562
+ capabilities: { tools: true, slashCommands: true },
2560
2563
  defaultConfig: {
2561
2564
  tagPrefix: "v",
2562
2565
  changelogFile: "CHANGELOG.md",
2563
2566
  autoTag: true,
2564
- tagMessage: "Release {{version}}"
2567
+ tagMessage: "Release {{version}}",
2568
+ defaultPart: "patch"
2565
2569
  },
2566
2570
  configSchema: {
2567
2571
  type: "object",
@@ -2569,12 +2573,114 @@ var plugin10 = {
2569
2573
  tagPrefix: { type: "string", default: "v" },
2570
2574
  changelogFile: { type: "string", default: "CHANGELOG.md" },
2571
2575
  autoTag: { type: "boolean", default: true },
2572
- tagMessage: { type: "string", default: "Release {{version}}" }
2576
+ tagMessage: { type: "string", default: "Release {{version}}" },
2577
+ defaultPart: { type: "string", enum: ["major", "minor", "patch", "auto"], default: "patch" }
2573
2578
  }
2574
2579
  },
2575
2580
  setup(api) {
2576
2581
  const tagPrefix = api.config.extensions?.["semver-bump"]?.["tagPrefix"] ?? "v";
2577
2582
  const autoTag = api.config.extensions?.["semver-bump"]?.["autoTag"] ?? true;
2583
+ const VALID_PARTS = ["major", "minor", "patch", "auto"];
2584
+ function readDefaultPart(cfg) {
2585
+ const raw = cfg.extensions?.["semver-bump"]?.["defaultPart"];
2586
+ return VALID_PARTS.includes(raw) ? raw : "patch";
2587
+ }
2588
+ let defaultPart = readDefaultPart(api.config);
2589
+ api.onConfigChange?.((next) => {
2590
+ defaultPart = readDefaultPart(next);
2591
+ });
2592
+ async function performBump(part, dryRun, cwd) {
2593
+ const pkg = getPackageJson(cwd);
2594
+ if (!pkg) {
2595
+ return { ok: false, error: "No package.json found" };
2596
+ }
2597
+ const currentVersion = pkg.version;
2598
+ let bumpPart = part;
2599
+ let commits = [];
2600
+ if (part === "auto") {
2601
+ let lastTag;
2602
+ try {
2603
+ const tagsOutput = runGit2(["describe", "--tags", "--abbrev=0"], cwd);
2604
+ lastTag = tagsOutput || void 0;
2605
+ } catch {
2606
+ }
2607
+ try {
2608
+ commits = getRecentCommits(lastTag, cwd);
2609
+ } catch (err) {
2610
+ const msg = err instanceof Error ? err.message : String(err);
2611
+ return { ok: false, error: `Git error: ${msg}`, bumpPart: "patch" };
2612
+ }
2613
+ bumpPart = determineBump(commits);
2614
+ } else {
2615
+ bumpPart = part;
2616
+ }
2617
+ const newVersion = bumpVersion(currentVersion, bumpPart);
2618
+ if (dryRun) {
2619
+ return {
2620
+ ok: true,
2621
+ dryRun: true,
2622
+ currentVersion,
2623
+ suggestedBump: bumpPart,
2624
+ newVersion,
2625
+ commitCount: part === "auto" ? commits.length : void 0,
2626
+ message: `Would bump ${currentVersion} \u2192 ${newVersion} (${bumpPart})`
2627
+ };
2628
+ }
2629
+ const root = cwd ?? process.cwd();
2630
+ const bumpScript = join(root, "scripts", "bump-version.mjs");
2631
+ const changed = collectManifests(root);
2632
+ if (existsSync(bumpScript)) {
2633
+ try {
2634
+ execFileSync(process.execPath, [bumpScript, "set", newVersion], {
2635
+ cwd: root,
2636
+ stdio: ["pipe", "pipe", "pipe"],
2637
+ timeout: 3e4,
2638
+ windowsHide: true
2639
+ });
2640
+ } catch (err) {
2641
+ const msg = err instanceof Error ? err.message : String(err);
2642
+ return { ok: false, error: `bump script failed: ${msg}` };
2643
+ }
2644
+ for (const rel of ["package.json", "package-lock.json", "src/lib/utils.ts", "index.html"]) {
2645
+ const p = join(root, "website", rel);
2646
+ if (existsSync(p)) changed.push(p);
2647
+ }
2648
+ } else {
2649
+ for (const manifest of changed) {
2650
+ const pkgData = JSON.parse(readFileSync(manifest, "utf-8"));
2651
+ pkgData.version = newVersion;
2652
+ writeFileSync(manifest, JSON.stringify(pkgData, null, 2) + "\n", "utf-8");
2653
+ }
2654
+ }
2655
+ try {
2656
+ runGit2(["add", "--", ...changed], cwd);
2657
+ runGit2(["commit", "-m", `chore: bump version to ${newVersion}`], cwd);
2658
+ } catch {
2659
+ }
2660
+ if (autoTag) {
2661
+ try {
2662
+ runGit2(["tag", "-a", `${tagPrefix}${newVersion}`, "-m", `Release ${newVersion}`], cwd);
2663
+ } catch {
2664
+ }
2665
+ }
2666
+ api.log.info("semver-bump: bumped", { from: currentVersion, to: newVersion, bump: bumpPart });
2667
+ api.metrics.counter("version_bump", 1, { bump: bumpPart });
2668
+ await api.session.append({
2669
+ type: "semver-bump:bumped",
2670
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
2671
+ from: currentVersion,
2672
+ to: newVersion,
2673
+ bump: bumpPart
2674
+ });
2675
+ return {
2676
+ ok: true,
2677
+ currentVersion,
2678
+ newVersion,
2679
+ bump: bumpPart,
2680
+ tag: `${tagPrefix}${newVersion}`,
2681
+ message: `Bumped ${currentVersion} \u2192 ${newVersion} (${bumpPart})`
2682
+ };
2683
+ }
2578
2684
  api.tools.register({
2579
2685
  name: "semver_bump",
2580
2686
  description: "Determine the next version bump from conventional commits since the last tag, or force a specific bump. Creates a git tag.",
@@ -2583,7 +2689,7 @@ var plugin10 = {
2583
2689
  properties: {
2584
2690
  cwd: { type: "string", description: "Working directory (defaults to project root)" },
2585
2691
  dryRun: { type: "boolean", default: false },
2586
- part: { type: "string", enum: ["major", "minor", "patch", "auto"], default: "patch", description: "Version part to bump (defaults to patch; use auto to infer from commits)" }
2692
+ part: { type: "string", enum: ["major", "minor", "patch", "auto"], default: defaultPart, description: "Version part to bump. Omitted \u2192 the configured default (/settings semver-part, factory default: patch). Use auto to infer from commits." }
2587
2693
  }
2588
2694
  },
2589
2695
  permission: "confirm",
@@ -2591,76 +2697,62 @@ var plugin10 = {
2591
2697
  async execute(input) {
2592
2698
  const cwd = input["cwd"];
2593
2699
  const dryRun = input["dryRun"] ?? false;
2594
- const part = input["part"] ?? "auto";
2595
- const pkg = getPackageJson(cwd);
2596
- if (!pkg) {
2597
- return { ok: false, error: "No package.json found" };
2598
- }
2599
- const currentVersion = pkg.version;
2600
- let bumpPart = part;
2601
- let commits = [];
2602
- if (part === "auto") {
2700
+ const part = input["part"] ?? defaultPart;
2701
+ return performBump(part, dryRun, cwd);
2702
+ }
2703
+ });
2704
+ api.slashCommands.register({
2705
+ name: "semver",
2706
+ description: "Show the current version or bump it (patch/minor/major/auto)",
2707
+ category: "Run",
2708
+ argsHint: "[status|patch|minor|major|auto] [--dry]",
2709
+ help: [
2710
+ "/semver Show current version, latest tag and the suggested bump",
2711
+ "/semver status Same as bare /semver",
2712
+ "/semver patch Bump the patch version (commit + tag)",
2713
+ "/semver minor Bump the minor version (commit + tag)",
2714
+ "/semver major Bump the major version (commit + tag)",
2715
+ "/semver auto Infer the bump from conventional commits since the last tag",
2716
+ "/semver <part> --dry Preview without writing anything"
2717
+ ].join("\n"),
2718
+ async run(args, ctx) {
2719
+ const tokens = args.trim().split(/\s+/).filter(Boolean);
2720
+ const dry = tokens.includes("--dry") || tokens.includes("--dry-run");
2721
+ const mode = tokens.find((t) => !t.startsWith("--")) ?? "status";
2722
+ const cwd = ctx?.cwd;
2723
+ if (mode === "status") {
2724
+ const pkg = getPackageJson(cwd);
2725
+ if (!pkg) return { message: "No package.json found" };
2603
2726
  let lastTag;
2604
2727
  try {
2605
- const tagsOutput = runGit2(["describe", "--tags", "--abbrev=0"], cwd);
2606
- lastTag = tagsOutput || void 0;
2728
+ lastTag = runGit2(["describe", "--tags", "--abbrev=0"], cwd) || void 0;
2607
2729
  } catch {
2608
2730
  }
2731
+ let suggestion = "patch";
2732
+ let commitCount = 0;
2609
2733
  try {
2610
- commits = getRecentCommits(lastTag, cwd);
2611
- } catch (err) {
2612
- const msg = err instanceof Error ? err.message : String(err);
2613
- return { ok: false, error: `Git error: ${msg}`, bumpPart: "patch" };
2734
+ const commits = getRecentCommits(lastTag, cwd);
2735
+ commitCount = commits.length;
2736
+ suggestion = determineBump(commits);
2737
+ } catch {
2614
2738
  }
2615
- bumpPart = determineBump(commits);
2616
- } else {
2617
- bumpPart = part;
2618
- }
2619
- const newVersion = bumpVersion(currentVersion, bumpPart);
2620
- if (dryRun) {
2621
2739
  return {
2622
- ok: true,
2623
- dryRun: true,
2624
- currentVersion,
2625
- suggestedBump: bumpPart,
2626
- newVersion,
2627
- commitCount: part === "auto" ? commits.length : void 0,
2628
- message: `Would bump ${currentVersion} \u2192 ${newVersion} (${bumpPart})`
2740
+ message: [
2741
+ `Current version: ${pkg.version}`,
2742
+ `Latest tag: ${lastTag ?? "(none)"}`,
2743
+ `Commits since: ${commitCount}`,
2744
+ `Suggested bump: ${suggestion} \u2192 ${bumpVersion(pkg.version, suggestion)}`,
2745
+ `Default part: ${defaultPart} (change: /settings semver-part)`,
2746
+ "",
2747
+ "Run /semver patch|minor|major|auto to apply (add --dry to preview)."
2748
+ ].join("\n")
2629
2749
  };
2630
2750
  }
2631
- const fs = await import('fs');
2632
- const pkgPath = cwd ? `${cwd}/package.json` : "package.json";
2633
- const pkgData = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
2634
- pkgData.version = newVersion;
2635
- fs.writeFileSync(pkgPath, JSON.stringify(pkgData, null, 2) + "\n", "utf-8");
2636
- try {
2637
- runGit2(["add", "package.json"], cwd);
2638
- runGit2(["commit", "-m", `chore: bump version to ${newVersion}`], cwd);
2639
- } catch {
2751
+ if (mode !== "patch" && mode !== "minor" && mode !== "major" && mode !== "auto") {
2752
+ return { message: `Unknown mode "${mode}". Use status, patch, minor, major or auto.` };
2640
2753
  }
2641
- if (autoTag) {
2642
- try {
2643
- runGit2(["tag", "-a", `${tagPrefix}${newVersion}`, "-m", `Release ${newVersion}`], cwd);
2644
- } catch {
2645
- }
2646
- }
2647
- api.log.info("semver-bump: bumped", { from: currentVersion, to: newVersion, bump: bumpPart });
2648
- api.metrics.counter("version_bump", 1, { bump: bumpPart });
2649
- await api.session.append({
2650
- type: "semver-bump:bumped",
2651
- ts: (/* @__PURE__ */ new Date()).toISOString(),
2652
- from: currentVersion,
2653
- to: newVersion,
2654
- bump: bumpPart
2655
- });
2656
- return {
2657
- ok: true,
2658
- currentVersion,
2659
- newVersion,
2660
- bump: bumpPart,
2661
- tag: `${tagPrefix}${newVersion}`,
2662
- message: `Bumped ${currentVersion} \u2192 ${newVersion} (${bumpPart})`
2663
- };
2754
+ const result = await performBump(mode, dry, cwd);
2755
+ return { message: String(result["message"] ?? result["error"] ?? JSON.stringify(result)) };
2664
2756
  }
2665
2757
  });
2666
2758
  api.tools.register({
@@ -2685,7 +2777,7 @@ var plugin10 = {
2685
2777
  latestTag = tagsOutput || null;
2686
2778
  if (latestTag) {
2687
2779
  const countOutput = runGit2(["rev-list", "--count", `${latestTag}..HEAD`], cwd);
2688
- commitsSinceTag = Number.parseInt(countOutput) || 0;
2780
+ commitsSinceTag = Number.parseInt(countOutput, 10) || 0;
2689
2781
  }
2690
2782
  } catch {
2691
2783
  latestTag = null;
@@ -2726,15 +2818,7 @@ var plugin10 = {
2726
2818
  const spaceIdx = line.indexOf(" ");
2727
2819
  const hash = line.slice(0, spaceIdx);
2728
2820
  const message = line.slice(spaceIdx + 1);
2729
- const m = message.match(/^(\w+)(!)?(?:\(([^)]+)\))?:\s(.+)/);
2730
- const type = m?.[1] ?? "chore";
2731
- return {
2732
- hash,
2733
- type,
2734
- scope: m?.[2],
2735
- message: m?.[3] ?? message,
2736
- breaking: !!m?.[2]
2737
- };
2821
+ return { hash, ...parseConventional(message) };
2738
2822
  });
2739
2823
  } catch (err) {
2740
2824
  return { ok: false, error: `Failed to get git log: ${err}` };
@@ -9,6 +9,18 @@ import { Plugin } from '@wrongstack/core';
9
9
  * - semver_changelog: Generate a changelog between two versions
10
10
  */
11
11
 
12
+ type BumpType = 'major' | 'minor' | 'patch' | 'auto';
13
+ interface ConventionalCommit {
14
+ hash: string;
15
+ type: string;
16
+ scope?: string | undefined;
17
+ message: string;
18
+ breaking: boolean;
19
+ }
20
+ /** Parse a conventional-commit subject line. Accepts the breaking `!` both
21
+ * before and after the scope (`feat!: x`, `feat(api)!: x`). */
22
+ declare function parseConventional(subject: string): Omit<ConventionalCommit, 'hash'>;
23
+ declare function determineBump(commits: ConventionalCommit[]): BumpType;
12
24
  declare const plugin: Plugin;
13
25
 
14
- export { plugin as default };
26
+ export { plugin as default, determineBump, parseConventional };
@@ -1,6 +1,7 @@
1
1
  import { expectDefined } from '@wrongstack/core';
2
2
  import { execFileSync } from 'child_process';
3
- import { existsSync, readFileSync } from 'fs';
3
+ import { existsSync, readFileSync, writeFileSync, readdirSync } from 'fs';
4
+ import { join } from 'path';
4
5
 
5
6
  // src/semver-bump/index.ts
6
7
  var API_VERSION = "^0.1.10";
@@ -10,7 +11,8 @@ function runGit(args, cwd) {
10
11
  encoding: "utf-8",
11
12
  cwd,
12
13
  stdio: ["pipe", "pipe", "pipe"],
13
- timeout: 3e4
14
+ timeout: 3e4,
15
+ windowsHide: true
14
16
  }).trim();
15
17
  } catch (err) {
16
18
  const e = err;
@@ -27,10 +29,25 @@ function getPackageJson(cwd) {
27
29
  return null;
28
30
  }
29
31
  }
32
+ function collectManifests(root) {
33
+ const paths = [];
34
+ const rootPkg = join(root, "package.json");
35
+ if (existsSync(rootPkg)) paths.push(rootPkg);
36
+ for (const group of ["packages", "apps"]) {
37
+ const groupDir = join(root, group);
38
+ if (!existsSync(groupDir)) continue;
39
+ for (const entry of readdirSync(groupDir, { withFileTypes: true })) {
40
+ if (!entry.isDirectory()) continue;
41
+ const candidate = join(groupDir, entry.name, "package.json");
42
+ if (existsSync(candidate)) paths.push(candidate);
43
+ }
44
+ }
45
+ return paths;
46
+ }
30
47
  function parseVersion(v) {
31
48
  const m = v.match(/^v?(\d+)\.(\d+)\.(\d+)/);
32
49
  if (!m) return [0, 0, 0];
33
- return [Number.parseInt(expectDefined(m[1])), Number.parseInt(expectDefined(m[2])), Number.parseInt(expectDefined(m[3]))];
50
+ return [Number.parseInt(expectDefined(m[1]), 10), Number.parseInt(expectDefined(m[2]), 10), Number.parseInt(expectDefined(m[3]), 10)];
34
51
  }
35
52
  function bumpVersion(version, part) {
36
53
  let [major, minor, patch] = parseVersion(version);
@@ -48,6 +65,15 @@ function bumpVersion(version, part) {
48
65
  }
49
66
  return `${major}.${minor}.${patch}`;
50
67
  }
68
+ function parseConventional(subject) {
69
+ const m = subject.match(/^(\w+)(!)?(?:\(([^)]+)\))?(!)?:\s+(.+)/);
70
+ return {
71
+ type: m?.[1] ?? "chore",
72
+ breaking: !!(m?.[2] ?? m?.[4]),
73
+ scope: m?.[3],
74
+ message: m?.[5] ?? subject
75
+ };
76
+ }
51
77
  function getRecentCommits(sinceTag, cwd) {
52
78
  const range = sinceTag ? `${sinceTag}..HEAD` : "-30";
53
79
  const output = runGit(["log", range, "--format=%H %s"], cwd);
@@ -56,25 +82,12 @@ function getRecentCommits(sinceTag, cwd) {
56
82
  const spaceIdx = line.indexOf(" ");
57
83
  const hash = line.slice(0, spaceIdx);
58
84
  const message = line.slice(spaceIdx + 1);
59
- const m = message.match(/^(\w+)(!)?(?:\(([^)]+)\))?:\s(.+)/);
60
- const type = m?.[1] ?? "chore";
61
- const breaking = !!m?.[2];
62
- const scope = m?.[2];
63
- const msg = m?.[3] ?? message;
64
- return { hash, type, scope, message: msg, breaking };
85
+ return { hash, ...parseConventional(message) };
65
86
  });
66
87
  }
67
88
  function determineBump(commits) {
68
- for (const c of commits) {
69
- if (c.breaking || c.type === "feat!:" || c.type === "fix!") {
70
- return "major";
71
- }
72
- }
73
- for (const c of commits) {
74
- if (c.type === "feat" || c.type === "refactor" && c.scope) {
75
- return "minor";
76
- }
77
- }
89
+ if (commits.some((c) => c.breaking)) return "major";
90
+ if (commits.some((c) => c.type === "feat")) return "minor";
78
91
  return "patch";
79
92
  }
80
93
  function generateChangelog(commits) {
@@ -136,12 +149,13 @@ var plugin = {
136
149
  version: "0.1.0",
137
150
  description: "Conventional-commit-driven semver version bumps with changelog generation",
138
151
  apiVersion: API_VERSION,
139
- capabilities: { tools: true },
152
+ capabilities: { tools: true, slashCommands: true },
140
153
  defaultConfig: {
141
154
  tagPrefix: "v",
142
155
  changelogFile: "CHANGELOG.md",
143
156
  autoTag: true,
144
- tagMessage: "Release {{version}}"
157
+ tagMessage: "Release {{version}}",
158
+ defaultPart: "patch"
145
159
  },
146
160
  configSchema: {
147
161
  type: "object",
@@ -149,12 +163,114 @@ var plugin = {
149
163
  tagPrefix: { type: "string", default: "v" },
150
164
  changelogFile: { type: "string", default: "CHANGELOG.md" },
151
165
  autoTag: { type: "boolean", default: true },
152
- tagMessage: { type: "string", default: "Release {{version}}" }
166
+ tagMessage: { type: "string", default: "Release {{version}}" },
167
+ defaultPart: { type: "string", enum: ["major", "minor", "patch", "auto"], default: "patch" }
153
168
  }
154
169
  },
155
170
  setup(api) {
156
171
  const tagPrefix = api.config.extensions?.["semver-bump"]?.["tagPrefix"] ?? "v";
157
172
  const autoTag = api.config.extensions?.["semver-bump"]?.["autoTag"] ?? true;
173
+ const VALID_PARTS = ["major", "minor", "patch", "auto"];
174
+ function readDefaultPart(cfg) {
175
+ const raw = cfg.extensions?.["semver-bump"]?.["defaultPart"];
176
+ return VALID_PARTS.includes(raw) ? raw : "patch";
177
+ }
178
+ let defaultPart = readDefaultPart(api.config);
179
+ api.onConfigChange?.((next) => {
180
+ defaultPart = readDefaultPart(next);
181
+ });
182
+ async function performBump(part, dryRun, cwd) {
183
+ const pkg = getPackageJson(cwd);
184
+ if (!pkg) {
185
+ return { ok: false, error: "No package.json found" };
186
+ }
187
+ const currentVersion = pkg.version;
188
+ let bumpPart = part;
189
+ let commits = [];
190
+ if (part === "auto") {
191
+ let lastTag;
192
+ try {
193
+ const tagsOutput = runGit(["describe", "--tags", "--abbrev=0"], cwd);
194
+ lastTag = tagsOutput || void 0;
195
+ } catch {
196
+ }
197
+ try {
198
+ commits = getRecentCommits(lastTag, cwd);
199
+ } catch (err) {
200
+ const msg = err instanceof Error ? err.message : String(err);
201
+ return { ok: false, error: `Git error: ${msg}`, bumpPart: "patch" };
202
+ }
203
+ bumpPart = determineBump(commits);
204
+ } else {
205
+ bumpPart = part;
206
+ }
207
+ const newVersion = bumpVersion(currentVersion, bumpPart);
208
+ if (dryRun) {
209
+ return {
210
+ ok: true,
211
+ dryRun: true,
212
+ currentVersion,
213
+ suggestedBump: bumpPart,
214
+ newVersion,
215
+ commitCount: part === "auto" ? commits.length : void 0,
216
+ message: `Would bump ${currentVersion} \u2192 ${newVersion} (${bumpPart})`
217
+ };
218
+ }
219
+ const root = cwd ?? process.cwd();
220
+ const bumpScript = join(root, "scripts", "bump-version.mjs");
221
+ const changed = collectManifests(root);
222
+ if (existsSync(bumpScript)) {
223
+ try {
224
+ execFileSync(process.execPath, [bumpScript, "set", newVersion], {
225
+ cwd: root,
226
+ stdio: ["pipe", "pipe", "pipe"],
227
+ timeout: 3e4,
228
+ windowsHide: true
229
+ });
230
+ } catch (err) {
231
+ const msg = err instanceof Error ? err.message : String(err);
232
+ return { ok: false, error: `bump script failed: ${msg}` };
233
+ }
234
+ for (const rel of ["package.json", "package-lock.json", "src/lib/utils.ts", "index.html"]) {
235
+ const p = join(root, "website", rel);
236
+ if (existsSync(p)) changed.push(p);
237
+ }
238
+ } else {
239
+ for (const manifest of changed) {
240
+ const pkgData = JSON.parse(readFileSync(manifest, "utf-8"));
241
+ pkgData.version = newVersion;
242
+ writeFileSync(manifest, JSON.stringify(pkgData, null, 2) + "\n", "utf-8");
243
+ }
244
+ }
245
+ try {
246
+ runGit(["add", "--", ...changed], cwd);
247
+ runGit(["commit", "-m", `chore: bump version to ${newVersion}`], cwd);
248
+ } catch {
249
+ }
250
+ if (autoTag) {
251
+ try {
252
+ runGit(["tag", "-a", `${tagPrefix}${newVersion}`, "-m", `Release ${newVersion}`], cwd);
253
+ } catch {
254
+ }
255
+ }
256
+ api.log.info("semver-bump: bumped", { from: currentVersion, to: newVersion, bump: bumpPart });
257
+ api.metrics.counter("version_bump", 1, { bump: bumpPart });
258
+ await api.session.append({
259
+ type: "semver-bump:bumped",
260
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
261
+ from: currentVersion,
262
+ to: newVersion,
263
+ bump: bumpPart
264
+ });
265
+ return {
266
+ ok: true,
267
+ currentVersion,
268
+ newVersion,
269
+ bump: bumpPart,
270
+ tag: `${tagPrefix}${newVersion}`,
271
+ message: `Bumped ${currentVersion} \u2192 ${newVersion} (${bumpPart})`
272
+ };
273
+ }
158
274
  api.tools.register({
159
275
  name: "semver_bump",
160
276
  description: "Determine the next version bump from conventional commits since the last tag, or force a specific bump. Creates a git tag.",
@@ -163,7 +279,7 @@ var plugin = {
163
279
  properties: {
164
280
  cwd: { type: "string", description: "Working directory (defaults to project root)" },
165
281
  dryRun: { type: "boolean", default: false },
166
- part: { type: "string", enum: ["major", "minor", "patch", "auto"], default: "patch", description: "Version part to bump (defaults to patch; use auto to infer from commits)" }
282
+ part: { type: "string", enum: ["major", "minor", "patch", "auto"], default: defaultPart, description: "Version part to bump. Omitted \u2192 the configured default (/settings semver-part, factory default: patch). Use auto to infer from commits." }
167
283
  }
168
284
  },
169
285
  permission: "confirm",
@@ -171,76 +287,62 @@ var plugin = {
171
287
  async execute(input) {
172
288
  const cwd = input["cwd"];
173
289
  const dryRun = input["dryRun"] ?? false;
174
- const part = input["part"] ?? "auto";
175
- const pkg = getPackageJson(cwd);
176
- if (!pkg) {
177
- return { ok: false, error: "No package.json found" };
178
- }
179
- const currentVersion = pkg.version;
180
- let bumpPart = part;
181
- let commits = [];
182
- if (part === "auto") {
290
+ const part = input["part"] ?? defaultPart;
291
+ return performBump(part, dryRun, cwd);
292
+ }
293
+ });
294
+ api.slashCommands.register({
295
+ name: "semver",
296
+ description: "Show the current version or bump it (patch/minor/major/auto)",
297
+ category: "Run",
298
+ argsHint: "[status|patch|minor|major|auto] [--dry]",
299
+ help: [
300
+ "/semver Show current version, latest tag and the suggested bump",
301
+ "/semver status Same as bare /semver",
302
+ "/semver patch Bump the patch version (commit + tag)",
303
+ "/semver minor Bump the minor version (commit + tag)",
304
+ "/semver major Bump the major version (commit + tag)",
305
+ "/semver auto Infer the bump from conventional commits since the last tag",
306
+ "/semver <part> --dry Preview without writing anything"
307
+ ].join("\n"),
308
+ async run(args, ctx) {
309
+ const tokens = args.trim().split(/\s+/).filter(Boolean);
310
+ const dry = tokens.includes("--dry") || tokens.includes("--dry-run");
311
+ const mode = tokens.find((t) => !t.startsWith("--")) ?? "status";
312
+ const cwd = ctx?.cwd;
313
+ if (mode === "status") {
314
+ const pkg = getPackageJson(cwd);
315
+ if (!pkg) return { message: "No package.json found" };
183
316
  let lastTag;
184
317
  try {
185
- const tagsOutput = runGit(["describe", "--tags", "--abbrev=0"], cwd);
186
- lastTag = tagsOutput || void 0;
318
+ lastTag = runGit(["describe", "--tags", "--abbrev=0"], cwd) || void 0;
187
319
  } catch {
188
320
  }
321
+ let suggestion = "patch";
322
+ let commitCount = 0;
189
323
  try {
190
- commits = getRecentCommits(lastTag, cwd);
191
- } catch (err) {
192
- const msg = err instanceof Error ? err.message : String(err);
193
- return { ok: false, error: `Git error: ${msg}`, bumpPart: "patch" };
324
+ const commits = getRecentCommits(lastTag, cwd);
325
+ commitCount = commits.length;
326
+ suggestion = determineBump(commits);
327
+ } catch {
194
328
  }
195
- bumpPart = determineBump(commits);
196
- } else {
197
- bumpPart = part;
198
- }
199
- const newVersion = bumpVersion(currentVersion, bumpPart);
200
- if (dryRun) {
201
329
  return {
202
- ok: true,
203
- dryRun: true,
204
- currentVersion,
205
- suggestedBump: bumpPart,
206
- newVersion,
207
- commitCount: part === "auto" ? commits.length : void 0,
208
- message: `Would bump ${currentVersion} \u2192 ${newVersion} (${bumpPart})`
330
+ message: [
331
+ `Current version: ${pkg.version}`,
332
+ `Latest tag: ${lastTag ?? "(none)"}`,
333
+ `Commits since: ${commitCount}`,
334
+ `Suggested bump: ${suggestion} \u2192 ${bumpVersion(pkg.version, suggestion)}`,
335
+ `Default part: ${defaultPart} (change: /settings semver-part)`,
336
+ "",
337
+ "Run /semver patch|minor|major|auto to apply (add --dry to preview)."
338
+ ].join("\n")
209
339
  };
210
340
  }
211
- const fs = await import('fs');
212
- const pkgPath = cwd ? `${cwd}/package.json` : "package.json";
213
- const pkgData = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
214
- pkgData.version = newVersion;
215
- fs.writeFileSync(pkgPath, JSON.stringify(pkgData, null, 2) + "\n", "utf-8");
216
- try {
217
- runGit(["add", "package.json"], cwd);
218
- runGit(["commit", "-m", `chore: bump version to ${newVersion}`], cwd);
219
- } catch {
220
- }
221
- if (autoTag) {
222
- try {
223
- runGit(["tag", "-a", `${tagPrefix}${newVersion}`, "-m", `Release ${newVersion}`], cwd);
224
- } catch {
225
- }
341
+ if (mode !== "patch" && mode !== "minor" && mode !== "major" && mode !== "auto") {
342
+ return { message: `Unknown mode "${mode}". Use status, patch, minor, major or auto.` };
226
343
  }
227
- api.log.info("semver-bump: bumped", { from: currentVersion, to: newVersion, bump: bumpPart });
228
- api.metrics.counter("version_bump", 1, { bump: bumpPart });
229
- await api.session.append({
230
- type: "semver-bump:bumped",
231
- ts: (/* @__PURE__ */ new Date()).toISOString(),
232
- from: currentVersion,
233
- to: newVersion,
234
- bump: bumpPart
235
- });
236
- return {
237
- ok: true,
238
- currentVersion,
239
- newVersion,
240
- bump: bumpPart,
241
- tag: `${tagPrefix}${newVersion}`,
242
- message: `Bumped ${currentVersion} \u2192 ${newVersion} (${bumpPart})`
243
- };
344
+ const result = await performBump(mode, dry, cwd);
345
+ return { message: String(result["message"] ?? result["error"] ?? JSON.stringify(result)) };
244
346
  }
245
347
  });
246
348
  api.tools.register({
@@ -265,7 +367,7 @@ var plugin = {
265
367
  latestTag = tagsOutput || null;
266
368
  if (latestTag) {
267
369
  const countOutput = runGit(["rev-list", "--count", `${latestTag}..HEAD`], cwd);
268
- commitsSinceTag = Number.parseInt(countOutput) || 0;
370
+ commitsSinceTag = Number.parseInt(countOutput, 10) || 0;
269
371
  }
270
372
  } catch {
271
373
  latestTag = null;
@@ -306,15 +408,7 @@ var plugin = {
306
408
  const spaceIdx = line.indexOf(" ");
307
409
  const hash = line.slice(0, spaceIdx);
308
410
  const message = line.slice(spaceIdx + 1);
309
- const m = message.match(/^(\w+)(!)?(?:\(([^)]+)\))?:\s(.+)/);
310
- const type = m?.[1] ?? "chore";
311
- return {
312
- hash,
313
- type,
314
- scope: m?.[2],
315
- message: m?.[3] ?? message,
316
- breaking: !!m?.[2]
317
- };
411
+ return { hash, ...parseConventional(message) };
318
412
  });
319
413
  } catch (err) {
320
414
  return { ok: false, error: `Failed to get git log: ${err}` };
@@ -344,4 +438,4 @@ var plugin = {
344
438
  };
345
439
  var semver_bump_default = plugin;
346
440
 
347
- export { semver_bump_default as default };
441
+ export { semver_bump_default as default, determineBump, parseConventional };
@@ -7,7 +7,7 @@ var API_VERSION = "^0.1.10";
7
7
  function runShellCheck(files, severity, cwd) {
8
8
  if (!existsSync("shellcheck")) {
9
9
  try {
10
- execSync("shellcheck --version", { encoding: "utf-8", stdio: "ignore" });
10
+ execSync("shellcheck --version", { encoding: "utf-8", stdio: "ignore", windowsHide: true });
11
11
  } catch {
12
12
  throw new Error("shellcheck is not installed. Install via: apt install shellcheck / brew install shellcheck");
13
13
  }
@@ -31,7 +31,8 @@ function runShellCheck(files, severity, cwd) {
31
31
  encoding: "utf-8",
32
32
  cwd,
33
33
  stdio: ["pipe", "pipe", "pipe"],
34
- timeout: 6e4
34
+ timeout: 6e4,
35
+ windowsHide: true
35
36
  });
36
37
  } catch (err) {
37
38
  const e = err;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/plugins",
3
- "version": "0.236.0",
3
+ "version": "0.255.0",
4
4
  "description": "Official WrongStack plugin collection — auto-doc, git-autocommit, shell-check, cost-tracker, file-watcher, web-search, json-path, cron, template-engine, semver-bump",
5
5
  "license": "MIT",
6
6
  "author": "ECOSTACK TECHNOLOGY OÜ",
@@ -57,13 +57,13 @@
57
57
  "dist"
58
58
  ],
59
59
  "devDependencies": {
60
- "@types/node": "^25.9.2",
60
+ "@types/node": "^25.9.3",
61
61
  "tsup": "^8.5.1",
62
62
  "typescript": "^6.0.3",
63
63
  "vitest": "^4.1.8"
64
64
  },
65
65
  "dependencies": {
66
- "@wrongstack/core": "0.236.0"
66
+ "@wrongstack/core": "0.255.0"
67
67
  },
68
68
  "scripts": {
69
69
  "build": "tsup",