@curdx/flow 7.1.17 → 7.1.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,39 @@
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
+ ## 7.1.19 — 2026-05-11
6
+
7
+ ### Added
8
+
9
+ - **Third-party capability graph for smart routing.** New `hooks/scripts/lib/tool-capabilities.mjs` maps installed companion capabilities (`claude-mem`, `context7`, `sequential-thinking`, `chrome-devtools-mcp`, `frontend-design`, and `pua`) to concrete AI-facing triggers, phases, skip rules, and invocations.
10
+ - **Capability recommendations in `smart-route`.** Route output now includes `recommendedCapabilities` so `/curdx-flow:start` can choose docs lookup, memory search, UI design, browser verification, structured reasoning, or retry tooling from goal/topology facts without forcing every task through every tool.
11
+
12
+ ### Changed
13
+
14
+ - **Global CLAUDE.md guidance is generated from the capability graph.** The managed block now emits concise installed-tool routing rules and a decision tree instead of hand-maintained duplicated plugin/MCP prose.
15
+ - **Start workflow stores tool hints with spec state.** New specs can persist compact `recommendedCapabilities` next to `projectTopology`, and the state schema/reference docs now declare both fields so later phases can reuse the original routing context without re-deriving it.
16
+
17
+ ### Tests
18
+
19
+ - Added capability-router unit coverage, smart-route capability filtering checks, CLAUDE.md rendering regression coverage, and claudecc smoke assertions for empty direct-change hints and frontend/browser/docs recommendations.
20
+
21
+ ## 7.1.18 — 2026-05-11
22
+
23
+ ### Added
24
+
25
+ - **Dev-context project topology detection.** New `hooks/scripts/lib/project-topology.mjs` reads short `CLAUDE.md` Dev sections, optional `.claude/curdx-flow.local.md` `code_roots`, cheap manifests, and obvious sibling repo names to classify roots such as Vue/React frontends, Spring Boot/Spring Cloud backends, shared libraries, CLIs, infra, and Claude Code plugins.
26
+ - **Topology-aware smart routing.** `/curdx-flow:start` now blocks early when a goal needs a related code root outside current Claude Code access and returns an exact `/add-dir <path>` fix instead of asking users to understand `--add-dir`.
27
+ - **Topology cache guidance for indexing.** `/curdx-flow:index` now generates `specs/.index/project-topology.json` and `specs/.index/context-map.md` before component scanning, and stops with the access fix when related roots are missing.
28
+
29
+ ### Changed
30
+
31
+ - **Settings and start guidance now favor user-friendly Dev context.** The settings template documents the recommended minimal `CLAUDE.md` form (`frontend`, `backend`, `database`) while keeping structured `code_roots` as an optional advanced override.
32
+ - **Sensitive local-service data is omitted from topology output.** Database and credential-looking lines in Dev context produce only a generic warning; real passwords, tokens, and production URLs are not copied into topology JSON or context maps.
33
+
34
+ ### Tests
35
+
36
+ - Added split-repo topology tests for Vue/React frontends, Spring Boot/Spring Cloud backends, missing frontend access, configured `additionalDirectories`, context-map redaction, and the bundled `project-topology` CLI.
37
+
5
38
  ## 7.1.17 — 2026-05-11
6
39
 
7
40
  ### Added
package/dist/index.mjs CHANGED
@@ -845,6 +845,261 @@ import { promises as fs2 } from "fs";
845
845
  import path4 from "path";
846
846
  import os2 from "os";
847
847
  import * as p4 from "@clack/prompts";
