@curdx/flow 2.0.17 → 2.0.19

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.
@@ -0,0 +1,189 @@
1
+ import { BUNDLED_MCPS, REQUIRED_PLUGINS, RECOMMENDED_PLUGINS } from "../registry.js";
2
+ import {
3
+ findDuplicateMcps,
4
+ findPluginByRegistryEntry,
5
+ hasMarketplace,
6
+ } from "./claude.js";
7
+
8
+ export function buildDoctorReport({
9
+ claudeVersionValue,
10
+ nodeVersion,
11
+ plugins = [],
12
+ marketplaces = [],
13
+ mcps = [],
14
+ userMcpConfig,
15
+ runtimeStatus,
16
+ cwd,
17
+ projectState,
18
+ }) {
19
+ const lines = [];
20
+ const sections = [];
21
+ let errors = 0;
22
+ let warnings = 0;
23
+
24
+ const pushLine = (target, level, text, details = []) => {
25
+ target.push({ level, text, details });
26
+ if (level === "err") errors++;
27
+ if (level === "warn") warnings++;
28
+ };
29
+ const pushSectionLine = (section, level, text, details = []) => {
30
+ pushLine(section.lines, level, text, details);
31
+ };
32
+ const createSection = (title) => {
33
+ const section = { title, lines: [] };
34
+ sections.push(section);
35
+ return section;
36
+ };
37
+
38
+ if (claudeVersionValue) {
39
+ pushLine(lines, "ok", `claude CLI ${claudeVersionValue}`);
40
+ } else {
41
+ pushLine(lines, "err", "claude CLI not found (install Claude Code)");
42
+ }
43
+ pushLine(lines, "ok", `Node ${nodeVersion}`);
44
+
45
+ const curdx = plugins.find((plugin) => plugin.name === "curdx-flow");
46
+ if (curdx) {
47
+ if (curdx.status === "enabled") {
48
+ pushLine(lines, "ok", `curdx-flow v${curdx.version} (enabled)`);
49
+ } else {
50
+ pushLine(lines, "err", `curdx-flow v${curdx.version} (${curdx.status})`);
51
+ }
52
+ } else {
53
+ pushLine(lines, "warn", "curdx-flow not installed → run curdx-flow install");
54
+ }
55
+
56
+ const requiredSection = createSection("Required plugins:");
57
+ for (const entry of REQUIRED_PLUGINS) {
58
+ const plugin = findPluginByRegistryEntry(plugins, entry);
59
+ if (!hasMarketplace(marketplaces, entry)) {
60
+ pushSectionLine(
61
+ requiredSection,
62
+ "warn",
63
+ `${entry.marketplaceId.padEnd(22)} marketplace missing`,
64
+ [`run: claude plugin marketplace add --scope ${entry.scope} ${entry.marketplaceSource}`]
65
+ );
66
+ }
67
+ if (plugin && plugin.status === "enabled") {
68
+ pushSectionLine(requiredSection, "ok", `${entry.name.padEnd(22)} v${plugin.version || "unknown"}`);
69
+ } else if (plugin && plugin.status === "failed") {
70
+ pushSectionLine(requiredSection, "err", `${entry.name.padEnd(22)} load failed`);
71
+ } else {
72
+ pushSectionLine(
73
+ requiredSection,
74
+ "warn",
75
+ `${entry.name.padEnd(22)} not installed`,
76
+ [`run: claude plugin install --scope ${entry.scope} ${entry.installSpec}`]
77
+ );
78
+ }
79
+ }
80
+
81
+ const mcpSection = createSection("MCP Servers (required by L2 mandatory tools):");
82
+ for (const expected of BUNDLED_MCPS) {
83
+ const userLevel = mcps.find((entry) => entry.name === expected.name && entry.plugin === null);
84
+ const pluginLevel = mcps.find((entry) => entry.name === expected.name && entry.plugin !== null);
85
+
86
+ if (userLevel) {
87
+ pushSectionLine(mcpSection, "ok", `${expected.name.padEnd(22)} user-level (standard)`);
88
+ } else if (pluginLevel) {
89
+ pushSectionLine(
90
+ mcpSection,
91
+ "warn",
92
+ `${expected.name.padEnd(22)} registered via plugin:${pluginLevel.plugin} (legacy)`,
93
+ ["run: npx @curdx/flow install --all"]
94
+ );
95
+ } else if (curdx) {
96
+ pushSectionLine(
97
+ mcpSection,
98
+ "warn",
99
+ `${expected.name.padEnd(22)} missing`,
100
+ [`run: claude mcp add --scope user ${expected.name} -- ${expected.command} ${expected.args.join(" ")}`]
101
+ );
102
+ } else {
103
+ pushSectionLine(mcpSection, "info", `${expected.name.padEnd(22)} waiting for curdx-flow install`);
104
+ }
105
+ }
106
+
107
+ const recommendedSection = createSection("Recommended plugins:");
108
+ let claudeMemEnabled = false;
109
+ for (const entry of RECOMMENDED_PLUGINS) {
110
+ const plugin = findPluginByRegistryEntry(plugins, entry);
111
+ if (!hasMarketplace(marketplaces, entry)) {
112
+ pushSectionLine(
113
+ recommendedSection,
114
+ "warn",
115
+ `${entry.marketplaceId.padEnd(22)} marketplace missing`,
116
+ [`run: claude plugin marketplace add --scope ${entry.scope} ${entry.marketplaceSource}`]
117
+ );
118
+ }
119
+ if (plugin && plugin.status === "enabled") {
120
+ pushSectionLine(recommendedSection, "ok", `${entry.name.padEnd(22)} v${plugin.version}`);
121
+ if (entry.postInstall === "claude-mem-runtimes") claudeMemEnabled = true;
122
+ } else if (plugin && plugin.status === "failed") {
123
+ pushSectionLine(recommendedSection, "err", `${entry.name.padEnd(22)} load failed`);
124
+ } else {
125
+ pushSectionLine(
126
+ recommendedSection,
127
+ "warn",
128
+ `${entry.name.padEnd(22)} not installed`,
129
+ [`run: claude plugin install --scope ${entry.scope} ${entry.installSpec}`]
130
+ );
131
+ }
132
+ }
133
+
134
+ const duplicates = findDuplicateMcps(mcps, userMcpConfig);
135
+ if (duplicates.length > 0) {
136
+ const duplicateSection = createSection("Legacy plugin-bundled MCPs still present:");
137
+ for (const duplicate of duplicates) {
138
+ pushSectionLine(
139
+ duplicateSection,
140
+ "warn",
141
+ `${duplicate.name.padEnd(22)} both user-level AND plugin:${duplicate.pluginEntry.plugin} active`,
142
+ [
143
+ "migration: claude plugin update curdx-flow@curdx-flow-marketplace",
144
+ "then restart Claude Code",
145
+ ]
146
+ );
147
+ }
148
+ }
149
+
150
+ if (claudeMemEnabled && runtimeStatus) {
151
+ const runtimeSection = createSection("Runtime (claude-mem dependencies):");
152
+ for (const [name, status] of Object.entries(runtimeStatus)) {
153
+ if (status.status === "ok") {
154
+ pushSectionLine(runtimeSection, "ok", `${name.padEnd(22)} visible on PATH`);
155
+ } else if (status.status === "linked") {
156
+ pushSectionLine(runtimeSection, "ok", `${name.padEnd(22)} auto-linked ${status.link} → ${status.path}`);
157
+ } else if (status.status === "missing") {
158
+ pushSectionLine(
159
+ runtimeSection,
160
+ "warn",
161
+ `${name.padEnd(22)} not installed`,
162
+ ["claude-mem will auto-install on next Claude Code session"]
163
+ );
164
+ } else if (status.status === "path-unwritable") {
165
+ const dir = status.path.split("/").slice(0, -1).join("/");
166
+ pushSectionLine(
167
+ runtimeSection,
168
+ "err",
169
+ `${name.padEnd(22)} installed but not on PATH`,
170
+ [`add export PATH="${dir}:$PATH" to your shell rc`]
171
+ );
172
+ }
173
+ }
174
+ }
175
+
176
+ const localProjectSection = createSection("Local project:");
177
+ if (projectState?.exists) {
178
+ pushSectionLine(localProjectSection, "ok", `.flow/ ${cwd}`);
179
+ if (projectState.activeSpec) {
180
+ pushSectionLine(localProjectSection, "info", `Active spec ${projectState.activeSpec}`);
181
+ } else {
182
+ pushSectionLine(localProjectSection, "info", "Active spec (none)");
183
+ }
184
+ } else {
185
+ pushSectionLine(localProjectSection, "info", ".flow/ not a curdx-flow project (run: curdx-flow init)");
186
+ }
187
+
188
+ return { lines, sections, errors, warnings };
189
+ }