@dawipong/opcflow 0.4.0 → 0.6.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 (98) hide show
  1. package/README.en.md +3 -1
  2. package/README.md +3 -2
  3. package/dist/cli.mjs +346 -118
  4. package/package.json +3 -1
  5. package/web/dist/assets/{abnfDiagram-VRR7QNED-DAx6EpGI.js → abnfDiagram-VRR7QNED-Cwj4KBKb.js} +1 -1
  6. package/web/dist/assets/{arc-DPoWLLje.js → arc-Ci--dbFA.js} +1 -1
  7. package/web/dist/assets/architecture-TIHT7OUA-Dx_ZsFnL.js +1 -0
  8. package/web/dist/assets/{architectureDiagram-ZJ3FMSHR-Dvr2nr59.js → architectureDiagram-ZJ3FMSHR-B83QZuM-.js} +2 -2
  9. package/web/dist/assets/{blockDiagram-677ZJIJ3-CUYUzMZC.js → blockDiagram-677ZJIJ3-C5lnvD0j.js} +1 -1
  10. package/web/dist/assets/{c4Diagram-LMCZKHZV-DhT2uxnj.js → c4Diagram-LMCZKHZV-Di1TmZQy.js} +1 -1
  11. package/web/dist/assets/channel-Bk0jbqCs.js +1 -0
  12. package/web/dist/assets/{chunk-32BRIVSS-CztnJRkA.js → chunk-32BRIVSS-BWSjcFoD.js} +1 -1
  13. package/web/dist/assets/{chunk-C7G6YPKG-jsbUGVSb.js → chunk-C7G6YPKG-DSkQqM00.js} +1 -1
  14. package/web/dist/assets/{chunk-EX3LRPZG-M3QhKBUG.js → chunk-EX3LRPZG-DOdm7LPI.js} +1 -1
  15. package/web/dist/assets/{chunk-HOUHSVGY-DDAtZMtg.js → chunk-HOUHSVGY-D1LueKb6.js} +1 -1
  16. package/web/dist/assets/{chunk-ICXQ74PX-C2h9d1ob.js → chunk-ICXQ74PX-CFh6Ch4J.js} +1 -1
  17. package/web/dist/assets/{chunk-MOJQB5TN-DXlFl9Jv.js → chunk-MOJQB5TN-BrFLYoih.js} +1 -1
  18. package/web/dist/assets/{chunk-OGEWGWER-B5mrusUD.js → chunk-OGEWGWER-C1n7EcC_.js} +1 -1
  19. package/web/dist/assets/{chunk-PUDLZKDR-BTWHaeTZ.js → chunk-PUDLZKDR-D4rKEnrx.js} +1 -1
  20. package/web/dist/assets/{chunk-Q4XR5HBZ-Cfw3UWMb.js → chunk-Q4XR5HBZ-DVm0yUET.js} +1 -1
  21. package/web/dist/assets/{chunk-RYQCIY6F-CCUojmgb.js → chunk-RYQCIY6F-BlZZOayN.js} +1 -1
  22. package/web/dist/assets/{chunk-V7JOEXUC-Cbgml1c1.js → chunk-V7JOEXUC-Bb-id9Vk.js} +1 -1
  23. package/web/dist/assets/{chunk-VR4S4FIN-CUMv178C.js → chunk-VR4S4FIN-DbbNoX70.js} +1 -1
  24. package/web/dist/assets/{chunk-WYO6CB5R-BaRUDpDl.js → chunk-WYO6CB5R-C0vPAP7X.js} +1 -1
  25. package/web/dist/assets/{chunk-XXDRQBXY-tmq33VNh.js → chunk-XXDRQBXY-BS9H__hw.js} +1 -1
  26. package/web/dist/assets/{chunk-ZGVPDNZ5-DK9a-Iwy.js → chunk-ZGVPDNZ5-BiBGMdZe.js} +1 -1
  27. package/web/dist/assets/classDiagram-OUVF2IWQ-XODf2seK.js +1 -0
  28. package/web/dist/assets/classDiagram-v2-EOCWNBFH-XODf2seK.js +1 -0
  29. package/web/dist/assets/{cose-bilkent-JH36ORCC-BV8ttTSE.js → cose-bilkent-JH36ORCC-TcTcCWyr.js} +1 -1
  30. package/web/dist/assets/{cynefin-VYW2F7L2-BncvlU8s.js → cynefin-VYW2F7L2-D6vfZhRU.js} +1 -1
  31. package/web/dist/assets/{cynefinDiagram-TSTJHNR4-DrrKaXmw.js → cynefinDiagram-TSTJHNR4-BQ_p-vVW.js} +1 -1
  32. package/web/dist/assets/{dagre-VKFMJZFB-BxDsa977.js → dagre-VKFMJZFB-sC49sUfv.js} +1 -1
  33. package/web/dist/assets/{diagram-FQU43EPY-DkeAFpQM.js → diagram-FQU43EPY-znBmjqaC.js} +1 -1
  34. package/web/dist/assets/{diagram-G47NLZAW-BxVnT5Li.js → diagram-G47NLZAW-BFZHUq5q.js} +1 -1
  35. package/web/dist/assets/{diagram-NH7WQ7WH-BT56mGuK.js → diagram-NH7WQ7WH-CnF3KhFt.js} +1 -1
  36. package/web/dist/assets/{diagram-OA4YK3LP-CezybdJA.js → diagram-OA4YK3LP-DyxQbSTl.js} +1 -1
  37. package/web/dist/assets/{diagram-WEI45ONY-CO8JNWNf.js → diagram-WEI45ONY-u45fnoLo.js} +1 -1
  38. package/web/dist/assets/{dist-zlHGdC4Y.js → dist-DeM6hNwF.js} +1 -1
  39. package/web/dist/assets/{ebnfDiagram-CCIWWBDH-B-OkdWz4.js → ebnfDiagram-CCIWWBDH-CRWFKre6.js} +1 -1
  40. package/web/dist/assets/{erDiagram-Q63AITRT-DQNn2L0y.js → erDiagram-Q63AITRT-BWW0EF63.js} +1 -1
  41. package/web/dist/assets/eventmodeling-45OFAUF4-C8O_2_YB.js +1 -0
  42. package/web/dist/assets/flowDiagram-23GEKE2U-ADg31f6W.js +1 -0
  43. package/web/dist/assets/{ganttDiagram-NO4QXBWP-CFmDEUpW.js → ganttDiagram-NO4QXBWP-Cs24RF6t.js} +2 -2
  44. package/web/dist/assets/{gitGraph-TEB2WS4Q-BiEBwxQy.js → gitGraph-TEB2WS4Q-CWN94LWl.js} +1 -1
  45. package/web/dist/assets/{gitGraphDiagram-IHSO6WYX--62v4OfD.js → gitGraphDiagram-IHSO6WYX-B_EZLwDH.js} +1 -1
  46. package/web/dist/assets/index-C58B02oD.css +1 -0
  47. package/web/dist/assets/index-Cf-76g9P.js +420 -0
  48. package/web/dist/assets/{info-DKCQHKI2-BLKxCcH7.js → info-DKCQHKI2-_3TeAGLT.js} +1 -1
  49. package/web/dist/assets/{infoDiagram-FWYZ7A6U-DM429PMt.js → infoDiagram-FWYZ7A6U-C2mnh_tb.js} +1 -1
  50. package/web/dist/assets/{ishikawaDiagram-FXEZZL3T-D-nr9wXb.js → ishikawaDiagram-FXEZZL3T-B_eQA-ns.js} +1 -1
  51. package/web/dist/assets/{journeyDiagram-5HDEW3XC-CsPvawEB.js → journeyDiagram-5HDEW3XC-pWgngszB.js} +1 -1
  52. package/web/dist/assets/{kanban-definition-HUTT4EX6-Cq6fbJBF.js → kanban-definition-HUTT4EX6-ov2PISLL.js} +1 -1
  53. package/web/dist/assets/{line-CE9BvxIa.js → line-DdBTKf-T.js} +1 -1
  54. package/web/dist/assets/linear-BcjVZ8a_.js +1 -0
  55. package/web/dist/assets/{mermaid-parser.core-CAAH_OSq.js → mermaid-parser.core-CICKlqAN.js} +3 -3
  56. package/web/dist/assets/{mindmap-definition-LN4V7U3C-zZ5w-lPU.js → mindmap-definition-LN4V7U3C-0NmEERgv.js} +1 -1
  57. package/web/dist/assets/{packet-7NZHBO7P-ap_XD9pB.js → packet-7NZHBO7P-TpNRNd61.js} +1 -1
  58. package/web/dist/assets/{pegDiagram-2B236MQR-BL13lL1j.js → pegDiagram-2B236MQR-CiAlAiVi.js} +1 -1
  59. package/web/dist/assets/{pie-RZYD4A2V-C7RWGzoe.js → pie-RZYD4A2V-x4UQuGC2.js} +1 -1
  60. package/web/dist/assets/{pieDiagram-ENE6RG2P-B1mmu34A.js → pieDiagram-ENE6RG2P-inbfmWLn.js} +1 -1
  61. package/web/dist/assets/{quadrantDiagram-ABIIQ3AL-CotpR7ZY.js → quadrantDiagram-ABIIQ3AL-BG-L4IlR.js} +1 -1
  62. package/web/dist/assets/{radar-I7S5WNFK-D8ICPnt1.js → radar-I7S5WNFK-ifrr9rf8.js} +1 -1
  63. package/web/dist/assets/{railroad-3IZDKUUU-B7ZJ3IRr.js → railroad-3IZDKUUU-3WK9FStR.js} +1 -1
  64. package/web/dist/assets/railroad-abnf-AHOZXSZD-DI7lfwbp.js +1 -0
  65. package/web/dist/assets/railroad-ebnf-EBAXGLYW-COZaOyR2.js +1 -0
  66. package/web/dist/assets/railroad-peg-LSFZ7HO6-BE8vI_mk.js +1 -0
  67. package/web/dist/assets/{railroadDiagram-RFXS5EU6-BUbf08v3.js → railroadDiagram-RFXS5EU6-zGdHxvjI.js} +1 -1
  68. package/web/dist/assets/{requirementDiagram-TGXJPOKE-CgepHMpy.js → requirementDiagram-TGXJPOKE-DYBqltr1.js} +1 -1
  69. package/web/dist/assets/{sankeyDiagram-HTMAVEWB-9CV0aY5J.js → sankeyDiagram-HTMAVEWB-Cwthl6yk.js} +1 -1
  70. package/web/dist/assets/{sequenceDiagram-DBY2YBRQ-CkIJuJ5O.js → sequenceDiagram-DBY2YBRQ-yoZvzY4D.js} +1 -1
  71. package/web/dist/assets/src-Bp5IDfvJ.js +1 -0
  72. package/web/dist/assets/{stateDiagram-2N3HPSRC-wX6zU3Hv.js → stateDiagram-2N3HPSRC-UXu_N6p7.js} +1 -1
  73. package/web/dist/assets/stateDiagram-v2-6OUMAXLB-K2x-LOU8.js +1 -0
  74. package/web/dist/assets/{swimlanes-5IMT3BWC-YXgGP77C.js → swimlanes-5IMT3BWC-CJml4vNZ.js} +1 -1
  75. package/web/dist/assets/swimlanesDiagram-G3AALYLV-BJTq-tR6.js +8 -0
  76. package/web/dist/assets/{timeline-definition-FHXFAJF6-CtMf684d.js → timeline-definition-FHXFAJF6-DmHx8Bri.js} +1 -1
  77. package/web/dist/assets/{treeView-QDETBFTQ-lUySd_Pd.js → treeView-QDETBFTQ-CW1zfcRK.js} +1 -1
  78. package/web/dist/assets/{treemap-6X3UGDF4-BewRw9PG.js → treemap-6X3UGDF4-ReFheQXq.js} +1 -1
  79. package/web/dist/assets/{vennDiagram-L72KCM5P-DbFoFgBM.js → vennDiagram-L72KCM5P-jIe_jCw3.js} +1 -1
  80. package/web/dist/assets/{wardley-OPB4EBWU-BCrrgDHP.js → wardley-OPB4EBWU-DqwQFz9-.js} +1 -1
  81. package/web/dist/assets/{wardleyDiagram-EHGQE667-C3J4of-x.js → wardleyDiagram-EHGQE667-DEJFp47s.js} +1 -1
  82. package/web/dist/assets/{xychartDiagram-FW5EYKEG-Fbzeb84K.js → xychartDiagram-FW5EYKEG-DBBG2OXr.js} +1 -1
  83. package/web/dist/index.html +12 -12
  84. package/web/dist/assets/architecture-TIHT7OUA-Bgyz4pDY.js +0 -1
  85. package/web/dist/assets/channel-vHKEEV51.js +0 -1
  86. package/web/dist/assets/classDiagram-OUVF2IWQ-Blusw6t4.js +0 -1
  87. package/web/dist/assets/classDiagram-v2-EOCWNBFH-Blusw6t4.js +0 -1
  88. package/web/dist/assets/eventmodeling-45OFAUF4-CA5_fuzk.js +0 -1
  89. package/web/dist/assets/flowDiagram-23GEKE2U-CPO1BGjg.js +0 -1
  90. package/web/dist/assets/index-BUs4sN1r.css +0 -1
  91. package/web/dist/assets/index-HsPwY2CU.js +0 -414
  92. package/web/dist/assets/linear-B8yeZliB.js +0 -1
  93. package/web/dist/assets/railroad-abnf-AHOZXSZD-uSXjMlP2.js +0 -1
  94. package/web/dist/assets/railroad-ebnf-EBAXGLYW-9im8bOEZ.js +0 -1
  95. package/web/dist/assets/railroad-peg-LSFZ7HO6-62Yk_rP2.js +0 -1
  96. package/web/dist/assets/src-W4W9ly-y.js +0 -1
  97. package/web/dist/assets/stateDiagram-v2-6OUMAXLB-CAdDNary.js +0 -1
  98. package/web/dist/assets/swimlanesDiagram-G3AALYLV--Ha4WrTi.js +0 -8