848
+
849
+ // src/hooks/lib/tool-capabilities.ts
850
+ import { basename } from "path";
851
+ import { fileURLToPath } from "url";
852
+ var CAPABILITIES = {
853
+ "context7": {
854
+ id: "context7",
855
+ name: "Context7",
856
+ type: "mcp",
857
+ invocation: "Context7 MCP",
858
+ summary: "current official docs for libraries, SDKs, APIs, and Claude Code",
859
+ useWhen: "use the Context7 MCP before implementation when external library, SDK, API, framework, or Claude Code behavior matters.",
860
+ skipWhen: "Skip for pure local logic, typos, and code paths fully understood from this repository."
861
+ },
862
+ "claude-mem": {
863
+ id: "claude-mem",
864
+ name: "claude-mem",
865
+ type: "plugin",
866
+ invocation: "/claude-mem:mem-search",
867
+ summary: "cross-session memory search and phased plan/execution commands",
868
+ useWhen: "Use /claude-mem:mem-search when similar work, prior decisions, or repeated failures may exist; use /claude-mem:make-plan only for genuinely phased work.",
869
+ skipWhen: "Skip when the task is new, obvious, and smaller than a short local edit."
870
+ },
871
+ "sequential-thinking": {
872
+ id: "sequential-thinking",
873
+ name: "sequential-thinking",
874
+ type: "mcp",
875
+ invocation: "sequential-thinking MCP",
876
+ summary: "structured hypothesis breakdown for hard architecture and debugging problems",
877
+ useWhen: "Use for architecture tradeoffs, migrations, security/data/release risk, or debugging where assumptions may change.",
878
+ skipWhen: "Skip for direct edits, simple lookups, and deterministic fixes."
879
+ },
880
+ "chrome-devtools-mcp": {
881
+ id: "chrome-devtools-mcp",
882
+ name: "Chrome DevTools MCP",
883
+ type: "plugin",
884
+ invocation: "Chrome DevTools MCP",
885
+ summary: "real browser console, network, DOM, performance, and screenshot/snapshot verification",
886
+ useWhen: "Use for browser runtime behavior, UI regressions, DOM/CSS issues, network failures, and frontend verification.",
887
+ skipWhen: "Skip for backend-only code with no browser-facing behavior."
888
+ },
889
+ "frontend-design": {
890
+ id: "frontend-design",
891
+ name: "frontend-design",
892
+ type: "plugin",
893
+ invocation: "frontend-design plugin skills",
894
+ summary: "frontend UX/design guidance for UI pages, components, and interaction polish",
895
+ useWhen: "Use when building or changing visible UI, interaction design, frontend layout, or visual quality.",
896
+ skipWhen: "Skip for backend-only changes, copy-only edits, and internal CLI/library work."
897
+ },
898
+ "pua": {
899
+ id: "pua",
900
+ name: "pua",
901
+ type: "plugin",
902
+ invocation: "/pua:pua-loop or /pua:p9",
903
+ summary: "structured retries and parallel task decomposition",
904
+ useWhen: "Use after multiple failed attempts or for truly independent parallel work slices.",
905
+ skipWhen: "Skip on first-attempt failures, known fixes, and work that is sequential by dependency."
906
+ }
907
+ };
908
+ var ORDER = [
909
+ "context7",
910
+ "claude-mem",
911
+ "frontend-design",
912
+ "chrome-devtools-mcp",
913
+ "sequential-thinking",
914
+ "pua"
915
+ ];
916
+ var DOCS_RE = /\b(api|sdk|library|libraries|framework|docs?|documentation|version|upgrade|dependency|dependencies|claude code|plugin|mcp|hook|hooks|skill|skills|agent|agents|react|vue|spring|spring boot|spring cloud|next\.?js|vite|webpack|npm|node)\b|最新|文档|依赖|框架|插件|官方|联网|搜索/i;
917
+ var MEMORY_RE = /\b(previous|before|again|remember|memory|history|similar|repeated|regression|already solved|same bug|past decision)\b|之前|上次|记得|历史|做过|又|重复|老问题/i;
918
+ var UI_RE = /\b(ui|ux|frontend|front-end|browser|chrome|dom|css|html|layout|component|page|form|modal|responsive|visual|render|react|vue|vite|next\.?js|screenshot|interaction)\b|前端|页面|浏览器|样式|交互|组件|布局|视觉|截图/i;
919
+ var BROWSER_VERIFY_RE = /\b(browser|chrome|dom|css|network|console|performance|render|screenshot|e2e|playwright|visual regression|interaction)\b|浏览器|控制台|网络|性能|渲染|截图|端到端/i;
920
+ var COMPLEX_RE = /\b(architecture|architect|migration|migrate|security|auth|authentication|authorization|permission|oauth|payment|billing|database|schema|release|publish|npm|tag|hook|subagent|multi[- ]?repo|monorepo|cross[- ]?system|concurrency|race|cache|rewrite|refactor)\b|架构|迁移|安全|权限|认证|数据库|发布|重写|并发|跨仓库|多仓库/i;
921
+ var STUCK_RE = /\b(stuck|failed|failure|fails|flaky|retry|debug|investigate|root cause|not working|broken|regression)\b|卡住|失败|报错|不行|修不好|定位|排查/i;
922
+ var PARALLEL_RE = /\b(parallel|multi-agent|team|decompose|split|epic|multiple subsystems|large refactor)\b|并行|多智能体|拆分|史诗|多模块/i;
923
+ var LOW_RISK_LOCAL_RE = /\b(typo|readme|docs?|comment|comments|copy|wording|rename label|format text)\b|错别字|注释|文案/i;
924
+ function normalize(input) {
925
+ return (input ?? "").trim().replace(/\s+/g, " ");
926
+ }
927
+ function hasAny(values, candidates) {
928
+ const set = new Set((values ?? []).map((v) => v.toLowerCase()));
929
+ return candidates.some((candidate) => set.has(candidate.toLowerCase()));
930
+ }
931
+ function capabilityAllowed(id, available) {
932
+ return available === null || available.has(id);
933
+ }
934
+ function pushRecommendation(out, available, id, phase, reason, instruction) {
935
+ if (!capabilityAllowed(id, available)) return;
936
+ if (out.some((rec) => rec.id === id)) return;
937
+ const cap = CAPABILITIES[id];
938
+ out.push({
939
+ id,
940
+ name: cap.name,
941
+ type: cap.type,
942
+ invocation: cap.invocation,
943
+ phase,
944
+ reason,
945
+ instruction
946
+ });
947
+ }
948
+ function sortRecommendations(recs) {
949
+ return [...recs].sort((a, b) => ORDER.indexOf(a.id) - ORDER.indexOf(b.id));
950
+ }
951
+ function recommendToolCapabilities(input) {
952
+ const goal = normalize(input.goal);
953
+ const route = normalize(input.route);
954
+ const risk = normalize(input.risk);
955
+ const topologyKinds = input.topologyKinds ?? [];
956
+ const topologyFrameworks = input.topologyFrameworks ?? [];
957
+ const missingRoots = input.missingRoots ?? 0;
958
+ const available = input.availableCapabilities === void 0 ? null : new Set(input.availableCapabilities.filter(Boolean));
959
+ const recs = [];
960
+ if (missingRoots > 0) {
961
+ return recs;
962
+ }
963
+ const localLowRisk = LOW_RISK_LOCAL_RE.test(goal) && route === "direct-change";
964
+ if (localLowRisk) {
965
+ return recs;
966
+ }
967
+ const hasFrontend = UI_RE.test(goal) || hasAny(topologyKinds, ["frontend-app"]) || hasAny(topologyFrameworks, ["react", "vue", "next.js", "vite"]);
968
+ const browserRuntime = BROWSER_VERIFY_RE.test(goal) || hasFrontend;
969
+ const complex = COMPLEX_RE.test(goal) || risk === "high" || risk === "critical" || route === "full-spec" || route === "epic-split";
970
+ const stuck = STUCK_RE.test(goal);
971
+ const parallel = PARALLEL_RE.test(goal) || route === "epic-split";
972
+ if (DOCS_RE.test(goal)) {
973
+ pushRecommendation(
974
+ recs,
975
+ available,
976
+ "context7",
977
+ "before-coding",
978
+ "external documentation or current API behavior is likely relevant",
979
+ "Use Context7 before editing so version-specific behavior is grounded in current docs."
980
+ );
981
+ }
982
+ if (MEMORY_RE.test(goal) || stuck || route === "full-spec" || route === "epic-split") {
983
+ pushRecommendation(
984
+ recs,
985
+ available,
986
+ "claude-mem",
987
+ "planning",
988
+ "similar prior work or longer-running plan may exist",
989
+ "Search memory before planning; use make-plan only when the work is genuinely phased."
990
+ );
991
+ }
992
+ if (hasFrontend) {
993
+ pushRecommendation(
994
+ recs,
995
+ available,
996
+ "frontend-design",
997
+ "implementation",
998
+ "visible frontend behavior or UI quality is in scope",
999
+ "Use frontend-design guidance for UI structure, interaction, responsive behavior, and visual polish."
1000
+ );
1001
+ }
1002
+ if (browserRuntime) {
1003
+ pushRecommendation(
1004
+ recs,
1005
+ available,
1006
+ "chrome-devtools-mcp",
1007
+ "verification",
1008
+ "browser runtime behavior should be verified in a real browser",
1009
+ "Use Chrome DevTools MCP for console, network, DOM, performance, or visual proof after implementation."
1010
+ );
1011
+ }
1012
+ if (complex || stuck) {
1013
+ pushRecommendation(
1014
+ recs,
1015
+ available,
1016
+ "sequential-thinking",
1017
+ "planning",
1018
+ "risk or uncertainty requires explicit hypothesis management",
1019
+ "Use sequential-thinking to break assumptions before choosing the implementation path."
1020
+ );
1021
+ }
1022
+ if (stuck || parallel) {
1023
+ pushRecommendation(
1024
+ recs,
1025
+ available,
1026
+ "pua",
1027
+ stuck ? "recovery" : "planning",
1028
+ stuck ? "the goal indicates repeated failure or debugging difficulty" : "large work may contain independent parallel slices",
1029
+ stuck ? "Use /pua:pua-loop only after local triage confirms the first fix path is not working." : "Use /pua:p9 only after dependencies prove the slices can run independently."
1030
+ );
1031
+ }
1032
+ return sortRecommendations(recs);
1033
+ }
1034
+ function renderInstalledCapabilityRules(availableCapabilities) {
1035
+ const available = new Set(availableCapabilities);
1036
+ const lines = [
1037
+ "Use installed capabilities by trigger, not by habit. Prefer the first matching rule; skip absent capabilities."
1038
+ ];
1039
+ for (const id of ORDER) {
1040
+ if (!available.has(id)) continue;
1041
+ const cap = CAPABILITIES[id];
1042
+ lines.push(`- ${cap.invocation}: ${cap.useWhen} ${cap.skipWhen}`);
1043
+ }
1044
+ return lines;
1045
+ }
1046
+ function renderCapabilityDecisionTree(availableCapabilities) {
1047
+ const available = new Set(availableCapabilities);
1048
+ const lines = [
1049
+ "1. Can the edit be finished safely from local code in 1-2 steps? -> Do it directly."
1050
+ ];
1051
+ if (available.has("context7")) {
1052
+ lines.push("2. Does correctness depend on external docs, SDKs, APIs, or Claude Code behavior? -> use the Context7 MCP before editing.");
1053
+ }
1054
+ if (available.has("claude-mem")) {
1055
+ lines.push("3. Might similar work, a prior decision, or a repeated failure exist? -> Start with `/claude-mem:mem-search`.");
1056
+ }
1057
+ if (available.has("frontend-design") || available.has("chrome-devtools-mcp")) {
1058
+ lines.push("4. Is visible frontend behavior in scope? -> Use frontend-design for UI decisions and Chrome DevTools MCP for runtime proof when installed.");
1059
+ }
1060
+ if (available.has("sequential-thinking")) {
1061
+ lines.push("5. Is the work high-risk, architectural, or assumption-heavy? -> Use sequential-thinking after reading the relevant code.");
1062
+ }
1063
+ if (available.has("pua")) {
1064
+ lines.push("6. Are there multiple failed attempts or truly independent parallel slices? -> Use `/pua:pua-loop` for recovery or `/pua:p9` for bounded parallel planning.");
1065
+ }
1066
+ return lines;
1067
+ }
1068
+ function parseList(value) {
1069
+ if (!value) return [];
1070
+ return value.split(/[,;\n]/).map((s) => s.trim()).filter(Boolean);
1071
+ }
1072
+ function readArg(name, argv2) {
1073
+ const idx = argv2.indexOf(name);
1074
+ if (idx === -1) return void 0;
1075
+ return argv2[idx + 1];
1076
+ }
1077
+ function main() {
1078
+ const argv2 = process.argv.slice(2);
1079
+ const recommendations = recommendToolCapabilities({
1080
+ goal: readArg("--goal", argv2),
1081
+ route: readArg("--route", argv2),
1082
+ risk: readArg("--risk", argv2),
1083
+ topologyKinds: parseList(readArg("--topology-kinds", argv2)),
1084
+ topologyFrameworks: parseList(readArg("--topology-frameworks", argv2)),
1085
+ missingRoots: Number(readArg("--missing-roots", argv2) ?? 0),
1086
+ availableCapabilities: readArg("--available-capabilities", argv2) ? parseList(readArg("--available-capabilities", argv2)) : void 0
1087
+ });
1088
+ process.stdout.write(JSON.stringify(recommendations, null, 2) + "\n");
1089
+ }
1090
+ function isDirectRun() {
1091
+ try {
1092
+ const entry = fileURLToPath(import.meta.url);
1093
+ return process.argv[1] === entry && basename(entry).startsWith("tool-capabilities.");
1094
+ } catch {
1095
+ return false;
1096
+ }
1097
+ }
1098
+ if (isDirectRun()) {
1099
+ main();
1100
+ }
1101
+
1102
+ // src/runner/claudeMd.ts
848
1103
  var BEGIN_MARKER = "<!-- BEGIN @curdx/flow v1 -->";
