@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 +25 -0
- package/README.md +4 -2
- package/dist/index.mjs +106 -19
- package/package.json +1 -1
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
|
-
| `
|
|
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
|
-
>
|
|
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/
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
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
|
|
529
|
-
|
|
530
|
-
|
|
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: "/
|
|
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) =>
|
|
633
|
+
update: async (ctx) => {
|
|
634
|
+
await uninstallLegacyIfPresent(ctx);
|
|
635
|
+
await updatePluginById(PLUGIN_ID5, ctx);
|
|
636
|
+
}
|
|
550
637
|
};
|
|
551
|
-
var
|
|
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
|
-
|
|
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
|
|
660
|
-
import
|
|
661
|
-
import
|
|
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
|
|
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
|
|
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
|
|
885
|
+
await fs3.mkdir(path3.dirname(file), { recursive: true });
|
|
799
886
|
const tmp = `${file}.tmp.${process.pid}`;
|
|
800
|
-
await
|
|
801
|
-
await
|
|
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 };
|