@curdx/flow 2.3.7 → 2.3.9
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/.claude-plugin/marketplace.json +2 -3
- package/.claude-plugin/plugin.json +1 -3
- package/CHANGELOG.md +6 -0
- package/cli/README.md +2 -0
- package/cli/doctor-workflow.js +32 -0
- package/cli/install-context7-config.js +57 -76
- package/cli/install-required-plugins.js +2 -2
- package/cli/lib/doctor-report.js +35 -0
- package/cli/lib/process.js +7 -2
- package/knowledge/claude-code-runtime-contracts.md +4 -2
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Claude Code Discipline Layer — spec-driven workflow + goal-backward verification + Karpathy 4 principles enforced via gates. Stops Claude from faking \"done\" on non-trivial features.",
|
|
9
|
-
"version": "2.3.
|
|
9
|
+
"version": "2.3.9"
|
|
10
10
|
},
|
|
11
11
|
"allowCrossMarketplaceDependenciesOn": [
|
|
12
12
|
"context7-marketplace"
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"name": "curdx-flow",
|
|
17
17
|
"source": "./",
|
|
18
18
|
"description": "Claude Code Discipline Layer — spec-driven workflow + goal-backward verification + Karpathy 4 principles enforced via gates. Stops Claude from faking \"done\" on non-trivial features.",
|
|
19
|
-
"version": "2.3.
|
|
19
|
+
"version": "2.3.9",
|
|
20
20
|
"author": {
|
|
21
21
|
"name": "wdx",
|
|
22
22
|
"email": "bydongxin@gmail.com"
|
|
@@ -40,7 +40,6 @@
|
|
|
40
40
|
"dependencies": [
|
|
41
41
|
{
|
|
42
42
|
"name": "context7-plugin",
|
|
43
|
-
"version": "^1.0.0",
|
|
44
43
|
"marketplace": "context7-marketplace"
|
|
45
44
|
}
|
|
46
45
|
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "curdx-flow",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.9",
|
|
4
4
|
"description": "Claude Code Discipline Layer — spec-driven workflow + goal-backward verification + Karpathy 4 principles enforced via gates. Stops Claude from faking \"done\" on non-trivial features.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "wdx",
|
|
@@ -37,7 +37,6 @@
|
|
|
37
37
|
"./agents/flow-ux-designer.md",
|
|
38
38
|
"./agents/flow-verifier.md"
|
|
39
39
|
],
|
|
40
|
-
"hooks": "./hooks/hooks.json",
|
|
41
40
|
"outputStyles": "./output-styles/",
|
|
42
41
|
"monitors": "./monitors/monitors.json",
|
|
43
42
|
"userConfig": {
|
|
@@ -65,7 +64,6 @@
|
|
|
65
64
|
"dependencies": [
|
|
66
65
|
{
|
|
67
66
|
"name": "context7-plugin",
|
|
68
|
-
"version": "^1.0.0",
|
|
69
67
|
"marketplace": "context7-marketplace"
|
|
70
68
|
}
|
|
71
69
|
]
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.3.8
|
|
4
|
+
|
|
5
|
+
- taught `doctor` to detect dirty CurDX-Flow source checkouts, so plugin developers immediately see when Claude cannot possibly be running their latest local edits yet
|
|
6
|
+
- surfaced bundled source repo metadata (`branch`, `shortSha`, `exactTag`, `dirty`) alongside bundled plugin body version in diagnostics and JSON output
|
|
7
|
+
- documented the reinstall workflow for “same version, but local source changed” plugin-development scenarios
|
|
8
|
+
|
|
3
9
|
## 2.3.7
|
|
4
10
|
|
|
5
11
|
- taught `doctor` to detect source/package-vs-installed plugin version drift so local plugin development and release validation no longer silently run against stale Claude plugin cache state
|
package/cli/README.md
CHANGED
|
@@ -46,6 +46,8 @@ External diagnostics: claude CLI / curdx-flow / required MCPs / recommended plug
|
|
|
46
46
|
|
|
47
47
|
`doctor` also compares the plugin body shipped with the current CLI/package against the `curdx-flow` version Claude actually has installed, so local development and release validation do not silently run against stale plugin cache state.
|
|
48
48
|
|
|
49
|
+
When the CLI is running from a git checkout, `doctor` also reports whether that source repo is dirty. This catches the common plugin-development trap where Claude is still running the previously installed cache while your local checkout has uninstalled edits.
|
|
50
|
+
|
|
49
51
|
### Project initialization (not a CLI command)
|
|
50
52
|
|
|
51
53
|
Project initialization is a Claude Code slash command, not a CLI one. After `install`, open your project in Claude Code and run:
|
package/cli/doctor-workflow.js
CHANGED
|
@@ -169,6 +169,13 @@ export async function readBundledPluginRuntimeDefaults(packageRoot = PACKAGE_ROO
|
|
|
169
169
|
exists: false,
|
|
170
170
|
packageVersion: null,
|
|
171
171
|
pluginVersion: null,
|
|
172
|
+
sourceRepo: {
|
|
173
|
+
isGitRepo: false,
|
|
174
|
+
branch: null,
|
|
175
|
+
shortSha: null,
|
|
176
|
+
exactTag: null,
|
|
177
|
+
dirty: false,
|
|
178
|
+
},
|
|
172
179
|
defaultAgent: null,
|
|
173
180
|
subagentStatusLineCommand: null,
|
|
174
181
|
monitorCount: 0,
|
|
@@ -199,6 +206,31 @@ export async function readBundledPluginRuntimeDefaults(packageRoot = PACKAGE_ROO
|
|
|
199
206
|
state.packageVersion = null;
|
|
200
207
|
}
|
|
201
208
|
|
|
209
|
+
const gitRepoCheck = runSync("git", ["rev-parse", "--is-inside-work-tree"], { cwd: packageRoot });
|
|
210
|
+
if (gitRepoCheck.code === 0 && gitRepoCheck.stdout.trim() === "true") {
|
|
211
|
+
state.sourceRepo.isGitRepo = true;
|
|
212
|
+
|
|
213
|
+
const branch = runSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: packageRoot });
|
|
214
|
+
if (branch.code === 0) {
|
|
215
|
+
state.sourceRepo.branch = branch.stdout.trim() || null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const shortSha = runSync("git", ["rev-parse", "--short", "HEAD"], { cwd: packageRoot });
|
|
219
|
+
if (shortSha.code === 0) {
|
|
220
|
+
state.sourceRepo.shortSha = shortSha.stdout.trim() || null;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const exactTag = runSync("git", ["describe", "--tags", "--exact-match", "HEAD"], { cwd: packageRoot });
|
|
224
|
+
if (exactTag.code === 0) {
|
|
225
|
+
state.sourceRepo.exactTag = exactTag.stdout.trim() || null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const status = runSync("git", ["status", "--short"], { cwd: packageRoot });
|
|
229
|
+
if (status.code === 0) {
|
|
230
|
+
state.sourceRepo.dirty = status.stdout.trim().length > 0;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
202
234
|
try {
|
|
203
235
|
monitors = JSON.parse(await fs.readFile(monitorsPath, "utf-8"));
|
|
204
236
|
} catch {
|
|
@@ -1,97 +1,78 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Context7
|
|
3
|
-
* required-plugins concern; not re-exported from
|
|
2
|
+
* Reconcile legacy Context7 install state after the official plugin is
|
|
3
|
+
* installed. Private to the required-plugins concern; not re-exported from
|
|
4
|
+
* install-companions.js.
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
|
-
import {
|
|
7
|
+
import { removeMcp } from "./lib/claude-ops.js";
|
|
7
8
|
import {
|
|
8
9
|
color,
|
|
9
10
|
log,
|
|
10
|
-
|
|
11
|
+
readUserMcpConfig,
|
|
11
12
|
resultLastLine,
|
|
12
|
-
text,
|
|
13
13
|
writeConfig,
|
|
14
14
|
} from "./utils.js";
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
export async function installContext7Config(
|
|
17
|
+
plugin,
|
|
18
|
+
language,
|
|
19
|
+
config,
|
|
20
|
+
{
|
|
21
|
+
removeMcpImpl = removeMcp,
|
|
22
|
+
readUserMcpConfigImpl = readUserMcpConfig,
|
|
23
|
+
writeConfigImpl = writeConfig,
|
|
24
|
+
logImpl = log,
|
|
25
|
+
} = {}
|
|
26
|
+
) {
|
|
20
27
|
if (plugin.name !== "context7-plugin") return;
|
|
21
28
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
: "Enter your Context7 API key (or press Enter to skip)",
|
|
34
|
-
placeholder: "ctx7sk-...",
|
|
35
|
-
validate: (value) => {
|
|
36
|
-
if (!value) return;
|
|
37
|
-
if (!value.startsWith("ctx7sk-") && !value.startsWith("ctx7_")) {
|
|
38
|
-
return language === "zh"
|
|
39
|
-
? "API key 应该以 ctx7sk- 或 ctx7_ 开头"
|
|
40
|
-
: "API key should start with ctx7sk- or ctx7_";
|
|
41
|
-
}
|
|
42
|
-
},
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
if (apiKey) {
|
|
46
|
-
config.context7ApiKey = apiKey;
|
|
47
|
-
writeConfig(config);
|
|
29
|
+
let removedStoredApiKey = false;
|
|
30
|
+
if (config && Object.hasOwn(config, "context7ApiKey")) {
|
|
31
|
+
delete config.context7ApiKey;
|
|
32
|
+
writeConfigImpl(config);
|
|
33
|
+
removedStoredApiKey = true;
|
|
34
|
+
logImpl.info(
|
|
35
|
+
language === "zh"
|
|
36
|
+
? " 已移除旧的 Context7 API key 本地配置,当前官方插件不再使用该安装方式"
|
|
37
|
+
: " Removed legacy local Context7 API key config; the official plugin no longer uses that install path"
|
|
38
|
+
);
|
|
39
|
+
}
|
|
48
40
|
|
|
49
|
-
|
|
41
|
+
const userMcps = readUserMcpConfigImpl();
|
|
42
|
+
if (!userMcps.has("context7")) {
|
|
43
|
+
return {
|
|
44
|
+
removedLegacyMcp: false,
|
|
45
|
+
removedStoredApiKey,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
50
48
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
? " Context7 API key 已配置"
|
|
55
|
-
: " Context7 API key configured"
|
|
56
|
-
);
|
|
57
|
-
} else if (r.stderr.includes("already exists")) {
|
|
58
|
-
await removeMcp({ name: "context7" });
|
|
59
|
-
const r2 = await addMcp(buildContext7Mcp(apiKey));
|
|
60
|
-
if (r2.code === 0) {
|
|
61
|
-
log.ok(
|
|
62
|
-
language === "zh"
|
|
63
|
-
? " Context7 API key 已更新"
|
|
64
|
-
: " Context7 API key updated"
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
} else {
|
|
68
|
-
log.warn(
|
|
69
|
-
language === "zh"
|
|
70
|
-
? ` Context7 MCP 配置失败: ${resultLastLine(r)}`
|
|
71
|
-
: ` Context7 MCP configuration failed: ${resultLastLine(r)}`
|
|
72
|
-
);
|
|
73
|
-
log.info(
|
|
74
|
-
color.dim(
|
|
75
|
-
language === "zh"
|
|
76
|
-
? ` 手动运行: claude mcp add --scope user context7 --env CONTEXT7_API_KEY=${apiKey} -- ${CONTEXT7_COMMAND} ${CONTEXT7_ARGS.join(" ")}`
|
|
77
|
-
: ` Run manually: claude mcp add --scope user context7 --env CONTEXT7_API_KEY=${apiKey} -- ${CONTEXT7_COMMAND} ${CONTEXT7_ARGS.join(" ")}`
|
|
78
|
-
)
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
} else {
|
|
82
|
-
log.info(
|
|
49
|
+
const result = await removeMcpImpl({ name: "context7" });
|
|
50
|
+
if (result.code === 0) {
|
|
51
|
+
logImpl.ok(
|
|
83
52
|
language === "zh"
|
|
84
|
-
? "
|
|
85
|
-
: "
|
|
53
|
+
? " 已移除遗留的用户级 context7 MCP,避免与官方 Context7 插件重复注册"
|
|
54
|
+
: " Removed legacy user-level context7 MCP so the official Context7 plugin is the only owner"
|
|
86
55
|
);
|
|
56
|
+
return {
|
|
57
|
+
removedLegacyMcp: true,
|
|
58
|
+
removedStoredApiKey,
|
|
59
|
+
};
|
|
87
60
|
}
|
|
88
|
-
}
|
|
89
61
|
|
|
90
|
-
|
|
62
|
+
logImpl.warn(
|
|
63
|
+
language === "zh"
|
|
64
|
+
? ` 清理旧的 context7 MCP 失败: ${resultLastLine(result)}`
|
|
65
|
+
: ` Failed to remove legacy context7 MCP: ${resultLastLine(result)}`
|
|
66
|
+
);
|
|
67
|
+
logImpl.info(
|
|
68
|
+
color.dim(
|
|
69
|
+
language === "zh"
|
|
70
|
+
? " 手动运行: claude mcp remove --scope user context7"
|
|
71
|
+
: " Run manually: claude mcp remove --scope user context7"
|
|
72
|
+
)
|
|
73
|
+
);
|
|
91
74
|
return {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
command: CONTEXT7_COMMAND,
|
|
95
|
-
args: CONTEXT7_ARGS,
|
|
75
|
+
removedLegacyMcp: false,
|
|
76
|
+
removedStoredApiKey,
|
|
96
77
|
};
|
|
97
78
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Install required Claude Code companion plugins (today: context7-plugin).
|
|
3
|
-
* Post-install,
|
|
3
|
+
* Post-install, reconcile any legacy Context7 user-level MCP/config state.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { addPluginMarketplace, installPlugin } from "./lib/claude-ops.js";
|
|
@@ -35,7 +35,7 @@ export async function installRequiredPlugins({
|
|
|
35
35
|
if (ir.code === 0) {
|
|
36
36
|
console.log(` ${color.green("✓")} ${plugin.name} installed`);
|
|
37
37
|
|
|
38
|
-
if (plugin.
|
|
38
|
+
if (plugin.name === "context7-plugin") {
|
|
39
39
|
await installContext7Config(plugin, language, config);
|
|
40
40
|
}
|
|
41
41
|
} else {
|
package/cli/lib/doctor-report.js
CHANGED
|
@@ -265,6 +265,7 @@ export function buildDoctorReport({
|
|
|
265
265
|
const curdx = plugins.find((plugin) => plugin.name === "curdx-flow");
|
|
266
266
|
const bundledVersion =
|
|
267
267
|
bundledPluginRuntimeDefaults?.pluginVersion || bundledPluginRuntimeDefaults?.packageVersion || null;
|
|
268
|
+
const bundledSourceRepo = bundledPluginRuntimeDefaults?.sourceRepo || {};
|
|
268
269
|
if (curdx) {
|
|
269
270
|
if (curdx.status === "enabled") {
|
|
270
271
|
pushLine(lines, "ok", `curdx-flow v${curdx.version} (enabled)`);
|
|
@@ -280,6 +281,18 @@ export function buildDoctorReport({
|
|
|
280
281
|
]
|
|
281
282
|
);
|
|
282
283
|
}
|
|
284
|
+
if (bundledSourceRepo.isGitRepo && bundledSourceRepo.dirty) {
|
|
285
|
+
pushLine(
|
|
286
|
+
lines,
|
|
287
|
+
"warn",
|
|
288
|
+
"curdx-flow source repo has uninstalled local changes",
|
|
289
|
+
[
|
|
290
|
+
"the current CLI/source checkout has git modifications that Claude's installed plugin cannot see until you reinstall it",
|
|
291
|
+
"re-run: npx @curdx/flow install --all",
|
|
292
|
+
"then fully restart Claude Code before validating plugin behavior",
|
|
293
|
+
]
|
|
294
|
+
);
|
|
295
|
+
}
|
|
283
296
|
} else {
|
|
284
297
|
pushLine(
|
|
285
298
|
lines,
|
|
@@ -464,6 +477,28 @@ export function buildDoctorReport({
|
|
|
464
477
|
);
|
|
465
478
|
}
|
|
466
479
|
|
|
480
|
+
if (bundledSourceRepo.isGitRepo) {
|
|
481
|
+
const sourceSummary = [
|
|
482
|
+
bundledSourceRepo.branch,
|
|
483
|
+
bundledSourceRepo.shortSha,
|
|
484
|
+
].filter(Boolean).join(" @ ");
|
|
485
|
+
const sourceDetails = [];
|
|
486
|
+
if (bundledSourceRepo.exactTag) {
|
|
487
|
+
sourceDetails.push(`exact tag: ${bundledSourceRepo.exactTag}`);
|
|
488
|
+
}
|
|
489
|
+
sourceDetails.push(
|
|
490
|
+
bundledSourceRepo.dirty
|
|
491
|
+
? "git worktree dirty: local source changes are not reflected in Claude until reinstall"
|
|
492
|
+
: "git worktree clean"
|
|
493
|
+
);
|
|
494
|
+
pushSectionLine(
|
|
495
|
+
bundledRuntimeSection,
|
|
496
|
+
bundledSourceRepo.dirty ? "warn" : "info",
|
|
497
|
+
`Bundled source repo ${sourceSummary || "git checkout detected"}`,
|
|
498
|
+
sourceDetails
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
|
|
467
502
|
if (bundledPluginRuntimeDefaults.defaultAgent) {
|
|
468
503
|
pushSectionLine(
|
|
469
504
|
bundledRuntimeSection,
|
package/cli/lib/process.js
CHANGED
|
@@ -29,8 +29,13 @@ export function run(cmd, args = [], opts = {}) {
|
|
|
29
29
|
/**
|
|
30
30
|
* Sync run — for quick checks (e.g. "which claude").
|
|
31
31
|
*/
|
|
32
|
-
export function runSync(cmd, args = []) {
|
|
33
|
-
const res = spawnSync(cmd, args, {
|
|
32
|
+
export function runSync(cmd, args = [], opts = {}) {
|
|
33
|
+
const res = spawnSync(cmd, args, {
|
|
34
|
+
encoding: "utf-8",
|
|
35
|
+
shell: false,
|
|
36
|
+
cwd: opts.cwd,
|
|
37
|
+
env: opts.env ? { ...process.env, ...opts.env } : process.env,
|
|
38
|
+
});
|
|
34
39
|
return {
|
|
35
40
|
code: res.status ?? -1,
|
|
36
41
|
stdout: res.stdout ?? "",
|
|
@@ -22,6 +22,7 @@ When a behavior is unclear, prefer the official docs and `claude plugin validate
|
|
|
22
22
|
|
|
23
23
|
## Hook Output Rules
|
|
24
24
|
|
|
25
|
+
- Standard plugin hooks live at `hooks/hooks.json` in the plugin root and are discovered automatically. Do not also set `plugin.json.hooks` to that same file; current Claude runtimes treat that as a duplicate load.
|
|
25
26
|
- `SessionStart` context injection must use:
|
|
26
27
|
- `hookSpecificOutput.hookEventName = "SessionStart"`
|
|
27
28
|
- `hookSpecificOutput.additionalContext = "..."`
|
|
@@ -74,9 +75,10 @@ Guarded artifact targets:
|
|
|
74
75
|
- Claude Code plugin-root `settings.json` currently supports only `agent` and `subagentStatusLine`.
|
|
75
76
|
- CurDX-Flow ships both:
|
|
76
77
|
- `agent: "flow-orchestrator"` to route the main thread through the CurDX-Flow coordinator by default.
|
|
77
|
-
|
|
78
|
+
- `subagentStatusLine`, pointing at `${CLAUDE_PLUGIN_ROOT}/hooks/scripts/subagent-statusline.sh`.
|
|
78
79
|
- The status-line script must fail open on malformed input or missing `python3`; UI decoration must never break agent execution.
|
|
79
80
|
- Plugin-root references must never traverse outside the plugin directory. Installed marketplace plugins run from Claude Code's plugin cache, so parent-directory references are invalid even if they work in a development checkout.
|
|
81
|
+
- CurDX-Flow should not declare `plugin.json.hooks` while using the standard `hooks/hooks.json` location; keep the file on disk, let Claude discover it implicitly, and reserve `plugin.json.hooks` for non-default or additional hook files only.
|
|
80
82
|
- If adding plugin settings, update `schemas/plugin-settings.schema.json`, `test/plugin-structure-contract.test.js`, `test/pack-tarball-smoke.test.js`, and `scripts/validate-plugin-contracts.mjs` in the same change.
|
|
81
83
|
|
|
82
84
|
## Plugin Monitors and User Config
|
|
@@ -94,7 +96,7 @@ Guarded artifact targets:
|
|
|
94
96
|
## Plugin Dependency Constraints
|
|
95
97
|
|
|
96
98
|
- Official dependency version constraints require upstream plugin release tags in the `{plugin-name}--v{version}` format.
|
|
97
|
-
- Do not add a version constraint to the `context7-plugin` dependency unless the Upstash marketplace has matching `context7-plugin--v*` tags.
|
|
99
|
+
- Do not add a version constraint to the `context7-plugin` dependency unless the Upstash marketplace has matching `context7-plugin--v*` tags. Claude resolves plugin dependency ranges against `{plugin-name}--v*` tags; a semver range without those tags can disable dependency resolution and surface the installed plugin version as `unknown`.
|
|
98
100
|
- Keep the CLI registry and `.claude-plugin/plugin.json` dependency entry aligned: Context7 remains a required companion plugin, while optional tools stay in `RECOMMENDED_PLUGINS`.
|
|
99
101
|
|
|
100
102
|
## Shared Settings Guardrails
|