849
1104
  var END_MARKER = "<!-- END @curdx/flow v1 -->";
850
1105
  var BLOCK_RE = /<!-- BEGIN @curdx\/flow v\d+[^>]*-->[\s\S]*?<!-- END @curdx\/flow v\d+ -->/;
@@ -852,63 +1107,11 @@ function claudeMdPath() {
852
1107
  return path4.join(os2.homedir(), ".claude", "CLAUDE.md");
853
1108
  }
854
1109
  function buildCombinationPatterns(ids) {
855
- const has = (k) => ids.has(k);
856
- const out = [
857
- "Combine tools by capability. Keep slash commands, MCP tools, and plugin skills distinct.",
858
- ""
859
- ];
860
- if (has("context7") || has("curdx-flow") || has("claude-mem")) {
861
- out.push("- **Starting a new feature**");
862
- let step = 1;
863
- if (has("context7")) {
864
- out.push(` ${step++}. If external libraries, SDKs, frameworks, or APIs are involved, use the Context7 MCP to pull current official docs.`);
865
- }
866
- const planners = [];
867
- if (has("claude-mem")) planners.push("`/claude-mem:make-plan` for a phased plan");
868
- if (has("curdx-flow")) planners.push("`/curdx-flow:new` or the spec flow for a full specification");
869
- if (planners.length > 0) {
870
- out.push(` ${step++}. Only move into ${planners.join(" or ")} when the work is multi-step, cross-cutting, or uncertain.`);
871
- }
872
- out.push(` ${step++}. For small, clear one-shot changes, implement directly instead of forcing the full workflow.`);
873
- out.push("");
874
- }
875
- const stuckLines = [];
876
- let s = 1;
877
- if (has("chrome-devtools-mcp")) {
878
- stuckLines.push(` ${s++}. For browser-side issues, use the Chrome DevTools MCP for network, console, performance, and DOM snapshots.`);
879
- }
880
- if (has("context7")) {
881
- stuckLines.push(` ${s++}. If the issue may come from library or API behavior, use the Context7 MCP instead of relying on memory.`);
882
- }
883
- const stillStuck = [];
884
- if (has("sequential-thinking")) stillStuck.push("switch to the sequential-thinking MCP to break down hypotheses");
885
- if (has("pua")) stillStuck.push("enter `/pua:pua-loop` for structured retries");
886
- if (stillStuck.length > 0) {
887
- stuckLines.push(` ${s++}. If you are still stuck after multiple attempts, ${stillStuck.join(" or ")}.`);
888
- }
889
- if (stuckLines.length > 0) {
890
- out.push("- **Debugging and repeated failures**", ...stuckLines, "");
891
- }
892
- if (has("frontend-design") || has("chrome-devtools-mcp")) {
893
- out.push("- **UI and frontend work**");
894
- if (has("frontend-design")) {
895
- out.push(" - Prioritize the `frontend-design` plugin skills for UI work; if they do not trigger automatically, invoke the relevant skill explicitly.");
896
- }
897
- if (has("chrome-devtools-mcp")) {
898
- out.push(" - For rendering issues, interaction bugs, or visual regressions, verify with the Chrome DevTools MCP instead of relying on visual guesswork alone.");
899
- }
900
- out.push("");
1110
+ const out = renderInstalledCapabilityRules([...ids]);
1111
+ if (ids.has("curdx-flow")) {
1112
+ out.push("- /curdx-flow:start: Use for ambiguous, cross-cutting, phase-based, or multi-root work; skip for small direct edits.");
1113
+ out.push("- /curdx-flow:triage: Use when one request is too large for a single coherent spec.");
901
1114
  }
902
- if (has("pua") || has("curdx-flow")) {
903
- out.push("- **Large, cross-cutting, or multi-agent work**");
904
- if (has("pua")) {
905
- out.push(" - Use `/pua:p9` for parallel task decomposition and team coordination; reserve `/pua:p10` for higher-level strategy work.");
906
- }
907
- if (has("curdx-flow")) {
908
- out.push(" - Use `/curdx-flow:triage` when one large feature needs to be split into multiple dependent specs.");
909
- }
910
- }
911
- while (out.length > 0 && out[out.length - 1] === "") out.pop();
912
1115
  return out;
913
1116
  }
914
1117
  function buildSkipRules(ids) {
@@ -928,18 +1131,9 @@ function buildSkipRules(ids) {
928
1131
  return out;
929
1132
  }
930
1133
  function buildDecisionTree(ids) {
931
- const has = (k) => ids.has(k);
932
- const out = [];
933
- out.push("1. Can it be finished in 1-2 steps? -> Do it directly.");
934
- out.push("2. Is it multi-step but still clear? -> Break it into a short task list and execute without defaulting to the full spec flow.");
935
- const planners = [];
936
- if (has("curdx-flow")) planners.push("`/curdx-flow:new`");
937
- if (has("claude-mem")) planners.push("`/claude-mem:make-plan`");
938
- if (planners.length > 0) {
939
- out.push(`3. Is the request ambiguous, cross-cutting, or phase-based? -> ${planners.join(" or ")}.`);
940
- }
941
- if (has("claude-mem")) {
942
- out.push("4. Might this work have been done before? -> Start with `/claude-mem:mem-search`.");
1134
+ const out = renderCapabilityDecisionTree([...ids]);
1135
+ if (ids.has("curdx-flow")) {
1136
+ out.push("7. Is the request ambiguous, cross-cutting, phase-based, or multi-root? -> Run /curdx-flow:start.");
943
1137
  }
944
1138
  return out;
945
1139
  }
@@ -1677,8 +1871,8 @@ var analyze_default = analyzeCmd;
1677
1871
  // src/runner/buildFreshness.ts
1678
1872
  import { readdirSync, statSync, existsSync as existsSync3 } from "fs";
1679
1873
  import path5 from "path";
1680
- import { fileURLToPath } from "url";
1681
- var SELF_PATH = fileURLToPath(import.meta.url);
1874
+ import { fileURLToPath as fileURLToPath2 } from "url";
1875
+ var SELF_PATH = fileURLToPath2(import.meta.url);
1682
1876
  var RUNNER_DIR = path5.dirname(SELF_PATH);
1683
1877
  var PROJECT_ROOT = path5.resolve(RUNNER_DIR, "..", "..");
1684
1878
  var SRC_DIR = path5.join(PROJECT_ROOT, "src");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@curdx/flow",
3
- "version": "7.1.17",
3
+ "version": "7.1.19",
4
4
  "description": "Interactive installer for Claude Code plugins and MCP servers",
5
5
  "type": "module",
6
6
  "bin": "./dist/index.mjs",