package/dist/cli.mjs CHANGED
@@ -66,6 +66,7 @@ var init_config = __esm({
66
66
  moduleMapping: {},
67
67
  feedbackHalfLifeDays: 15,
68
68
  gates: { approvalMode: "warn", writeGate: "observe" },
69
+ taskPreconditions: [{ role: "qa", type: "qa", requiresSiblingRoleCompleted: "developer" }],
69
70
  git: { taskTrailer: "off", trailerKey: "Task" },
70
71
  legacyDb: "tasks/task.db",
71
72
  dataDir: ".workbench",
@@ -279,7 +280,14 @@ CREATE INDEX IF NOT EXISTS idx_events_event ON events(event);
279
280
  }
280
281
  },
281
282
  // 迁移 3:claim 时记录 git HEAD,供 hotfix 契约触碰检测与 trailer 交叉验证
282
- { version: 3, up: (db) => db.exec("ALTER TABLE tasks ADD COLUMN claim_commit TEXT") }
283
+ { version: 3, up: (db) => db.exec("ALTER TABLE tasks ADD COLUMN claim_commit TEXT") },
284
+ // 迁移 4:边来源(derived=scan 按 parents×坐标推导,对账维护;manual=用户手动声明,可解绑、scan 永不动)
285
+ {
286
+ version: 4,
287
+ up: (db) => db.exec(
288
+ "ALTER TABLE artifact_edges ADD COLUMN source TEXT NOT NULL DEFAULT 'derived' CHECK (source IN ('derived','manual'))"
289
+ )
290
+ }
283
291
  ];
284
292
  CURRENT_SCHEMA_VERSION = MIGRATIONS[MIGRATIONS.length - 1].version;
285
293
  }
@@ -402,6 +410,13 @@ var init_events = __esm({
402
410
  });
403
411
 
404
412
  // core/platforms.ts
413
+ var platforms_exports = {};
414
+ __export(platforms_exports, {
415
+ PLATFORM_IDS: () => PLATFORM_IDS,
416
+ getAdapter: () => getAdapter,
417
+ resolveModel: () => resolveModel,
418
+ resolvePlatforms: () => resolvePlatforms
419
+ });
405
420
  import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync } from "node:fs";
406
421
  import { dirname as dirname2, join as join4 } from "node:path";
407
422
  import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
