@hupan56/wlkj 2.7.12 → 3.0.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.
Files changed (152) hide show
  1. package/bin/cli.js +344 -78
  2. package/package.json +29 -29
  3. package/templates/.qoder/.runtime/ctx-cache-5660152f1d6dd819.md +23 -0
  4. package/templates/.qoder/.runtime/ctx-cache-afdce0dac06b25b0.md +23 -0
  5. package/templates/.qoder/.runtime/search-cache-eae7644e7b122f35.txt +1 -0
  6. package/templates/.qoder/learning/eval-history.jsonl +28 -0
  7. package/templates/data/index/wiki-index.json +8 -0
  8. package/templates/qoder/agents/insight-planning.md +1 -1
  9. package/templates/qoder/agents/insight-research.md +28 -15
  10. package/templates/qoder/commands/optional/wl-insight.md +159 -161
  11. package/templates/qoder/commands/optional/wl-report.md +4 -4
  12. package/templates/qoder/commands/optional/wl-status.md +2 -2
  13. package/templates/qoder/commands/wl-code.md +2 -2
  14. package/templates/qoder/commands/wl-design.md +2 -2
  15. package/templates/qoder/commands/wl-init.md +3 -3
  16. package/templates/qoder/commands/wl-prd.md +2 -2
  17. package/templates/qoder/commands/wl-req.md +43 -0
  18. package/templates/qoder/commands/wl-search.md +8 -8
  19. package/templates/qoder/commands/wl-task.md +17 -17
  20. package/templates/qoder/commands/wl-test.md +41 -15
  21. package/templates/qoder/config.yaml +17 -1
  22. package/templates/qoder/hooks/session-start.py +12 -22
  23. package/templates/qoder/nul +4 -0
  24. package/templates/qoder/rules/wl-pipeline.md +22 -48
  25. package/templates/qoder/scripts/README.md +139 -0
  26. package/templates/qoder/scripts/common/autotest_auth.py +109 -0
  27. package/templates/qoder/scripts/common/bootstrap.py +145 -0
  28. package/templates/qoder/scripts/common/check_publish.py +98 -0
  29. package/templates/qoder/scripts/common/cmd_registry.py +112 -0
  30. package/templates/qoder/scripts/common/config.py +187 -0
  31. package/templates/qoder/scripts/common/contract.py +317 -0
  32. package/templates/qoder/scripts/common/developer.py +2 -1
  33. package/templates/qoder/scripts/common/feishu.py +10 -9
  34. package/templates/qoder/scripts/common/guard.py +159 -0
  35. package/templates/qoder/scripts/common/identity.py +121 -2
  36. package/templates/qoder/scripts/common/kg_capabilities.py +182 -0
  37. package/templates/qoder/scripts/common/mcp_base.py +268 -0
  38. package/templates/qoder/scripts/common/paths.py +187 -1
  39. package/templates/qoder/scripts/common/result.py +223 -0
  40. package/templates/qoder/scripts/common/roles.py +60 -0
  41. package/templates/qoder/scripts/common/task_utils.py +21 -9
  42. package/templates/qoder/scripts/common/test_extract.py +115 -0
  43. package/templates/qoder/scripts/kg/__init__.py +11 -0
  44. package/templates/qoder/scripts/kg/build_entity_registry.py +196 -0
  45. package/templates/qoder/scripts/kg/build_relations.py +127 -0
  46. package/templates/qoder/scripts/{build_style_index.py → kg/build_style_index.py} +39 -10
  47. package/templates/qoder/scripts/kg/build_workflows.py +144 -0
  48. package/templates/qoder/scripts/{context_pack.py → kg/context_pack.py} +18 -11
  49. package/templates/qoder/scripts/{enrich_prompt.py → kg/enrich_prompt.py} +232 -226
  50. package/templates/qoder/scripts/{extract_api_params.py → kg/extract.py} +398 -246
  51. package/templates/qoder/scripts/{kg.py → kg/kg.py} +638 -708
  52. package/templates/qoder/scripts/{kg_build.py → kg/kg_build.py} +618 -612
  53. package/templates/qoder/scripts/{kg_build_db.py → kg/kg_build_db.py} +333 -327
  54. package/templates/qoder/scripts/{kg_duckdb.py → kg/kg_duckdb.py} +38 -37
  55. package/templates/qoder/scripts/{kg_incremental.py → kg/kg_incremental.py} +420 -393
  56. package/templates/qoder/scripts/{kg_link_db.py → kg/kg_link_db.py} +230 -224
  57. package/templates/qoder/scripts/{kg_semantic.py → kg/kg_semantic.py} +156 -150
  58. package/templates/qoder/scripts/kg/prefetch.py +359 -0
  59. package/templates/qoder/scripts/{search_index.py → kg/search_index.py} +70 -14
  60. package/templates/qoder/scripts/mcp/__init__.py +11 -0
  61. package/templates/qoder/scripts/{kg_mcp_server.py → mcp/kg_mcp_server.py} +77 -272
  62. package/templates/qoder/scripts/{lanhu_stdio_wrapper.py → mcp/lanhu_stdio_wrapper.py} +125 -119
  63. package/templates/qoder/scripts/{check_mcp.py → mcp/mcp_doctor.py} +515 -298
  64. package/templates/qoder/scripts/{mcp_launcher.py → mcp/mcp_launcher.py} +442 -414
  65. package/templates/qoder/scripts/{mysql_mcp_server.py → mcp/mysql_mcp_server.py} +347 -396
  66. package/templates/qoder/scripts/{zentao_mcp_server.py → mcp/zentao_mcp_server.py} +384 -424
  67. package/templates/qoder/scripts/report/__init__.py +11 -0
  68. package/templates/qoder/scripts/{add_session.py → report/add_session.py} +250 -244
  69. package/templates/qoder/scripts/{archive_prd.py → report/archive_prd.py} +383 -377
  70. package/templates/qoder/scripts/{eval_prd.py → report/eval_prd.py} +73 -11
  71. package/templates/qoder/scripts/{export.py → report/export.py} +63 -0
  72. package/templates/qoder/scripts/{fill_prototype.py → report/fill_prototype.py} +6 -0
  73. package/templates/qoder/scripts/{gen_design_doc.py → report/gen_design_doc.py} +400 -394
  74. package/templates/qoder/scripts/{learn.py → report/learn.py} +152 -146
  75. package/templates/qoder/scripts/{learn_aggregate.py → report/learn_aggregate.py} +207 -201
  76. package/templates/qoder/scripts/{report.py → report/report.py} +287 -281
  77. package/templates/qoder/scripts/report/req.py +222 -0
  78. package/templates/qoder/scripts/report/role.py +33 -0
  79. package/templates/qoder/scripts/{status.py → report/status.py} +634 -628
  80. package/templates/qoder/scripts/setup/__init__.py +11 -0
  81. package/templates/qoder/scripts/setup/carriers.py +662 -0
  82. package/templates/qoder/scripts/{init_doctor.py → setup/init_doctor.py} +63 -26
  83. package/templates/qoder/scripts/{install_qoderwork.py → setup/install_qoderwork.py} +36 -26
  84. package/templates/qoder/scripts/{platform_doctor.py → setup/platform_doctor.py} +265 -259
  85. package/templates/qoder/scripts/{repo_root.py → setup/repo_root.py} +112 -106
  86. package/templates/qoder/scripts/{setup.py → setup/setup.py} +113 -4
  87. package/templates/qoder/scripts/{setup_lanhu.py → setup/setup_lanhu.py} +973 -963
  88. package/templates/qoder/scripts/task/__init__.py +11 -0
  89. package/templates/qoder/scripts/{git_sync.py → task/git_sync.py} +52 -31
  90. package/templates/qoder/scripts/{syncgate.py → task/syncgate.py} +6 -0
  91. package/templates/qoder/scripts/task/task.py +221 -0
  92. package/templates/qoder/scripts/task/task_lifecycle.py +596 -0
  93. package/templates/qoder/scripts/task/task_query.py +161 -0
  94. package/templates/qoder/scripts/task/task_relations.py +424 -0
  95. package/templates/qoder/scripts/{team_sync.py → task/team_sync.py} +93 -20
  96. package/templates/qoder/scripts/test/__init__.py +11 -0
  97. package/templates/qoder/scripts/{autotest.py → test/autotest.py} +1174 -1751
  98. package/templates/qoder/scripts/{autotest_batch.py → test/autotest_batch.py} +242 -224
  99. package/templates/qoder/scripts/test/autotest_data.py +675 -0
  100. package/templates/qoder/scripts/{autotest_run.py → test/autotest_run.py} +309 -297
  101. package/templates/qoder/scripts/{benchmark.py → test/benchmark.py} +6 -0
  102. package/templates/qoder/scripts/{kg_auto_login.py → test/kg_auto_login.py} +202 -196
  103. package/templates/qoder/scripts/{kg_test_runner.py → test/kg_test_runner.py} +7 -1
  104. package/templates/qoder/scripts/{page_probe.py → test/page_probe.py} +465 -459
  105. package/templates/qoder/scripts/wlkj.py +116 -0
  106. package/templates/qoder/settings.json +1 -10
  107. package/templates/qoder/skills/design-import/SKILL.md +226 -226
  108. package/templates/qoder/skills/design-review/SKILL.md +82 -82
  109. package/templates/qoder/skills/prd-generator/SKILL.md +26 -16
  110. package/templates/qoder/skills/prd-review/SKILL.md +5 -5
  111. package/templates/qoder/skills/prototype-generator/SKILL.md +256 -256
  112. package/templates/qoder/skills/spec-coder/SKILL.md +4 -4
  113. package/templates/qoder/skills/spec-generator/SKILL.md +4 -4
  114. package/templates/qoder/skills/test-generator/SKILL.md +5 -5
  115. package/templates/qoder/skills/wl-code/SKILL.md +4 -4
  116. package/templates/qoder/skills/wl-commit/SKILL.md +4 -4
  117. package/templates/qoder/skills/wl-design/SKILL.md +3 -3
  118. package/templates/qoder/skills/wl-init/SKILL.md +8 -8
  119. package/templates/qoder/skills/wl-insight/SKILL.md +5 -5
  120. package/templates/qoder/skills/wl-prd-full/SKILL.md +6 -6
  121. package/templates/qoder/skills/wl-prd-quick/SKILL.md +6 -6
  122. package/templates/qoder/skills/wl-prd-review/SKILL.md +4 -4
  123. package/templates/qoder/skills/wl-report/SKILL.md +7 -7
  124. package/templates/qoder/skills/wl-search/SKILL.md +13 -13
  125. package/templates/qoder/skills/wl-spec/SKILL.md +5 -5
  126. package/templates/qoder/skills/wl-status/SKILL.md +5 -5
  127. package/templates/qoder/skills/wl-task/SKILL.md +6 -6
  128. package/templates/qoder/skills/wl-test/SKILL.md +102 -39
  129. package/templates/root/AGENTS.md +39 -40
  130. package/templates/qoder/hooks/inject-workflow-state.py +0 -169
  131. package/templates/qoder/scripts/__pycache__/check_mcp_launch.cpython-39.pyc +0 -0
  132. package/templates/qoder/scripts/__pycache__/install_qoderwork.cpython-39.pyc +0 -0
  133. package/templates/qoder/scripts/__pycache__/mcp_launcher.cpython-39.pyc +0 -0
  134. package/templates/qoder/scripts/__pycache__/platform_doctor.cpython-39.pyc +0 -0
  135. package/templates/qoder/scripts/check_carriers.py +0 -238
  136. package/templates/qoder/scripts/check_mcp_launch.py +0 -183
  137. package/templates/qoder/scripts/check_qoderwork_consistency.py +0 -166
  138. package/templates/qoder/scripts/collect_prds.py +0 -31
  139. package/templates/qoder/scripts/common/mentions.py +0 -134
  140. package/templates/qoder/scripts/common/utf8.py +0 -38
  141. package/templates/qoder/scripts/extract_routes.py +0 -54
  142. package/templates/qoder/scripts/extract_routes_tree.py +0 -78
  143. package/templates/qoder/scripts/handoff.py +0 -22
  144. package/templates/qoder/scripts/init_developer.py +0 -76
  145. package/templates/qoder/scripts/parse_prds.py +0 -33
  146. package/templates/qoder/scripts/role.py +0 -51
  147. package/templates/qoder/scripts/sync_carriers.py +0 -259
  148. package/templates/qoder/scripts/task.py +0 -1261
  149. package/templates/qoder/scripts/workspace_init.py +0 -102
  150. package/templates/qoder/skills/prompt-enrich/SKILL.md +0 -90
  151. package/templates/qoder/skills/prototype-generator/SKILL.md.zcode-79180-2af4721f-f9a6-412c-88db-c0af680d211b.tmp +0 -0
  152. /package/templates/qoder/scripts/{secure-ls.js → test/secure-ls.js} +0 -0
