@curdx/flow 3.0.0 → 3.2.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.
- package/CHANGELOG.md +33 -82
- package/LICENSE +1 -1
- package/README.md +28 -129
- package/dist/index.mjs +1165 -0
- package/package.json +33 -44
- package/.claude-plugin/marketplace.json +0 -48
- package/.claude-plugin/plugin.json +0 -52
- package/agent-preamble/preamble.md +0 -314
- package/agents/flow-adversary.md +0 -203
- package/agents/flow-architect.md +0 -198
- package/agents/flow-brownfield-analyst.md +0 -143
- package/agents/flow-debugger.md +0 -321
- package/agents/flow-edge-hunter.md +0 -289
- package/agents/flow-executor.md +0 -269
- package/agents/flow-orchestrator.md +0 -145
- package/agents/flow-planner.md +0 -247
- package/agents/flow-product-designer.md +0 -159
- package/agents/flow-qa-engineer.md +0 -282
- package/agents/flow-researcher.md +0 -166
- package/agents/flow-reviewer.md +0 -304
- package/agents/flow-security-auditor.md +0 -401
- package/agents/flow-triage-analyst.md +0 -272
- package/agents/flow-ui-researcher.md +0 -230
- package/agents/flow-ux-designer.md +0 -221
- package/agents/flow-verifier.md +0 -350
- package/bin/curdx-flow +0 -5
- package/bin/curdx-flow-state +0 -104
- package/bin/curdx-flow.js +0 -54
- package/cli/README.md +0 -104
- package/cli/doctor-workflow.js +0 -483
- package/cli/doctor.js +0 -73
- package/cli/help.js +0 -59
- package/cli/install-bundled-mcps.js +0 -37
- package/cli/install-companions.js +0 -19
- package/cli/install-context7-config.js +0 -80
- package/cli/install-curdx-plugin.js +0 -96
- package/cli/install-language.js +0 -35
- package/cli/install-next-steps.js +0 -29
- package/cli/install-options.js +0 -9
- package/cli/install-paths.js +0 -52
- package/cli/install-recommended-plugins.js +0 -104
- package/cli/install-required-plugins.js +0 -57
- package/cli/install-self-update.js +0 -62
- package/cli/install-workflow.js +0 -209
- package/cli/install.js +0 -101
- package/cli/lib/claude-commands.js +0 -41
- package/cli/lib/claude-ops.js +0 -47
- package/cli/lib/claude.js +0 -183
- package/cli/lib/config.js +0 -24
- package/cli/lib/doctor-claude-settings.js +0 -1186
- package/cli/lib/doctor-report.js +0 -978
- package/cli/lib/doctor-runtime-environment.js +0 -196
- package/cli/lib/frontmatter.js +0 -44
- package/cli/lib/json-schema.js +0 -57
- package/cli/lib/logging.js +0 -25
- package/cli/lib/process.js +0 -60
- package/cli/lib/prompts.js +0 -135
- package/cli/lib/runtime.js +0 -107
- package/cli/lib/semver.js +0 -109
- package/cli/lib/version.js +0 -12
- package/cli/protocols-body.md +0 -22
- package/cli/protocols.js +0 -162
- package/cli/registry.js +0 -123
- package/cli/router.js +0 -49
- package/cli/uninstall-actions.js +0 -360
- package/cli/uninstall-workflow.js +0 -146
- package/cli/uninstall.js +0 -42
- package/cli/upgrade-workflow.js +0 -80
- package/cli/upgrade.js +0 -91
- package/cli/utils.js +0 -40
- package/gates/adversarial-review-gate.md +0 -219
- package/gates/coverage-audit-gate.md +0 -182
- package/gates/devex-gate.md +0 -254
- package/gates/edge-case-gate.md +0 -194
- package/gates/karpathy-gate.md +0 -130
- package/gates/security-gate.md +0 -218
- package/gates/tdd-gate.md +0 -182
- package/gates/test-quality-gate.md +0 -59
- package/gates/verification-gate.md +0 -179
- package/hooks/hooks.json +0 -130
- package/hooks/scripts/common.sh +0 -237
- package/hooks/scripts/config-change-guard.sh +0 -94
- package/hooks/scripts/flow-context-watch.sh +0 -94
- package/hooks/scripts/inject-karpathy.sh +0 -53
- package/hooks/scripts/quick-mode-guard.sh +0 -69
- package/hooks/scripts/session-start.sh +0 -94
- package/hooks/scripts/session-title.sh +0 -87
- package/hooks/scripts/stop-watcher.sh +0 -231
- package/hooks/scripts/subagent-artifact-guard.sh +0 -92
- package/hooks/scripts/subagent-statusline.sh +0 -111
- package/hooks/scripts/task-lifecycle-guard.sh +0 -106
- package/hooks/scripts/teammate-idle-guard.sh +0 -83
- package/knowledge/artifact-output-discipline.md +0 -24
- package/knowledge/artifact-summary-contracts.md +0 -50
- package/knowledge/atomic-commits.md +0 -262
- package/knowledge/claude-code-runtime-contracts.md +0 -240
- package/knowledge/epic-decomposition.md +0 -307
- package/knowledge/execution-strategies.md +0 -303
- package/knowledge/karpathy-guidelines.md +0 -219
- package/knowledge/planning-reviews.md +0 -211
- package/knowledge/poc-first-workflow.md +0 -223
- package/knowledge/review-feedback-intake.md +0 -57
- package/knowledge/spec-driven-development.md +0 -180
- package/knowledge/systematic-debugging.md +0 -378
- package/knowledge/two-stage-review.md +0 -249
- package/knowledge/wave-execution.md +0 -403
- package/monitors/monitors.json +0 -8
- package/monitors/scripts/flow-state-monitor.sh +0 -102
- package/output-styles/curdx-evidence-first.md +0 -34
- package/output-styles/curdx-fast-mode.md +0 -42
- package/output-styles/curdx-spec-mode.md +0 -46
- package/schemas/agent-frontmatter.schema.json +0 -66
- package/schemas/config.schema.json +0 -134
- package/schemas/gate-frontmatter.schema.json +0 -30
- package/schemas/hooks.schema.json +0 -115
- package/schemas/output-style-frontmatter.schema.json +0 -22
- package/schemas/plugin-manifest.schema.json +0 -436
- package/schemas/plugin-settings.schema.json +0 -29
- package/schemas/skill-frontmatter.schema.json +0 -177
- package/schemas/spec-frontmatter.schema.json +0 -42
- package/schemas/spec-state.schema.json +0 -165
- package/settings.json +0 -8
- package/skills/brownfield-index/SKILL.md +0 -53
- package/skills/brownfield-index/references/applicability.md +0 -12
- package/skills/brownfield-index/references/handoff.md +0 -8
- package/skills/brownfield-index/references/index-contract.md +0 -10
- package/skills/browser-qa/SKILL.md +0 -39
- package/skills/browser-qa/references/handoff.md +0 -6
- package/skills/browser-qa/references/prerequisites.md +0 -10
- package/skills/browser-qa/references/qa-contract.md +0 -20
- package/skills/cancel/SKILL.md +0 -41
- package/skills/cancel/references/destructive-mode.md +0 -17
- package/skills/cancel/references/reporting.md +0 -18
- package/skills/cancel/references/state-recovery.md +0 -30
- package/skills/cancel/references/target-resolution.md +0 -7
- package/skills/debug/SKILL.md +0 -45
- package/skills/debug/references/context-gathering.md +0 -11
- package/skills/debug/references/failure-guard.md +0 -25
- package/skills/debug/references/intake.md +0 -12
- package/skills/debug/references/phase-workflow.md +0 -34
- package/skills/debug/references/reporting.md +0 -20
- package/skills/epic/SKILL.md +0 -39
- package/skills/epic/references/epic-artifacts.md +0 -20
- package/skills/epic/references/epic-intake.md +0 -9
- package/skills/epic/references/slice-handoff.md +0 -16
- package/skills/fast/SKILL.md +0 -62
- package/skills/fast/references/applicability.md +0 -25
- package/skills/fast/references/clarification.md +0 -20
- package/skills/fast/references/execution-contract.md +0 -56
- package/skills/help/SKILL.md +0 -55
- package/skills/help/references/dispatch.md +0 -20
- package/skills/help/references/overview.md +0 -39
- package/skills/help/references/troubleshoot.md +0 -47
- package/skills/help/references/workflow.md +0 -37
- package/skills/implement/SKILL.md +0 -104
- package/skills/implement/references/error-recovery.md +0 -36
- package/skills/implement/references/linear-execution.md +0 -43
- package/skills/implement/references/native-task-sync.md +0 -107
- package/skills/implement/references/preflight.md +0 -43
- package/skills/implement/references/progress-contract.md +0 -36
- package/skills/implement/references/state-init.md +0 -36
- package/skills/implement/references/stop-hook-execution.md +0 -50
- package/skills/implement/references/strategy-router.md +0 -38
- package/skills/implement/references/subagent-execution.md +0 -57
- package/skills/implement/references/wave-execution.md +0 -180
- package/skills/init/SKILL.md +0 -49
- package/skills/init/references/gitignore-and-health.md +0 -26
- package/skills/init/references/next-steps.md +0 -22
- package/skills/init/references/preflight.md +0 -15
- package/skills/init/references/scaffold-contract.md +0 -27
- package/skills/review/SKILL.md +0 -82
- package/skills/review/references/optional-passes.md +0 -48
- package/skills/review/references/preflight.md +0 -38
- package/skills/review/references/report-contract.md +0 -49
- package/skills/review/references/reporting.md +0 -20
- package/skills/review/references/stage-execution.md +0 -32
- package/skills/security-audit/SKILL.md +0 -47
- package/skills/security-audit/references/audit-contract.md +0 -21
- package/skills/security-audit/references/gate-handoff.md +0 -8
- package/skills/security-audit/references/scope-and-depth.md +0 -9
- package/skills/spec/SKILL.md +0 -100
- package/skills/spec/references/artifact-landing.md +0 -31
- package/skills/spec/references/phase-execution.md +0 -50
- package/skills/spec/references/planning-review.md +0 -31
- package/skills/spec/references/preflight-and-routing.md +0 -46
- package/skills/spec/references/reporting.md +0 -21
- package/skills/start/SKILL.md +0 -84
- package/skills/start/references/branch-routing.md +0 -51
- package/skills/start/references/mode-semantics.md +0 -12
- package/skills/start/references/preflight.md +0 -13
- package/skills/start/references/reporting.md +0 -20
- package/skills/start/references/state-seeding.md +0 -44
- package/skills/start/references/workflow-handoff.md +0 -26
- package/skills/status/SKILL.md +0 -41
- package/skills/status/references/gather-contract.md +0 -30
- package/skills/status/references/health-rules.md +0 -27
- package/skills/status/references/output-contract.md +0 -25
- package/skills/status/references/preflight.md +0 -10
- package/skills/status/references/recovery-hints.md +0 -18
- package/skills/ui-sketch/SKILL.md +0 -39
- package/skills/ui-sketch/references/brief-intake.md +0 -10
- package/skills/ui-sketch/references/iteration-handoff.md +0 -5
- package/skills/ui-sketch/references/variant-contract.md +0 -15
- package/skills/verify/SKILL.md +0 -56
- package/skills/verify/references/evidence-workflow.md +0 -39
- package/skills/verify/references/output-contract.md +0 -23
- package/skills/verify/references/preflight.md +0 -11
- package/skills/verify/references/report-handoff.md +0 -35
- package/skills/verify/references/strict-mode.md +0 -12
- package/templates/CONTEXT.md.tmpl +0 -53
- package/templates/PROJECT.md.tmpl +0 -59
- package/templates/ROADMAP.md.tmpl +0 -50
- package/templates/STATE.md.tmpl +0 -49
- package/templates/config.json.tmpl +0 -51
- package/templates/design.md.tmpl +0 -83
- package/templates/progress.md.tmpl +0 -77
- package/templates/requirements.md.tmpl +0 -76
- package/templates/research.md.tmpl +0 -83
- package/templates/tasks.md.tmpl +0 -107
package/bin/curdx-flow.js
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* curdx-flow CLI entry
|
|
4
|
-
*
|
|
5
|
-
* Usage:
|
|
6
|
-
* npx @curdx/flow <command> [args]
|
|
7
|
-
*
|
|
8
|
-
* The CLI only handles install-time and lifecycle operations that must
|
|
9
|
-
* happen outside Claude Code. Anything a user does while Claude Code is
|
|
10
|
-
* running (init a project, start a spec, run the workflow, etc.) is a
|
|
11
|
-
* slash command: /curdx-flow:init, /curdx-flow:start, and so on.
|
|
12
|
-
*
|
|
13
|
-
* Commands:
|
|
14
|
-
* install Install curdx-flow plugin + optional recommended plugins
|
|
15
|
-
* doctor Check health (claude CLI, plugin, MCPs, recommended plugins)
|
|
16
|
-
* upgrade Update curdx-flow + recommended plugins to latest
|
|
17
|
-
* uninstall Remove curdx-flow plugin (and optionally recommended / artifacts)
|
|
18
|
-
* --version / -v
|
|
19
|
-
* --help / -h (CLI usage summary — use /curdx-flow:help in Claude Code
|
|
20
|
-
* for the full command/workflow reference)
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import { fileURLToPath } from "node:url";
|
|
24
|
-
import { realpathSync } from "node:fs";
|
|
25
|
-
|
|
26
|
-
import { runCli } from "../cli/router.js";
|
|
27
|
-
|
|
28
|
-
// Only execute main() when invoked directly (`node bin/curdx-flow.js ...`
|
|
29
|
-
// or via the npm bin shim at node_modules/.bin/<name>). When the file is
|
|
30
|
-
// imported by tests or tooling, we want the module graph to load without
|
|
31
|
-
// side-effects.
|
|
32
|
-
//
|
|
33
|
-
// CRITICAL: compare RESOLVED real paths. npm installs the bin as a symlink
|
|
34
|
-
// (node_modules/.bin/curdx-flow → ../@curdx/flow/bin/curdx-flow.js), so
|
|
35
|
-
// process.argv[1] is the symlink path while import.meta.url resolves to
|
|
36
|
-
// the real file. Comparing them directly (the pre-beta.13 behavior)
|
|
37
|
-
// silently skipped main() for every single npx / global-install user,
|
|
38
|
-
// producing a completely broken CLI that exited with no output. Regression
|
|
39
|
-
// caught by user report + reproduced in CI via test/cli-entrypoints.test.js.
|
|
40
|
-
function isInvokedDirectly() {
|
|
41
|
-
if (!process.argv[1]) return false;
|
|
42
|
-
try {
|
|
43
|
-
return realpathSync(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
44
|
-
} catch {
|
|
45
|
-
// argv[1] is not a real filesystem path (e.g. `node -e` eval forms or
|
|
46
|
-
// a worker pipe). Treat as "not invoked directly" — the caller is
|
|
47
|
-
// doing something non-standard.
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (isInvokedDirectly()) {
|
|
53
|
-
runCli();
|
|
54
|
-
}
|
package/cli/README.md
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
# curdx-flow CLI
|
|
2
|
-
|
|
3
|
-
One-shot installer + external diagnostics + external init. Requires Node.js 18+ and Claude Code v2.1.110+.
|
|
4
|
-
|
|
5
|
-
## Quick Start
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
# Interactive install (pick recommended plugins)
|
|
9
|
-
npx github:curdx/curdx-flow install
|
|
10
|
-
|
|
11
|
-
# Or install everything automatically
|
|
12
|
-
npx github:curdx/curdx-flow install --all
|
|
13
|
-
|
|
14
|
-
# Health check
|
|
15
|
-
npx @curdx/flow doctor
|
|
16
|
-
|
|
17
|
-
# Update all plugins
|
|
18
|
-
npx @curdx/flow upgrade
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## Commands
|
|
22
|
-
|
|
23
|
-
### `install [--all] [--no-deps]`
|
|
24
|
-
|
|
25
|
-
Steps:
|
|
26
|
-
1. Verify the `claude` CLI is installed
|
|
27
|
-
2. `claude plugin marketplace add --scope user curdx/curdx-flow`
|
|
28
|
-
3. Pre-register required companion marketplaces so plugin dependencies can resolve
|
|
29
|
-
4. `claude plugin install --scope user curdx-flow@curdx-flow-marketplace`
|
|
30
|
-
5. Install required companion plugin: **context7-plugin@context7-marketplace**
|
|
31
|
-
6. Register required user-level MCP: **sequential-thinking**
|
|
32
|
-
7. Interactively (or automatically) install recommended plugins: **pua**, **claude-mem**, **frontend-design**, **chrome-devtools-mcp**
|
|
33
|
-
|
|
34
|
-
| Flag | Purpose |
|
|
35
|
-
|------|---------|
|
|
36
|
-
| `--all` | Install all recommended plugins, no prompt |
|
|
37
|
-
| `--no-deps` | Install only curdx-flow itself |
|
|
38
|
-
|
|
39
|
-
### `doctor [--verbose] [--fix] [--json]`
|
|
40
|
-
|
|
41
|
-
External diagnostics: claude CLI / curdx-flow / required MCPs / recommended plugins / current directory `.flow/` state.
|
|
42
|
-
|
|
43
|
-
`--fix` applies the safe automatic repairs the CLI can perform without guessing — currently the `bun` / `uv` PATH symlinks used by `claude-mem`. Everything else remains diagnostic-only.
|
|
44
|
-
|
|
45
|
-
`--json` emits the full health result as machine-readable JSON for CI, wrappers, or external diagnostics. It includes `contractVersion`, `metadata.appliedFixes`, settings inspection scope metadata, the rendered report structure, and raw `doctorData`, including CurDX-Flow plugin option precedence, file-based managed settings, and runtime env projection.
|
|
46
|
-
|
|
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
|
-
|
|
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
|
-
|
|
51
|
-
### Project initialization (not a CLI command)
|
|
52
|
-
|
|
53
|
-
Project initialization is a Claude Code slash command, not a CLI one. After `install`, open your project in Claude Code and run:
|
|
54
|
-
|
|
55
|
-
```
|
|
56
|
-
claude
|
|
57
|
-
/curdx-flow:init
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
This keeps the CLI scoped to install-time and lifecycle operations only — anything the user does while Claude Code is running stays inside Claude Code.
|
|
61
|
-
|
|
62
|
-
### `upgrade`
|
|
63
|
-
|
|
64
|
-
`claude plugin marketplace update` + `claude plugin update --scope user` for every installed curdx-flow-related plugin.
|
|
65
|
-
|
|
66
|
-
After updating, `upgrade` also reconciles retired Context7 install artifacts:
|
|
67
|
-
|
|
68
|
-
- removes the old user-level `context7` MCP if the official `context7-plugin` already owns it
|
|
69
|
-
- removes any stale `context7ApiKey` entry from `~/.claude/curdx-flow-config.json`
|
|
70
|
-
|
|
71
|
-
### `uninstall [-y] [--keep-recommended] [--purge]`
|
|
72
|
-
|
|
73
|
-
Inverse of `install`. By default removes only the curdx-flow plugin. Recommended plugins are kept unless selected interactively. With `--purge`, also removes third-party marketplaces, the `~/.local/bin/bun` / `~/.local/bin/uv` symlinks created by install, and any retired Context7 user-level MCP / stored API key left behind by older CurDX-Flow releases.
|
|
74
|
-
|
|
75
|
-
## Why a CLI?
|
|
76
|
-
|
|
77
|
-
The **core workflow** still lives inside Claude Code (`/curdx-flow:*` commands). The CLI provides:
|
|
78
|
-
|
|
79
|
-
- **One-line install**: Docker/CI friendly (`RUN npx github:curdx/curdx-flow install --all`)
|
|
80
|
-
- **External diagnostics**: check health without entering Claude Code
|
|
81
|
-
- **Batch deployment**: write a script for your team — `for host in ...; do ssh $host npx github:curdx/curdx-flow install; done`
|
|
82
|
-
|
|
83
|
-
## Small-dependency design
|
|
84
|
-
|
|
85
|
-
- Pure ES Modules (Node 18+)
|
|
86
|
-
- Tiny runtime dependency surface (`@clack/prompts`, `picocolors`)
|
|
87
|
-
- No `commander` / `inquirer` / `execa`
|
|
88
|
-
- Fast `npx` and offline-capable plugin install
|
|
89
|
-
|
|
90
|
-
## Local development
|
|
91
|
-
|
|
92
|
-
```bash
|
|
93
|
-
# clone
|
|
94
|
-
git clone https://github.com/curdx/curdx-flow
|
|
95
|
-
cd curdx-flow
|
|
96
|
-
|
|
97
|
-
# run directly
|
|
98
|
-
node bin/curdx-flow.js --help
|
|
99
|
-
node bin/curdx-flow.js doctor
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
## License
|
|
103
|
-
|
|
104
|
-
MIT
|
package/cli/doctor-workflow.js
DELETED
|
@@ -1,483 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
|
|
5
|
-
import { removeMcp } from "./lib/claude-ops.js";
|
|
6
|
-
import { readProjectClaudeSettings } from "./lib/doctor-claude-settings.js";
|
|
7
|
-
import { CONFIG_FILE, readConfig, writeConfig } from "./lib/config.js";
|
|
8
|
-
import { inspectRuntimeEnvironment } from "./lib/doctor-runtime-environment.js";
|
|
9
|
-
import {
|
|
10
|
-
claudeVersion,
|
|
11
|
-
color,
|
|
12
|
-
ensureClaudeMemRuntimes,
|
|
13
|
-
inspectClaudeMemRuntimes,
|
|
14
|
-
listMcps,
|
|
15
|
-
listPluginMarketplaces,
|
|
16
|
-
listPlugins,
|
|
17
|
-
log,
|
|
18
|
-
readUserMcpConfig,
|
|
19
|
-
runSync,
|
|
20
|
-
} from "./utils.js";
|
|
21
|
-
|
|
22
|
-
export { readProjectClaudeSettings };
|
|
23
|
-
export { inspectRuntimeEnvironment };
|
|
24
|
-
|
|
25
|
-
const PACKAGE_ROOT = fileURLToPath(new URL("../", import.meta.url));
|
|
26
|
-
export const DOCTOR_JSON_CONTRACT_VERSION = 2;
|
|
27
|
-
export const DOCTOR_SETTINGS_INSPECTION = Object.freeze({
|
|
28
|
-
pluginOptionScopesInspected: ["managed-file", "user", "project", "local"],
|
|
29
|
-
pluginOptionScopesNotInspected: ["managed-server", "managed-mdm", "cli"],
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
export function createDoctorContext(args = []) {
|
|
33
|
-
return {
|
|
34
|
-
fix: args.includes("--fix"),
|
|
35
|
-
json: args.includes("--json"),
|
|
36
|
-
verbose: args.includes("--verbose") || args.includes("-v"),
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function isUrlLike(value) {
|
|
41
|
-
return /^[a-z][a-z0-9+.-]*:\/\//i.test(value);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function isWindowsAbsolutePath(value) {
|
|
45
|
-
return /^[A-Za-z]:[\\/]/.test(value);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function looksLikeRelativePathToken(value) {
|
|
49
|
-
if (typeof value !== "string") return false;
|
|
50
|
-
const token = value.trim();
|
|
51
|
-
if (!token) return false;
|
|
52
|
-
|
|
53
|
-
if (token.startsWith("./") || token.startsWith("../")) return true;
|
|
54
|
-
if (!/[\\/]/.test(token)) return false;
|
|
55
|
-
|
|
56
|
-
if (
|
|
57
|
-
token.startsWith("/") ||
|
|
58
|
-
token.startsWith("~/") ||
|
|
59
|
-
token.startsWith("${") ||
|
|
60
|
-
token.startsWith("@") ||
|
|
61
|
-
token.startsWith("--") ||
|
|
62
|
-
isUrlLike(token) ||
|
|
63
|
-
isWindowsAbsolutePath(token)
|
|
64
|
-
) {
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return (
|
|
69
|
-
/\.(?:[cm]?js|mjs|ts|tsx|jsx|py|rb|sh|bash|zsh|fish|ps1|cmd|bat|exe)$/i.test(token) ||
|
|
70
|
-
/(^|[\\/])(?:scripts?|bin|dist|src|tools|vendor|node_modules|venv|\.venv)([\\/]|$)/i.test(token)
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
export async function readProjectMcpConfig(cwd = process.cwd()) {
|
|
74
|
-
const rootPath = path.join(cwd, ".mcp.json");
|
|
75
|
-
const misplacedPath = path.join(cwd, ".claude", ".mcp.json");
|
|
76
|
-
|
|
77
|
-
const state = {
|
|
78
|
-
exists: false,
|
|
79
|
-
misplacedExists: false,
|
|
80
|
-
invalid: false,
|
|
81
|
-
parseError: null,
|
|
82
|
-
shapeError: null,
|
|
83
|
-
serverCount: 0,
|
|
84
|
-
relativePathWarnings: [],
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
try {
|
|
88
|
-
const misplacedStat = await fs.stat(misplacedPath);
|
|
89
|
-
state.misplacedExists = misplacedStat.isFile();
|
|
90
|
-
} catch {
|
|
91
|
-
state.misplacedExists = false;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
try {
|
|
95
|
-
const stat = await fs.stat(rootPath);
|
|
96
|
-
if (!stat.isFile()) return state;
|
|
97
|
-
state.exists = true;
|
|
98
|
-
} catch {
|
|
99
|
-
return state;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
let parsed;
|
|
103
|
-
try {
|
|
104
|
-
parsed = JSON.parse(await fs.readFile(rootPath, "utf-8"));
|
|
105
|
-
} catch (error) {
|
|
106
|
-
state.invalid = true;
|
|
107
|
-
state.parseError = error.message;
|
|
108
|
-
return state;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
112
|
-
state.shapeError = "root value must be a JSON object";
|
|
113
|
-
return state;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (!parsed.mcpServers || typeof parsed.mcpServers !== "object" || Array.isArray(parsed.mcpServers)) {
|
|
117
|
-
state.shapeError = 'missing top-level "mcpServers" object';
|
|
118
|
-
return state;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
state.serverCount = Object.keys(parsed.mcpServers).length;
|
|
122
|
-
|
|
123
|
-
for (const [serverName, config] of Object.entries(parsed.mcpServers)) {
|
|
124
|
-
if (!config || typeof config !== "object" || Array.isArray(config)) continue;
|
|
125
|
-
|
|
126
|
-
if (looksLikeRelativePathToken(config.command)) {
|
|
127
|
-
state.relativePathWarnings.push({
|
|
128
|
-
serverName,
|
|
129
|
-
field: "command",
|
|
130
|
-
value: config.command,
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const args = Array.isArray(config.args) ? config.args : [];
|
|
135
|
-
args.forEach((arg, index) => {
|
|
136
|
-
if (!looksLikeRelativePathToken(arg)) return;
|
|
137
|
-
state.relativePathWarnings.push({
|
|
138
|
-
serverName,
|
|
139
|
-
field: `args[${index}]`,
|
|
140
|
-
value: arg,
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return state;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export async function readProjectTeamConfig(cwd = process.cwd()) {
|
|
149
|
-
const teamConfigPath = path.join(cwd, ".claude", "teams", "teams.json");
|
|
150
|
-
const state = {
|
|
151
|
-
exists: false,
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
try {
|
|
155
|
-
const stat = await fs.stat(teamConfigPath);
|
|
156
|
-
state.exists = stat.isFile();
|
|
157
|
-
} catch {
|
|
158
|
-
state.exists = false;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return state;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
export async function readBundledPluginRuntimeDefaults(packageRoot = PACKAGE_ROOT) {
|
|
165
|
-
const pluginSettingsPath = path.join(packageRoot, "settings.json");
|
|
166
|
-
const pluginManifestPath = path.join(packageRoot, ".claude-plugin", "plugin.json");
|
|
167
|
-
const monitorsPath = path.join(packageRoot, "monitors", "monitors.json");
|
|
168
|
-
const packageJsonPath = path.join(packageRoot, "package.json");
|
|
169
|
-
|
|
170
|
-
const state = {
|
|
171
|
-
exists: false,
|
|
172
|
-
packageVersion: null,
|
|
173
|
-
pluginVersion: null,
|
|
174
|
-
sourceRepo: {
|
|
175
|
-
isGitRepo: false,
|
|
176
|
-
branch: null,
|
|
177
|
-
shortSha: null,
|
|
178
|
-
exactTag: null,
|
|
179
|
-
dirty: false,
|
|
180
|
-
},
|
|
181
|
-
defaultAgent: null,
|
|
182
|
-
subagentStatusLineCommand: null,
|
|
183
|
-
monitorCount: 0,
|
|
184
|
-
monitors: [],
|
|
185
|
-
userConfig: [],
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
let pluginSettings = null;
|
|
189
|
-
let pluginManifest = null;
|
|
190
|
-
let monitors = null;
|
|
191
|
-
|
|
192
|
-
try {
|
|
193
|
-
pluginSettings = JSON.parse(await fs.readFile(pluginSettingsPath, "utf-8"));
|
|
194
|
-
} catch {
|
|
195
|
-
pluginSettings = null;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
try {
|
|
199
|
-
pluginManifest = JSON.parse(await fs.readFile(pluginManifestPath, "utf-8"));
|
|
200
|
-
} catch {
|
|
201
|
-
pluginManifest = null;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
try {
|
|
205
|
-
const pkg = JSON.parse(await fs.readFile(packageJsonPath, "utf-8"));
|
|
206
|
-
state.packageVersion = typeof pkg?.version === "string" ? pkg.version : null;
|
|
207
|
-
} catch {
|
|
208
|
-
state.packageVersion = null;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const gitRepoCheck = runSync("git", ["rev-parse", "--is-inside-work-tree"], { cwd: packageRoot });
|
|
212
|
-
if (gitRepoCheck.code === 0 && gitRepoCheck.stdout.trim() === "true") {
|
|
213
|
-
state.sourceRepo.isGitRepo = true;
|
|
214
|
-
|
|
215
|
-
const branch = runSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: packageRoot });
|
|
216
|
-
if (branch.code === 0) {
|
|
217
|
-
state.sourceRepo.branch = branch.stdout.trim() || null;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const shortSha = runSync("git", ["rev-parse", "--short", "HEAD"], { cwd: packageRoot });
|
|
221
|
-
if (shortSha.code === 0) {
|
|
222
|
-
state.sourceRepo.shortSha = shortSha.stdout.trim() || null;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const exactTag = runSync("git", ["describe", "--tags", "--exact-match", "HEAD"], { cwd: packageRoot });
|
|
226
|
-
if (exactTag.code === 0) {
|
|
227
|
-
state.sourceRepo.exactTag = exactTag.stdout.trim() || null;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
const status = runSync("git", ["status", "--short"], { cwd: packageRoot });
|
|
231
|
-
if (status.code === 0) {
|
|
232
|
-
state.sourceRepo.dirty = status.stdout.trim().length > 0;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
try {
|
|
237
|
-
monitors = JSON.parse(await fs.readFile(monitorsPath, "utf-8"));
|
|
238
|
-
} catch {
|
|
239
|
-
monitors = null;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
if (!pluginSettings && !pluginManifest && !monitors) {
|
|
243
|
-
return state;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
state.exists = true;
|
|
247
|
-
state.pluginVersion = typeof pluginManifest?.version === "string" ? pluginManifest.version : null;
|
|
248
|
-
state.defaultAgent = typeof pluginSettings?.agent === "string" ? pluginSettings.agent : null;
|
|
249
|
-
state.subagentStatusLineCommand =
|
|
250
|
-
typeof pluginSettings?.subagentStatusLine?.command === "string"
|
|
251
|
-
? pluginSettings.subagentStatusLine.command
|
|
252
|
-
: null;
|
|
253
|
-
|
|
254
|
-
if (Array.isArray(monitors)) {
|
|
255
|
-
state.monitorCount = monitors.length;
|
|
256
|
-
state.monitors = monitors
|
|
257
|
-
.filter((entry) => entry && typeof entry === "object")
|
|
258
|
-
.map((entry) => ({
|
|
259
|
-
name: typeof entry.name === "string" ? entry.name : null,
|
|
260
|
-
when: typeof entry.when === "string" ? entry.when : "always",
|
|
261
|
-
}))
|
|
262
|
-
.filter((entry) => entry.name);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
if (pluginManifest?.userConfig && typeof pluginManifest.userConfig === "object") {
|
|
266
|
-
state.userConfig = Object.entries(pluginManifest.userConfig)
|
|
267
|
-
.filter(([, value]) => value && typeof value === "object" && !Array.isArray(value))
|
|
268
|
-
.map(([key, value]) => ({
|
|
269
|
-
key,
|
|
270
|
-
type: value.type ?? null,
|
|
271
|
-
default: Object.prototype.hasOwnProperty.call(value, "default") ? value.default : undefined,
|
|
272
|
-
sensitive: value.sensitive === true,
|
|
273
|
-
}));
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
return state;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
export async function readProjectState(cwd = process.cwd()) {
|
|
280
|
-
const flowDir = path.join(cwd, ".flow");
|
|
281
|
-
try {
|
|
282
|
-
const stat = await fs.stat(flowDir);
|
|
283
|
-
if (!stat.isDirectory()) {
|
|
284
|
-
return { exists: false, activeSpec: null };
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
try {
|
|
288
|
-
const activeSpec = await fs.readFile(path.join(flowDir, ".active-spec"), "utf-8");
|
|
289
|
-
return { exists: true, activeSpec: activeSpec.trim() };
|
|
290
|
-
} catch {
|
|
291
|
-
return { exists: true, activeSpec: null };
|
|
292
|
-
}
|
|
293
|
-
} catch {
|
|
294
|
-
return { exists: false, activeSpec: null };
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
export async function collectDoctorData(
|
|
299
|
-
{
|
|
300
|
-
cwd = process.cwd(),
|
|
301
|
-
} = {},
|
|
302
|
-
{
|
|
303
|
-
claudeVersionImpl = claudeVersion,
|
|
304
|
-
listPluginsImpl = listPlugins,
|
|
305
|
-
listPluginMarketplacesImpl = listPluginMarketplaces,
|
|
306
|
-
listMcpsImpl = listMcps,
|
|
307
|
-
readUserMcpConfigImpl = readUserMcpConfig,
|
|
308
|
-
inspectClaudeMemRuntimesImpl = inspectClaudeMemRuntimes,
|
|
309
|
-
inspectRuntimeEnvironmentImpl = inspectRuntimeEnvironment,
|
|
310
|
-
readProjectStateImpl = readProjectState,
|
|
311
|
-
readProjectMcpConfigImpl = readProjectMcpConfig,
|
|
312
|
-
readProjectTeamConfigImpl = readProjectTeamConfig,
|
|
313
|
-
readProjectClaudeSettingsImpl = readProjectClaudeSettings,
|
|
314
|
-
readBundledPluginRuntimeDefaultsImpl = readBundledPluginRuntimeDefaults,
|
|
315
|
-
readConfigImpl = readConfig,
|
|
316
|
-
} = {}
|
|
317
|
-
) {
|
|
318
|
-
const claudeVersionValue = claudeVersionImpl();
|
|
319
|
-
const plugins = claudeVersionValue ? listPluginsImpl() : [];
|
|
320
|
-
const marketplaces = claudeVersionValue ? listPluginMarketplacesImpl() : [];
|
|
321
|
-
const mcps = claudeVersionValue ? listMcpsImpl() : [];
|
|
322
|
-
const userMcpConfig = claudeVersionValue ? readUserMcpConfigImpl() : new Map();
|
|
323
|
-
const runtimeStatus = plugins.some(
|
|
324
|
-
(plugin) => plugin.name === "claude-mem" && plugin.status === "enabled"
|
|
325
|
-
)
|
|
326
|
-
? inspectClaudeMemRuntimesImpl()
|
|
327
|
-
: null;
|
|
328
|
-
|
|
329
|
-
return {
|
|
330
|
-
claudeVersionValue,
|
|
331
|
-
nodeVersion: process.version,
|
|
332
|
-
plugins,
|
|
333
|
-
marketplaces,
|
|
334
|
-
mcps,
|
|
335
|
-
userMcpConfig,
|
|
336
|
-
runtimeStatus,
|
|
337
|
-
runtimeEnvironment: inspectRuntimeEnvironmentImpl(),
|
|
338
|
-
cwd,
|
|
339
|
-
projectState: await readProjectStateImpl(cwd),
|
|
340
|
-
projectMcpConfig: await readProjectMcpConfigImpl(cwd),
|
|
341
|
-
projectTeamConfig: await readProjectTeamConfigImpl(cwd),
|
|
342
|
-
projectClaudeSettings: await readProjectClaudeSettingsImpl(cwd),
|
|
343
|
-
bundledPluginRuntimeDefaults: await readBundledPluginRuntimeDefaultsImpl(),
|
|
344
|
-
legacyInstallState: {
|
|
345
|
-
configPath: CONFIG_FILE,
|
|
346
|
-
hasLegacyContext7ApiKey: Object.prototype.hasOwnProperty.call(readConfigImpl(), "context7ApiKey"),
|
|
347
|
-
},
|
|
348
|
-
};
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
export async function applyDoctorFixes(
|
|
352
|
-
doctorData,
|
|
353
|
-
{
|
|
354
|
-
ensureClaudeMemRuntimesImpl = ensureClaudeMemRuntimes,
|
|
355
|
-
removeMcpImpl = removeMcp,
|
|
356
|
-
readConfigImpl = readConfig,
|
|
357
|
-
writeConfigImpl = writeConfig,
|
|
358
|
-
} = {}
|
|
359
|
-
) {
|
|
360
|
-
const fixes = [];
|
|
361
|
-
const context7PluginOwnsMcp = doctorData.mcps.some(
|
|
362
|
-
(entry) => entry.name === "context7" && entry.plugin === "context7-plugin"
|
|
363
|
-
);
|
|
364
|
-
const hasLegacyUserContext7Mcp =
|
|
365
|
-
doctorData.userMcpConfig instanceof Map && doctorData.userMcpConfig.has("context7");
|
|
366
|
-
|
|
367
|
-
if (context7PluginOwnsMcp && hasLegacyUserContext7Mcp) {
|
|
368
|
-
const result = await removeMcpImpl({ name: "context7" });
|
|
369
|
-
if (result.code === 0) {
|
|
370
|
-
doctorData.userMcpConfig.delete("context7");
|
|
371
|
-
doctorData.mcps = doctorData.mcps.filter(
|
|
372
|
-
(entry) => !(entry.name === "context7" && entry.plugin == null)
|
|
373
|
-
);
|
|
374
|
-
fixes.push({
|
|
375
|
-
kind: "legacy-context7-user-mcp-removed",
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
if (doctorData.legacyInstallState?.hasLegacyContext7ApiKey) {
|
|
381
|
-
const config = readConfigImpl();
|
|
382
|
-
if (Object.prototype.hasOwnProperty.call(config, "context7ApiKey")) {
|
|
383
|
-
delete config.context7ApiKey;
|
|
384
|
-
writeConfigImpl(config);
|
|
385
|
-
doctorData.legacyInstallState = {
|
|
386
|
-
...doctorData.legacyInstallState,
|
|
387
|
-
hasLegacyContext7ApiKey: false,
|
|
388
|
-
};
|
|
389
|
-
fixes.push({
|
|
390
|
-
kind: "legacy-context7-api-key-removed",
|
|
391
|
-
configPath: CONFIG_FILE,
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
const claudeMemEnabled = doctorData.plugins.some(
|
|
397
|
-
(plugin) => plugin.name === "claude-mem" && plugin.status === "enabled"
|
|
398
|
-
);
|
|
399
|
-
|
|
400
|
-
if (!claudeMemEnabled) {
|
|
401
|
-
return fixes;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
const runtimeStatus = ensureClaudeMemRuntimesImpl();
|
|
405
|
-
doctorData.runtimeStatus = runtimeStatus;
|
|
406
|
-
fixes.push({
|
|
407
|
-
kind: "claude-mem-runtimes",
|
|
408
|
-
runtimeStatus,
|
|
409
|
-
});
|
|
410
|
-
return fixes;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
export function renderReportLines(lines, { logImpl = log } = {}) {
|
|
414
|
-
for (const line of lines) {
|
|
415
|
-
if (line.level === "ok") {
|
|
416
|
-
logImpl.ok(line.text);
|
|
417
|
-
} else if (line.level === "warn") {
|
|
418
|
-
logImpl.warn(line.text);
|
|
419
|
-
} else if (line.level === "err") {
|
|
420
|
-
logImpl.err(line.text);
|
|
421
|
-
} else {
|
|
422
|
-
logImpl.info(line.text);
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
for (const detail of line.details || []) {
|
|
426
|
-
console.log(color.dim(` → ${detail}`));
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
export function buildDoctorJsonPayload({
|
|
432
|
-
context = {},
|
|
433
|
-
doctorData,
|
|
434
|
-
fixes = [],
|
|
435
|
-
report,
|
|
436
|
-
} = {}) {
|
|
437
|
-
return {
|
|
438
|
-
contractVersion: DOCTOR_JSON_CONTRACT_VERSION,
|
|
439
|
-
generatedAt: new Date().toISOString(),
|
|
440
|
-
context: {
|
|
441
|
-
fix: context.fix === true,
|
|
442
|
-
json: context.json === true,
|
|
443
|
-
verbose: context.verbose === true,
|
|
444
|
-
},
|
|
445
|
-
summary: {
|
|
446
|
-
errors: report?.errors || 0,
|
|
447
|
-
warnings: report?.warnings || 0,
|
|
448
|
-
healthy: (report?.errors || 0) === 0,
|
|
449
|
-
},
|
|
450
|
-
metadata: {
|
|
451
|
-
appliedFixes: fixes,
|
|
452
|
-
settingsInspection: DOCTOR_SETTINGS_INSPECTION,
|
|
453
|
-
},
|
|
454
|
-
doctorData,
|
|
455
|
-
report,
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
export function printDoctorSummary(
|
|
460
|
-
report,
|
|
461
|
-
{ logImpl = log, exitImpl = process.exit } = {}
|
|
462
|
-
) {
|
|
463
|
-
console.log();
|
|
464
|
-
if (report.errors > 0) {
|
|
465
|
-
console.log(color.red(`Summary: ${report.errors} error(s), ${report.warnings} warning(s)`));
|
|
466
|
-
console.log(color.dim("Fix errors and re-run curdx-flow doctor"));
|
|
467
|
-
exitImpl(1);
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
if (report.warnings > 0) {
|
|
472
|
-
console.log(color.yellow(`Summary: ${report.warnings} warning(s). Usable, but worth addressing.`));
|
|
473
|
-
return;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
console.log(color.green("Summary: all healthy ✓"));
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
export function printVerboseDoctorDetails({ runSyncImpl = runSync } = {}) {
|
|
480
|
-
console.log(`\n${color.bold("Details:")}`);
|
|
481
|
-
console.log(color.dim(" Plugins raw:"));
|
|
482
|
-
console.log(runSyncImpl("claude", ["plugin", "list"]).stdout);
|
|
483
|
-
}
|