@curdx/flow 7.1.3 → 7.1.5

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,24 @@
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.5 — 2026-05-06
6
+
7
+ ### Fixed
8
+
9
+ - **Prevent local source checkouts from silently running a stale CLI bundle.** A repo-local execution path could still write the old `~/.claude/CLAUDE.md` block even after `7.1.4` was published correctly, because `.gitignore` excludes `dist/` and `npx @curdx/flow` run from the source checkout may execute the checkout's local `bin` (`dist/index.mjs`) instead of the freshly published npm tarball. If `src/` had newer changes but `dist/` had not been rebuilt, the user would unknowingly run old installer logic and keep re-injecting the pre-7.1.4 managed block. Added `src/runner/buildFreshness.ts` and wired it into `src/index.ts` startup so source checkouts now fail fast with a clear error when `src/**/*.ts` is newer than `dist/index.mjs`, instructing the user to run `npm run build` or execute the published package explicitly. New tests in `tests/runner/buildFreshness.test.ts` cover both stale and fresh build states.
10
+
11
+ ## 7.1.4 — 2026-05-06
12
+
13
+ ### Changed
14
+
15
+ - **`~/.claude/CLAUDE.md` managed block is now language-aware.** `src/runner/claudeMd.ts` no longer emits a single Chinese-only block regardless of installer language. The managed `<!-- BEGIN @curdx/flow v1 -->` block now renders in English when the installer runs with `--lang en` (or `CURDX_FLOW_LANG=en`) and in Chinese when it runs with `zh`. This keeps the generated guidance aligned with the installer's selected language instead of mixing an English install flow with Chinese managed instructions.
16
+ - **Chinese installs inject an explicit language policy into the managed block.** When the current installer language is `zh`, flow now prepends a `Language Policy` section that instructs Claude to keep tool/model interaction in English while replying to the user in Simplified Chinese. English installs do not inject this section. This behavior is covered by new tests in `tests/runner/claudeMd.test.ts`.
17
+ - **Managed-block guidance now uses clearer Claude Code terminology.** The block now distinguishes slash commands from MCP capabilities and plugin skills instead of mixing them together. `claude-mem` calls are rendered as `/claude-mem:...`, Context7 / sequential-thinking / Chrome DevTools are described as MCP capabilities, and the overly-strong `frontend-design` "auto fire" wording was softened to "prioritize, then invoke explicitly if needed". The global decision tree also stopped leaking `TaskCreate` as a user-facing universal rule, matching current Claude Code best-practice guidance more closely.
18
+
19
+ ### Added
20
+
21
+ - **README alignment for managed `CLAUDE.md` behavior.** `README.md` and `README.zh-CN.md` now document that language selection affects not only the installer UI, but also the rendering of the managed `CLAUDE.md` block, including the extra Chinese-mode language policy injection.
22
+
5
23
  ## 7.1.3 — 2026-05-05
6
24
 
7
25
  ### Added
package/README.md CHANGED
@@ -133,6 +133,8 @@ npx @curdx/flow
133
133
 
134
134
  On first run, you pick a language (中文 / English), then select what to install. The bundled `curdx-flow` plugin (the spec workflow itself) is always installed. Everything else is opt-in.
135
135
 
136
+ That language choice also controls the managed `~/.claude/CLAUDE.md` block that flow writes. The block is rendered in the selected language, and when you choose `zh` it additionally injects a language policy telling Claude to keep tool/model interaction in English while replying to the user in Simplified Chinese.
137
+
136
138
  ### Common workflows
137
139
 
