@curdx/flow 3.5.0 → 4.0.1

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
@@ -2,6 +2,31 @@
2
2
 
3
3
  All notable changes to `@curdx/flow` are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/) and the project follows [Semantic Versioning](https://semver.org/).
4
4
 
5
+ ## 4.0.1 — 2026-04-27
6
+
7
+ ### Fixed
8
+
9
+ - **Migration cleanup is now exhaustive.** v4.0.0's auto-migration for the `ralph-specum` → `curdx-flow` rename only invoked `claude plugin uninstall`, which leaves substantial residue when the marketplace's plugin id has been renamed (the CLI can't resolve the legacy id and bails). The installer now manually purges every leftover artifact for legacy slugs (`ralph-specum@curdx-flow`, `ralph-specum@smart-ralph`):
10
+ - `~/.claude/settings.json` → removes `enabledPlugins[<legacyId>]`
11
+ - `~/.claude/plugins/installed_plugins.json` → removes `plugins[<legacyId>]`
12
+ - `~/.claude/plugins/cache/<marketplace>/<name>/` → recursive remove
13
+ - `~/.claude/plugins/data/<name>-<marketplace>/` → recursive remove
14
+ - Marketplace registrations (`known_marketplaces.json`, `extraKnownMarketplaces`) are deliberately left alone — those are user-managed.
15
+ - Implementation lives in `src/runner/legacy-cleanup.ts::purgeLegacyPluginArtifacts`. Idempotent and safe: every step swallows ENOENT silently and reports JSON / IO errors via the install task log without failing the flow.
16
+
17
+ ## 4.0.0 — 2026-04-27
18
+
19
+ ### Breaking
20
+
21
+ - **Plugin renamed `ralph-specum` → `curdx-flow`.** The bundled spec-driven workflow plugin now lives under the `curdx-flow` brand, slash namespace `/curdx-flow:*`, full slug `curdx-flow@curdx`. The marketplace identifier was simultaneously shortened from `curdx-flow` to `curdx` (the GitHub repo source `curdx/curdx-flow` is unchanged). All in-plugin slash references, env vars (`RALPH_SPECUM_*` → `CURDX_FLOW_*`), and labels were updated accordingly.
22
+ - **Auto-migration:** the installer now detects legacy `ralph-specum@curdx-flow` and `ralph-specum@smart-ralph` installs and uninstalls them automatically before installing the new slug, so users on v3.4 / v3.5 transparently transition. Your `specs/` directory and any in-progress spec state files are not touched — the rename is purely a plugin-identity change.
23
+ - **Manual fallback:** if you'd rather run the migration yourself, execute `claude plugin uninstall ralph-specum@curdx-flow` (or `@smart-ralph`) before re-running `npx @curdx/flow install`. The old marketplace `curdx-flow` may remain registered alongside the new `curdx` marketplace; this is harmless and you can `claude plugin marketplace remove curdx-flow` if you want to clean up.
24
+
25
+ ### Notes
26
+
27
+ - The plugin's authorship and license lineage (smart-ralph by tzachbon → ralph-specum fork → curdx-flow) is recorded in `plugins/curdx-flow/NOTICE.md`. MIT License preserved.
28
+ - `.claude-plugin/marketplace.json` now declares one plugin: `curdx-flow` v4.9.1 sourced from `./plugins/curdx-flow`.
29
+
5
30
  ## 3.5.0 — 2026-04-27
6
31
 
7
32
  ### Changed
package/README.md CHANGED
@@ -31,11 +31,13 @@ npx @curdx/flow --lang en # override language
31
31
  | `claude-mem` | plugin | `thedotmack/claude-mem` |
32
32
  | `chrome-devtools-mcp` | plugin | `ChromeDevTools/chrome-devtools-mcp` |
33
33
  | `frontend-design` | plugin | `claude-plugins-official` (built-in) |
34
- | `ralph-specum` | plugin | bundled in this repo — spec-driven dev with autonomous task execution (originally [tzachbon/smart-ralph](https://github.com/tzachbon/smart-ralph), MIT) |
34
+ | `curdx-flow` | plugin | bundled in this repo (always installed) — spec-driven dev with autonomous task execution (originally [tzachbon/smart-ralph](https://github.com/tzachbon/smart-ralph), MIT, intermediate fork: ralph-specum) |
35
35
  | `sequential-thinking` | mcp | `@modelcontextprotocol/server-sequential-thinking` |
36
36
  | `context7` | mcp | HTTP — `https://mcp.context7.com/mcp` (optional API key) |
37
37
 
38
- > If you previously installed ralph-specum from `tzachbon/smart-ralph`, run `claude plugin uninstall ralph-specum@smart-ralph` before installing this version. Only the `ralph-specum@curdx-flow` build is maintained going forward.
38
+ > Migration notes:
39
+ > - If you installed the upstream `ralph-specum@smart-ralph` build, run `claude plugin uninstall ralph-specum@smart-ralph` before upgrading.
40
+ > - If you installed `ralph-specum@curdx-flow` (curdx-flow v3.4.0 / v3.5.0), run `claude plugin uninstall ralph-specum@curdx-flow` and re-run `npx @curdx/flow install`. The plugin is now `curdx-flow@curdx` with slash namespace `/curdx-flow:*`.
39
41
 
40
42
  ## What it writes to your filesystem
41
43
 
package/dist/index.mjs CHANGED
@@ -520,18 +520,101 @@ var frontendDesign = {
520
520
  };
521
521
  var frontend_design_default = frontendDesign;
522
522
 
523
- // src/registry/plugins/ralph-specum.ts
524
- var PLUGIN_ID5 = "ralph-specum@curdx-flow";
525
- var PLUGIN_NAME3 = "ralph-specum";
526
- var MARKETPLACE_NAME4 = "curdx-flow";
523
+ // src/runner/legacy-cleanup.ts
524
+ import { promises as fs2 } from "fs";
525
+ import path2 from "path";
526
+ import os2 from "os";
527
+ async function purgeLegacyPluginArtifacts(legacyId, ctx) {
528
+ const at = legacyId.indexOf("@");
529
+ if (at <= 0 || at === legacyId.length - 1) return;
530
+ const name = legacyId.slice(0, at);
531
+ const marketplace = legacyId.slice(at + 1);
532
+ const home = os2.homedir();
533
+ const settingsPath = path2.join(home, ".claude", "settings.json");
534
+ const installedPath = path2.join(home, ".claude", "plugins", "installed_plugins.json");
535
+ const cacheDir = path2.join(home, ".claude", "plugins", "cache", marketplace, name);
536
+ const dataDir = path2.join(home, ".claude", "plugins", "data", `${name}-${marketplace}`);
537
+ let removedAny = false;
538
+ removedAny = await deleteJsonKey(settingsPath, ["enabledPlugins", legacyId], ctx) || removedAny;
539
+ removedAny = await deleteJsonKey(installedPath, ["plugins", legacyId], ctx) || removedAny;
540
+ removedAny = await rmDir(cacheDir, ctx) || removedAny;
541
+ removedAny = await rmDir(dataDir, ctx) || removedAny;
542
+ if (removedAny) {
543
+ ctx.log.message(`Purged legacy artifacts for ${legacyId}.`);
544
+ clearStateCache();
545
+ }
546
+ }
547
+ async function deleteJsonKey(filePath, keyPath, ctx) {
548
+ let raw;
549
+ try {
550
+ raw = await fs2.readFile(filePath, "utf8");
551
+ } catch (err) {
552
+ if (err.code === "ENOENT") return false;
553
+ ctx.log.message(`Skip purge of ${filePath}: ${err.message}`);
554
+ return false;
555
+ }
556
+ let json;
557
+ try {
558
+ json = JSON.parse(raw);
559
+ } catch (err) {
560
+ ctx.log.message(`Skip purge of ${filePath}: invalid JSON (${err.message})`);
561
+ return false;
562
+ }
563
+ let cursor = json;
564
+ for (let i = 0; i < keyPath.length - 1; i++) {
565
+ const next = cursor?.[keyPath[i]];
566
+ if (!next || typeof next !== "object") return false;
567
+ cursor = next;
568
+ }
569
+ const finalKey = keyPath[keyPath.length - 1];
570
+ if (!cursor || !(finalKey in cursor)) return false;
571
+ delete cursor[finalKey];
572
+ try {
573
+ await fs2.writeFile(filePath, JSON.stringify(json, null, 2) + "\n", "utf8");
574
+ return true;
575
+ } catch (err) {
576
+ ctx.log.message(`Failed to rewrite ${filePath}: ${err.message}`);
577
+ return false;
578
+ }
579
+ }
580
+ async function rmDir(dirPath, ctx) {
581
+ try {
582
+ await fs2.access(dirPath);
583
+ } catch {
584
+ return false;
585
+ }
586
+ try {
587
+ await fs2.rm(dirPath, { recursive: true, force: true });
588
+ return true;
589
+ } catch (err) {
590
+ ctx.log.message(`Failed to remove ${dirPath}: ${err.message}`);
591
+ return false;
592
+ }
593
+ }
594
+
595
+ // src/registry/plugins/curdx-flow.ts
596
+ var PLUGIN_ID5 = "curdx-flow@curdx";
597
+ var PLUGIN_NAME3 = "curdx-flow";
598
+ var MARKETPLACE_NAME4 = "curdx";
527
599
  var MARKETPLACE_SOURCE4 = "curdx/curdx-flow";
528
- var ralphSpecum = {
529
- id: "ralph-specum",
530
- name: "ralph-specum",
600
+ var LEGACY_PLUGIN_IDS = ["ralph-specum@curdx-flow", "ralph-specum@smart-ralph"];
601
+ async function uninstallLegacyIfPresent(ctx) {
602
+ for (const legacyId of LEGACY_PLUGIN_IDS) {
603
+ const installed = await isPluginInstalled(legacyId);
604
+ if (installed) {
605
+ ctx.log.message(`Removing legacy plugin ${legacyId} (renamed to ${PLUGIN_ID5})\u2026`);
606
+ await uninstallPluginById(legacyId, ctx);
607
+ }
608
+ await purgeLegacyPluginArtifacts(legacyId, ctx);
609
+ }
610
+ }
611
+ var curdxFlow = {
612
+ id: "curdx-flow",
613
+ name: "curdx-flow",
531
614
  description: "curdx-flow (originally tzachbon/smart-ralph) \u2014 spec-driven dev with autonomous task execution",
532
615
  type: "plugin",
533
616
  required: true,
534
- slashNamespace: "/ralph-specum:*",
617
+ slashNamespace: "/curdx-flow:*",
535
618
  whenToUse: "for spec-driven multi-task work \u2014 research \u2192 requirements \u2192 design \u2192 tasks \u2192 autonomous execution per task. Use when starting a feature that benefits from upfront spec; skip for one-shot fixes or simple edits.",
536
619
  marketplaces: () => [MARKETPLACE_NAME4],
537
620
  isInstalled: () => isPluginInstalled(PLUGIN_ID5),
@@ -542,13 +625,17 @@ var ralphSpecum = {
542
625
  },
543
626
  latestVersion: () => getMarketplacePluginVersion(MARKETPLACE_NAME4, PLUGIN_NAME3),
544
627
  install: async (ctx) => {
628
+ await uninstallLegacyIfPresent(ctx);
545
629
  await ensureMarketplace(MARKETPLACE_NAME4, MARKETPLACE_SOURCE4, ctx);
546
630
  await installPluginById(PLUGIN_ID5, ctx);
547
631
  },
548
632
  uninstall: (ctx) => uninstallPluginById(PLUGIN_ID5, ctx),
549
- update: (ctx) => updatePluginById(PLUGIN_ID5, ctx)
633
+ update: async (ctx) => {
634
+ await uninstallLegacyIfPresent(ctx);
635
+ await updatePluginById(PLUGIN_ID5, ctx);
636
+ }
550
637
  };
551
- var ralph_specum_default = ralphSpecum;
638
+ var curdx_flow_default = curdxFlow;
552
639
 
553
640
  // src/registry/mcps/sequential-thinking.ts
554
641
  var MCP_NAME = "sequential-thinking";
@@ -647,7 +734,7 @@ var PKGS = [
647
734
  claude_mem_default,
648
735
  chrome_devtools_mcp_default,
649
736
  frontend_design_default,
650
- ralph_specum_default,
737
+ curdx_flow_default,
651
738
  sequential_thinking_default,
652
739
  context7_default
653
740
  ];
@@ -656,15 +743,15 @@ function findPkg(id) {
656
743
  }
657
744
 
658
745
  // src/runner/claudeMd.ts
659
- import { promises as fs2 } from "fs";
660
- import path2 from "path";
661
- import os2 from "os";
746
+ import { promises as fs3 } from "fs";
747
+ import path3 from "path";
748
+ import os3 from "os";
662
749
  import * as p3 from "@clack/prompts";
663
750
  var BEGIN_MARKER = "<!-- BEGIN @curdx/flow v1 -->";
664
751
  var END_MARKER = "<!-- END @curdx/flow v1 -->";
665
752
  var BLOCK_RE = /<!-- BEGIN @curdx\/flow v\d+[^>]*-->[\s\S]*?<!-- END @curdx\/flow v\d+ -->/;
666
753
  function claudeMdPath() {
667
- return path2.join(os2.homedir(), ".claude", "CLAUDE.md");
754
+ return path3.join(os3.homedir(), ".claude", "CLAUDE.md");
668
755
  }
669
756
  function renderItemLine(item) {
670
757
  let line = `- ${item.name}`;
@@ -773,7 +860,7 @@ async function syncClaudeMd(opts) {
773
860
  let existing = "";
774
861
  let existed = true;
775
862
  try {
776
- existing = await fs2.readFile(file, "utf8");
863
+ existing = await fs3.readFile(file, "utf8");
777
864
  } catch (err) {
778
865
  if (err.code === "ENOENT") {
779
866
  existed = false;
@@ -795,10 +882,10 @@ async function syncClaudeMd(opts) {
795
882
  if (next === existing) {
796
883
  return { status: "unchanged", path: file };
797
884
  }
798
- await fs2.mkdir(path2.dirname(file), { recursive: true });
885
+ await fs3.mkdir(path3.dirname(file), { recursive: true });
799
886
  const tmp = `${file}.tmp.${process.pid}`;
800
- await fs2.writeFile(tmp, next, "utf8");
801
- await fs2.rename(tmp, file);
887
+ await fs3.writeFile(tmp, next, "utf8");
888
+ await fs3.rename(tmp, file);
802
889
  if (!existed) return { status: "created", path: file };
803
890
  if (hadBlock && items.length === 0) return { status: "removed", path: file };
804
891
  return { status: "updated", path: file };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@curdx/flow",
3
- "version": "3.5.0",
3
+ "version": "4.0.1",
4
4
  "description": "Interactive installer for Claude Code plugins and MCP servers",
5
5
  "type": "module",
6
6
  "bin": "./dist/index.mjs",