@@ -493,6 +508,12 @@ var init_platforms = __esm({
493
508
  hooksScanDir: ".claude/hooks",
494
509
  defaultModel: "opus",
495
510
  memoryBlock: (role, lang) => claudeMemory(role, lang),
511
+ projectDirEnvVar: "CLAUDE_PROJECT_DIR",
512
+ parseHookInput: (raw) => ({
513
+ filePath: raw?.tool_input?.file_path ?? raw?.tool_input?.filePath
514
+ }),
515
+ respondBlocked: (msg) => ({ exitCode: 2, stderr: msg }),
516
+ formatModel: (_providerId, modelId) => modelId,
496
517
  notes: [],
497
518
  agentFile: (role) => `${role}.md`,
498
519
  renderAgent: (spec) => yamlFrontmatter(
@@ -536,6 +557,12 @@ var init_platforms = __esm({
536
557
  hooksScanDir: null,
537
558
  defaultModel: "gpt-5.1-codex",
538
559
  memoryBlock: (_role, lang) => agentsMdMemory("Codex", lang),
560
+ projectDirEnvVar: "CODEX_PROJECT_DIR",
561
+ parseHookInput: (raw) => ({
562
+ filePath: raw?.arguments?.file_path ?? raw?.arguments?.filePath
563
+ }),
564
+ respondBlocked: (msg) => ({ exitCode: 2, stderr: msg }),
565
+ formatModel: (_providerId, modelId) => modelId,
539
566
  notes: [
540
567
  'Codex:\u9879\u76EE\u7EA7 .codex/* \u4EC5\u5F53\u9879\u76EE\u88AB\u6807\u8BB0 trusted \u624D\u52A0\u8F7D \u2014\u2014 \u5728 ~/.codex/config.toml \u91CC\u4E3A\u672C\u9879\u76EE\u8BBE trust_level="trusted"',
541
568
  "Codex:skill \u8D70 .agents/skills/(\u975E .codex/skills)"
@@ -576,6 +603,12 @@ var init_platforms = __esm({
576
603
  hooksScanDir: null,
577
604
  defaultModel: "anthropic/claude-opus-4-8",
578
605
  memoryBlock: (_role, lang) => agentsMdMemory("OpenCode", lang),
606
+ projectDirEnvVar: "OPENCODE_PROJECT_DIR",
607
+ parseHookInput: (raw) => ({
608
+ filePath: raw?.args?.file_path ?? raw?.args?.filePath
609
+ }),
610
+ respondBlocked: (msg) => ({ exitCode: 2, stderr: msg }),
611
+ formatModel: (providerId, modelId) => `${providerId}/${modelId}`,
579
612
  notes: [
580
613
  "OpenCode:\u6A21\u578B\u4E32\u662F provider/model \u683C\u5F0F(\u89C1 models.dev);API key \u5EFA\u8BAE\u8D70\u73AF\u5883\u53D8\u91CF\u6216 {env:...}"
581
614
  ],
@@ -644,6 +677,15 @@ export const opcflow = async () => ({
644
677
  hooksScanDir: null,
645
678
  defaultModel: "claude-opus-4-8",
646
679
  memoryBlock: (_role, lang) => cursorMemory(lang),
680
+ projectDirEnvVar: "CURSOR_PROJECT_DIR",
681
+ parseHookInput: (raw) => ({
682
+ filePath: raw?.file_path ?? raw?.filePath
683
+ }),
684
+ respondBlocked: (msg) => ({
685
+ exitCode: 0,
686
+ stdout: JSON.stringify({ permission: "deny", userMessage: msg, agentMessage: msg })
687
+ }),
688
+ formatModel: (_providerId, modelId) => modelId,
647
689
  notes: [
648
690
  "Cursor:\u4E3B agent \u6A21\u578B\u7531 UI \u9009,--model \u53EA\u4F5C\u7528\u4E8E\u751F\u6210\u7684 subagent(.cursor/agents/*.md)"
649
691
  ],
@@ -985,20 +1027,27 @@ function requirementSatisfied(ctx, req, matched) {
985
1027
  (a) => kindSpec(ctx.config, a.kind).approval === "thumbs" ? prototypeEndorsed(ctx.db, a) : reviewStatus(a) === "approved"
986
1028
  );
987
1029
  }
988
- function validateClaim(ctx, task) {
989
- const warnings = [];
990
- const inputs = /* @__PURE__ */ new Map();
991
- const mode = ctx.config.gates.approvalMode;
992
- if (task.role === "qa" && task.type === "qa") {
993
- const dev = ctx.db.prepare(
1030
+ function siblingRoleBlocking(ctx, task) {
1031
+ for (const rule of ctx.config.taskPreconditions ?? []) {
1032
+ if (rule.role !== task.role) continue;
1033
+ if (rule.type && rule.type !== task.type) continue;
1034
+ const done = ctx.db.prepare(
994
1035
  `SELECT COUNT(*) AS c FROM tasks
995
- WHERE role = 'developer' AND status = 'completed' AND module IS ? AND endpoint IS ?
1036
+ WHERE role = ? AND status = 'completed' AND module IS ? AND endpoint IS ?
996
1037
  AND (? IS NULL OR page IS ?)`
997
- ).get(task.module, task.endpoint, task.page, task.page);
998
- if (dev.c === 0) {
999
- throw new Error(`[\u524D\u7F6E\u6761\u4EF6] \u5BF9\u5E94\u7684 developer ${task.endpoint} \u4EFB\u52A1\u5C1A\u672A\u5B8C\u6210,\u65E0\u6CD5\u9886\u53D6 qa \u4EFB\u52A1\u3002`);
1038
+ ).get(rule.requiresSiblingRoleCompleted, task.module, task.endpoint, task.page, task.page);
1039
+ if (done.c === 0) {
1040
+ return `\u5BF9\u5E94\u7684 ${rule.requiresSiblingRoleCompleted} ${task.endpoint ?? ""} \u4EFB\u52A1\u5C1A\u672A\u5B8C\u6210,\u65E0\u6CD5\u9886\u53D6 ${task.role} \u4EFB\u52A1\u3002`;
1000
1041
  }
1001
1042
  }
1043
+ return null;
1044
+ }
1045
+ function validateClaim(ctx, task) {
1046
+ const warnings = [];
1047
+ const inputs = /* @__PURE__ */ new Map();
1048
+ const mode = ctx.config.gates.approvalMode;
1049
+ const blockMsg = siblingRoleBlocking(ctx, task);
1050
+ if (blockMsg) throw new Error(`[\u524D\u7F6E\u6761\u4EF6] ${blockMsg}`);
1002
1051
  if (task.module && (task.type === "build" || task.type === "hotfix") && !moduleCleared(ctx.db, task.module)) {
1003
1052
  warnings.push(
1004
1053
  `[\u6E05\u7B97] \u6A21\u5757 ${task.module} \u672A\u6E05\u7B97(module-prd \u672A\u5BA1\u6279)\u3002\u5EFA\u8BAE\u5148\u5BF9\u8D26:${ctx.config.cli} audit --module=${task.module}`
@@ -1061,19 +1110,18 @@ function validateComplete(ctx, task, status, opts = {}) {
1061
1110
  warnings.push(msg);
1062
1111
  }
1063
1112
  }
1064
- if (task.role === "developer" || task.role === "architect") {
1065
- const violations = runProtocolLints(ctx, { role: task.role, endpoint: task.endpoint });
1066
- if (violations.length > 0) {
1067
- const detail = violations.slice(0, 10).map((v) => ` ${v.file}:${v.line} [${v.lint}] ${v.message}
1113
+ const violations = runProtocolLints(ctx, { role: task.role, endpoint: task.endpoint });
1114
+ if (violations.length > 0) {
1115
+ const detail = violations.slice(0, 10).map((v) => ` ${v.file}:${v.line} [${v.lint}] ${v.message}
1068
1116
  ${v.text}`).join("\n");
1069
- throw new Error(
1070
- `[\u534F\u8BAE lint \u5931\u8D25] ${violations.length} \u5904\u8FDD\u4F8B,\u4FEE\u590D\u540E\u518D\u5B8C\u6210:
1117
+ throw new Error(
1118
+ `[\u534F\u8BAE lint \u5931\u8D25] ${violations.length} \u5904\u8FDD\u4F8B,\u4FEE\u590D\u540E\u518D\u5B8C\u6210:
1071
1119
  ${detail}${violations.length > 10 ? `
1072
1120
  ...\u7B49 ${violations.length} \u5904` : ""}`
1073
- );
1074
- }
1121
+ );
1075
1122
  }
1076
- if (task.role === "developer" && task.endpoint && ctx.config.machineChecks.enabled) {
1123
+ const producesMachineKind = producedKinds(ctx, task).some((k) => kindSpec(ctx.config, k).approval === "machine");
1124
+ if (producesMachineKind && task.endpoint && ctx.config.machineChecks.enabled) {
1077
1125
  const cmds = ctx.config.machineChecks[task.endpoint];
1078
1126
  if (Array.isArray(cmds)) {
1079
1127
  for (const cmd of cmds) {
@@ -1118,7 +1166,7 @@ function taskBlocked(ctx, task) {
1118
1166
  validateClaim(ctx, task);
1119
1167
  return false;
1120
1168
  } catch (err) {
1121
- return err instanceof Error && err.message.includes("\u4E0D\u5B58\u5728");
1169
+ return err instanceof Error && err.message.includes("[\u524D\u7F6E\u6761\u4EF6]");
1122
1170
  }
1123
1171
  }
1124
1172
  function coordHealth(ctx, artifacts, tasks) {
@@ -1199,6 +1247,7 @@ function buildTree(ctx, opts = {}) {
1199
1247
  eTasks.filter((t) => !t.page)
1200
1248
  );
1201
1249
  for (const child of pageNodes) health2 = worse(health2, child.health);
1250
+ if (coordFailed(ctx, module, endpoint, null)) health2 = "failed";
1202
1251
  return {
1203
1252
  key: `${module}/${endpoint}`,
1204
1253
  title: endpoint,
@@ -1747,16 +1796,16 @@ function ownerRole(ctx, kind) {
1747
1796
  if (kind === "code") return "developer";
1748
1797
  return null;
1749
1798
  }
1750
- function hasOpenReview(ctx, artifactId) {
1751
- const row = ctx.db.prepare(
1752
- `SELECT COUNT(*) AS c FROM tasks t
1799
+ function openReviewTargetKeys(ctx, artifactId) {
1800
+ const rows = ctx.db.prepare(
1801
+ `SELECT t.role, t.endpoint, t.module FROM tasks t
1753
1802
  JOIN task_inputs ti ON ti.task_id = t.id
1754
1803
  WHERE t.type = 'review' AND t.status IN ('pending', 'in_progress') AND ti.artifact_id = ?`
1755
- ).get(artifactId);
1756
- return row.c > 0;
1804
+ ).all(artifactId);
1805
+ return new Set(rows.map((r) => `${r.role}|${r.endpoint ?? ""}|${r.module ?? ""}`));
1757
1806
  }
1758
1807
  function spawnReviews(ctx, source, reason) {
1759
- if (hasOpenReview(ctx, source.id)) return 0;
1808
+ const openKeys = openReviewTargetKeys(ctx, source.id);
1760
1809
  const registry = getKindRegistry(ctx.config);
1761
1810
  const downstream = ctx.db.prepare(`SELECT a.* FROM artifacts a JOIN artifact_edges e ON e.to_id = a.id WHERE e.from_id = ?`).all(source.id);
1762
1811
  const targets = /* @__PURE__ */ new Map();
@@ -1765,6 +1814,7 @@ function spawnReviews(ctx, source, reason) {
1765
1814
  const role = ownerRole(ctx, child.kind);
1766
1815
  if (!role) continue;
1767
1816
  const key = `${role}|${child.endpoint ?? ""}|${child.module ?? source.module ?? ""}`;
1817
+ if (openKeys.has(key)) continue;
1768
1818
  targets.set(key, { role, endpoint: child.endpoint, module: child.module ?? source.module });
1769
1819
  }
1770
1820
  let spawned = 0;
@@ -1962,6 +2012,7 @@ function updateTask(ctx, { id, status, operator, force = false }) {
1962
2012
  throw new Error(`\u53EA\u6709\u6267\u884C\u4EBA\u624D\u80FD\u66F4\u65B0\u4EFB\u52A1\u72B6\u6001`);
1963
2013
  }
1964
2014
  const { warnings } = validateComplete(ctx, task, status, { force });
2015
+ let followupQaId = null;
1965
2016
  const tx = ctx.db.transaction(() => {
1966
2017
  ctx.db.prepare("UPDATE tasks SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?").run(status, id);
1967
2018
  logEvent(ctx.db, {
@@ -1974,15 +2025,15 @@ function updateTask(ctx, { id, status, operator, force = false }) {
1974
2025
  endpoint: task.endpoint,
1975
2026
  page: task.page
1976
2027
  });
2028
+ if (task.type === "rework" && status === "completed") {
2029
+ followupQaId = spawnFollowupQa(ctx, task);
2030
+ }
1977
2031
  });
1978
2032
  tx();
2033
+ if (followupQaId !== null) warnings.push(`[\u590D\u9A8C] \u5DF2\u81EA\u52A8\u6D3E\u65B0\u4E00\u8F6E qa \u4EFB\u52A1 #${followupQaId}`);
1979
2034
  if (task.type === "hotfix" && status === "completed") {
1980
2035
  warnings.push(...checkContractTouch(ctx, task, operator));
1981
2036
  }
1982
- if (task.type === "rework" && status === "completed") {
1983
- const qaId = spawnFollowupQa(ctx, task);
1984
- warnings.push(`[\u590D\u9A8C] \u5DF2\u81EA\u52A8\u6D3E\u65B0\u4E00\u8F6E qa \u4EFB\u52A1 #${qaId}`);
1985
- }
1986
2037
  if (status === "completed" && task.external_ref?.startsWith("gh#")) {
1987
2038
  const closed = closeLinkedIssue(ctx, task.external_ref, task.id);
1988
2039
  warnings.push(closed ? `[issue] \u5DF2\u5173\u95ED ${task.external_ref}` : `[issue] ${task.external_ref} \u5173\u95ED\u5931\u8D25(gh \u4E0D\u53EF\u7528),\u8BF7\u624B\u52A8\u5904\u7406`);
@@ -2512,30 +2563,42 @@ function deriveEdges(ctx) {
2512
2563
  list.push(a);
2513
2564
  byKind.set(a.kind, list);
2514
2565
  }
2515
- const insert = ctx.db.prepare("INSERT OR IGNORE INTO artifact_edges (from_id, to_id) VALUES (?, ?)");
2566
+ const expected = /* @__PURE__ */ new Set();
2567
+ for (const child of nonMeta) {
2568
+ const parents = registry[child.kind]?.parents ?? [];
2569
+ for (const parentKind of parents) {
2570
+ const level = registry[parentKind]?.level ?? "module";
2571
+ const candidates = (byKind.get(parentKind) ?? []).filter((p) => {
2572
+ if ((level === "module" || level === "page") && child.module && p.module !== child.module) return false;
2573
+ if ((level === "endpoint" || level === "page") && child.endpoint && p.endpoint !== child.endpoint) return false;
2574
+ if (level === "page" && child.page && p.page !== child.page) return false;
2575
+ return true;
2576
+ });
2577
+ for (const parent of candidates) expected.add(`${parent.id}|${child.id}`);
2578
+ }
2579
+ }
2580
+ const existing = ctx.db.prepare("SELECT id, from_id, to_id, source FROM artifact_edges").all();
2581
+ const have = new Set(existing.map((e) => `${e.from_id}|${e.to_id}`));
2582
+ const insert = ctx.db.prepare("INSERT OR IGNORE INTO artifact_edges (from_id, to_id, source) VALUES (?, ?, 'derived')");
2583
+ const del = ctx.db.prepare("DELETE FROM artifact_edges WHERE id = ?");
2516
2584
  let created = 0;
2585
+ let pruned = 0;
2517
2586
  const tx = ctx.db.transaction(() => {
2518
- for (const child of nonMeta) {
2519
- const parents = registry[child.kind]?.parents ?? [];
2520
- for (const parentKind of parents) {
2521
- const level = registry[parentKind]?.level ?? "module";
2522
- const candidates = (byKind.get(parentKind) ?? []).filter((p) => {
2523
- if ((level === "module" || level === "page") && child.module && p.module !== child.module) return false;
2524
- if ((level === "endpoint" || level === "page") && child.endpoint && p.endpoint !== child.endpoint) return false;
2525
- if (level === "page" && child.page && p.page !== child.page) return false;
2526
- return true;
2527
- });
2528
- for (const parent of candidates) {
2529
- created += insert.run(parent.id, child.id).changes;
2530
- }
2531
- }
2587
+ for (const key of expected) {
2588
+ if (have.has(key)) continue;
2589
+ const [from, to] = key.split("|");
2590
+ created += insert.run(Number(from), Number(to)).changes;
2591
+ }
2592
+ for (const e of existing) {
2593
+ if (e.source !== "derived") continue;
2594
+ if (!expected.has(`${e.from_id}|${e.to_id}`)) pruned += del.run(e.id).changes;
2532
2595
  }
2533
2596
  });
2534
2597
  tx();
2535
- return created;
2598
+ return { created, pruned };
2536
2599
  }
2537
2600
  function scanArtifacts(ctx, actor = "system") {
2538
- const summary = { registered: 0, refreshed: 0, remapped: 0, edges: 0, skipped: [], unresolved: [] };
2601
+ const summary = { registered: 0, refreshed: 0, remapped: 0, edges: 0, edgesPruned: 0, moved: 0, skipped: [], unresolved: [] };
2539
2602
  const files = [];
2540
2603
  for (const dir of Object.values(ctx.config.docs)) {
2541
2604
  const abs = join10(ctx.root, dir);
@@ -2588,6 +2651,26 @@ function scanArtifacts(ctx, actor = "system") {
2588
2651
  }
2589
2652
  const hash = hashPath(join10(ctx.root, relPath));
2590
2653
  if (hash === null) return;
2654
+ const movedFrom = ctx.db.prepare("SELECT * FROM artifacts WHERE content_hash = ? AND kind = ?").all(hash, kind).filter((a) => !existsSync9(join10(ctx.root, a.path)));
2655
+ if (movedFrom.length === 1) {
2656
+ const old = movedFrom[0];
2657
+ const tx2 = ctx.db.transaction(() => {
2658
+ ctx.db.prepare("UPDATE artifacts SET path = ?, module = ?, endpoint = ?, page = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?").run(relPath, coords.module, coords.endpoint, coords.page, old.id);
2659
+ logEvent(ctx.db, {
2660
+ entityType: "artifact",
2661
+ entityId: old.id,
2662
+ event: "auto_moved",
2663
+ actor,
2664
+ payload: { from: old.path, to: relPath },
2665
+ module: coords.module,
2666
+ endpoint: coords.endpoint,
2667
+ page: coords.page
2668
+ });
2669
+ });
2670
+ tx2();
2671
+ summary.moved++;
2672
+ return;
2673
+ }
2591
2674
  const tx = ctx.db.transaction(() => {
2592
2675
  const result = insert.run(kind, coords.module, coords.endpoint, coords.page, relPath, hash);
2593
2676
  logEvent(ctx.db, {
@@ -2615,9 +2698,35 @@ function scanArtifacts(ctx, actor = "system") {
2615
2698
  for (const code of expandCodeRoots(ctx)) {
2616
2699
  registerOne(code.dir, "code", { module: code.module, endpoint: code.endpoint, page: null });
2617
2700
  }
2618
- summary.edges = deriveEdges(ctx);
2701
+ const edgeResult = deriveEdges(ctx);
2702
+ summary.edges = edgeResult.created;
2703
+ summary.edgesPruned = edgeResult.pruned;
2619
2704
  return summary;
2620
2705
  }
2706
+ function registerAdHocArtifact(ctx, relPathRaw, actor) {
2707
+ const relPath = normalizeRelPath(ctx, relPathRaw);
2708
+ const existing = ctx.db.prepare("SELECT * FROM artifacts WHERE path = ?").get(relPath);
2709
+ if (existing) return existing;
2710
+ const hash = hashPath(join10(ctx.root, relPath));
2711
+ if (hash === null) throw new Error(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${relPath}`);
2712
+ const kind = inferKind(relPath, ctx.config);
2713
+ const coords = parseCoords(ctx, kind, relPath) ?? { module: null, endpoint: null, page: null };
2714
+ const tx = ctx.db.transaction(() => {
2715
+ const r = ctx.db.prepare("INSERT INTO artifacts (kind, module, endpoint, page, path, content_hash) VALUES (?, ?, ?, ?, ?, ?)").run(kind, coords.module, coords.endpoint, coords.page, relPath, hash);
2716
+ logEvent(ctx.db, {
2717
+ entityType: "artifact",
2718
+ entityId: r.lastInsertRowid,
2719
+ event: "scan_registered",
2720
+ actor,
2721
+ payload: { path: relPath, kind, adHoc: true },
2722
+ module: coords.module,
2723
+ endpoint: coords.endpoint,
2724
+ page: coords.page
2725
+ });
2726
+ });
2727
+ tx();
2728
+ return ctx.db.prepare("SELECT * FROM artifacts WHERE path = ?").get(relPath);
2729
+ }
2621
2730
  var stripExt;
2622
2731
  var init_scan_command = __esm({
2623
2732
  "core/commands/scan.command.ts"() {
@@ -2873,6 +2982,8 @@ var init_retro_command = __esm({
2873
2982
  });
2874
2983
 
2875
2984
  // core/commands/plan.command.ts
2985
+ import { existsSync as existsSync10 } from "node:fs";
2986
+ import { join as join12 } from "node:path";
2876
2987
  function planModule(ctx, moduleRaw, creator = "product-manager") {
2877
2988
  const module = normalizeModule(moduleRaw, ctx.config);
2878
2989
  const summary = { created: [], skipped: 0, cancelled: 0, warnings: [] };
@@ -2896,7 +3007,7 @@ function planModule(ctx, moduleRaw, creator = "product-manager") {
2896
3007
  if (ctx.config.gates.approvalMode === "enforce") throw new Error(`[\u524D\u7F6E\u6761\u4EF6] ${msg}`);
2897
3008
  summary.warnings.push(msg);
2898
3009
  }
2899
- const pagePrds = byKind("page-prd").filter((a) => a.endpoint && a.page);
3010
+ const pagePrds = byKind("page-prd").filter((a) => a.endpoint && a.page && existsSync10(join12(ctx.root, a.path)));
2900
3011
  const endpoints = [...new Set(pagePrds.map((a) => a.endpoint))];
2901
3012
  const desired = [
2902
3013
  { role: "architect", endpoint: "common", page: null, type: "build", assignee: "architect", content: `\u8BBE\u8BA1 ${module} \u6A21\u5757\u6570\u636E\u5E93` },
@@ -2976,11 +3087,11 @@ var init_plan_command = __esm({
2976
3087
  });
2977
3088
 
2978
3089
  // core/commands/install-hooks.command.ts
2979
- import { chmodSync, existsSync as existsSync10, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "node:fs";
2980
- import { join as join12 } from "node:path";
3090
+ import { chmodSync, existsSync as existsSync11, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "node:fs";
3091
+ import { join as join13 } from "node:path";
2981
3092
  function installGitHooks(ctx) {
2982
- const hooksDir = join12(ctx.root, ".git", "hooks");
2983
- if (!existsSync10(join12(ctx.root, ".git"))) throw new Error("\u4E0D\u662F git \u4ED3\u5E93");
3093
+ const hooksDir = join13(ctx.root, ".git", "hooks");
3094
+ if (!existsSync11(join13(ctx.root, ".git"))) throw new Error("\u4E0D\u662F git \u4ED3\u5E93");
2984
3095
  mkdirSync5(hooksDir, { recursive: true });
2985
3096
  const installed = [];
2986
3097
  const postCommit = `#!/bin/sh
@@ -2988,9 +3099,9 @@ function installGitHooks(ctx) {
2988
3099
  ${WORKBENCH_BIN} postcommit >/dev/null 2>&1 &
2989
3100
  exit 0
2990
3101
  `;
2991
- writeFileSync4(join12(hooksDir, "post-commit"), postCommit);
3102
+ writeFileSync4(join13(hooksDir, "post-commit"), postCommit);
2992
3103
  try {
2993
- chmodSync(join12(hooksDir, "post-commit"), 493);
3104
+ chmodSync(join13(hooksDir, "post-commit"), 493);
2994
3105
  } catch {
2995
3106
  }
2996
3107
  installed.push("post-commit(sync \u5BF9\u8D26)");
@@ -3004,9 +3115,9 @@ if [ -n "$WORKBENCH_TASK_ID" ] && [ "$2" != "merge" ]; then
3004
3115
  fi
3005
3116
  exit 0
3006
3117
  `;
3007
- writeFileSync4(join12(hooksDir, "prepare-commit-msg"), prepare);
3118
+ writeFileSync4(join13(hooksDir, "prepare-commit-msg"), prepare);
3008
3119
  try {
3009
- chmodSync(join12(hooksDir, "prepare-commit-msg"), 493);
3120
+ chmodSync(join13(hooksDir, "prepare-commit-msg"), 493);
3010
3121
  } catch {
3011
3122
  }
3012
3123
  installed.push(`prepare-commit-msg(${ctx.config.git.trailerKey} trailer \u6CE8\u5165)`);
@@ -3021,8 +3132,8 @@ var init_install_hooks_command = __esm({
3021
3132
  });
3022
3133
 
3023
3134
  // core/commands/gen-agents.command.ts
3024
- import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync6, readdirSync as readdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
3025
- import { dirname as dirname3, join as join13 } from "node:path";
3135
+ import { existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as readFileSync6, readdirSync as readdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
3136
+ import { dirname as dirname3, join as join14 } from "node:path";
3026
3137
  function expandPath(ctx, kind) {
3027
3138
  const spec = getKindRegistry(ctx.config)[kind];
3028
3139
  const raw = spec?.pathPatterns?.[0] ?? "";
@@ -3048,8 +3159,8 @@ function parseTemplate(rawInput) {
3048
3159
  function genAgents(ctx, templatesDir) {
3049
3160
  const lang = ctx.config.language;
3050
3161
  const blocks = SHARED[lang] ?? SHARED.zh;
3051
- const dir = templatesDir ?? join13(WORKBENCH_DIR, "templates/agents", lang);
3052
- if (!existsSync11(dir)) throw new Error(`\u6A21\u677F\u76EE\u5F55\u4E0D\u5B58\u5728: ${dir}`);
3162
+ const dir = templatesDir ?? join14(WORKBENCH_DIR, "templates/agents", lang);
3163
+ if (!existsSync12(dir)) throw new Error(`\u6A21\u677F\u76EE\u5F55\u4E0D\u5B58\u5728: ${dir}`);
3053
3164
  const cli = ctx.config.cli;
3054
3165
  const baseTokens = {
3055
3166
  CLI: cli,
@@ -3086,7 +3197,7 @@ function genAgents(ctx, templatesDir) {
3086
3197
  if (!file.endsWith(".md")) continue;
3087
3198
  const role = file.replace(/\.md$/, "");
3088
3199
  if (!pipeline.has(role)) continue;
3089
- const tpl = parseTemplate(readFileSync6(join13(dir, file), "utf-8"));
3200
+ const tpl = parseTemplate(readFileSync6(join14(dir, file), "utf-8"));
3090
3201
  for (const adapter of adapters) {
3091
3202
  const memory = adapter.memoryBlock(role, lang);
3092
3203
  const spec = {
@@ -3097,7 +3208,7 @@ function genAgents(ctx, templatesDir) {
3097
3208
  body: renderTokens(tpl.body, memory)
3098
3209
  };
3099
3210
  const rel = `${adapter.agentsDir}/${adapter.agentFile(role)}`;
3100
- const abs = join13(ctx.root, rel);
3211
+ const abs = join14(ctx.root, rel);
3101
3212
  mkdirSync6(dirname3(abs), { recursive: true });
3102
3213
  writeFileSync5(abs, adapter.renderAgent(spec));
3103
3214
  written.push(rel);
@@ -3181,8 +3292,8 @@ When MCP is registered, prefer the \`wb_*\` typed tools (same source & transacti
3181
3292
  });
3182
3293
 
3183
3294
  // core/commands/audit.command.ts
3184
- import { existsSync as existsSync12 } from "node:fs";
3185
- import { join as join14 } from "node:path";
3295
+ import { existsSync as existsSync13 } from "node:fs";
3296
+ import { join as join15 } from "node:path";
3186
3297
  function auditModule(ctx, moduleRaw) {
3187
3298
  const module = normalizeModule(moduleRaw, ctx.config);
3188
3299
  const kinds = contractKinds(ctx.config);
@@ -3192,9 +3303,9 @@ function auditModule(ctx, moduleRaw) {
3192
3303
  path: a.path,
3193
3304
  status: reviewStatus(a),
3194
3305
  everApproved: everApproved(a),
3195
- onDisk: existsSync12(join14(ctx.root, a.path))
3306
+ onDisk: existsSync13(join15(ctx.root, a.path))
3196
3307
  }));
3197
- const codeDirs = artifacts.filter((a) => a.kind === "code").map((a) => ({ path: a.path, endpoint: a.endpoint, onDisk: existsSync12(join14(ctx.root, a.path)) }));
3308
+ const codeDirs = artifacts.filter((a) => a.kind === "code").map((a) => ({ path: a.path, endpoint: a.endpoint, onDisk: existsSync13(join15(ctx.root, a.path)) }));
3198
3309
  return {
3199
3310
  module,
3200
3311
  cleared: moduleCleared(ctx.db, module),
@@ -3258,28 +3369,28 @@ var init_intake_command = __esm({
3258
3369
  // core/commands/init.command.ts
3259
3370
  import {
3260
3371
  copyFileSync,
3261
- existsSync as existsSync13,
3372
+ existsSync as existsSync14,
3262
3373
  mkdirSync as mkdirSync7,
3263
3374
  readFileSync as readFileSync7,
3264
3375
  readdirSync as readdirSync6,
3265
3376
  writeFileSync as writeFileSync6
3266
3377
  } from "node:fs";
3267
- import { dirname as dirname4, join as join15 } from "node:path";
3378
+ import { dirname as dirname4, join as join16 } from "node:path";
3268
3379
  function deployPreset(root) {
3269
3380
  const deployed = [];
3270
- if (!existsSync13(PRESET_DIR)) return deployed;
3381
+ if (!existsSync14(PRESET_DIR)) return deployed;
3271
3382
  const walk = (rel) => {
3272
- for (const ent of readdirSync6(join15(PRESET_DIR, rel), {
3383
+ for (const ent of readdirSync6(join16(PRESET_DIR, rel), {
3273
3384
  withFileTypes: true
3274
3385
  })) {
3275
- const childRel = rel ? join15(rel, ent.name) : ent.name;
3386
+ const childRel = rel ? join16(rel, ent.name) : ent.name;
3276
3387
  if (ent.isDirectory()) {
3277
3388
  walk(childRel);
3278
3389
  } else if (ent.isFile()) {
3279
- const dest = join15(root, childRel);
3280
- if (existsSync13(dest)) continue;
3390
+ const dest = join16(root, childRel);
3391
+ if (existsSync14(dest)) continue;
3281
3392
  mkdirSync7(dirname4(dest), { recursive: true });
3282
- copyFileSync(join15(PRESET_DIR, childRel), dest);
3393
+ copyFileSync(join16(PRESET_DIR, childRel), dest);
3283
3394
  deployed.push(childRel);
3284
3395
  }
3285
3396
  }
@@ -3288,8 +3399,8 @@ function deployPreset(root) {
3288
3399
  return deployed;
3289
3400
  }
3290
3401
  function ensureRootTsx(root) {
3291
- const pkgPath = join15(root, "package.json");
3292
- if (!existsSync13(pkgPath)) return false;
3402
+ const pkgPath = join16(root, "package.json");
3403
+ if (!existsSync14(pkgPath)) return false;
3293
3404
  const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
3294
3405
  if (pkg.devDependencies?.tsx) return false;
3295
3406
  pkg.devDependencies = { ...pkg.devDependencies ?? {}, tsx: TSX_VERSION };
@@ -3297,8 +3408,8 @@ function ensureRootTsx(root) {
3297
3408
  return true;
3298
3409
  }
3299
3410
  function initProject(root, opts) {
3300
- const configPath = join15(root, CONFIG_FILENAME);
3301
- if (existsSync13(configPath)) {
3411
+ const configPath = join16(root, CONFIG_FILENAME);
3412
+ if (existsSync14(configPath)) {
3302
3413
  throw new Error(
3303
3414
  `${CONFIG_FILENAME} \u5DF2\u5B58\u5728,init \u53EA\u7528\u4E8E\u7A7A\u9879\u76EE\u5F15\u5BFC(\u6539\u914D\u7F6E\u8BF7\u76F4\u63A5\u7F16\u8F91\u8BE5\u6587\u4EF6)`
3304
3415
  );
@@ -3332,10 +3443,10 @@ function initProject(root, opts) {
3332
3443
  const scaffolded = [];
3333
3444
  if (opts.scaffold !== false) {
3334
3445
  for (const dir of DOC_DIRS) {
3335
- const abs = join15(root, dir);
3336
- if (!existsSync13(abs)) {
3446
+ const abs = join16(root, dir);
3447
+ if (!existsSync14(abs)) {
3337
3448
  mkdirSync7(abs, { recursive: true });
3338
- writeFileSync6(join15(abs, ".gitkeep"), "");
3449
+ writeFileSync6(join16(abs, ".gitkeep"), "");
3339
3450
  scaffolded.push(dir);
3340
3451
  }
3341
3452
  }
@@ -3367,7 +3478,7 @@ function initProject(root, opts) {
3367
3478
  }
3368
3479
  const notes = adapters.flatMap((a) => a.notes);
3369
3480
  let hooks = [];
3370
- if (opts.gitHooks !== false && existsSync13(join15(root, ".git"))) {
3481
+ if (opts.gitHooks !== false && existsSync14(join16(root, ".git"))) {
3371
3482
  try {
3372
3483
  hooks = installGitHooks(ctx);
3373
3484
  } catch {
@@ -3409,7 +3520,7 @@ var init_init_command = __esm({
3409
3520
  "docs/design/prototypes",
3410
3521
  "docs/acceptance"
3411
3522
  ];
3412
- PRESET_DIR = join15(WORKBENCH_DIR, "preset");
3523
+ PRESET_DIR = join16(WORKBENCH_DIR, "preset");
3413
3524
  TSX_VERSION = "^4";
3414
3525
  }
3415
3526
  });
@@ -3487,6 +3598,7 @@ __export(core_exports, {
3487
3598
  recordNote: () => recordNote,
3488
3599
  recordQaResult: () => recordQaResult,
3489
3600
  refreshArtifact: () => refreshArtifact,
3601
+ registerAdHocArtifact: () => registerAdHocArtifact,
3490
3602
  registerMetaArtifacts: () => registerMetaArtifacts,
3491
3603
  registerOutput: () => registerOutput,
3492
3604
  rejectArtifact: () => rejectArtifact,
@@ -3696,17 +3808,17 @@ __export(app_exports, {
3696
3808
  import fastifyCors from "@fastify/cors";
3697
3809
  import fastifyStatic from "@fastify/static";
3698
3810
  import Fastify from "fastify";
3699
- import { existsSync as existsSync14, mkdirSync as mkdirSync8, readFileSync as readFileSync8, readdirSync as readdirSync7, statSync as statSync6 } from "node:fs";
3700
- import { isAbsolute as isAbsolute2, join as join16, normalize, relative as relative2 } from "node:path";
3811
+ import { existsSync as existsSync15, mkdirSync as mkdirSync8, readFileSync as readFileSync8, readdirSync as readdirSync7, statSync as statSync6 } from "node:fs";
3812
+ import { isAbsolute as isAbsolute2, join as join17, normalize, relative as relative2 } from "node:path";
3701
3813
  function getArtifact(ctx, id) {
3702
3814
  const row = ctx.db.prepare("SELECT * FROM artifacts WHERE id = ?").get(id);
3703
3815
  if (!row) throw Object.assign(new Error(`artifact #${id} \u4E0D\u5B58\u5728`), { statusCode: 404 });
3704
3816
  return row;
3705
3817
  }
3706
3818
  function guardedAbsPath(ctx, artifact, rel) {
3707
- const base = normalize(join16(ctx.root, artifact.path));
3819
+ const base = normalize(join17(ctx.root, artifact.path));
3708
3820
  if (!rel) return base;
3709
- const target = normalize(join16(base, rel));
3821
+ const target = normalize(join17(base, rel));
3710
3822
  const r = relative2(base, target);
3711
3823
  if (r === "" || r === "." || r.startsWith("..") || isAbsolute2(r)) {
3712
3824
  throw Object.assign(new Error("\u8DEF\u5F84\u8D8A\u754C"), { statusCode: 403 });
@@ -3716,11 +3828,11 @@ function guardedAbsPath(ctx, artifact, rel) {
3716
3828
  async function createServer(ctx) {
3717
3829
  const app = Fastify({ logger: false });
3718
3830
  await app.register(fastifyCors, { origin: true });
3719
- const webDist = join16(WORKBENCH_DIR, "web/dist");
3720
- if (existsSync14(webDist)) {
3831
+ const webDist = join17(WORKBENCH_DIR, "web/dist");
3832
+ if (existsSync15(webDist)) {
3721
3833
  await app.register(fastifyStatic, { root: webDist });
3722
3834
  }
3723
- const protoRoot = join16(ctx.root, ctx.config.docs.design, "prototypes");
3835
+ const protoRoot = join17(ctx.root, ctx.config.docs.design, "prototypes");
3724
3836
  mkdirSync8(protoRoot, { recursive: true });
3725
3837
  await app.register(fastifyStatic, { root: protoRoot, prefix: "/proto/", decorateReply: false });
3726
3838
  app.get("/api/meta", async () => ({
@@ -3755,7 +3867,7 @@ async function createServer(ctx) {
3755
3867
  let content = null;
3756
3868
  let isDirectory = false;
3757
3869
  let missing = false;
3758
- if (!existsSync14(abs)) {
3870
+ if (!existsSync15(abs)) {
3759
3871
  missing = true;
3760
3872
  } else if (statSync6(abs).isDirectory()) {
3761
3873
  isDirectory = true;
@@ -3771,20 +3883,20 @@ async function createServer(ctx) {
3771
3883
  const walk = (dir, rel) => {
3772
3884
  for (const name of readdirSync7(dir).sort()) {
3773
3885
  if (name === "node_modules" || name.startsWith(".")) continue;
3774
- const full = join16(dir, name);
3886
+ const full = join17(dir, name);
3775
3887
  const relPath = rel ? `${rel}/${name}` : name;
3776
3888
  const st = statSync6(full);
3777
3889
  if (st.isDirectory()) walk(full, relPath);
3778
3890
  else files.push({ rel: relPath, size: st.size });
3779
3891
  }
3780
3892
  };
3781
- if (existsSync14(base) && statSync6(base).isDirectory()) walk(base, "");
3893
+ if (existsSync15(base) && statSync6(base).isDirectory()) walk(base, "");
3782
3894
  return { files };
3783
3895
  });
3784
3896
  app.get("/api/artifact/:id/file", async (req) => {
3785
3897
  const artifact = getArtifact(ctx, parseInt(req.params.id));
3786
3898
  const abs = guardedAbsPath(ctx, artifact, req.query.rel);
3787
- if (!existsSync14(abs) || statSync6(abs).isDirectory()) {
3899
+ if (!existsSync15(abs) || statSync6(abs).isDirectory()) {
3788
3900
  throw Object.assign(new Error("\u6587\u4EF6\u4E0D\u5B58\u5728"), { statusCode: 404 });
3789
3901
  }
3790
3902
  if (statSync6(abs).size > MAX_INLINE_SIZE) {
@@ -3825,6 +3937,117 @@ data: {"cursor":${cursor2}}
3825
3937
  }, 500);
3826
3938
  req.raw.on("close", () => clearInterval(timer));
3827
3939
  });
3940
+ app.get("/api/graph", async () => {
3941
+ const registry = getKindRegistry(ctx.config);
3942
+ const rows = ctx.db.prepare("SELECT * FROM artifacts ORDER BY id").all().filter(
3943
+ (a) => !registry[a.kind]?.meta
3944
+ );
3945
+ const ids = new Set(rows.map((a) => a.id));
3946
+ const nodes = rows.map((a) => ({
3947
+ id: a.id,
3948
+ kind: a.kind,
3949
+ path: a.path,
3950
+ module: a.module,
3951
+ endpoint: a.endpoint,
3952
+ page: a.page,
3953
+ review_status: reviewStatus(a),
3954
+ missing: !existsSync15(join17(ctx.root, a.path))
3955
+ }));
3956
+ const edges = ctx.db.prepare("SELECT id, from_id, to_id, source FROM artifact_edges").all().filter((e) => ids.has(e.from_id) && ids.has(e.to_id));
3957
+ return { nodes, edges };
3958
+ });
3959
+ app.get("/api/search", async (req) => {
3960
+ const q = (req.query.q ?? "").trim().toLowerCase();
3961
+ if (!q) return { artifacts: [], files: [] };
3962
+ const like = `%${q.replace(/[%_\\]/g, (c) => "\\" + c)}%`;
3963
+ const artifacts = ctx.db.prepare("SELECT * FROM artifacts WHERE path LIKE ? ESCAPE '\\' ORDER BY path LIMIT 30").all(like).map((a) => ({ id: a.id, kind: a.kind, path: a.path, module: a.module, endpoint: a.endpoint, review_status: reviewStatus(a) }));
3964
+ const registered = new Set(ctx.db.prepare("SELECT path FROM artifacts").all().map((r) => r.path));
3965
+ const files = [];
3966
+ const walk = (dir, rel) => {
3967
+ if (files.length >= 30) return;
3968
+ for (const name of readdirSync7(dir).sort()) {
3969
+ if (files.length >= 30) return;
3970
+ if (name.startsWith(".") || name === "node_modules" || name === "dist" || name === "build") continue;
3971
+ const full = join17(dir, name);
3972
+ const relPath = rel ? `${rel}/${name}` : name;
3973
+ if (statSync6(full).isDirectory()) walk(full, relPath);
3974
+ else if (relPath.toLowerCase().includes(q) && !registered.has(relPath)) files.push(relPath);
3975
+ }
3976
+ };
3977
+ walk(ctx.root, "");
3978
+ return { artifacts, files };
3979
+ });
3980
+ app.post("/api/artifact/register", async (req) => {
3981
+ return registerAdHocArtifact(ctx, req.body.path, req.body.actor || "user");
3982
+ });
3983
+ app.post("/api/edge", async (req, reply) => {
3984
+ const { fromId, toId } = req.body;
3985
+ const actor = req.body.actor || "user";
3986
+ if (!fromId || !toId || fromId === toId) {
3987
+ return reply.code(400).send({ error: "\u65E0\u6548\u7684\u8FB9:\u9700\u8981\u4E24\u4E2A\u4E0D\u540C\u7684\u4EA7\u7269" });
3988
+ }
3989
+ const has = (id) => ctx.db.prepare("SELECT COUNT(*) c FROM artifacts WHERE id = ?").get(id).c > 0;
3990
+ if (!has(fromId) || !has(toId)) return reply.code(404).send({ error: "\u4EA7\u7269\u4E0D\u5B58\u5728" });
3991
+ const seen = /* @__PURE__ */ new Set([toId]);
3992
+ const queue = [toId];
3993
+ const next = ctx.db.prepare("SELECT to_id FROM artifact_edges WHERE from_id = ?");
3994
+ while (queue.length) {
3995
+ const cur = queue.pop();
3996
+ for (const row of next.all(cur)) {
3997
+ if (row.to_id === fromId) return reply.code(400).send({ error: "\u4F1A\u5F62\u6210\u73AF,\u5DF2\u62D2\u7EDD" });
3998
+ if (!seen.has(row.to_id)) {
3999
+ seen.add(row.to_id);
4000
+ queue.push(row.to_id);
4001
+ }
4002
+ }
4003
+ }
4004
+ const r = ctx.db.prepare("INSERT OR IGNORE INTO artifact_edges (from_id, to_id, source) VALUES (?, ?, 'manual')").run(fromId, toId);
4005
+ if (r.changes === 0) return reply.code(409).send({ error: "\u8BE5\u5173\u7CFB\u5DF2\u5B58\u5728" });
4006
+ logEvent(ctx.db, { entityType: "artifact", entityId: toId, event: "edge_added", actor, payload: { fromId, toId, source: "manual" } });
4007
+ return { id: r.lastInsertRowid, fromId, toId, source: "manual" };
4008
+ });
4009
+ app.delete("/api/artifact/:id", async (req, reply) => {
4010
+ const id = parseInt(req.params.id);
4011
+ const row = ctx.db.prepare("SELECT * FROM artifacts WHERE id = ?").get(id);
4012
+ if (!row) return reply.code(404).send({ error: "\u4EA7\u7269\u4E0D\u5B58\u5728" });
4013
+ if (row.approved_hash !== null) return reply.code(403).send({ error: "\u8BE5\u4EA7\u7269\u6709\u5BA1\u6279\u5386\u53F2,\u4E0D\u53EF\u53D6\u6D88\u767B\u8BB0" });
4014
+ const refs = ctx.db.prepare(
4015
+ `SELECT (SELECT COUNT(*) FROM task_inputs WHERE artifact_id = ?) +
4016
+ (SELECT COUNT(*) FROM task_outputs WHERE artifact_id = ?) +
4017
+ (SELECT COUNT(*) FROM artifact_feedback WHERE artifact_id = ?) AS c`
4018
+ ).get(id, id, id);
4019
+ if (refs.c > 0) return reply.code(403).send({ error: "\u8BE5\u4EA7\u7269\u5DF2\u88AB\u4EFB\u52A1/\u53CD\u9988\u5F15\u7528,\u4E0D\u53EF\u53D6\u6D88\u767B\u8BB0" });
4020
+ const tx = ctx.db.transaction(() => {
4021
+ logEvent(ctx.db, {
4022
+ entityType: "artifact",
4023
+ entityId: id,
4024
+ event: "unregistered",
4025
+ actor: req.body?.actor || "user",
4026
+ payload: { path: row.path, kind: row.kind },
4027
+ module: row.module,
4028
+ endpoint: row.endpoint,
4029
+ page: row.page
4030
+ });
4031
+ ctx.db.prepare("DELETE FROM artifacts WHERE id = ?").run(id);
4032
+ });
4033
+ tx();
4034
+ return { ok: true };
4035
+ });
4036
+ app.delete("/api/edge/:id", async (req, reply) => {
4037
+ const id = parseInt(req.params.id);
4038
+ const edge = ctx.db.prepare("SELECT * FROM artifact_edges WHERE id = ?").get(id);
4039
+ if (!edge) return reply.code(404).send({ error: "\u8FB9\u4E0D\u5B58\u5728" });
4040
+ if (edge.source !== "manual") return reply.code(403).send({ error: "\u81EA\u52A8\u63A8\u5BFC\u7684\u5173\u7CFB\u4E0D\u53EF\u89E3\u7ED1(\u7531 scan \u6309\u5750\u6807\u4E8B\u5B9E\u7EF4\u62A4)" });
4041
+ ctx.db.prepare("DELETE FROM artifact_edges WHERE id = ?").run(id);
4042
+ logEvent(ctx.db, {
4043
+ entityType: "artifact",
4044
+ entityId: edge.to_id,
4045
+ event: "edge_removed",
4046
+ actor: req.body?.actor || "user",
4047
+ payload: { fromId: edge.from_id, toId: edge.to_id }
4048
+ });
4049
+ return { ok: true };
4050
+ });
3828
4051
  app.get("/api/review-queue", async () => {
3829
4052
  return listArtifacts(ctx, {}).filter((r) => r.review_status === "pending" || r.review_status === "invalidated").map((r) => ({
3830
4053
  ...r,
@@ -3845,7 +4068,7 @@ data: {"cursor":${cursor2}}
3845
4068
  app.get("/api/artifact/:id/diff", async (req) => {
3846
4069
  const artifact = getArtifact(ctx, parseInt(req.params.id));
3847
4070
  const abs = guardedAbsPath(ctx, artifact);
3848
- const current = existsSync14(abs) && !statSync6(abs).isDirectory() ? readFileSync8(abs, "utf-8") : null;
4071
+ const current = existsSync15(abs) && !statSync6(abs).isDirectory() ? readFileSync8(abs, "utf-8") : null;
3849
4072
  return { approved: approvedContent(ctx, artifact.id), current };
3850
4073
  });
3851
4074
  app.post(
@@ -3894,11 +4117,13 @@ var init_app = __esm({
3894
4117
  "use strict";
3895
4118
  init_artifact_commands();
3896
4119
  init_retro_command();
4120
+ init_scan_command();
3897
4121
  init_sync_command();
3898
4122
  init_config();
3899
4123
  init_task_commands();
3900
4124
  init_derive();
3901
4125
  init_events();
4126
+ init_kind();
3902
4127
  init_tree();
3903
4128
  MAX_INLINE_SIZE = 1024 * 1024;
3904
4129
  }
@@ -3909,15 +4134,17 @@ function hookPlatform() {
3909
4134
  const arg = process.argv.find((a) => a.startsWith("--platform="));
3910
4135
  return arg ? arg.slice("--platform=".length) : "claude";
3911
4136
  }
3912
- function extractFilePath(input) {
4137
+ function extractFilePath(input, adapter) {
4138
+ const fromAdapter = adapter?.parseHookInput(input).filePath;
4139
+ if (fromAdapter) return fromAdapter;
3913
4140
  return input?.tool_input?.file_path ?? // claude PreToolUse/PostToolUse
3914
4141
  input?.tool_input?.filePath ?? input?.file_path ?? // cursor afterFileEdit / 通用
3915
4142
  input?.filePath ?? input?.args?.file_path ?? // opencode tool 参数
3916
4143
  input?.args?.filePath ?? input?.arguments?.file_path ?? // codex 兜底
3917
4144
  input?.arguments?.filePath ?? input?.input?.file_path ?? void 0;
3918
4145
  }
3919
- function hookProjectDir() {
3920
- return process.env.CLAUDE_PROJECT_DIR ?? process.env.CODEX_PROJECT_DIR ?? process.env.CURSOR_PROJECT_DIR ?? process.env.OPENCODE_PROJECT_DIR ?? process.env.WORKBENCH_PROJECT ?? void 0;
4146
+ function hookProjectDir(adapter) {
4147
+ return (adapter && process.env[adapter.projectDirEnvVar]) ?? process.env.CLAUDE_PROJECT_DIR ?? process.env.CODEX_PROJECT_DIR ?? process.env.CURSOR_PROJECT_DIR ?? process.env.OPENCODE_PROJECT_DIR ?? process.env.WORKBENCH_PROJECT ?? void 0;
3921
4148
  }
3922
4149
  async function readStdinJson() {
3923
4150
  const chunks = [];
@@ -3941,14 +4168,16 @@ __export(hook_pretooluse_exports, {
3941
4168
  });
3942
4169
  async function writeGateHook(platform) {
3943
4170
  const input = await readStdinJson();
3944
- const filePath = extractFilePath(input);
4171
+ const { getAdapter: getAdapter2, PLATFORM_IDS: PLATFORM_IDS2 } = await Promise.resolve().then(() => (init_platforms(), platforms_exports));
4172
+ const adapter = PLATFORM_IDS2.includes(platform) ? getAdapter2(platform) : void 0;
4173
+ const filePath = extractFilePath(input, adapter);
3945
4174
  if (!filePath) return;
3946
4175
  const { openWorkbench: openWorkbench2 } = await Promise.resolve().then(() => (init_db(), db_exports));
3947
4176
  const { normalizeRelPath: normalizeRelPath2 } = await Promise.resolve().then(() => (init_artifact_commands(), artifact_commands_exports));
3948
4177
  const { reviewStatus: reviewStatus2 } = await Promise.resolve().then(() => (init_derive(), derive_exports));
3949
4178
  const { contractKinds: contractKinds2 } = await Promise.resolve().then(() => (init_kind(), kind_exports));
3950
4179
  const { logEvent: logEvent2 } = await Promise.resolve().then(() => (init_events(), events_exports));
3951
- const ctx = openWorkbench2(hookProjectDir());
4180
+ const ctx = openWorkbench2(hookProjectDir(adapter));
3952
4181
  const mode = ctx.config.gates.writeGate;
3953
4182
  if (mode === "off") return;
3954
4183
  const rel = normalizeRelPath2(ctx, filePath);
@@ -3975,12 +4204,10 @@ async function writeGateHook(platform) {
3975
4204
  const msg = `\u8BE5\u6587\u4EF6\u662F\u5DF2\u5BA1\u6279\u5951\u7EA6(${artifact.kind}): ${rel}\u3002
3976
4205
  \u4FEE\u6539\u524D\u8BF7\u5148\u9886\u53D6\u5BF9\u5E94\u4EFB\u52A1:${ctx.config.cli} list --module=${artifact.module ?? ""} --status=pending
3977
4206
  \u9886\u53D6\u540E\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF WORKBENCH_TASK_ID=<\u4EFB\u52A1id> \u518D\u4FEE\u6539;\u82E5\u5BF9\u5185\u5BB9\u672C\u8EAB\u6709\u5F02\u8BAE,\u7528 dispute \u547D\u4EE4\u7559\u75D5\u5E76\u505C\u6B62\u3002`;
3978
- if (platform === "cursor") {
3979
- process.stdout.write(JSON.stringify({ permission: "deny", userMessage: msg, agentMessage: msg }));
3980
- process.exit(0);
3981
- }
3982
- console.error(msg);
3983
- process.exit(2);
4207
+ const resp = adapter?.respondBlocked(msg) ?? { exitCode: 2, stderr: msg };
4208
+ if (resp.stdout) process.stdout.write(resp.stdout);
4209
+ if (resp.stderr) console.error(resp.stderr);
4210
+ process.exit(resp.exitCode);
3984
4211
  }
3985
4212
  }
3986
4213
  var init_hook_pretooluse = __esm({
@@ -3999,13 +4226,15 @@ var hook_refresh_exports = {};
3999
4226
  __export(hook_refresh_exports, {
4000
4227
  refreshHook: () => refreshHook
4001
4228
  });
4002
- async function refreshHook(_platform) {
4229
+ async function refreshHook(platform) {
4003
4230
  const input = await readStdinJson();
4004
- const filePath = extractFilePath(input);
4231
+ const { getAdapter: getAdapter2, PLATFORM_IDS: PLATFORM_IDS2 } = await Promise.resolve().then(() => (init_platforms(), platforms_exports));
4232
+ const adapter = platform && PLATFORM_IDS2.includes(platform) ? getAdapter2(platform) : void 0;
4233
+ const filePath = extractFilePath(input, adapter);
4005
4234
  if (!filePath) return;
4006
4235
  const { openWorkbench: openWorkbench2 } = await Promise.resolve().then(() => (init_db(), db_exports));
4007
4236
  const { refreshArtifact: refreshArtifact2, normalizeRelPath: normalizeRelPath2 } = await Promise.resolve().then(() => (init_artifact_commands(), artifact_commands_exports));
4008
- const ctx = openWorkbench2(hookProjectDir());
4237
+ const ctx = openWorkbench2(hookProjectDir(adapter));
4009
4238
  const rel = normalizeRelPath2(ctx, filePath);
4010
4239
  const exact = ctx.db.prepare("SELECT id FROM artifacts WHERE path = ?").get(rel);
4011
4240
  if (exact) {
@@ -4105,7 +4334,6 @@ function printTaskDetail(ctx, id) {
4105
4334
  }
4106
4335
  console.log();
4107
4336
  }
4108
- var ALL_PLATFORMS = ["claude", "codex", "opencode", "cursor"];
4109
4337
  var PRIORITY_PROVIDERS = [
4110
4338
  "anthropic",
4111
4339
  "openai",
@@ -4151,7 +4379,7 @@ async function promptInit() {
4151
4379
  const zh = language === "zh";
4152
4380
  const platform = await select({
4153
4381
  message: zh ? "\u5E73\u53F0" : "Platform",
4154
- choices: ALL_PLATFORMS.map((p) => ({ name: p, value: p })),
4382
+ choices: PLATFORM_IDS.map((p) => ({ name: p, value: p })),
4155
4383
  default: "claude"
4156
4384
  });
4157
4385
  const platforms = [platform];
@@ -4179,14 +4407,14 @@ async function promptInit() {
4179
4407
  choices: prov.models.map((m) => ({ name: m.name, value: m.id })),
4180
4408
  pageSize: 12
4181
4409
  });
4182
- if (modelId) modelObj[p] = p === "opencode" ? `${provId}/${modelId}` : modelId;
4410
+ if (modelId) modelObj[p] = getAdapter(p).formatModel(provId, modelId);
4183
4411
  }
4184
4412
  return { platforms, endpoints, model: Object.keys(modelObj).length ? modelObj : void 0, language };
4185
4413
  }
4186
4414
  async function runInit(root, a) {
4187
- const { existsSync: existsSync15 } = await import("node:fs");
4188
- const { join: join17 } = await import("node:path");
4189
- if (existsSync15(join17(root, "workbench.config.json"))) {
4415
+ const { existsSync: existsSync16 } = await import("node:fs");
4416
+ const { join: join18 } = await import("node:path");
4417
+ if (existsSync16(join18(root, "workbench.config.json"))) {
4190
4418
  console.log(chalk.red("\u9519\u8BEF: workbench.config.json \u5DF2\u5B58\u5728,init \u53EA\u7528\u4E8E\u7A7A\u9879\u76EE\u5F15\u5BFC(\u6539\u914D\u7F6E\u8BF7\u76F4\u63A5\u7F16\u8F91\u8BE5\u6587\u4EF6)"));
4191
4419
  process.exit(1);
4192
4420
  }
@@ -4447,7 +4675,7 @@ async function runCommand(ctx, command, a) {
4447
4675
  }
4448
4676
  case "scan": {
4449
4677
  const s = scanArtifacts(ctx, a.actor || "system");
4450
- console.log(chalk.green(`\u2705 \u626B\u63CF\u5B8C\u6210:\u65B0\u767B\u8BB0 ${s.registered},\u5185\u5BB9\u5237\u65B0 ${s.refreshed},\u91CD\u6302\u5750\u6807 ${s.remapped},\u65B0\u589E\u8FB9 ${s.edges},\u6392\u9664\u5143\u4EA7\u7269 ${s.skipped.length}`));
4678
+ console.log(chalk.green(`\u2705 \u626B\u63CF\u5B8C\u6210:\u65B0\u767B\u8BB0 ${s.registered},\u5185\u5BB9\u5237\u65B0 ${s.refreshed},\u91CD\u6302\u5750\u6807 ${s.remapped},\u81EA\u52A8\u8DDF\u968F\u79FB\u52A8 ${s.moved},\u65B0\u589E\u8FB9 ${s.edges},\u6E05\u7406\u6B8B\u7559\u8FB9 ${s.edgesPruned},\u6392\u9664\u5143\u4EA7\u7269 ${s.skipped.length}`));
4451
4679
  if (s.unresolved.length > 0) {
4452
4680
  console.log(chalk.yellow(`\u26A0 ${s.unresolved.length} \u4E2A\u6587\u4EF6\u547D\u4E2D kind \u4F46\u8DEF\u5F84\u4E0D\u7B26 coords \u6587\u6CD5,\u672A\u767B\u8BB0(\u975E\u89C4\u8303/\u5E9F\u5F03\u7AEF?):`));
4453
4681
  for (const p of s.unresolved) console.log(chalk.yellow(` - ${p}`));