138
140
  ```bash
@@ -337,7 +339,7 @@ Run `npx @curdx/flow status` to see what is installed on your machine.
337
339
  | --- | --- |
338
340
  | `--all` | Apply to every available item (with `install` or `update`). |
339
341
  | `--yes` | Skip all confirmations (non-interactive). |
340
- | `--lang en` / `--lang zh` | Override the language for the current invocation. |
342
+ | `--lang en` / `--lang zh` | Override the language for the current invocation, including the language used when rendering the managed `CLAUDE.md` block. |
341
343
  | `--no-claude-md` | Do not write the managed block to `~/.claude/CLAUDE.md`. |
342
344
  | `--json` | Output machine-readable JSON (with `status`). |
343
345
  | `--quick` | (Plugin) Run all phases of a spec sequentially without pausing for approval. Use with caution. |
@@ -350,7 +352,7 @@ Run `npx @curdx/flow status` to see what is installed on your machine.
350
352
  | Variable | Effect |
351
353
  | --- | --- |
352
354
  | `CURDX_FLOW_NO_CLAUDE_MD=1` | Equivalent to `--no-claude-md`. |
353
- | `CURDX_FLOW_LANG=en` / `=zh` | Default language for the installer. |
355
+ | `CURDX_FLOW_LANG=en` / `=zh` | Default language for the installer and for managed `CLAUDE.md` block rendering. |
354
356
  | `CONTEXT7_API_KEY` | Optional API key for the context7 MCP server. |
355
357
 
356
358
  ### Per-project overrides
@@ -394,7 +396,7 @@ Inside any project that uses flow:
394
396
 
395
397
  The four canonical artifacts (`research.md` … `tasks.md`) are committed with your code. State and progress files are gitignored.
396
398
 
397
- The `<!-- BEGIN @curdx/flow v1 -->` block in `~/.claude/CLAUDE.md` tells Claude what is installed and how to invoke it. flow only ever rewrites this block — content outside the markers is preserved verbatim. Pass `--no-claude-md` (or set `CURDX_FLOW_NO_CLAUDE_MD=1`) to opt out of the managed block.
399
+ The `<!-- BEGIN @curdx/flow v1 -->` block in `~/.claude/CLAUDE.md` tells Claude what is installed and how to invoke it. flow only ever rewrites this block — content outside the markers is preserved verbatim. The block is language-aware: `--lang en` renders the guidance in English, `--lang zh` renders it in Chinese, and the `zh` variant also injects a language policy requiring English for tool/model interaction and Simplified Chinese for user-facing replies. Pass `--no-claude-md` (or set `CURDX_FLOW_NO_CLAUDE_MD=1`) to opt out of the managed block.
398
400
 
399
401
  ---
400
402
 
package/README.zh-CN.md CHANGED
@@ -133,6 +133,8 @@ npx @curdx/flow
133
133
 
134
134
  首次运行会让你选择语言(中文 / English),随后选择安装项。内置的 `curdx-flow` 插件(即工作流本身)始终安装;其余工具均可按需选择。
135
135
 
136
+ 这个语言选择同时决定 flow 写入 `~/.claude/CLAUDE.md` 的受管理块内容。受管理块会按所选语言渲染;若选择 `zh`,还会额外注入语言策略,要求 Claude 在工具 / 模型交互时使用英文、面向用户输出时使用简体中文。
137
+
136
138
  ### 常用操作
137
139
 
138
140
  ```bash
@@ -337,7 +339,7 @@ flow 内置九个专项子 agent,各自承担明确的单一职责。它们由
337
339
  | --- | --- |
338
340
  | `--all` | 应用于全部可用项(与 `install` 或 `update` 配合)。 |
339
341
  | `--yes` | 跳过所有确认提示(非交互模式)。 |
340
- | `--lang en` / `--lang zh` | 单次调用覆盖语言设置。 |
342
+ | `--lang en` / `--lang zh` | 单次调用覆盖语言设置,同时影响受管理 `CLAUDE.md` 区块的渲染语言。 |
341
343
  | `--no-claude-md` | 不向 `~/.claude/CLAUDE.md` 写入受管理块。 |
342
344
  | `--json` | 机器可读 JSON 输出(与 `status` 配合)。 |
343
345
  | `--quick` | (插件)连续运行 spec 的全部阶段,不在阶段间暂停等待确认。请谨慎使用。 |
@@ -350,7 +352,7 @@ flow 内置九个专项子 agent,各自承担明确的单一职责。它们由
350
352
  | 变量 | 作用 |
351
353
  | --- | --- |
352
354
  | `CURDX_FLOW_NO_CLAUDE_MD=1` | 等价于 `--no-claude-md`。 |
353
- | `CURDX_FLOW_LANG=en` / `=zh` | 安装器默认语言。 |
355
+ | `CURDX_FLOW_LANG=en` / `=zh` | 安装器默认语言,同时作为受管理 `CLAUDE.md` 区块的默认渲染语言。 |
354
356
  | `CONTEXT7_API_KEY` | context7 MCP 服务器的可选 API 密钥。 |
355
357
 
356
358
  ### 项目级覆盖
@@ -394,7 +396,7 @@ flow 内置九个专项子 agent,各自承担明确的单一职责。它们由
394
396
 
395
397
  四份标准产出物(`research.md` … `tasks.md`)随代码一同提交至仓库。状态与进度文件已加入 gitignore。
396
398
 
397
- `~/.claude/CLAUDE.md` 中的 `<!-- BEGIN @curdx/flow v1 -->` 块告知 Claude 当前已安装的内容及调用方式。flow 仅会重写该块,标记之外的内容**原样保留**。如需关闭受管理块,传入 `--no-claude-md`,或设置环境变量 `CURDX_FLOW_NO_CLAUDE_MD=1`。
399
+ `~/.claude/CLAUDE.md` 中的 `<!-- BEGIN @curdx/flow v1 -->` 块告知 Claude 当前已安装的内容及调用方式。flow 仅会重写该块,标记之外的内容**原样保留**。该区块会根据语言设置渲染:`--lang en` 输出英文提示,`--lang zh` 输出中文提示;其中 `zh` 版本还会额外注入语言策略,要求工具 / 模型交互使用英文、面向用户回复使用简体中文。如需关闭受管理块,传入 `--no-claude-md`,或设置环境变量 `CURDX_FLOW_NO_CLAUDE_MD=1`。
398
400
 