package/bin/cli.js CHANGED
@@ -220,6 +220,36 @@ function normalizeRole(r) {
220
220
  return ROLES.some(x => x.key === low) ? low : null;
221
221
  }
222
222
 
223
+ // reset --hard 前备份本地改过的团队数据文件 (防丢真实密码/配置)。
224
+ // 只备份 .qoder/config.yaml|config.toml|settings.json, 且仅当本地内容与远程不同时。
225
+ // 返回被备份的相对路径列表。引擎文件 (gitignored) 不备份, 反正由 update 重新生成。
226
+ function _backupLocalTeamData(cwd, remoteBranch, _es) {
227
+ const DATA_FILES = [".qoder/config.yaml", ".qoder/config.toml", ".qoder/settings.json"];
228
+ const backed = [];
229
+ const backupDir = path.join(cwd, ".wlkj-pre-reset");
230
+ try {
231
+ fs.mkdirSync(backupDir, { recursive: true });
232
+ for (const rel of DATA_FILES) {
233
+ const local = path.join(cwd, rel);
234
+ if (!fs.existsSync(local)) continue;
235
+ let localContent = "";
236
+ try { localContent = fs.readFileSync(local, "utf-8"); } catch { continue; }
237
+ // 拿远程版本对比
238
+ let remoteContent = null;
239
+ try {
240
+ remoteContent = _es(`git show origin/${remoteBranch}:${rel}`, { cwd, encoding: "utf-8", timeout: 5000, stdio: "pipe" });
241
+ } catch { /* 远程没这文件 */ }
242
+ const same = (remoteContent !== null && remoteContent.trim() === localContent.trim());
243
+ if (!same) {
244
+ const target = path.join(backupDir, rel.replace(/[\\/]/g, "__"));
245
+ fs.copyFileSync(local, target);
246
+ backed.push(rel);
247
+ }
248
+ }
249
+ } catch { /* 备份失败不阻塞, 但少一层保护 */ }
250
+ return backed;
251
+ }
252
+
223
253
  async function doInit(name, roleArg) {
224
254
  const cwd = process.cwd();
225
255
  const hasExisting = fs.existsSync(path.join(cwd, ".qoder", "scripts", "setup.py"));
@@ -242,15 +272,20 @@ async function doInit(name, roleArg) {
242
272
 
243
273
  // === 1. 拷贝完整引擎 (镜像 .qoder/ 结构) ===
244
274
  // 源: templates/qoder/* 目标: .qoder/*
245
- // 注意: init "update" 模式的保护逻辑更安全 —— 新装时目标文件不存在,
246
- // 保护逻辑不触发; 重跑 init config.yaml/settings.json 不会被覆盖。
275
+ // init 复用 update 的三路快照保护: 新装时目标不存在, 保护不触发;
276
+ // 重跑 init 时用户改过的引擎文件 (如自定义 skill) 不会被覆盖。
247
277
  const qoderSrc = path.join(T, "qoder");
248
278
  let copied = 0;
249
279
  if (fs.existsSync(qoderSrc)) {
250
- const r = copyDirRecursive(qoderSrc, path.join(cwd, ".qoder"), "update");
280
+ const snap = readSnapshot(cwd);
281
+ const newSnap = {};
282
+ const r = copyDirRecursive(qoderSrc, path.join(cwd, ".qoder"), "update", snap, newSnap);
251
283
  copied = r.copied;
284
+ writeSnapshot(cwd, newSnap);
252
285
  if (r.protectedN > 0) {
253
- console.log(` [保护] ${r.protectedN} 个配置文件未覆盖(新版存为 .new)`);
286
+ console.log(` [保护] ${r.protectedN} 个文件保留你的改动 (新版存为 .new):`);
287
+ for (const f of r.protectedFiles.slice(0, 8)) console.log(` .qoder/${f}`);
288
+ if (r.protectedFiles.length > 8) console.log(` ...等 ${r.protectedFiles.length} 个`);
254
289
  }
255
290
  }
256
291
  console.log(` 引擎: ${copied} 个文件 (${hasExisting ? "更新" : "新建"})`);
@@ -285,15 +320,21 @@ async function doInit(name, roleArg) {
285
320
  const gitignorePath = path.join(cwd, ".gitignore");
286
321
  if (!fs.existsSync(gitignorePath)) {
287
322
  const baseGitignore = [
323
+ "# 引擎代码 (由 npm 包 @hupan56/wlkj init/update 生成, 不进团队 git)",
324
+ ".qoder/scripts/", ".qoder/skills/", ".qoder/commands/", ".qoder/agents/",
325
+ ".qoder/contracts/", ".qoder/hooks/", ".qoder/templates/", ".qoder/rules/",
326
+ ".qoder/repowiki/", ".qoder/archive/",
327
+ "",
328
+ "# 团队数据 (保持跟踪): .qoder/config.yaml, .qoder/settings.json, .qoder/learning/, data/, workspace/",
329
+ "",
288
330
  "# Source code repos (cloned by git_sync.py)", "data/code/",
289
331
  "", "# AI pipeline runtime", ".qoder/.developer", ".qoder/.current-task",
290
- ".qoder/.engine-version", ".qoder/.runtime/", "",
291
- "# Personal learning", ".qoder/learning/feedback.jsonl",
292
- "", "# Machine-local", "data/index/.last-sync", "data/index/.prd-collected.json",
332
+ ".qoder/.engine-version", ".qoder/.engine-snapshot.json", ".qoder/.runtime/", "",
333
+ "# Machine-local", "data/index/.last-sync", "data/index/.prd-collected.json",
293
334
  "data/index/.index-meta.json", "data/index/.sync-lock", "data/index/.file-keys.json",
294
335
  "data/index/.inverted-cache.json", "data/index/*.corrupt",
295
336
  "", "# Identity (secret)", "workspace/members/*/.signing_key", "",
296
- "# Python", "__pycache__/", "*.pyc", "*.lock", "",
337
+ "# Python", "__pycache__/", "*.pyc", "*.lock", "*.tmp", "",
297
338
  ].join("\n");
298
339
  fs.writeFileSync(gitignorePath, baseGitignore, "utf-8");
299
340
  }
@@ -354,7 +395,7 @@ async function doInit(name, roleArg) {
354
395
  console.log(` [OK] mcp.json 已生成 (选装了依赖的Python, MCP 不会黄)`);
355
396
  } catch (e) {
356
397
  console.log(` [WARN] MCP 配置未完全成功 (不阻塞): ${(e.message || "").slice(0, 80)}`);
357
- console.log(` 可手动补: ${pyCmd} .qoder/scripts/install_qoderwork.py`);
398
+ console.log(` 可手动补: ${pyCmd} .qoder/scripts/setup/install_qoderwork.py`);
358
399
  }
359
400
  }
360
401
  } catch (e) {
@@ -373,24 +414,31 @@ async function doInit(name, roleArg) {
373
414
  console.log(` 或输 / 看命令列表 (/wl-prd /wl-search /wl-task)`);
374
415
  console.log(`\n ✓ QoderWork MCP 已自动配好 (kg/mysql/lanhu/playwright)`);
375
416
  console.log(` 用 QoderWork 桌面端想装技能(软链到 ~/.qoderwork/skills/)?`);
376
- console.log(` python .qoder/scripts/install_qoderwork.py`);
417
+ console.log(` python .qoder/scripts/setup/install_qoderwork.py`);
377
418
  console.log(`\n 看不到命令? 新建对话 / 重启 QoderWork`);
378
- console.log(` 环境问题? python .qoder/scripts/init_doctor.py --fix\n`);
419
+ console.log(` 环境问题? python .qoder/scripts/setup/init_doctor.py --fix\n`);
379
420
  }
380
421
 
381
- // 递归拷贝目录, 返回 {copied, protected, skipped}
422
+ // 递归拷贝目录, 返回 {copied, protected, skipped, protectedFiles}
382
423
  // mode: "init" (新装, 全拷) | "update" (升级, 保护用户数据)
383
- function copyDirRecursive(src, dst, mode = "init") {
424
+ // update 模式下采用三路快照保护 (见 readSnapshot/writeSnapshot):
425
+ // 用户改过的引擎文件 (与上次安装快照不同) → 保留用户的, 新版存 .new
426
+ // 用户没动过的引擎文件 → 正常升级
427
+ // 非引擎文件已存在 → 跳过 (留用户数据)
428
+ function copyDirRecursive(src, dst, mode = "init", snap = {}, newSnap = {}, relBase = "") {
384
429
  let copied = 0, protectedN = 0, skipped = 0;
385
- if (!fs.existsSync(src)) return { copied, protectedN, skipped };
430
+ const protectedFiles = [];
431
+ if (!fs.existsSync(src)) return { copied, protectedN, skipped, protectedFiles };
386
432
  fs.mkdirSync(dst, { recursive: true });
387
433
  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
388
434
  if (entry.name === "__pycache__" || entry.name.endsWith(".pyc")) continue;
389
435
  const s = path.join(src, entry.name);
390
436
  const d = path.join(dst, entry.name);
437
+ const rel = relBase ? `${relBase}/${entry.name}` : entry.name;
391
438
  if (entry.isDirectory()) {
392
- const r = copyDirRecursive(s, d, mode);
439
+ const r = copyDirRecursive(s, d, mode, snap, newSnap, rel);
393
440
  copied += r.copied; protectedN += r.protectedN; skipped += r.skipped;
441
+ protectedFiles.push(...r.protectedFiles);
394
442
  } else {
395
443
  const isEngine = entry.name.endsWith(".py") || entry.name.endsWith(".md") ||
396
444
  entry.name.endsWith(".yaml") || entry.name.endsWith(".yml") ||
@@ -398,24 +446,47 @@ function copyDirRecursive(src, dst, mode = "init") {
398
446
  entry.name.endsWith(".html") || entry.name.endsWith(".txt");
399
447
 
400
448
  if (mode === "update") {
401
- // 升级模式: 受保护文件绝不覆盖
449
+ // 受保护配置 (config.yaml/settings.json): 永不覆盖, 有差异存 .new
402
450
  if (PROTECTED_FILES.has(entry.name) && fs.existsSync(d)) {
403
- // 新版内容存为 .new 让用户手动合并(仅当内容不同)
404
451
  if (!filesEqual(s, d)) {
405
452
  fs.copyFileSync(s, d + ".new");
406
- protectedN++;
453
+ protectedN++; protectedFiles.push(rel);
407
454
  } else {
408
455
  skipped++;
409
456
  }
457
+ // 快照记上游 hash: 用户当前 == 上游 → 下次安全升级; 否则持续保护
458
+ const sh = fileHash(s); if (sh) newSnap[rel] = sh;
410
459
  continue;
411
460
  }
412
- // 引擎文件: 无条件刷新(纯引擎, 不含用户数据)
413
- // 非引擎文件: 已存在跳过, 不存在才建
414
- if (isEngine || !fs.existsSync(d)) {
415
- fs.copyFileSync(s, d);
416
- copied++;
461
+ // 引擎文件: 三路快照保护
462
+ if (isEngine) {
463
+ const userHash = fs.existsSync(d) ? fileHash(d) : null;
464
+ const snapHash = snap[rel] || null;
465
+ if (fs.existsSync(d) && userHash && snapHash && userHash !== snapHash) {
466
+ // 用户改过这个文件! 保留用户的, 新版存 .new (不覆盖)
467
+ if (!filesEqual(s, d)) {
468
+ fs.copyFileSync(s, d + ".new");
469
+ protectedN++; protectedFiles.push(rel);
470
+ } else {
471
+ skipped++;
472
+ }
473
+ // 关键: 快照记上游 hash (不是用户的 hash)。
474
+ // 这样下次升级: 用户文件若仍 != 上游 → 继续保护; 若用户手动改成上游 → 自动恢复正常升级。
475
+ const sh = fileHash(s); if (sh) newSnap[rel] = sh;
476
+ } else {
477
+ // 用户没动过 (或新文件), 正常升级, 刷新快照为新版 hash
478
+ fs.copyFileSync(s, d);
479
+ copied++;
480
+ const sh = fileHash(s); if (sh) newSnap[rel] = sh;
481
+ }
417
482
  } else {
418
- skipped++;
483
+ // 非引擎文件: 已存在跳过 (保护用户数据), 不存在才建
484
+ if (!fs.existsSync(d)) {
485
+ fs.copyFileSync(s, d);
486
+ copied++;
487
+ } else {
488
+ skipped++;
489
+ }
419
490
  }
420
491
  } else {
421
492
  // init 模式: 引擎文件全覆盖, 非引擎已存在跳过 (保留用户改动)
@@ -423,10 +494,12 @@ function copyDirRecursive(src, dst, mode = "init") {
423
494
  fs.copyFileSync(s, d);
424
495
  }
425
496
  copied++;
497
+ // 快照记上游 hash (基线), 供将来升级对比
498
+ const sh = fileHash(s); if (sh) newSnap[rel] = sh;
426
499
  }
427
500
  }
428
501
  }
429
- return { copied, protectedN, skipped };
502
+ return { copied, protectedN, skipped, protectedFiles };
430
503
  }
431
504
 
432
505
  // 两文件内容是否相同(先比大小再比内容)
@@ -455,6 +528,53 @@ function readEngineVersion(cwd) {
455
528
  return null;
456
529
  }
457
530
 
531
+ // ─────────────────────────────────────────────────────────────
532
+ // 三路快照保护 (dpkg/brew conffiles 同款机制, 彻底解决"我原本的技能呢?")
533
+ //
534
+ // 问题: update 用 isEngine 一刀切强制刷新 .md/.py/.yaml 等,
535
+ // 用户改过的 stock skill (如 .qoder/skills/wl-prd-full/SKILL.md)
536
+ // 被无声覆盖 → "更新完我的技能没了"。
537
+ //
538
+ // 解法: 每次安装/升级后, 把所有"从 npm 包拷过来的引擎文件"的 hash 记到
539
+ // .qoder/.engine-snapshot.json。下次升级时, 三路对比:
540
+ // 用户当前文件 hash == 快照 hash → 没动过, 安全升级
541
+ // 用户当前文件 hash != 快照 hash → 动过! 保留用户的, 新版存 .new
542
+ // 这就是 apt/dpkg 对 /etc 下 conffiles 的处理, 行业标准, 不瞎猜。
543
+ // ─────────────────────────────────────────────────────────────
544
+ const SNAPSHOT_FILE = ".engine-snapshot.json";
545
+
546
+ function snapshotPath(cwd) {
547
+ return path.join(cwd, ".qoder", SNAPSHOT_FILE);
548
+ }
549
+
550
+ // 读快照: { "skills/wl-prd-full/SKILL.md": "<sha256前16位>", ... }
551
+ function readSnapshot(cwd) {
552
+ try {
553
+ const p = snapshotPath(cwd);
554
+ if (fs.existsSync(p)) return JSON.parse(fs.readFileSync(p, "utf-8"));
555
+ } catch { /* 损坏的快照当空 */ }
556
+ return {};
557
+ }
558
+
559
+ // 写快照: 传入 {相对路径: hash} 的对象
560
+ function writeSnapshot(cwd, snap) {
561
+ try {
562
+ const p = snapshotPath(cwd);
563
+ fs.mkdirSync(path.dirname(p), { recursive: true });
564
+ fs.writeFileSync(p, JSON.stringify(snap, null, 2), "utf-8");
565
+ } catch { /* 快照写失败不阻塞升级 */ }
566
+ }
567
+
568
+ // 计算文件内容 hash (sha256 前 16 位, 足够检测"是否动过")
569
+ function fileHash(p) {
570
+ try {
571
+ const crypto = require("crypto");
572
+ const h = crypto.createHash("sha256");
573
+ h.update(fs.readFileSync(p));
574
+ return h.digest("hex").slice(0, 16);
575
+ } catch { return null; }
576
+ }
577
+
458
578
  // 跑 install_qoderwork.py(升级时强制刷新 commands)
459
579
  function runQoderWorkInstall(cwd, forceCommands) {
460
580
  const script = path.join(cwd, ".qoder", "scripts", "install_qoderwork.py");
@@ -524,32 +644,30 @@ function doUpdate() {
524
644
  console.log(" .qoder/.developer (你的身份)");
525
645
  console.log(" .qoder/.current-task (当前任务)");
526
646
  console.log(" .qoder/.engine-version (版本戳)");
647
+ console.log(" 你改过的引擎文件 (三路快照: 改过的留你的, 新版存 .new)");
527
648
  console.log(" workspace/ (你的全部产出)");
528
649
  console.log(" data/ (代码仓库+知识图谱)");
529
650
  console.log("");
530
651
 
531
- // === 1. 刷新引擎文件(保护 config.yaml / settings.json)===
652
+ // === 1. 刷新引擎文件 (三路快照保护用户改动) ===
532
653
  let copied = 0, protectedN = 0, skipped = 0;
654
+ let protectedFiles = [];
533
655
  if (fs.existsSync(qoderSrc)) {
534
- const r = copyDirRecursive(qoderSrc, path.join(cwd, ".qoder"), "update");
656
+ const snap = readSnapshot(cwd);
657
+ const newSnap = {};
658
+ const r = copyDirRecursive(qoderSrc, path.join(cwd, ".qoder"), "update", snap, newSnap);
535
659
  copied = r.copied; protectedN = r.protectedN; skipped = r.skipped;
660
+ protectedFiles = r.protectedFiles || [];
661
+ writeSnapshot(cwd, newSnap); // 刷新基线 (供下次升级对比)
536
662
  }
537
663
  console.log(` 引擎刷新: ${copied} 个文件${skipped ? ` / ${skipped} 无变化` : ""}`);
538
664
 
539
- // 报告受保护文件
665
+ // 报告受保护文件 (用户的改动 + 配置文件)
540
666
  if (protectedN > 0) {
541
- console.log(` [保护] ${protectedN} 个配置文件已保留你的本地设置:`);
542
- console.log(` 新版模板存为 .new 文件, 如需合并新结构请手动对比:`);
543
- // 列出实际生成的 .new 文件
544
- try {
545
- for (const f of fs.readdirSync(path.join(cwd, ".qoder"))) {
546
- if (f.endsWith(".new")) {
547
- console.log(` .qoder/${f}`);
548
- }
549
- }
550
- } catch { /* */ }
551
- } else {
552
- console.log(` config.yaml / settings.json: 已保护(未改动)`);
667
+ console.log(` [保护] ${protectedN} 个文件已保留你的改动 (新版存为 .new):`);
668
+ for (const f of protectedFiles.slice(0, 10)) console.log(` .qoder/${f}`);
669
+ if (protectedFiles.length > 10) console.log(` ...等 ${protectedFiles.length} 个`);
670
+ console.log(` 确认无需合并后可删除 .new; 或手动对比合并后删除。`);
553
671
  }
554
672
 
555
673
  // === 2. 根文件(文档 + requirements.txt, 始终覆盖保证最新)===
@@ -561,14 +679,16 @@ function doUpdate() {
561
679
  });
562
680
  console.log(` 根文档 + requirements.txt: 已更新`);
563
681
 
564
- // === 2.5. git pull 拉最新团队数据 (kg.duckdb/kg_db.duckdb/PRD等在git) ===
682
+ // === 2.5. git pull 拉最新团队数据 (Trellis: 引擎已由上面 copyDirRecursive 生成, git 只存数据) ===
683
+ // 团队数据 = config.yaml / settings.json / learning/ + data/ (kg.duckdb/kg_db.duckdb/索引) + workspace/ (PRD/任务)。
684
+ // 引擎代码 (scripts/skills/...) 已被 gitignore, 不在 git 里, 不会与 pull 冲突。
565
685
  // git pull 失败会导致 kg.duckdb 拿不到 → kg MCP 无数据(但仍能连)。
566
- // 失败原因常见: 未配 git 身份 / 远程仓库需要认证 / 网络问题 / 分支没tracking。
686
+ // 失败原因常见: 未配 git 身份 / 远程仓库要认证 / 网络问题 / 分支没tracking / unrelated histories
567
687
  // 这里给出清晰诊断, 不让"git pull 失败"变成无声的静默跳过。
568
688
  // 前置: 自愈分支 tracking (老版 init 配了 remote 但没 set-upstream, pull 会报
569
689
  // "no tracking information"。这里检测到就补上, 老用户 update 也能自动修)
690
+ const { execSync: _es } = require("child_process"); // 提到 try 外, catch 块的自愈逻辑也要用
570
691
  try {
571
- const { execSync: _es } = require("child_process");
572
692
  // 先看当前分支有没有 upstream
573
693
  let _trackOk = false;
574
694
  try {
@@ -625,10 +745,43 @@ function doUpdate() {
625
745
  console.log(` 修复: npx @hupan56/wlkj init <你的名字> (会配团队远程仓库)`);
626
746
  } else if (combined.includes("timed out") || combined.includes("timeout") ||
627
747
  combined.includes("could not resolve host") || combined.includes("connection refused") ||
628
- combined.includes("network is unreachable")) {
748
+ combined.includes("network is unreachable") || combined.includes("unable to access") ||
749
+ combined.includes("ssl") || combined.includes("failed to connect")) {
629
750
  console.log(` git: ⚠️ 网络问题 — 连不上远程仓库`);
630
751
  console.log(` 原因: ${stdout.trim().slice(0, 80) || "网络超时/无法解析主机"}`);
631
752
  console.log(` (kg.duckdb 拉不到, MCP 能连但搜索无结果)`);
753
+ } else if (combined.includes("unrelated histories") || combined.includes("refusing to merge")) {
754
+ // 自愈: 本地是空的 git init 历史, 与团队远程无公共祖先。
755
+ // 引擎代码现在由 npm 生成 (不在 git 里), 团队数据在远程 → 采用远程历史是正确的。
756
+ // 但 reset --hard 会覆盖工作树 → 先把本地改过的团队数据文件备份, 防丢密码/配置。
757
+ console.log(` git: ⚠️ 无关历史 (本地 git init 产生的平行历史与团队远程无公共祖先)`);
758
+ try {
759
+ // 探测远程分支名
760
+ let _rb = null;
761
+ for (const _c of ["master", "main"]) {
762
+ try {
763
+ _es(`git rev-parse --verify remotes/origin/${_c}`, { cwd, encoding: "utf-8", timeout: 5000, stdio: "pipe" });
764
+ _rb = _c; break;
765
+ } catch { /* */ }
766
+ }
767
+ if (_rb) {
768
+ // 备份本地改过的团队数据 (config 等可能含未提交的真实密码)
769
+ const _backed = _backupLocalTeamData(cwd, _rb, _es);
770
+ _es(`git reset --hard origin/${_rb}`, { cwd, encoding: "utf-8", timeout: 30000, stdio: "pipe" });
771
+ console.log(` git: ✓ 已采用远程历史 (origin/${_rb}), 团队数据已落地`);
772
+ console.log(` (引擎随后由本次 update 重新生成)`);
773
+ if (_backed.length > 0) {
774
+ console.log(` ⚠️ reset 前已备份你本地改过的数据文件 (可能含未提交的真实密码/配置):`);
775
+ for (const rel of _backed) console.log(` → .wlkj-pre-reset/${rel.replace(/[\\/]/g, "__")}`);
776
+ console.log(` 检查这些文件, 把需要的配置/密码重新填回 .qoder/config.yaml 后删备份目录。`);
777
+ }
778
+ } else {
779
+ console.log(` git: 远程无 master/main 分支, 无法自愈。请确认团队仓库地址正确。`);
780
+ }
781
+ } catch (re) {
782
+ console.log(` git: 自愈失败 (${String(re.message || "").slice(0, 80)})`);
783
+ console.log(` 可手动: git fetch origin && git reset --hard origin/master`);
784
+ }
632
785
  } else {
633
786
  // 兜底: 显示 git 的真实报错, 不再笼统说 "Command failed"
634
787
  const realErr = stdout.trim() || errMsg;
@@ -652,7 +805,10 @@ function doUpdate() {
652
805
  }
653
806
 
654
807
  // === 4. 刷新 QoderWork commands(强制覆盖已存在的)===
655
- runQoderWorkInstall(cwd, true);
808
+ // WLKJ_SKIP_QW_INSTALL=1 时跳过 (测试隔离用, 防止测试目录污染 ~/.qoderwork/.repo-root)
809
+ if (process.env.WLKJ_SKIP_QW_INSTALL !== "1") {
810
+ runQoderWorkInstall(cwd, true);
811
+ }
656
812
 
657
813
  // === 5. 写版本戳 ===
658
814
  writeEngineVersion(cwd);
@@ -714,16 +870,6 @@ function doUpdate() {
714
870
  } catch (e) { /* 删不掉跳过 */ }
715
871
  }
716
872
  }
717
- // 清理 .new 临时文件 (上次升级生成的配置对比文件)
718
- try {
719
- for (const f of fs.readdirSync(path.join(cwd, ".qoder"))) {
720
- if (f.endsWith(".new")) {
721
- fs.unlinkSync(path.join(cwd, ".qoder", f));
722
- cleaned++;
723
- }
724
- }
725
- } catch { /* */ }
726
-
727
873
  // 清理用户级旧命令 (已合并的, ~/.qoderwork/commands/)
728
874
  const home = process.env.USERPROFILE || require("os").homedir();
729
875
  const userCmdDir = path.join(home, ".qoderwork", "commands");
@@ -738,16 +884,8 @@ function doUpdate() {
738
884
  catch { /* */ }
739
885
  }
740
886
  }
741
- // 清理 .new 临时文件 (上次升级生成的配置对比文件)
742
- try {
743
- for (const f of fs.readdirSync(path.join(cwd, ".qoder"))) {
744
- if (f.endsWith(".new")) {
745
- fs.unlinkSync(path.join(cwd, ".qoder", f));
746
- console.log(` [清理] 删除临时文件: .qoder/${f}`);
747
- cleaned++;
748
- }
749
- }
750
- } catch { /* */ }
887
+ // 注意: .new 文件不再自动删除! 它们是三路快照保护的"新版待对比"信号,
888
+ // 用户需要看到 .new 才知道"我的改动被保留了, 这里是新版"。用户手动合并后删。
751
889
  // 清理缓存 (索引重建后失效的旧缓存)
752
890
  const runtimeDir = path.join(cwd, ".qoder", ".runtime");
753
891
  let cacheCleaned = 0;
@@ -763,8 +901,25 @@ function doUpdate() {
763
901
  console.log(` [清理] 废弃文件 ${cleaned} 个 + 过期缓存 ${cacheCleaned} 个`);
764
902
  }
765
903
 
904
+ // === 5c. 清理 .qoder/.qoder 嵌套残留 (致命 bug 源) ===
905
+ // 旧版 paths.py 的 _find_repo_root 会误把 .qoder 当 PROJECT_ROOT, 然后往
906
+ // "PROJECT_ROOT/.qoder/.runtime/" 写缓存 → 误建 .qoder/.qoder/ 目录 →
907
+ // 这个残留反过来又让 _find_repo_root 再次误判 → 死循环。
908
+ // 表现: MCP cookie 路径全错 (workspace 跑到 .qoder/workspace), lanhu 永远 cookie 未配置。
909
+ // v3.0 修了 paths.py 不再误建, 但老用户机器上的残留要清掉, 否则 bug 还在。
910
+ const nestedQoder = path.join(cwd, ".qoder", ".qoder");
911
+ if (fs.existsSync(nestedQoder)) {
912
+ try {
913
+ fs.rmSync(nestedQoder, { recursive: true, force: true });
914
+ console.log(` [清理] 删除嵌套残留 .qoder/.qoder/ (曾导致 MCP cookie 路径错位)`);
915
+ cleaned++;
916
+ } catch { /* 删不掉不阻塞 (可能被占用), 下次再清 */ }
917
+ }
918
+
766
919
  // === 6. 刷新 launcher + mcp.json (老用户的可能是旧版/写死路径) ===
767
920
  // 注意: pip 依赖已在第3步装好, 此处 install_qoderwork 会选到"已装依赖的Python"写进 mcp.json
921
+ // WLKJ_SKIP_QW_INSTALL=1 时跳过 (测试隔离, 防止污染全局 ~/.qoderwork/)
922
+ if (process.env.WLKJ_SKIP_QW_INSTALL !== "1") {
768
923
  try {
769
924
  const home = process.env.USERPROFILE || require("os").homedir();
770
925
  const launcherSrc = path.join(cwd, ".qoder", "scripts", "mcp_launcher.py");
@@ -783,6 +938,7 @@ function doUpdate() {
783
938
  } catch (e) { /* --mcp-only 可能不支持, 静默跳过 */ }
784
939
  }
785
940
  } catch (e) { /* 刷新失败不阻塞升级 */ }
941
+ } // end WLKJ_SKIP_QW_INSTALL guard
786
942
 
787
943
  // === 6.5. MCP 启动实测 (发 initialize 看哪个起不来) ===
788
944
  // 这是"根治黄色"的关键一步: 配完 mcp.json 后立即模拟 QoderWork 发 initialize,
@@ -824,7 +980,9 @@ function doUpdate() {
824
980
  } else {
825
981
  console.log(` [✗] ${label} 缺失! ${desc}`);
826
982
  if (label.includes("duckdb")) {
827
- console.log(` 修复: npx @hupan56/wlkj run kg_build.py --rebuild`);
983
+ // duckdb 是团队共享数据, 在 git 里 非管理员靠 git pull 拿; 管理员靠本地构建。
984
+ console.log(` → 修复: 先 npx @hupan56/wlkj doctor (拉团队数据); `);
985
+ console.log(` 管理员可 npx @hupan56/wlkj run kg_build.py --rebuild`);
828
986
  } else if (label.includes("commands")) {
829
987
  console.log(` → 修复: npx @hupan56/wlkj update`);
830
988
  }
@@ -877,28 +1035,134 @@ function doUpdate() {
877
1035
  console.log("");
878
1036
  }
879
1037
 
1038
+ // ─────────────────────────────────────────────────────────────
1039
+ // 一次性迁移: 把引擎代码从团队 git 跟踪里摘出来 (Trellis 化)
1040
+ // 团队管理员只跑一次, 其余成员 wlkj update 后自动同步。
1041
+ // 原理: git rm -r --cached (只删 git 索引, 不删磁盘文件) + commit。
1042
+ // 之后引擎文件仍在磁盘 (由 wlkj update 重新生成), 但不再被 git 跟踪 →
1043
+ // 彻底消除"引擎更新污染团队 git"和"新机 init 产生 unrelated histories"。
1044
+ // 团队数据 (.qoder/config.yaml, settings.json, learning/, data/, workspace/) 保持不动。
1045
+ // ─────────────────────────────────────────────────────────────
1046
+ function doMigrate() {
1047
+ const { execSync } = require("child_process");
1048
+ const cwd = process.cwd();
1049
+ const env = { ...process.env, PYTHONIOENCODING: "utf-8", PYTHONUTF8: "1" };
1050
+ function _es(cmd, opts = {}) {
1051
+ return execSync(cmd, { cwd, encoding: "utf-8", timeout: 30000, stdio: "pipe", env, ...opts });
1052
+ }
1053
+
1054
+ console.log("");
1055
+ console.log("=== wlkj migrate: 引擎代码改为 npm 生成 (Trellis 化) ===");
1056
+ console.log("");
1057
+
1058
+ // 1. 必须在 git 仓库里
1059
+ if (!fs.existsSync(path.join(cwd, ".git"))) {
1060
+ console.log(" ✗ 当前目录不是 git 仓库。请在团队仓库根目录运行。");
1061
+ console.log("");
1062
+ return;
1063
+ }
1064
+
1065
+ // 2. 要从 git 跟踪摘除的引擎目录 (团队数据 config/learning 等不在此列)
1066
+ const ENGINE_DIRS = [
1067
+ ".qoder/scripts", ".qoder/skills", ".qoder/commands", ".qoder/agents",
1068
+ ".qoder/contracts", ".qoder/hooks", ".qoder/templates", ".qoder/rules",
1069
+ ];
1070
+
1071
+ // 3. 哪些目录当前确被跟踪 (只摘这些, 避免在已迁移过的仓库上空跑)
1072
+ const tracked = [];
1073
+ for (const d of ENGINE_DIRS) {
1074
+ let isTracked = false;
1075
+ try {
1076
+ // git ls-files 能列出被跟踪的文件; 有输出说明该目录还有文件在跟踪
1077
+ const out = _es(`git ls-files "${d}"`, { timeout: 10000 });
1078
+ if (out.trim()) isTracked = true;
1079
+ } catch { /* 命令失败当未跟踪 */ }
1080
+ if (isTracked) tracked.push(d);
1081
+ }
1082
+
1083
+ if (tracked.length === 0) {
1084
+ console.log(" ✓ 引擎目录已不在 git 跟踪中, 无需迁移。");
1085
+ console.log(" (这通常是已经迁移过了, 或是个全新仓库)");
1086
+ console.log("");
1087
+ return;
1088
+ }
1089
+
1090
+ console.log(` 将从 git 跟踪中移除 (磁盘文件保留, 不删):`);
1091
+ for (const d of tracked) console.log(` - ${d}`);
1092
+ console.log("");
1093
+
1094
+ // 4. 先确保 .gitignore 已含引擎忽略规则 (防止 rm --cached 后又被加回)
1095
+ // 逐条补缺, 不重复不乱插。
1096
+ const giPath = path.join(cwd, ".gitignore");
1097
+ const RULES_TO_ENSURE = [
1098
+ "# 引擎代码 (由 npm 包 @hupan56/wlkj init/update 生成, 不进团队 git)",
1099
+ ".qoder/scripts/", ".qoder/skills/", ".qoder/commands/", ".qoder/agents/",
1100
+ ".qoder/contracts/", ".qoder/hooks/", ".qoder/templates/", ".qoder/rules/",
1101
+ ".qoder/repowiki/", ".qoder/archive/",
1102
+ ".wlkj-pre-reset/",
1103
+ ];
1104
+ let gi = fs.existsSync(giPath) ? fs.readFileSync(giPath, "utf-8") : "";
1105
+ const existingLines = new Set(gi.split(/\r?\n/));
1106
+ const missing = RULES_TO_ENSURE.filter(rule => !existingLines.has(rule));
1107
+ if (missing.length > 0) {
1108
+ // 缺哪条补哪条 (注释行单独补, 规则行单独补), 补在最前面
1109
+ gi = missing.join("\n") + "\n" + (gi ? "\n" + gi : "");
1110
+ fs.writeFileSync(giPath, gi, "utf-8");
1111
+ console.log(` ✓ .gitignore 已补 ${missing.length} 条忽略规则`);
1112
+ }
1113
+
1114
+ // 5. git rm -r --cached (只删索引, 不删磁盘文件)
1115
+ console.log(" 从 git 索引移除 (git rm -r --cached, 磁盘文件不动)...");
1116
+ try {
1117
+ _es(`git rm -r --cached ${tracked.map(t => `"${t}"`).join(" ")}`, { timeout: 60000 });
1118
+ console.log(" ✓ 已移除");
1119
+ } catch (e) {
1120
+ console.log(` ✗ git rm 失败: ${(e.stderr || e.message || "").slice(0, 200)}`);
1121
+ console.log(" 可手动跑: " + tracked.map(t => `git rm -r --cached ${t}`).join(" && "));
1122
+ return;
1123
+ }
1124
+
1125
+ // 6. 自动 commit (标记这次架构变更)
1126
+ console.log(" 提交迁移...");
1127
+ try {
1128
+ _es(`git add -A .gitignore`, { timeout: 10000 });
1129
+ _es(`git commit -m "chore(wlkj): 引擎代码改为 npm 生成, 不再进团队 git (Trellis 化)"`, { timeout: 30000 });
1130
+ console.log(" ✓ 已提交");
1131
+ } catch (e) {
1132
+ console.log(` ⚠ 自动提交失败 (可手动 commit): ${(e.stderr || e.message || "").slice(0, 150)}`);
1133
+ }
1134
+
1135
+ console.log("");
1136
+ console.log("=== 迁移完成 ===");
1137
+ console.log(" 接下来:");
1138
+ console.log(" 1. npx @hupan56/wlkj sync # 推到团队远程");
1139
+ console.log(" 2. 团队其他成员各自跑: npx @hupan56/wlkj update");
1140
+ console.log(" (本地引擎文件不会被删, 之后由 update 重新生成, 互不冲突)");
1141
+ console.log("");
1142
+ }
1143
+
880
1144
  function doHelp() {
881
1145
  console.log("");
882
1146
  console.log(`wlkj - AI 产品研发工作流 (v${PKG_VERSION})`);
883
1147
  console.log("");
884
- console.log("=== 5 个核心命令 (记住这 5 个就够) ===");
1148
+ console.log("=== 终端只用 3 (从下载到日常) ===");
885
1149
  console.log("");
886
- console.log(" npx @hupan56/wlkj install-env 装环境 (Node/Python/git/pip依赖)");
887
- console.log(" npx @hupan56/wlkj init [名字] 装引擎 + 初始化身份");
888
- console.log(" npx @hupan56/wlkj update 升级 (刷新引擎+补依赖+刷MCP)");
889
- console.log(" npx @hupan56/wlkj doctor ④ 体检 (环境/依赖/图谱健康度)");
890
- console.log(" npx @hupan56/wlkj sync ⑤ 推送产出 (PRD/任务/原型)");
1150
+ console.log(" npx @hupan56/wlkj install-env 首次: 装环境 (Node/Python/git/pip)");
1151
+ console.log(" npx @hupan56/wlkj init [名字] 首次: 装引擎 + 初始化身份");
1152
+ console.log(" npx @hupan56/wlkj update 日常: 升级 (刷新引擎+补依赖+刷MCP)");
891
1153
  console.log("");
892
- console.log("=== 万能跑 (想跑任何脚本) ===");
893
- console.log(" npx @hupan56/wlkj run <脚本名> [参数]");
894
- console.log(" 例: npx wlkj run kg_build.py --rebuild");
895
- console.log(" npx wlkj run eval_prd.py xxx.md");
896
- console.log("");
897
- console.log("=== Qoder 里怎么用 (日常就在这里干) ===");
1154
+ console.log("=== 日常工作全在 Qoder 里 (不用回终端) ===");
898
1155
  console.log(" 说中文: '写个XX的需求' '查XX代码' '建任务' '测一下XX'");
899
1156
  console.log(" 或斜杠: /wl-prd /wl-design /wl-task /wl-code /wl-test /wl-commit");
1157
+ console.log(" (提交/同步 /wl-task 和 /wl-commit 会自动做, 不用单独 npx sync)");
1158
+ console.log("");
1159
+ console.log("=== 进阶 (管理员/特殊情况才用, 不常用) ===");
1160
+ console.log(" npx @hupan56/wlkj doctor 体检 (或在 Qoder 里 /wl-init 无参数)");
1161
+ console.log(" npx @hupan56/wlkj migrate 老团队一次性升级到 Trellis 架构");
1162
+ console.log(" npx @hupan56/wlkj run <脚本> 万能跑脚本 (AI 内部用, 用户很少需要)");
900
1163
  console.log("");
901
1164
  console.log(" 看不到命令? 新建对话 / 重启 QoderWork");
1165
+ console.log(" 环境问题? python .qoder/scripts/setup/init_doctor.py --fix");
902
1166
  console.log("");
903
1167
  }
904
1168
 
@@ -1065,6 +1329,7 @@ const mapped = rest.map(a => a === "-p" ? "--priority" : a);
1065
1329
  switch (cmd) {
1066
1330
  case "init": doInit(rest[0], rest[1]); break;
1067
1331
  case "update": case "upgrade": doUpdate(); break;
1332
+ case "migrate": doMigrate(); break;
1068
1333
  case "install-env": {
1069
1334
  const checkOnly = rest.includes("--check");
1070
1335
  // 提取 --location <dir> 或 --location=<dir>
@@ -1096,6 +1361,7 @@ switch (cmd) {
1096
1361
  // npx wlkj run eval_prd.py xxx.md
1097
1362
  // npx wlkj run autotest.py quick
1098
1363
  case "run": {
1364
+ // 万能跑脚本 (AI 内部用, 用户日常不直接用 — 用 /命令 或 init/update)
1099
1365
  if (!rest[0]) { console.log("用法: npx wlkj run <脚本名> [参数...]\n例: npx wlkj run kg_build.py --rebuild"); break; }
1100
1366
  runScript(rest[0], rest.slice(1));
1101
1367
  break;