399
401
  ---
400
402
 
package/dist/index.mjs CHANGED
@@ -195,6 +195,9 @@ var currentLang = "zh";
195
195
  function setLang(lang) {
196
196
  currentLang = lang;
197
197
  }
198
+ function getLang() {
199
+ return currentLang;
200
+ }
198
201
  function t(key, vars) {
199
202
  const raw = tables[currentLang][key] ?? tables.en[key] ?? key;
200
203
  if (!vars) return raw;
@@ -848,44 +851,111 @@ var BLOCK_RE = /<!-- BEGIN @curdx\/flow v\d+[^>]*-->[\s\S]*?<!-- END @curdx\/flo
848
851
  function claudeMdPath() {
849
852
  return path4.join(os2.homedir(), ".claude", "CLAUDE.md");
850
853
  }
854
+ function isZh() {
855
+ return getLang() === "zh";
856
+ }
851
857
  function buildCombinationPatterns(ids) {
852
858
  const has = (k) => ids.has(k);
853
- const out = ["\u6309\u573A\u666F\u4E32\u8054\uFF0C\u4E0D\u8981\u4E00\u4E2A\u4E2A\u5B64\u7ACB\u8C03\u7528\uFF1A", ""];
859
+ const out = [
860
+ isZh() ? "\u6309\u573A\u666F\u4E32\u8054\uFF0C\u4F18\u5148\u6309\u80FD\u529B\u8FB9\u754C\u8C03\u7528\uFF1Aslash command\u3001MCP\u3001\u63D2\u4EF6 skill \u4E0D\u8981\u6DF7\u5199\u3002" : "Combine tools by capability. Keep slash commands, MCP tools, and plugin skills distinct.",
861
+ ""
862
+ ];
854
863
  if (has("claude-mem") || has("context7") || has("curdx-flow")) {
855
- out.push("- **\u63A5\u5230\u65B0\u9700\u6C42 / \u65B0 feature \u8D77\u624B\u5F0F**");
864
+ out.push(isZh() ? "- **\u63A5\u5230\u65B0\u9700\u6C42 / \u65B0 feature**" : "- **Starting a new feature**");
856
865
  let step = 1;
857
- if (has("claude-mem")) out.push(` ${step++}. \`claude-mem:mem-search\` \u67E5\u5386\u53F2\u2014\u2014"\u4EE5\u524D\u6709\u6CA1\u6709\u7C7B\u4F3C\u7684\u6D3B/\u5751"`);
858
- if (has("context7")) out.push(` ${step++}. \u6D89\u53CA\u5916\u90E8\u5E93 \u2192 \`context7\` \u62C9\u6700\u65B0\u6587\u6863`);
866
+ if (has("claude-mem")) {
867
+ out.push(
868
+ isZh() ? ` ${step++}. \u5148\u7528 \`/claude-mem:mem-search\` \u67E5\u5386\u53F2\uFF0C\u786E\u8BA4\u4E4B\u524D\u6709\u6CA1\u6709\u7C7B\u4F3C\u5B9E\u73B0\u3001\u8E29\u5751\u6216\u51B3\u7B56\u3002` : ` ${step++}. Start with \`/claude-mem:mem-search\` to check whether this work, decision, or pitfall already exists in memory.`
869
+ );
870
+ }
871
+ if (has("context7")) {
872
+ out.push(
873
+ isZh() ? ` ${step++}. \u6D89\u53CA\u5916\u90E8\u5E93\u3001SDK\u3001\u6846\u67B6\u6216 API \u65F6\uFF0C\u4F7F\u7528 Context7 MCP \u62C9\u5B98\u65B9\u6700\u65B0\u6587\u6863\u3002` : ` ${step++}. If external libraries, SDKs, frameworks, or APIs are involved, use the Context7 MCP to pull current official docs.`
874
+ );
875
+ }
859
876
  const planners = [];
860
- if (has("claude-mem")) planners.push("`claude-mem:make-plan` \u51FA phased plan");
861
- if (has("curdx-flow")) planners.push("`/curdx-flow:new` \u8D77 spec");
862
- if (planners.length > 0) out.push(` ${step++}. \u590D\u6742\u591A\u6B65 \u2192 ${planners.join("\uFF0C\u6216 ")}`);
863
- out.push(` ${step++}. \u7B80\u5355\u4E00\u6B21\u6027\u6539\u52A8 \u2192 \u76F4\u63A5\u52A8\u624B\uFF0C\u8DF3\u8FC7\u4E0A\u9762\u51E0\u6B65`);
877
+ if (has("claude-mem")) {
878
+ planners.push(
879
+ isZh() ? "`/claude-mem:make-plan` \u4EA7\u51FA\u5206\u9636\u6BB5\u8BA1\u5212" : "`/claude-mem:make-plan` for a phased plan"
880
+ );
881
+ }
882
+ if (has("curdx-flow")) {
883
+ planners.push(
884
+ isZh() ? "`/curdx-flow:new` \u6216\u76F8\u5173 spec flow \u8D77\u5B8C\u6574\u89C4\u683C" : "`/curdx-flow:new` or the spec flow for a full specification"
885
+ );
886
+ }
887
+ if (planners.length > 0) {
888
+ out.push(
889
+ isZh() ? ` ${step++}. \u591A\u6B65\u9AA4\u3001\u9AD8\u4E0D\u786E\u5B9A\u6027\u3001\u8DE8\u6A21\u5757\u65F6\uFF0C\u518D\u8FDB\u5165 ${planners.join("\uFF0C\u6216 ")}\u3002` : ` ${step++}. Only move into ${planners.join(" or ")} when the work is multi-step, cross-cutting, or uncertain.`
890
+ );
891
+ }
892
+ out.push(
893
+ isZh() ? ` ${step++}. \u7B80\u5355\u3001\u8303\u56F4\u660E\u786E\u7684\u4E00\u6B21\u6027\u6539\u52A8\uFF0C\u76F4\u63A5\u505A\uFF0C\u4E0D\u8981\u5148\u628A\u6D41\u7A0B\u62C9\u6EE1\u3002` : ` ${step++}. For small, clear one-shot changes, implement directly instead of forcing the full workflow.`
894
+ );
864
895
  out.push("");
865
896
  }
866
897
  const stuckLines = [];
867
898
  let s = 1;
868
- if (has("claude-mem")) stuckLines.push(` ${s++}. \u5148\u770B \`claude-mem:mem-search\`\u2014\u2014\u4EE5\u524D\u662F\u5426\u89E3\u8FC7\u540C\u6837\u7684 bug`);
869
- if (has("chrome-devtools-mcp")) stuckLines.push(` ${s++}. \u6D4F\u89C8\u5668\u4FA7 bug \u2192 \`chrome-devtools-mcp\`\uFF08network / console / perf trace\uFF09`);
870
- if (has("context7")) stuckLines.push(` ${s++}. \u5E93 / API \u884C\u4E3A\u4E0D\u7B26\u9884\u671F \u2192 \`context7\` \u67E5\u5B98\u65B9 doc\uFF0C\u4E0D\u8981\u51ED\u8BB0\u5FC6`);
899
+ if (has("claude-mem")) {
900
+ stuckLines.push(
901
+ isZh() ? ` ${s++}. \u5148\u7528 \`/claude-mem:mem-search\` \u67E5\u8BB0\u5FC6\uFF0C\u786E\u8BA4\u662F\u4E0D\u662F\u4EE5\u524D\u89E3\u8FC7\u540C\u7C7B bug\u3002` : ` ${s++}. Check \`/claude-mem:mem-search\` first to see whether the same bug was solved before.`
902
+ );
903
+ }
904
+ if (has("chrome-devtools-mcp")) {
905
+ stuckLines.push(
906
+ isZh() ? ` ${s++}. \u6D4F\u89C8\u5668\u4FA7\u95EE\u9898\u4F7F\u7528 Chrome DevTools MCP\uFF1A\u770B network\u3001console\u3001performance\u3001DOM snapshot\u3002` : ` ${s++}. For browser-side issues, use the Chrome DevTools MCP for network, console, performance, and DOM snapshots.`
907
+ );
908
+ }
909
+ if (has("context7")) {
910
+ stuckLines.push(
911
+ isZh() ? ` ${s++}. \u5982\u679C\u6000\u7591\u662F\u5E93 / API \u884C\u4E3A\u53D8\u5316\uFF0C\u4F7F\u7528 Context7 MCP \u67E5\u5B98\u65B9\u6587\u6863\uFF0C\u4E0D\u8981\u51ED\u8BB0\u5FC6\u3002` : ` ${s++}. If the issue may come from library or API behavior, use the Context7 MCP instead of relying on memory.`
912
+ );
913
+ }
871
914
  const stillStuck = [];
872
- if (has("sequential-thinking")) stillStuck.push("`sequential-thinking` \u62C6\u5047\u8BBE");
873
- if (has("pua")) stillStuck.push("`/pua:pua-loop` \u81EA\u52A8\u8FED\u4EE3");
874
- if (stillStuck.length > 0) stuckLines.push(` ${s++}. \u8FD8\u5361 \u2192 ${stillStuck.join("\uFF0C\u6216 ")}`);
915
+ if (has("sequential-thinking")) {
916
+ stillStuck.push(
917
+ isZh() ? "\u4F7F\u7528 sequential-thinking MCP \u62C6\u5047\u8BBE" : "use the sequential-thinking MCP to break down hypotheses"
918
+ );
919
+ }
920
+ if (has("pua")) {
921
+ stillStuck.push(
922
+ isZh() ? "\u518D\u8FDB\u5165 `/pua:pua-loop` \u8FED\u4EE3" : "then enter `/pua:pua-loop` for structured retries"
923
+ );
924
+ }
925
+ if (stillStuck.length > 0) {
926
+ stuckLines.push(
927
+ isZh() ? ` ${s++}. \u4E24\u8F6E\u4EE5\u4E0A\u4ECD\u5361\u4F4F\uFF0C\u518D ${stillStuck.join("\uFF0C\u6216 ")}\u3002` : ` ${s++}. If you are still stuck after multiple attempts, ${stillStuck.join(" or ")}.`
928
+ );
929
+ }
875
930
  if (stuckLines.length > 0) {
876
- out.push("- **\u9047\u5230 bug / \u5361\u4F4F 2 \u6B21\u4EE5\u4E0A**", ...stuckLines, "");
931
+ out.push(isZh() ? "- **\u9047\u5230 bug / \u8FDE\u7EED\u5361\u4F4F**" : "- **Debugging and repeated failures**", ...stuckLines, "");
877
932
  }
878
933
  if (has("frontend-design") || has("chrome-devtools-mcp")) {
879
- out.push("- **\u505A UI / \u524D\u7AEF\u9875\u9762**");
880
- if (has("frontend-design")) out.push(" - `frontend-design` \u81EA\u52A8 fire\uFF0C\u65E0\u9700\u624B\u52A8\u8C03");
881
- if (has("chrome-devtools-mcp")) out.push(" - \u6E32\u67D3\u5F02\u5E38\u6216\u4EA4\u4E92 bug \u2192 `chrome-devtools-mcp` \u9A8C\u8BC1\uFF0C\u4E0D\u9760\u8089\u773C");
934
+ out.push(isZh() ? "- **\u505A UI / \u524D\u7AEF\u9875\u9762**" : "- **UI and frontend work**");
935
+ if (has("frontend-design")) {
936
+ out.push(
937
+ isZh() ? " - \u4F18\u5148\u4F7F\u7528 `frontend-design` \u63D2\u4EF6\u7684 UI skills\uFF1B\u82E5\u672A\u81EA\u52A8\u89E6\u53D1\uFF0C\u518D\u663E\u5F0F\u8C03\u7528\u5BF9\u5E94 skill\u3002" : " - Prioritize the `frontend-design` plugin skills for UI work; if they do not trigger automatically, invoke the relevant skill explicitly."
938
+ );
939
+ }
940
+ if (has("chrome-devtools-mcp")) {
941
+ out.push(
942
+ isZh() ? " - \u6E32\u67D3\u5F02\u5E38\u3001\u4EA4\u4E92\u95EE\u9898\u6216\u89C6\u89C9\u56DE\u5F52\uFF0C\u4F7F\u7528 Chrome DevTools MCP \u9A8C\u8BC1\uFF0C\u4E0D\u8981\u53EA\u9760\u8089\u773C\u731C\u3002" : " - For rendering issues, interaction bugs, or visual regressions, verify with the Chrome DevTools MCP instead of relying on visual guesswork alone."
943
+ );
944
+ }
882
945
  out.push("");
883
946
  }
884
947
  if (has("pua") || has("curdx-flow")) {
885
- out.push("- **\u5927\u578B / \u8DE8\u6A21\u5757 / \u591A agent \u534F\u4F5C**");
886
- if (has("pua")) out.push(" - `/pua:p9` \u62C6 task prompt + \u7BA1 P8 \u56E2\u961F");
887
- if (has("curdx-flow")) out.push(" - `/curdx-flow:triage` \u628A\u5927 feature \u62C6\u6210\u591A\u4E2A spec");
888
- if (has("pua")) out.push(" - \u6218\u7565\u7EA7 \u2192 `/pua:p10`");
948
+ out.push(isZh() ? "- **\u5927\u578B / \u8DE8\u6A21\u5757 / \u591A agent \u534F\u4F5C**" : "- **Large, cross-cutting, or multi-agent work**");
949
+ if (has("pua")) {
950
+ out.push(
951
+ isZh() ? " - \u9700\u8981\u5E76\u884C\u62C6\u89E3\u4E0E\u56E2\u961F\u534F\u4F5C\u65F6\uFF0C\u7528 `/pua:p9`\uFF1B\u66F4\u9AD8\u5C42\u6218\u7565\u89C4\u5212\u518D\u8003\u8651 `/pua:p10`\u3002" : " - Use `/pua:p9` for parallel task decomposition and team coordination; reserve `/pua:p10` for higher-level strategy work."
952
+ );
953
+ }
954
+ if (has("curdx-flow")) {
955
+ out.push(
956
+ isZh() ? " - \u4E00\u4E2A\u5927\u529F\u80FD\u9700\u8981\u62C6\u6210\u591A\u4E2A\u76F8\u4E92\u4F9D\u8D56\u7684\u89C4\u683C\u65F6\uFF0C\u4F7F\u7528 `/curdx-flow:triage`\u3002" : " - Use `/curdx-flow:triage` when one large feature needs to be split into multiple dependent specs."
957
+ );
958
+ }
889
959
  }
890
960
  while (out.length > 0 && out[out.length - 1] === "") out.pop();
891
961
  return out;
@@ -893,48 +963,76 @@ function buildCombinationPatterns(ids) {
893
963
  function buildSkipRules(ids) {
894
964
  const has = (k) => ids.has(k);
895
965
  const out = [];
896
- out.push("- \u4E00\u884C\u6539\u52A8 / typo / \u91CD\u547D\u540D\u53D8\u91CF \u2014\u2014 \u4E0D\u8981 plan\uFF0C\u4E0D\u8981 mem-search\uFF0C\u76F4\u63A5 Edit");
966
+ out.push(
967
+ isZh() ? "- \u4E00\u884C\u6539\u52A8\u3001typo\u3001\u7EAF\u91CD\u547D\u540D\u53D8\u91CF\uFF1A\u4E0D\u8981\u5148 plan\uFF0C\u4E0D\u8981\u5148\u5F00 spec\uFF0C\u76F4\u63A5\u6539\u3002" : "- For one-line changes, typos, or pure renames, skip planning and spec flow. Just make the edit."
968
+ );
897
969
  const skips = [];
898
970
  if (has("pua")) skips.push("`/pua:pua`");
899
971
  if (has("sequential-thinking")) skips.push("`sequential-thinking`");
900
972
  if (skips.length > 0) {
901
- out.push(`- \u5DF2\u77E5\u786E\u5B9A\u7684 fix \u2014\u2014 \u4E0D\u8981 ${skips.join("\u3001")}`);
973
+ out.push(
974
+ isZh() ? `- \u5DF2\u77E5\u786E\u5B9A\u7684 fix\uFF1A\u4E0D\u8981\u5148\u4E0A ${skips.join("\u3001")}\u3002` : `- For a known, deterministic fix, do not reach for ${skips.join(" or ")} first.`
975
+ );
902
976
  }
903
- out.push('- \u7528\u6237\u95EE"\u8FD9\u662F\u4EC0\u4E48\u610F\u601D"\u7C7B\u7684\u89E3\u91CA\u9898 \u2014\u2014 \u4E0D\u8C03\u4EFB\u4F55\u5DE5\u5177\uFF0C\u76F4\u63A5\u7B54');
977
+ out.push(
978
+ isZh() ? "- \u7EAF\u6982\u5FF5\u89E3\u91CA\u9898\u53EF\u4EE5\u76F4\u63A5\u7B54\uFF1B\u5982\u679C\u662F\u5728\u95EE\u5F53\u524D\u4ED3\u5E93\u91CC\u7684\u4EE3\u7801\u542B\u4E49\uFF0C\u5148\u8BFB\u76F8\u5173\u6587\u4EF6\u518D\u89E3\u91CA\u3002" : "- Answer pure conceptual explanation questions directly. If the question is about code in this repository, read the relevant files first."
979
+ );
904
980
  if (has("curdx-flow")) {
905
- out.push("- \u5355\u6587\u4EF6\u5C40\u90E8\u91CD\u6784 \u2014\u2014 \u4E0D\u8D77 spec\uFF0C\u4E0D\u8FDB curdx-flow");
981
+ out.push(
982
+ isZh() ? "- \u5355\u6587\u4EF6\u5C40\u90E8\u91CD\u6784\u6216\u5F88\u5C0F\u8303\u56F4\u7684\u6574\u7406\uFF1A\u901A\u5E38\u4E0D\u8981\u8FDB\u5165 curdx-flow spec \u6D41\u3002" : "- For a single-file refactor or a very local cleanup, usually do not enter the curdx-flow spec workflow."
983
+ );
906
984
  }
907
985
  return out;
908
986
  }
909
987
  function buildDecisionTree(ids) {
910
988
  const has = (k) => ids.has(k);
911
989
  const out = [];
912
- out.push("1. \u80FD 1\u20132 \u6B65\u641E\u5B9A\uFF1F\u2192 \u76F4\u63A5\u505A");
913
- out.push("2. \u591A\u6B65\u9AA4\u4F46\u8DEF\u5F84\u6E05\u6670\uFF1F\u2192 TaskCreate \u62C6\u4EFB\u52A1\uFF0C\u4E0D\u8FDB spec");
990
+ out.push(isZh() ? "1. \u80FD 1\u20132 \u6B65\u641E\u5B9A\uFF1F\u2192 \u76F4\u63A5\u505A\u3002" : "1. Can it be finished in 1-2 steps? -> Do it directly.");
991
+ out.push(
992
+ isZh() ? "2. \u591A\u6B65\u9AA4\u4F46\u8DEF\u5F84\u6E05\u6670\uFF1F\u2192 \u62C6\u6210\u7B80\u77ED\u4EFB\u52A1\u5217\u8868\u9010\u4E2A\u63A8\u8FDB\uFF0C\u4E0D\u8981\u9ED8\u8BA4\u8FDB\u5165\u5B8C\u6574 spec \u6D41\u3002" : "2. Is it multi-step but still clear? -> Break it into a short task list and execute without defaulting to the full spec flow."
993
+ );
914
994
  const planners = [];
915
995
  if (has("curdx-flow")) planners.push("`/curdx-flow:new`");
916
- if (has("claude-mem")) planners.push("`claude-mem:make-plan`");
996
+ if (has("claude-mem")) planners.push("`/claude-mem:make-plan`");
917
997
  if (planners.length > 0) {
918
- out.push(`3. \u9700\u6C42\u6A21\u7CCA / \u8DE8\u6A21\u5757 / \u8981\u5206\u9636\u6BB5\u4EA4\u4ED8\uFF1F\u2192 ${planners.join(" \u6216 ")}`);
998
+ out.push(
999
+ isZh() ? `3. \u9700\u6C42\u6A21\u7CCA\u3001\u8DE8\u6A21\u5757\u3001\u9700\u8981\u5206\u9636\u6BB5\u4EA4\u4ED8\uFF1F\u2192 ${planners.join(" \u6216 ")}\u3002` : `3. Is the request ambiguous, cross-cutting, or phase-based? -> ${planners.join(" or ")}.`
1000
+ );
919
1001
  }
920
1002
  if (has("claude-mem")) {
921
- out.push("4. \u540C\u6837\u7684\u6D3B\u4EE5\u524D\u53EF\u80FD\u5E72\u8FC7\uFF1F\u2192 \u5148 `claude-mem:mem-search`");
1003
+ out.push(
1004
+ isZh() ? "4. \u8FD9\u7C7B\u6D3B\u4EE5\u524D\u53EF\u80FD\u505A\u8FC7\uFF1F\u2192 \u5148 `/claude-mem:mem-search`\u3002" : "4. Might this work have been done before? -> Start with `/claude-mem:mem-search`."
1005
+ );
922
1006
  }
923
1007
  return out;
924
1008
  }
1009
+ function buildLanguagePolicy() {
1010
+ if (getLang() !== "zh") return [];
1011
+ return [
1012
+ "- Tool and model interaction must be in English.",
1013
+ "- All user-facing responses must be in Simplified Chinese."
1014
+ ];
1015
+ }
925
1016
  function renderBlock(items) {
926
1017
  const installedIds = new Set(items.map((i) => i.id));
927
1018
  const sections = [
928
- ["## Tool Combination Patterns\uFF08\u7EC4\u5408\u5DE5\u4F5C\u6D41\uFF09", buildCombinationPatterns(installedIds)],
929
- ["## Skip Rules\uFF08\u9632\u8FC7\u5EA6\u5DE5\u5177\u5316\uFF09", buildSkipRules(installedIds)],
930
- ["## Decision Tree\uFF08\u9047\u5230\u6A21\u7CCA\u8BF7\u6C42\u65F6\uFF09", buildDecisionTree(installedIds)]
1019
+ ["## Language Policy\uFF08\u8BED\u8A00\u89C4\u5219\uFF09", buildLanguagePolicy()],
1020
+ [
1021
+ isZh() ? "## Tool Combination Patterns\uFF08\u7EC4\u5408\u5DE5\u4F5C\u6D41\uFF09" : "## Tool Combination Patterns",
1022
+ buildCombinationPatterns(installedIds)
1023
+ ],
1024
+ [isZh() ? "## Skip Rules\uFF08\u9632\u8FC7\u5EA6\u5DE5\u5177\u5316\uFF09" : "## Skip Rules", buildSkipRules(installedIds)],
1025
+ [isZh() ? "## Decision Tree\uFF08\u9047\u5230\u6A21\u7CCA\u8BF7\u6C42\u65F6\uFF09" : "## Decision Tree", buildDecisionTree(installedIds)]
931
1026
  ];
932
1027
  const lines = [BEGIN_MARKER];
933
1028
  for (const [heading, body] of sections) {
934
1029
  if (body.length === 0) continue;
935
1030
  lines.push(heading, "", ...body, "");
936
1031
  }
937
- lines.push("Run `npx @curdx/flow` to install / update / uninstall.", END_MARKER);
1032
+ lines.push(
1033
+ isZh() ? "\u8FD0\u884C `npx @curdx/flow` \u4EE5 install / update / uninstall\u3002" : "Run `npx @curdx/flow` to install / update / uninstall.",
1034
+ END_MARKER
1035
+ );
938
1036
  return lines.join("\n");
939
1037
  }
940
1038
  function withEol(s, eol) {
@@ -1613,6 +1711,58 @@ var analyzeCmd = defineCommand({
1613
1711
  });
1614
1712
  var analyze_default = analyzeCmd;
1615
1713
 
1714
+ // src/runner/buildFreshness.ts
1715
+ import { readdirSync, statSync, existsSync as existsSync3 } from "fs";
1716
+ import path5 from "path";
1717
+ import { fileURLToPath } from "url";
1718
+ var SELF_PATH = fileURLToPath(import.meta.url);
1719
+ var RUNNER_DIR = path5.dirname(SELF_PATH);
1720
+ var PROJECT_ROOT = path5.resolve(RUNNER_DIR, "..", "..");
1721
+ var SRC_DIR = path5.join(PROJECT_ROOT, "src");
1722
+ var DIST_ENTRY = path5.join(PROJECT_ROOT, "dist", "index.mjs");
1723
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", ".git"]);
1724
+ function newestTsMtimeMs(dir) {
1725
+ let newest = 0;
1726
+ const entries = readdirSync(dir, { withFileTypes: true });
1727
+ for (const entry of entries) {
1728
+ if (SKIP_DIRS.has(entry.name)) continue;
1729
+ const full = path5.join(dir, entry.name);
1730
+ if (entry.isDirectory()) {
1731
+ newest = Math.max(newest, newestTsMtimeMs(full));
1732
+ continue;
1733
+ }
1734
+ if (!entry.isFile() || !entry.name.endsWith(".ts")) continue;
1735
+ newest = Math.max(newest, statSync(full).mtimeMs);
1736
+ }
1737
+ return newest;
1738
+ }
1739
+ function assertFreshLocalBuild() {
1740
+ assertFreshBuild({ projectRoot: PROJECT_ROOT, srcDir: SRC_DIR, distEntry: DIST_ENTRY });
1741
+ }
1742
+ function assertFreshBuild(opts) {
1743
+ const { projectRoot, srcDir, distEntry } = opts;
1744
+ if (!existsSync3(srcDir) || !existsSync3(distEntry)) return;
1745
+ let distMtimeMs = 0;
1746
+ let newestSrcMtimeMs = 0;
1747
+ try {
1748
+ distMtimeMs = statSync(distEntry).mtimeMs;
1749
+ newestSrcMtimeMs = newestTsMtimeMs(srcDir);
1750
+ } catch {
1751
+ return;
1752
+ }
1753
+ if (newestSrcMtimeMs <= distMtimeMs) return;
1754
+ const relDist = path5.relative(projectRoot, distEntry);
1755
+ const relSrc = path5.relative(projectRoot, srcDir);
1756
+ throw new Error(
1757
+ [
1758
+ "Local build is stale: source files are newer than the bundled CLI entry.",
1759
+ `Detected source checkout at ${projectRoot}.`,
1760
+ `Run \`npm run build\` to refresh ${relDist}, or execute the published package explicitly with \`npx --package @curdx/flow@$(node -p "require('./package.json').version") @curdx/flow\`.`,
1761
+ `This guard only applies when running from a local checkout that still contains ${relSrc}/.`
1762
+ ].join(" ")
1763
+ );
1764
+ }
1765
+
1616
1766
  // src/index.ts
1617
1767
  function parseLang(v) {
1618
1768
  return v === "zh" || v === "en" ? v : void 0;
@@ -1748,6 +1898,7 @@ async function runInteractive(argv2) {
1748
1898
  }
1749
1899
  var argv = process.argv.slice(2);
1750
1900
  var first = firstNonFlag(argv);
1901
+ assertFreshLocalBuild();
1751
1902
  if (first === void 0 || first !== void 0 && !SUBCOMMANDS.has(first) && first !== "--help" && first !== "-h") {
1752
1903
  if (first === void 0) {
1753
1904
  runInteractive(argv).catch((err) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@curdx/flow",
3
- "version": "7.1.3",
3
+ "version": "7.1.5",
4
4
  "description": "Interactive installer for Claude Code plugins and MCP servers",
5
5
  "type": "module",
6
6
  "bin": "./dist/index.mjs",