@joekytc/dsh-swarm 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -788,6 +788,175 @@ window.__ModuleLoader__.load({
788
788
 
789
789
  // client/TaskDrawer.tsx
790
790
  var import_react4 = require("react");
791
+
792
+ // client/timeline-model.ts
793
+ var EXCEPTION_KINDS = /* @__PURE__ */ new Set([
794
+ "task/blocked",
795
+ "task/failed",
796
+ "review/failed",
797
+ "review/gave-up",
798
+ "chain/audit-warning",
799
+ "chain/aborted"
800
+ ]);
801
+ var STATUS_OF = {
802
+ "chain/created": "neutral",
803
+ "chain/executing": "running",
804
+ "chain/completed": "success",
805
+ "chain/aborted": "exception",
806
+ "chain/root-task-set": "neutral",
807
+ "chain/audit-warning": "exception",
808
+ "chain/audit-confirmed": "neutral",
809
+ "chain/title-updated": "neutral",
810
+ "spec-card/created": "neutral",
811
+ "spec-card/edited": "neutral",
812
+ "spec-card/approved": "success",
813
+ "task/created": "neutral",
814
+ "task/claimed": "running",
815
+ "task/heartbeat": "running",
816
+ "task/commented": "neutral",
817
+ "task/completed": "success",
818
+ "task/blocked": "exception",
819
+ "task/unblocked": "neutral",
820
+ "task/archived": "neutral",
821
+ "task/failed": "exception",
822
+ "task/renamed": "neutral",
823
+ "review/passed": "success",
824
+ "review/failed": "exception",
825
+ "review/gave-up": "exception"
826
+ };
827
+ function timelineStatusOf(kind) {
828
+ return STATUS_OF[kind] ?? "neutral";
829
+ }
830
+ var KIND_LABEL = {
831
+ "chain/created": "\u94FE\u8DEF\u521B\u5EFA",
832
+ "chain/executing": "\u94FE\u8DEF\u5F00\u59CB\u6267\u884C",
833
+ "chain/completed": "\u94FE\u8DEF\u5B8C\u6210",
834
+ "chain/aborted": "\u94FE\u8DEF\u4E2D\u6B62",
835
+ "chain/root-task-set": "\u8BBE\u4E3A\u6839\u4EFB\u52A1",
836
+ "chain/audit-warning": "\u8D8A\u6743\u8B66\u544A",
837
+ "chain/audit-confirmed": "\u8D8A\u6743\u5DF2\u786E\u8BA4",
838
+ "chain/title-updated": "\u94FE\u8DEF\u6539\u540D",
839
+ "spec-card/created": "\u89C4\u683C\u5361\u521B\u5EFA",
840
+ "spec-card/edited": "\u89C4\u683C\u5361\u7F16\u8F91",
841
+ "spec-card/approved": "\u89C4\u683C\u5361\u6279\u51C6",
842
+ "task/created": "\u4EFB\u52A1\u521B\u5EFA",
843
+ "task/claimed": "\u4EFB\u52A1\u8BA4\u9886",
844
+ "task/heartbeat": "\u4EFB\u52A1\u5FC3\u8DF3",
845
+ "task/commented": "\u8BC4\u8BBA",
846
+ "task/completed": "\u4EFB\u52A1\u5B8C\u6210",
847
+ "task/blocked": "\u4EFB\u52A1\u963B\u585E",
848
+ "task/unblocked": "\u89E3\u9664\u963B\u585E",
849
+ "task/archived": "\u4EFB\u52A1\u5F52\u6863",
850
+ "task/failed": "\u4EFB\u52A1\u5931\u8D25",
851
+ "task/renamed": "\u4EFB\u52A1\u6539\u540D",
852
+ "review/passed": "\u8BC4\u5BA1\u901A\u8FC7",
853
+ "review/failed": "\u8BC4\u5BA1\u9A73\u56DE",
854
+ "review/gave-up": "\u8BC4\u5BA1\u8D85\u9650\u653E\u5F03"
855
+ };
856
+ var AUTHOR_NAME = {
857
+ v: "orchestrator",
858
+ p: "planner",
859
+ w: "wiki-bridge",
860
+ d: "fullstack-dev",
861
+ pt: "plan-review",
862
+ dt: "impl-review",
863
+ system: "\u7CFB\u7EDF",
864
+ human: "\u4F60"
865
+ };
866
+ function eventLabelOf(kind) {
867
+ return KIND_LABEL[kind] ?? kind;
868
+ }
869
+ function isExceptionEvent(e) {
870
+ return EXCEPTION_KINDS.has(e.kind);
871
+ }
872
+ function authorNameOf(author) {
873
+ return AUTHOR_NAME[author] ?? author;
874
+ }
875
+ function asRecord(value) {
876
+ return typeof value === "object" && value !== null ? value : {};
877
+ }
878
+ function strField(rec, key) {
879
+ const v = rec[key];
880
+ return typeof v === "string" ? v : "";
881
+ }
882
+ function truncate(text, max = 120) {
883
+ return text.length <= max ? text : `${text.slice(0, max)}\u2026`;
884
+ }
885
+ function eventSummary(e) {
886
+ const payload = asRecord(e.payload);
887
+ switch (e.kind) {
888
+ case "task/completed": {
889
+ const summary = truncate(strField(payload, "summary"));
890
+ return summary;
891
+ }
892
+ case "task/blocked":
893
+ case "task/failed":
894
+ return truncate(strField(payload, "reason"));
895
+ case "task/commented":
896
+ return truncate(strField(payload, "body"));
897
+ case "task/renamed":
898
+ case "chain/title-updated": {
899
+ const from = strField(payload, "from");
900
+ const to = strField(payload, "to");
901
+ return from || to ? `${from} \u2192 ${to}` : "";
902
+ }
903
+ case "review/passed":
904
+ case "review/failed":
905
+ case "review/gave-up": {
906
+ const evidence = asRecord(payload["evidence"]);
907
+ const verdict = strField(evidence, "verdict") || strField(payload, "verdict");
908
+ const issues = Array.isArray(evidence["issues"]) ? evidence["issues"].length : void 0;
909
+ const parts = [];
910
+ if (verdict) parts.push(`verdict: ${verdict}`);
911
+ if (issues !== void 0) parts.push(`issues: ${issues}`);
912
+ const reason = strField(payload, "reason");
913
+ if (reason) parts.push(reason);
914
+ return truncate(parts.join(" \xB7 "));
915
+ }
916
+ default:
917
+ return "";
918
+ }
919
+ }
920
+ function foldTimeline(events) {
921
+ const sorted = [...events].sort((a, b) => b.seq - a.seq);
922
+ const items = [];
923
+ for (const e of sorted) {
924
+ if (e.kind !== "task/heartbeat") {
925
+ items.push({
926
+ seq: e.seq,
927
+ kind: e.kind,
928
+ label: eventLabelOf(e.kind),
929
+ author: authorNameOf(e.author),
930
+ at: e.at,
931
+ summary: eventSummary(e),
932
+ exception: isExceptionEvent(e),
933
+ status: timelineStatusOf(e.kind)
934
+ });
935
+ continue;
936
+ }
937
+ const prev = items[items.length - 1];
938
+ if (prev && prev.kind === "task/heartbeat") {
939
+ prev.count = (prev.count ?? 1) + 1;
940
+ prev.lastAt = e.at;
941
+ } else {
942
+ items.push({
943
+ seq: e.seq,
944
+ kind: e.kind,
945
+ label: eventLabelOf(e.kind),
946
+ author: authorNameOf(e.author),
947
+ at: e.at,
948
+ lastAt: e.at,
949
+ count: 1,
950
+ summary: "",
951
+ exception: false,
952
+ status: timelineStatusOf(e.kind)
953
+ });
954
+ }
955
+ }
956
+ return items;
957
+ }
958
+
959
+ // client/TaskDrawer.tsx
791
960
  var import_jsx_runtime4 = require("react/jsx-runtime");
792
961
  var ROLE_NAME = {
793
962
  v: "orchestrator",
@@ -921,17 +1090,28 @@ window.__ModuleLoader__.load({
921
1090
  ] }),
922
1091
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { children: task.body || "\u65E0\u9644\u52A0\u4EFB\u52A1\u63CF\u8FF0" })
923
1092
  ] }),
924
- tab === "timeline" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("section", { role: "tabpanel", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ol", { children: timeline.map((event) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("li", { children: [
925
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("code", { children: event.kind }),
926
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { children: [
927
- "seq ",
928
- event.seq,
929
- " \xB7 ",
930
- event.author,
931
- " \xB7 ",
932
- event.at
933
- ] })
934
- ] }, event.seq)) }) }),
1093
+ tab === "timeline" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("section", { role: "tabpanel", children: (() => {
1094
+ const items = foldTimeline(timeline);
1095
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ol", { className: "dsh-kb-timeline", children: items.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1096
+ "li",
1097
+ {
1098
+ className: `dsh-kb-timeline__item dsh-kb-timeline__item--${item.status}${i === 0 ? " dsh-kb-timeline__item--latest" : ""}`,
1099
+ "data-exception": item.exception || void 0,
1100
+ children: [
1101
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dsh-kb-timeline__axis", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dsh-kb-timeline__dot", "aria-hidden": "true" }) }),
1102
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dsh-kb-timeline__body", children: [
1103
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dsh-kb-timeline__row", children: [
1104
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { className: "dsh-kb-timeline__label", children: item.count ? `${item.label}\uFF08${item.count} \u6B21\uFF09` : item.label }),
1105
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("time", { dateTime: new Date(item.at).toISOString(), children: formatTime(item.at) })
1106
+ ] }),
1107
+ item.summary && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "dsh-kb-timeline__summary", title: item.summary, children: item.summary }),
1108
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dsh-kb-timeline__author", children: item.author })
1109
+ ] })
1110
+ ]
1111
+ },
1112
+ item.seq
1113
+ )) });
1114
+ })() }),
935
1115
  tab === "handoff" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("section", { role: "tabpanel", children: [
936
1116
  props.parentTasks && props.parentTasks.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
937
1117
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h4", { children: "\u7236\u4EFB\u52A1\u539F\u6587" }),
@@ -1172,7 +1352,7 @@ window.__ModuleLoader__.load({
1172
1352
  }
1173
1353
 
1174
1354
  // client/kanban.css
1175
- var kanban_default = "/* T32\uFF1A\u5361\u7247/\u8FDE\u63A5\u7EBF\u7B49\u5C40\u90E8\u8FC7\u6E21\u4FDD\u6301 120-180ms\uFF1Bprefers-reduced-motion \u5168\u5C40\u5173\u95ED */\n.dsh-kb-tab {\n /* DSH \u8BBE\u8BA1\u4EE4\u724C\uFF08\u6D45/\u6DF1\u8272\u968F body[data-ds-dark-theme] \u81EA\u52A8\u5207\u6362\uFF09\u2014\u2014\u7981\u6B62\u786C\u7F16\u7801\u989C\u8272 */\n --dsh-kb-surface: var(--dsw-alias-bg-layer-1);\n --dsh-kb-surface-elev: var(--dsw-alias-bg-layer-3);\n --dsh-kb-text-primary: var(--dsw-alias-label-primary);\n --dsh-kb-text-secondary: var(--dsw-alias-label-secondary);\n --dsh-kb-text-tertiary: var(--dsw-alias-label-tertiary);\n --dsh-kb-border: var(--dsw-alias-border-l2);\n --dsh-kb-hover: var(--dsw-alias-interactive-bg-hover);\n --dsh-kb-mask: var(--dsw-alias-bg-mask-3);\n --dsh-kb-input: var(--dsw-specific-input-major);\n --dsh-kb-inline-code: var(--dsw-alias-markdown-inline-code);\n /* \u5927\u5706\u89D2\u5951\u7EA6\uFF08\u5BF9\u9F50 DSH \u5361\u7247 16px\uFF09\uFF0C\u540E\u7EED UI \u4E00\u5F8B\u590D\u7528 */\n --dsh-kb-radius-lg: 16px;\n --dsh-kb-radius-md: 10px;\n --dsh-kb-radius-sm: 8px;\n /* \u72B6\u6001\u8272 \u2192 DSH \u8BED\u4E49\u4EE4\u724C */\n --dsh-kb-complete: var(--dsw-alias-state-success-primary);\n --dsh-kb-active: var(--dsw-alias-state-business-primary);\n --dsh-kb-blocked: var(--dsw-alias-state-error-primary);\n --dsh-kb-pending: currentColor;\n /* \u89D2\u8272\u8272\uFF1ADSH \u6709\u7B49\u4EF7\u4EE4\u724C\u5219\u590D\u7528\uFF1Bp/pt/dt \u65E0 DSH \u7B49\u4EF7\u8272\uFF0C\u4FDD\u7559\u539F\u503C */\n --dsh-kb-v: var(--dsw-alias-state-business-primary);\n --dsh-kb-p: #a855f7;\n --dsh-kb-w: var(--dsw-alias-state-success-primary);\n --dsh-kb-d: var(--dsw-alias-state-warn-primary);\n --dsh-kb-pt: #6366f1; /* \u8BA1\u5212\u8BC4\u5BA1\uFF1A\u975B/\u6D45\u84DD */\n --dsh-kb-dt: #ec4899; /* \u5B9E\u73B0\u8BC4\u5BA1\uFF1A\u73AB\u7EA2 */\n height: 100%;\n width: 100%;\n min-width: 715px; /* \u770B\u677F\u6700\u5C0F\u5BBD\u5EA6 */\n max-width: 780px; /* \u770B\u677F\u6700\u5927\u5BBD\u5EA6 */\n margin-inline: auto;\n display: flex;\n flex-direction: column;\n color: inherit;\n background: var(--dsw-alias-bg-base);\n pointer-events: auto;\n font-size: 13px;\n line-height: 1.45;\n overflow: hidden;\n}\n\n.dsh-kb-banner {\n flex: 0 0 auto;\n padding: 6px 12px;\n font-size: 12px;\n border-bottom: 1px solid var(--dsh-kb-border);\n}\n.dsh-kb-banner--reconnecting,\n.dsh-kb-banner--loading {\n color: var(--dsh-kb-active);\n}\n.dsh-kb-banner--error {\n color: var(--dsh-kb-blocked);\n}\n\n.dsh-kb-tab-body {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n padding: 8px;\n}\n\n.dsh-kb-loading {\n flex: 1 1 auto;\n display: flex;\n align-items: center;\n justify-content: center;\n color: currentColor;\n opacity: 0.7;\n}\n\n.dsh-kb-toolbar {\n position: sticky;\n top: 0;\n z-index: 1;\n padding-bottom: 8px;\n}\n.dsh-kb-toolbar input {\n width: 100%;\n box-sizing: border-box;\n padding: 6px 10px;\n border: 1px solid var(--dsh-kb-border);\n border-radius: var(--dsh-kb-radius-md);\n background: var(--dsh-kb-input);\n color: var(--dsh-kb-text-primary);\n}\n\n.dsh-kb-rail {\n display: flex;\n flex-direction: column;\n}\n/* \u72B6\u6001\u7B5B\u9009 chip \u7EC4\uFF1A\u6267\u884C\u4E2D/\u963B\u585E/\u5931\u8D25/\u5DF2\u5B8C\u6210\uFF08\u591A\u9009\u5E76\u96C6\uFF1B\u7A7A=\u9ED8\u8BA4\u89C6\u56FE\uFF09 */\n.dsh-kb-filters {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n padding: 0 0 8px;\n}\n.dsh-kb-filter {\n display: inline-flex;\n align-items: center;\n padding: 2px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n background: transparent;\n color: currentColor;\n font: inherit;\n cursor: pointer;\n user-select: none;\n transition: background-color 150ms ease, border-color 150ms ease, color 150ms ease;\n}\n.dsh-kb-filter:hover {\n background: var(--dsh-kb-hover);\n}\n.dsh-kb-filter--active {\n border-color: var(--dsh-kb-active);\n color: var(--dsh-kb-active);\n background: color-mix(in srgb, var(--dsh-kb-active) 10%, transparent);\n}\n\n.dsh-kb-chain {\n margin-bottom: 10px;\n}\n.dsh-kb-chain__title {\n display: flex;\n align-items: center;\n gap: 8px;\n box-sizing: border-box;\n width: 100%;\n padding: 6px 8px;\n border: 0;\n border-radius: var(--dsh-kb-radius-sm);\n background: transparent;\n color: var(--dsh-kb-text-primary);\n font: inherit;\n text-align: left;\n cursor: pointer;\n transition: background-color 150ms ease;\n}\n.dsh-kb-chain__title:hover {\n background: var(--dsh-kb-hover);\n}\n/* \u6BCF\u6761\u94FE\u8DEF\u524D\u7684\u6298\u53E0\u7BAD\u5934\uFF08\u4E0E DSH \u6298\u53E0\u4EA4\u4E92\u4E00\u81F4\uFF1A\u7EC6 chevron\uFF0C\u5C55\u5F00\u65F6\u65CB\u8F6C\uFF0C150ms \u8FC7\u6E21\uFF09 */\n.dsh-kb-chain__chevron {\n flex: 0 0 auto;\n width: 7px;\n height: 7px;\n border-top: 1.5px solid currentColor;\n border-right: 1.5px solid currentColor;\n transform: rotate(45deg);\n opacity: 0.7;\n transition: transform 150ms ease;\n}\n.dsh-kb-chain__title[aria-expanded='true'] .dsh-kb-chain__chevron {\n transform: rotate(135deg);\n}\n.dsh-kb-chain__name {\n flex: 1 1 auto;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.dsh-kb-chain__meta {\n flex: 0 0 auto;\n white-space: nowrap;\n opacity: 0.7;\n}\n.dsh-kb-chain__warning {\n padding: 4px 8px;\n margin: 2px 0 6px;\n border-left: 3px solid var(--dsh-kb-blocked);\n color: var(--dsh-kb-blocked);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n/* D23\uFF1A\u94FE\u5B8C\u6210\u9A8C\u6536\u6838\u5BF9\u8B66\u544A\u884C\uFF08completed \u94FE\u672A\u786E\u8BA4\u524D\u963B\u585E\u6700\u7EC8\u6C47\u62A5\uFF09 */\n.dsh-kb-chain__audit {\n display: flex;\n align-items: center;\n gap: 8px;\n white-space: normal;\n}\n.dsh-kb-chain__audit span {\n flex: 1 1 auto;\n overflow: hidden;\n}\n.dsh-kb-audit-confirm {\n flex: 0 0 auto;\n border: 1px solid var(--dsh-kb-blocked);\n border-radius: var(--dsh-kb-radius-sm);\n background: transparent;\n color: var(--dsh-kb-blocked);\n padding: 2px 10px;\n cursor: pointer;\n font: inherit;\n transition: background-color 150ms ease, color 150ms ease;\n}\n.dsh-kb-audit-confirm:hover {\n background: var(--dsh-kb-blocked);\n color: #fff;\n}\n\n.dsh-kb-nodes {\n list-style: none;\n margin: 0;\n padding: 0 0 0 12px;\n display: flex;\n flex-direction: column;\n gap: 0;\n}\n.dsh-kb-node {\n position: relative;\n padding-left: 12px;\n}\n.dsh-kb-node::before {\n content: '';\n position: absolute;\n left: 0;\n top: 0;\n bottom: 0;\n width: 2px;\n background: var(--dsh-kb-pending);\n transition: background-color 150ms ease;\n}\n.dsh-kb-node--complete::before { background: var(--dsh-kb-complete); }\n.dsh-kb-node--active::before { background: var(--dsh-kb-active); }\n.dsh-kb-node--pending::before {\n background: repeating-linear-gradient(to bottom, currentColor 0 3px, transparent 3px 6px);\n}\n.dsh-kb-node--blocked::before {\n background: repeating-linear-gradient(to bottom, var(--dsh-kb-blocked) 0 4px, transparent 4px 8px);\n}\n\n.dsh-kb-task {\n display: grid;\n grid-template-columns: 22px 1fr auto;\n grid-template-rows: auto auto;\n gap: 2px 8px;\n width: 100%;\n box-sizing: border-box;\n padding: 6px 8px;\n margin: 4px 0;\n border: 1px solid transparent;\n border-radius: var(--dsh-kb-radius-lg);\n background: transparent;\n color: var(--dsh-kb-text-primary);\n font: inherit;\n text-align: left;\n cursor: pointer;\n transition: border-color 150ms ease, background-color 150ms ease, opacity 150ms ease;\n}\n.dsh-kb-task:hover {\n background: var(--dsh-kb-hover);\n}\n.dsh-kb-task[data-selected],\n.dsh-kb-task--active {\n border-color: var(--dsh-kb-active);\n}\n/* T32\uFF1A\u5B8C\u6210\u4EFB\u52A1\u4FDD\u7559\u5728\u8F68\u9053\u4F46\u89C6\u89C9\u964D\u6743\uFF1B\u72B6\u6001\u8272\u4E0D\u53D8\u3001\u4E0D\u6574\u5361\u67D3\u8272 */\n.dsh-kb-task--complete {\n opacity: 0.72;\n}\n.dsh-kb-task__title {\n grid-column: 2;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.dsh-kb-task__status {\n grid-column: 3;\n color: currentColor;\n opacity: 0.75;\n}\n.dsh-kb-task__meta {\n grid-column: 2 / 4;\n opacity: 0.6;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.dsh-kb-task__warn {\n grid-column: 2 / 4;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n color: var(--dsh-kb-blocked);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.dsh-kb-task__warn svg {\n flex: 0 0 auto;\n}\n.dsh-kb-task__warn span {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.dsh-kb-task--complete .dsh-kb-task__status { color: var(--dsh-kb-complete); }\n.dsh-kb-task--blocked .dsh-kb-task__status { color: var(--dsh-kb-blocked); }\n/* D17\uFF1A\u9009\u4E2D\u4EFB\u52A1\u6240\u5728\u94FE\u8DEF\u7684\u4E0A\u6E38/\u5F53\u524D/\u4E0B\u6E38\u8DEF\u5F84\u5361\u9AD8\u4EAE\uFF1B\u4E0D\u6539\u53D8\u8FDE\u63A5\u7EBF\u8BED\u4E49\uFF0C\u9009\u4E2D\u5361\u4FDD\u6301\u5F3A\u8FB9\u6846 */\n.dsh-kb-task--related:not(.dsh-kb-task--active) {\n border-color: color-mix(in srgb, var(--dsh-kb-active) 35%, transparent);\n background: color-mix(in srgb, var(--dsh-kb-active) 6%, transparent);\n}\n\n.dsh-kb-empty {\n padding: 24px 12px;\n text-align: center;\n color: var(--dsh-kb-text-secondary);\n border: 1px dashed var(--dsh-kb-border);\n border-radius: var(--dsh-kb-radius-lg);\n}\n\n.dsh-kb-profile {\n grid-row: 1 / 3;\n align-self: start;\n width: 20px;\n height: 20px;\n border-radius: var(--dsh-kb-radius-sm);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-size: 11px;\n font-weight: 700;\n color: #fff;\n}\n.dsh-kb-profile--v { background: var(--dsh-kb-v); }\n.dsh-kb-profile--p { background: var(--dsh-kb-p); }\n.dsh-kb-profile--w { background: var(--dsh-kb-w); }\n.dsh-kb-profile--d { background: var(--dsh-kb-d); }\n.dsh-kb-profile--pt { background: var(--dsh-kb-pt); }\n.dsh-kb-profile--dt { background: var(--dsh-kb-dt); }\n\n/* T27\uFF1A\u4EFB\u52A1\u5361\u5F39\u7A97\uFF08\u539F\u4F4D\u8BE6\u60C5\uFF09\u2014\u2014DSH \u9762\u677F\u98CE\u683C\uFF1A\u5927\u5706\u89D2 + \u62AC\u5347\u8868\u9762 + \u6D45/\u6DF1\u8272\u4EE4\u724C */\n.dsh-kb-detail {\n display: flex;\n flex-direction: column;\n min-height: calc(100% - 16px);\n margin: 8px;\n padding: 12px 16px;\n box-sizing: border-box;\n border: 1px solid var(--dsh-kb-border);\n border-radius: var(--dsh-kb-radius-lg);\n background: var(--dsh-kb-surface);\n color: var(--dsh-kb-text-primary);\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);\n}\n.dsh-kb-detail__header {\n display: flex;\n align-items: center;\n gap: 8px;\n padding-bottom: 8px;\n border-bottom: 1px solid var(--dsh-kb-border);\n}\n.dsh-kb-detail__header > button:not(.dsh-kb-unread) {\n flex: 0 0 auto;\n width: 28px;\n height: 28px;\n border: 1px solid var(--dsh-kb-border);\n border-radius: var(--dsh-kb-radius-md);\n background: transparent;\n color: var(--dsh-kb-text-primary);\n cursor: pointer;\n font: inherit;\n line-height: 1;\n transition: background-color 150ms ease;\n}\n.dsh-kb-detail__header > button:not(.dsh-kb-unread):hover {\n background: var(--dsh-kb-hover);\n}\n.dsh-kb-detail__identity {\n flex: 1 1 auto;\n min-width: 0;\n}\n.dsh-kb-detail__identity strong,\n.dsh-kb-detail__identity span {\n display: block;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.dsh-kb-detail__actions {\n display: flex;\n gap: 4px;\n flex: 0 0 auto;\n}\n.dsh-kb-detail__actions button {\n border: 1px solid var(--dsh-kb-border);\n border-radius: var(--dsh-kb-radius-md);\n background: transparent;\n color: var(--dsh-kb-text-primary);\n padding: 4px 10px;\n cursor: pointer;\n font: inherit;\n transition: border-color 150ms ease, color 150ms ease, background-color 150ms ease;\n}\n.dsh-kb-detail__actions button:hover {\n background: var(--dsh-kb-hover);\n}\n.dsh-kb-detail__actions button[data-confirming='true'] {\n border-color: var(--dsh-kb-blocked);\n color: var(--dsh-kb-blocked);\n}\n.dsh-kb-detail__actions button:disabled {\n opacity: 0.5;\n cursor: default;\n}\n.dsh-kb-action-form {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n}\n.dsh-kb-action-form input {\n width: 140px;\n padding: 3px 8px;\n border: 1px solid var(--dsh-kb-border);\n border-radius: var(--dsh-kb-radius-sm);\n background: var(--dsh-kb-input);\n color: var(--dsh-kb-text-primary);\n font: inherit;\n font-size: 12px;\n}\n.dsh-kb-unread {\n flex: 0 0 auto;\n border: 1px solid var(--dsh-kb-active);\n border-radius: 999px;\n background: color-mix(in srgb, var(--dsh-kb-active) 12%, transparent);\n color: var(--dsh-kb-active);\n padding: 2px 8px;\n font: inherit;\n font-size: 12px;\n white-space: nowrap;\n cursor: pointer;\n}\n.dsh-kb-action-error {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 8px;\n margin: 4px 0;\n border-left: 3px solid var(--dsh-kb-blocked);\n color: var(--dsh-kb-blocked);\n font-size: 12px;\n}\n.dsh-kb-action-error span {\n flex: 1 1 auto;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.dsh-kb-action-error button {\n flex: 0 0 auto;\n border: 1px solid currentColor;\n border-radius: var(--dsh-kb-radius-sm);\n background: transparent;\n color: inherit;\n padding: 1px 6px;\n cursor: pointer;\n font: inherit;\n}\n.dsh-kb-detail [role='tablist'] {\n display: flex;\n gap: 4px;\n padding: 8px 0;\n border-bottom: 1px solid var(--dsh-kb-border);\n}\n.dsh-kb-detail [role='tab'] {\n border: 0;\n border-radius: var(--dsh-kb-radius-sm);\n background: transparent;\n color: var(--dsh-kb-text-secondary);\n padding: 4px 10px;\n cursor: pointer;\n transition: background-color 150ms ease, color 150ms ease;\n}\n.dsh-kb-detail [role='tab']:hover {\n color: var(--dsh-kb-text-primary);\n background: var(--dsh-kb-hover);\n}\n.dsh-kb-detail [role='tab'][aria-selected='true'] {\n background: var(--dsh-kb-hover);\n color: var(--dsh-kb-text-primary);\n}\n.dsh-kb-detail [role='tabpanel'] {\n padding: 8px 0;\n overflow: auto;\n}\n.dsh-kb-detail h4 {\n margin: 8px 0 4px;\n font-size: 12px;\n text-transform: none;\n color: var(--dsh-kb-text-secondary);\n}\n.dsh-kb-detail code {\n background: var(--dsh-kb-inline-code);\n border-radius: var(--dsh-kb-radius-sm);\n padding: 1px 4px;\n}\n.dsh-kb-detail dl {\n display: grid;\n grid-template-columns: auto 1fr;\n gap: 4px 8px;\n margin: 8px 0;\n}\n.dsh-kb-detail dt { opacity: 0.7; color: var(--dsh-kb-text-tertiary); }\n.dsh-kb-detail dd { margin: 0; }\n.dsh-kb-detail time { opacity: 0.6; font-size: 12px; margin-inline-end: 4px; }\n\n.dsh-kb-spec-attachments {\n list-style: none;\n margin: 4px 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.dsh-kb-spec-attachments li {\n display: flex;\n align-items: baseline;\n gap: 6px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.dsh-kb-spec-attachment__kind {\n font-size: 11px;\n border: 1px solid var(--dsh-kb-border);\n border-radius: var(--dsh-kb-radius-sm);\n padding: 0 4px;\n opacity: 0.8;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .dsh-kb-tab * {\n transition-duration: 0ms !important;\n }\n}\n\n/* \u6574\u94FE\u5220\u9664\u6309\u94AE\uFF1A\u9ED8\u8BA4\u9690\u85CF\uFF0Chover \u9700\u6C42\u6807\u9898\u884C\u65F6\u51FA\u73B0\uFF08GUI \u4EC5 human\uFF0C\u4E8C\u6B21\u786E\u8BA4\uFF09 */\n.dsh-kb-chain__delete {\n flex: 0 0 auto;\n border: 0;\n background: transparent;\n color: currentColor;\n opacity: 0;\n font: inherit;\n line-height: 1;\n padding: 2px 4px;\n cursor: pointer;\n transition: opacity 150ms ease, color 150ms ease;\n}\n.dsh-kb-chain__title:hover .dsh-kb-chain__delete,\n.dsh-kb-chain__delete:focus-visible { opacity: 0.7; }\n.dsh-kb-chain__delete:hover { opacity: 1; color: var(--dsh-kb-blocked); }\n/* \u5220\u9664\u4E8C\u6B21\u786E\u8BA4\u5F39\u7A97\uFF08\u590D\u7528 rename \u5F39\u7A97\u5916\u58F3/\u5706\u89D2\u5951\u7EA6\uFF09 */\n.dsh-kb-delete-modal__text {\n color: var(--dsh-kb-text-secondary);\n font-size: 13px;\n line-height: 1.5;\n}\n.dsh-kb-delete-modal__error {\n color: var(--dsh-kb-blocked);\n font-size: 12px;\n}\n.dsh-kb-rename-modal__actions .dsh-kb-delete-confirm {\n border-color: transparent;\n background: var(--dsw-alias-state-error-primary);\n color: #fff;\n}\n/* T7\uFF1A\u94FE\u6807\u9898\u6539\u540D\u94C5\u7B14\u6309\u94AE + \u8F7B\u91CF\u5F39\u7A97\uFF08GUI \u4EC5 human\uFF1B\u89D2\u8272\u5361\u4E0D\u53EF\u6539\u540D\uFF09 */\n.dsh-kb-chain__rename {\n flex: 0 0 auto;\n border: 0;\n background: transparent;\n color: currentColor;\n opacity: 0.55;\n font: inherit;\n line-height: 1;\n padding: 2px 4px;\n cursor: pointer;\n}\n.dsh-kb-chain__rename:hover { opacity: 1; }\n.dsh-kb-task__status-row {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n.dsh-kb-rename-overlay {\n position: fixed;\n inset: 0;\n z-index: 1000;\n background: var(--dsh-kb-mask);\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.dsh-kb-rename-modal {\n display: flex;\n flex-direction: column;\n gap: 8px;\n min-width: 280px;\n padding: 16px;\n border: 1px solid var(--dsh-kb-border);\n border-radius: var(--dsh-kb-radius-lg);\n background: var(--dsh-kb-surface-elev);\n color: var(--dsh-kb-text-primary);\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);\n}\n.dsh-kb-rename-modal__label {\n color: var(--dsh-kb-text-primary);\n font-size: 13px;\n font-weight: 600;\n}\n.dsh-kb-rename-modal__input {\n padding: 6px 10px;\n border: 1px solid var(--dsh-kb-border);\n border-radius: var(--dsh-kb-radius-md);\n background: var(--dsh-kb-input);\n color: var(--dsh-kb-text-primary);\n font: inherit;\n}\n.dsh-kb-rename-modal__input:focus {\n outline: none;\n border-color: var(--dsh-kb-active);\n}\n.dsh-kb-rename-modal__actions {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n}\n.dsh-kb-rename-modal__actions button {\n border: 1px solid var(--dsh-kb-border);\n border-radius: var(--dsh-kb-radius-md);\n background: transparent;\n color: var(--dsh-kb-text-primary);\n font: inherit;\n padding: 4px 14px;\n cursor: pointer;\n transition: background-color 150ms ease;\n}\n.dsh-kb-rename-modal__actions button:hover {\n background: var(--dsh-kb-hover);\n}\n.dsh-kb-rename-modal__actions .dsh-kb-rename-save {\n border-color: transparent;\n background: var(--dsw-alias-button-primary-fill);\n color: var(--dsw-alias-label-primary-foreground);\n}\n";
1355
+ var kanban_default = "/* T32\uFF1A\u5361\u7247/\u8FDE\u63A5\u7EBF\u7B49\u5C40\u90E8\u8FC7\u6E21\u4FDD\u6301 120-180ms\uFF1Bprefers-reduced-motion \u5168\u5C40\u5173\u95ED */\n.dsh-kb-tab {\n /* DSH \u8BBE\u8BA1\u4EE4\u724C\uFF08\u6D45/\u6DF1\u8272\u968F body[data-ds-dark-theme] \u81EA\u52A8\u5207\u6362\uFF09\u2014\u2014\u7981\u6B62\u786C\u7F16\u7801\u989C\u8272 */\n --dsh-kb-surface: var(--dsw-alias-bg-layer-1);\n --dsh-kb-surface-elev: var(--dsw-alias-bg-layer-3);\n --dsh-kb-text-primary: var(--dsw-alias-label-primary);\n --dsh-kb-text-secondary: var(--dsw-alias-label-secondary);\n --dsh-kb-text-tertiary: var(--dsw-alias-label-tertiary);\n --dsh-kb-border: var(--dsw-alias-border-l2);\n --dsh-kb-hover: var(--dsw-alias-interactive-bg-hover);\n --dsh-kb-mask: var(--dsw-alias-bg-mask-3);\n --dsh-kb-input: var(--dsw-specific-input-major);\n --dsh-kb-inline-code: var(--dsw-alias-markdown-inline-code);\n /* \u5927\u5706\u89D2\u5951\u7EA6\uFF08\u5BF9\u9F50 DSH \u5361\u7247 16px\uFF09\uFF0C\u540E\u7EED UI \u4E00\u5F8B\u590D\u7528 */\n --dsh-kb-radius-lg: 16px;\n --dsh-kb-radius-md: 10px;\n --dsh-kb-radius-sm: 8px;\n /* \u72B6\u6001\u8272 \u2192 DSH \u8BED\u4E49\u4EE4\u724C */\n --dsh-kb-complete: var(--dsw-alias-state-success-primary);\n --dsh-kb-active: var(--dsw-alias-state-business-primary);\n --dsh-kb-blocked: var(--dsw-alias-state-error-primary);\n --dsh-kb-pending: currentColor;\n /* \u89D2\u8272\u8272\uFF1ADSH \u6709\u7B49\u4EF7\u4EE4\u724C\u5219\u590D\u7528\uFF1Bp/pt/dt \u65E0 DSH \u7B49\u4EF7\u8272\uFF0C\u4FDD\u7559\u539F\u503C */\n --dsh-kb-v: var(--dsw-alias-state-business-primary);\n --dsh-kb-p: #a855f7;\n --dsh-kb-w: var(--dsw-alias-state-success-primary);\n --dsh-kb-d: var(--dsw-alias-state-warn-primary);\n --dsh-kb-pt: #6366f1; /* \u8BA1\u5212\u8BC4\u5BA1\uFF1A\u975B/\u6D45\u84DD */\n --dsh-kb-dt: #ec4899; /* \u5B9E\u73B0\u8BC4\u5BA1\uFF1A\u73AB\u7EA2 */\n height: 100%;\n width: 100%;\n min-width: 715px; /* \u770B\u677F\u6700\u5C0F\u5BBD\u5EA6 */\n max-width: 780px; /* \u770B\u677F\u6700\u5927\u5BBD\u5EA6 */\n margin-inline: auto;\n display: flex;\n flex-direction: column;\n color: inherit;\n background: var(--dsw-alias-bg-base);\n pointer-events: auto;\n font-size: 13px;\n line-height: 1.45;\n overflow: hidden;\n}\n\n.dsh-kb-banner {\n flex: 0 0 auto;\n padding: 6px 12px;\n font-size: 12px;\n border-bottom: 1px solid var(--dsh-kb-border);\n}\n.dsh-kb-banner--reconnecting,\n.dsh-kb-banner--loading {\n color: var(--dsh-kb-active);\n}\n.dsh-kb-banner--error {\n color: var(--dsh-kb-blocked);\n}\n\n.dsh-kb-tab-body {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n padding: 8px;\n}\n\n.dsh-kb-loading {\n flex: 1 1 auto;\n display: flex;\n align-items: center;\n justify-content: center;\n color: currentColor;\n opacity: 0.7;\n}\n\n.dsh-kb-toolbar {\n position: sticky;\n top: 0;\n z-index: 1;\n padding-bottom: 8px;\n}\n.dsh-kb-toolbar input {\n width: 100%;\n box-sizing: border-box;\n padding: 6px 10px;\n border: 1px solid var(--dsh-kb-border);\n border-radius: var(--dsh-kb-radius-md);\n background: var(--dsh-kb-input);\n color: var(--dsh-kb-text-primary);\n}\n\n.dsh-kb-rail {\n display: flex;\n flex-direction: column;\n}\n/* \u72B6\u6001\u7B5B\u9009 chip \u7EC4\uFF1A\u6267\u884C\u4E2D/\u963B\u585E/\u5931\u8D25/\u5DF2\u5B8C\u6210\uFF08\u591A\u9009\u5E76\u96C6\uFF1B\u7A7A=\u9ED8\u8BA4\u89C6\u56FE\uFF09 */\n.dsh-kb-filters {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n padding: 0 0 8px;\n}\n.dsh-kb-filter {\n display: inline-flex;\n align-items: center;\n padding: 2px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n background: transparent;\n color: currentColor;\n font: inherit;\n cursor: pointer;\n user-select: none;\n transition: background-color 150ms ease, border-color 150ms ease, color 150ms ease;\n}\n.dsh-kb-filter:hover {\n background: var(--dsh-kb-hover);\n}\n.dsh-kb-filter--active {\n border-color: var(--dsh-kb-active);\n color: var(--dsh-kb-active);\n background: color-mix(in srgb, var(--dsh-kb-active) 10%, transparent);\n}\n\n.dsh-kb-chain {\n margin-bottom: 10px;\n}\n.dsh-kb-chain__title {\n display: flex;\n align-items: center;\n gap: 8px;\n box-sizing: border-box;\n width: 100%;\n padding: 6px 8px;\n border: 0;\n border-radius: var(--dsh-kb-radius-sm);\n background: transparent;\n color: var(--dsh-kb-text-primary);\n font: inherit;\n text-align: left;\n cursor: pointer;\n transition: background-color 150ms ease;\n}\n.dsh-kb-chain__title:hover {\n background: var(--dsh-kb-hover);\n}\n/* \u6BCF\u6761\u94FE\u8DEF\u524D\u7684\u6298\u53E0\u7BAD\u5934\uFF08\u4E0E DSH \u6298\u53E0\u4EA4\u4E92\u4E00\u81F4\uFF1A\u7EC6 chevron\uFF0C\u5C55\u5F00\u65F6\u65CB\u8F6C\uFF0C150ms \u8FC7\u6E21\uFF09 */\n.dsh-kb-chain__chevron {\n flex: 0 0 auto;\n width: 7px;\n height: 7px;\n border-top: 1.5px solid currentColor;\n border-right: 1.5px solid currentColor;\n transform: rotate(45deg);\n opacity: 0.7;\n transition: transform 150ms ease;\n}\n.dsh-kb-chain__title[aria-expanded='true'] .dsh-kb-chain__chevron {\n transform: rotate(135deg);\n}\n.dsh-kb-chain__name {\n flex: 1 1 auto;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.dsh-kb-chain__meta {\n flex: 0 0 auto;\n white-space: nowrap;\n opacity: 0.7;\n}\n.dsh-kb-chain__warning {\n padding: 4px 8px;\n margin: 2px 0 6px;\n border-left: 3px solid var(--dsh-kb-blocked);\n color: var(--dsh-kb-blocked);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n/* D23\uFF1A\u94FE\u5B8C\u6210\u9A8C\u6536\u6838\u5BF9\u8B66\u544A\u884C\uFF08completed \u94FE\u672A\u786E\u8BA4\u524D\u963B\u585E\u6700\u7EC8\u6C47\u62A5\uFF09 */\n.dsh-kb-chain__audit {\n display: flex;\n align-items: center;\n gap: 8px;\n white-space: normal;\n}\n.dsh-kb-chain__audit span {\n flex: 1 1 auto;\n overflow: hidden;\n}\n.dsh-kb-audit-confirm {\n flex: 0 0 auto;\n border: 1px solid var(--dsh-kb-blocked);\n border-radius: var(--dsh-kb-radius-sm);\n background: transparent;\n color: var(--dsh-kb-blocked);\n padding: 2px 10px;\n cursor: pointer;\n font: inherit;\n transition: background-color 150ms ease, color 150ms ease;\n}\n.dsh-kb-audit-confirm:hover {\n background: var(--dsh-kb-blocked);\n color: #fff;\n}\n\n.dsh-kb-nodes {\n list-style: none;\n margin: 0;\n padding: 0 0 0 12px;\n display: flex;\n flex-direction: column;\n gap: 0;\n}\n.dsh-kb-node {\n position: relative;\n padding-left: 12px;\n}\n.dsh-kb-node::before {\n content: '';\n position: absolute;\n left: 0;\n top: 0;\n bottom: 0;\n width: 2px;\n background: var(--dsh-kb-pending);\n transition: background-color 150ms ease;\n}\n.dsh-kb-node--complete::before { background: var(--dsh-kb-complete); }\n.dsh-kb-node--active::before { background: var(--dsh-kb-active); }\n.dsh-kb-node--pending::before {\n background: repeating-linear-gradient(to bottom, currentColor 0 3px, transparent 3px 6px);\n}\n.dsh-kb-node--blocked::before {\n background: repeating-linear-gradient(to bottom, var(--dsh-kb-blocked) 0 4px, transparent 4px 8px);\n}\n\n.dsh-kb-task {\n display: grid;\n grid-template-columns: 22px 1fr auto;\n grid-template-rows: auto auto;\n gap: 2px 8px;\n width: 100%;\n box-sizing: border-box;\n padding: 6px 8px;\n margin: 4px 0;\n border: 1px solid transparent;\n border-radius: var(--dsh-kb-radius-lg);\n background: transparent;\n color: var(--dsh-kb-text-primary);\n font: inherit;\n text-align: left;\n cursor: pointer;\n transition: border-color 150ms ease, background-color 150ms ease, opacity 150ms ease;\n}\n.dsh-kb-task:hover {\n background: var(--dsh-kb-hover);\n}\n.dsh-kb-task[data-selected],\n.dsh-kb-task--active {\n border-color: var(--dsh-kb-active);\n}\n/* T32\uFF1A\u5B8C\u6210\u4EFB\u52A1\u4FDD\u7559\u5728\u8F68\u9053\u4F46\u89C6\u89C9\u964D\u6743\uFF1B\u72B6\u6001\u8272\u4E0D\u53D8\u3001\u4E0D\u6574\u5361\u67D3\u8272 */\n.dsh-kb-task--complete {\n opacity: 0.72;\n}\n.dsh-kb-task__title {\n grid-column: 2;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.dsh-kb-task__status {\n grid-column: 3;\n color: currentColor;\n opacity: 0.75;\n}\n.dsh-kb-task__meta {\n grid-column: 2 / 4;\n opacity: 0.6;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.dsh-kb-task__warn {\n grid-column: 2 / 4;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n color: var(--dsh-kb-blocked);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.dsh-kb-task__warn svg {\n flex: 0 0 auto;\n}\n.dsh-kb-task__warn span {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.dsh-kb-task--complete .dsh-kb-task__status { color: var(--dsh-kb-complete); }\n.dsh-kb-task--blocked .dsh-kb-task__status { color: var(--dsh-kb-blocked); }\n/* D17\uFF1A\u9009\u4E2D\u4EFB\u52A1\u6240\u5728\u94FE\u8DEF\u7684\u4E0A\u6E38/\u5F53\u524D/\u4E0B\u6E38\u8DEF\u5F84\u5361\u9AD8\u4EAE\uFF1B\u4E0D\u6539\u53D8\u8FDE\u63A5\u7EBF\u8BED\u4E49\uFF0C\u9009\u4E2D\u5361\u4FDD\u6301\u5F3A\u8FB9\u6846 */\n.dsh-kb-task--related:not(.dsh-kb-task--active) {\n border-color: color-mix(in srgb, var(--dsh-kb-active) 35%, transparent);\n background: color-mix(in srgb, var(--dsh-kb-active) 6%, transparent);\n}\n\n.dsh-kb-empty {\n padding: 24px 12px;\n text-align: center;\n color: var(--dsh-kb-text-secondary);\n border: 1px dashed var(--dsh-kb-border);\n border-radius: var(--dsh-kb-radius-lg);\n}\n\n.dsh-kb-profile {\n grid-row: 1 / 3;\n align-self: start;\n width: 20px;\n height: 20px;\n border-radius: var(--dsh-kb-radius-sm);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n font-size: 11px;\n font-weight: 700;\n color: #fff;\n}\n.dsh-kb-profile--v { background: var(--dsh-kb-v); }\n.dsh-kb-profile--p { background: var(--dsh-kb-p); }\n.dsh-kb-profile--w { background: var(--dsh-kb-w); }\n.dsh-kb-profile--d { background: var(--dsh-kb-d); }\n.dsh-kb-profile--pt { background: var(--dsh-kb-pt); }\n.dsh-kb-profile--dt { background: var(--dsh-kb-dt); }\n\n/* T27\uFF1A\u4EFB\u52A1\u5361\u5F39\u7A97\uFF08\u539F\u4F4D\u8BE6\u60C5\uFF09\u2014\u2014DSH \u9762\u677F\u98CE\u683C\uFF1A\u5927\u5706\u89D2 + \u62AC\u5347\u8868\u9762 + \u6D45/\u6DF1\u8272\u4EE4\u724C */\n.dsh-kb-detail {\n display: flex;\n flex-direction: column;\n min-height: calc(100% - 16px);\n margin: 8px;\n padding: 12px 16px;\n box-sizing: border-box;\n border: 1px solid var(--dsh-kb-border);\n border-radius: var(--dsh-kb-radius-lg);\n background: var(--dsh-kb-surface);\n color: var(--dsh-kb-text-primary);\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);\n}\n.dsh-kb-detail__header {\n display: flex;\n align-items: center;\n gap: 8px;\n padding-bottom: 8px;\n border-bottom: 1px solid var(--dsh-kb-border);\n}\n.dsh-kb-detail__header > button:not(.dsh-kb-unread) {\n flex: 0 0 auto;\n width: 28px;\n height: 28px;\n border: 1px solid var(--dsh-kb-border);\n border-radius: var(--dsh-kb-radius-md);\n background: transparent;\n color: var(--dsh-kb-text-primary);\n cursor: pointer;\n font: inherit;\n line-height: 1;\n transition: background-color 150ms ease;\n}\n.dsh-kb-detail__header > button:not(.dsh-kb-unread):hover {\n background: var(--dsh-kb-hover);\n}\n.dsh-kb-detail__identity {\n flex: 1 1 auto;\n min-width: 0;\n}\n.dsh-kb-detail__identity strong,\n.dsh-kb-detail__identity span {\n display: block;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.dsh-kb-detail__actions {\n display: flex;\n gap: 4px;\n flex: 0 0 auto;\n}\n.dsh-kb-detail__actions button {\n border: 1px solid var(--dsh-kb-border);\n border-radius: var(--dsh-kb-radius-md);\n background: transparent;\n color: var(--dsh-kb-text-primary);\n padding: 4px 10px;\n cursor: pointer;\n font: inherit;\n transition: border-color 150ms ease, color 150ms ease, background-color 150ms ease;\n}\n.dsh-kb-detail__actions button:hover {\n background: var(--dsh-kb-hover);\n}\n.dsh-kb-detail__actions button[data-confirming='true'] {\n border-color: var(--dsh-kb-blocked);\n color: var(--dsh-kb-blocked);\n}\n.dsh-kb-detail__actions button:disabled {\n opacity: 0.5;\n cursor: default;\n}\n.dsh-kb-action-form {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n}\n.dsh-kb-action-form input {\n width: 140px;\n padding: 3px 8px;\n border: 1px solid var(--dsh-kb-border);\n border-radius: var(--dsh-kb-radius-sm);\n background: var(--dsh-kb-input);\n color: var(--dsh-kb-text-primary);\n font: inherit;\n font-size: 12px;\n}\n.dsh-kb-unread {\n flex: 0 0 auto;\n border: 1px solid var(--dsh-kb-active);\n border-radius: 999px;\n background: color-mix(in srgb, var(--dsh-kb-active) 12%, transparent);\n color: var(--dsh-kb-active);\n padding: 2px 8px;\n font: inherit;\n font-size: 12px;\n white-space: nowrap;\n cursor: pointer;\n}\n.dsh-kb-action-error {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 8px;\n margin: 4px 0;\n border-left: 3px solid var(--dsh-kb-blocked);\n color: var(--dsh-kb-blocked);\n font-size: 12px;\n}\n.dsh-kb-action-error span {\n flex: 1 1 auto;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.dsh-kb-action-error button {\n flex: 0 0 auto;\n border: 1px solid currentColor;\n border-radius: var(--dsh-kb-radius-sm);\n background: transparent;\n color: inherit;\n padding: 1px 6px;\n cursor: pointer;\n font: inherit;\n}\n.dsh-kb-detail [role='tablist'] {\n display: flex;\n gap: 4px;\n padding: 8px 0;\n border-bottom: 1px solid var(--dsh-kb-border);\n}\n.dsh-kb-detail [role='tab'] {\n border: 0;\n border-radius: var(--dsh-kb-radius-sm);\n background: transparent;\n color: var(--dsh-kb-text-secondary);\n padding: 4px 10px;\n cursor: pointer;\n transition: background-color 150ms ease, color 150ms ease;\n}\n.dsh-kb-detail [role='tab']:hover {\n color: var(--dsh-kb-text-primary);\n background: var(--dsh-kb-hover);\n}\n.dsh-kb-detail [role='tab'][aria-selected='true'] {\n background: var(--dsh-kb-hover);\n color: var(--dsh-kb-text-primary);\n}\n.dsh-kb-detail [role='tabpanel'] {\n padding: 8px 0;\n overflow: auto;\n}\n.dsh-kb-detail h4 {\n margin: 8px 0 4px;\n font-size: 12px;\n text-transform: none;\n color: var(--dsh-kb-text-secondary);\n}\n.dsh-kb-detail code {\n background: var(--dsh-kb-inline-code);\n border-radius: var(--dsh-kb-radius-sm);\n padding: 1px 4px;\n}\n.dsh-kb-detail dl {\n display: grid;\n grid-template-columns: auto 1fr;\n gap: 4px 8px;\n margin: 8px 0;\n}\n.dsh-kb-detail dt { opacity: 0.7; color: var(--dsh-kb-text-tertiary); }\n.dsh-kb-detail dd { margin: 0; }\n.dsh-kb-detail time { opacity: 0.6; font-size: 12px; margin-inline-end: 4px; }\n\n/* \u8F68\u8FF9 tab \u53EF\u8BFB\u5316\uFF1A\u7269\u6D41\u5F0F\u7EB5\u5411\u65F6\u95F4\u8F74\uFF08\u56DB\u6001\u914D\u8272 + \u6700\u65B0\u8282\u70B9\u9AD8\u4EAE\uFF1B\u5F02\u5E38\u6574\u884C\u6807\u7EA2\uFF09 */\n.dsh-kb-timeline {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n}\n.dsh-kb-timeline__item {\n position: relative;\n display: grid;\n grid-template-columns: 16px 1fr;\n gap: 8px;\n padding: 0 0 14px;\n}\n.dsh-kb-timeline__item:last-child {\n padding-bottom: 0;\n}\n/* \u7EB5\u5411\u65F6\u95F4\u7EBF\uFF1A\u8D2F\u7A7F\u8282\u70B9\uFF08\u6700\u65B0\u8282\u70B9\u4E0A\u65B9\u7EBF\u4E0D\u5EF6\u4F38\uFF0C\u6A21\u62DF\u7269\u6D41\u5934\u8282\u70B9\uFF09 */\n.dsh-kb-timeline__axis {\n position: relative;\n}\n.dsh-kb-timeline__item:not(:last-child) .dsh-kb-timeline__axis::after {\n content: '';\n position: absolute;\n left: 50%;\n top: 14px;\n bottom: -4px;\n width: 2px;\n transform: translateX(-50%);\n background: var(--dsh-kb-border);\n}\n.dsh-kb-timeline__dot {\n position: relative;\n z-index: 1;\n display: block;\n width: 10px;\n height: 10px;\n /* \u6C34\u5E73\u5C45\u4E2D\u4E8E 16px \u8F74\u5217\uFF08\u4E2D\u5FC3=8px\uFF09\uFF0C\u4E0E left:50% \u8FDE\u63A5\u7EBF\u5BF9\u9F50 */\n margin: 2px auto 0;\n border-radius: 50%;\n border: 2px solid var(--dsh-kb-surface);\n box-sizing: border-box;\n background: var(--dsh-kb-text-tertiary);\n}\n/* \u56DB\u6001\u914D\u8272\uFF1Aneutral \u7070 / running \u84DD / success \u7EFF / exception \u7EA2 */\n.dsh-kb-timeline__item--running .dsh-kb-timeline__dot {\n background: var(--dsh-kb-active);\n}\n.dsh-kb-timeline__item--success .dsh-kb-timeline__dot {\n background: var(--dsh-kb-complete);\n}\n.dsh-kb-timeline__item--exception .dsh-kb-timeline__dot {\n background: var(--dsh-kb-blocked);\n}\n/* \u6700\u65B0\u8282\u70B9\u9AD8\u4EAE\uFF1A\u4E3B\u8272\u5706\u70B9\u653E\u5927 + \u67D4\u548C\u8F89\u5149 */\n.dsh-kb-timeline__item--latest .dsh-kb-timeline__dot {\n width: 12px;\n height: 12px;\n margin-top: 0;\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--dsh-kb-active) 22%, transparent);\n}\n.dsh-kb-timeline__item--latest.dsh-kb-timeline__item--success .dsh-kb-timeline__dot {\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--dsh-kb-complete) 22%, transparent);\n}\n.dsh-kb-timeline__item--latest.dsh-kb-timeline__item--exception .dsh-kb-timeline__dot {\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--dsh-kb-blocked) 22%, transparent);\n}\n/* \u5F02\u5E38\u884C\uFF1A\u6D45\u7EA2\u5E95 + \u5DE6\u4FA7\u7EA2\u6761\uFF0C\u6458\u8981\u7EA2\u52A0\u7C97 */\n.dsh-kb-timeline__item--exception .dsh-kb-timeline__body {\n background: color-mix(in srgb, var(--dsh-kb-blocked) 8%, transparent);\n border-left: 3px solid var(--dsh-kb-blocked);\n border-radius: var(--dsh-kb-radius-sm);\n padding: 6px 8px;\n}\n.dsh-kb-timeline__body {\n min-width: 0;\n}\n.dsh-kb-timeline__row {\n display: flex;\n align-items: baseline;\n gap: 8px;\n flex-wrap: wrap;\n}\n.dsh-kb-timeline__label {\n font-size: 13px;\n font-weight: 600;\n color: var(--dsh-kb-text-primary);\n}\n.dsh-kb-timeline__item--success .dsh-kb-timeline__label {\n color: var(--dsh-kb-complete);\n}\n.dsh-kb-timeline__item--exception .dsh-kb-timeline__label {\n color: var(--dsh-kb-blocked);\n}\n.dsh-kb-timeline__summary {\n margin: 2px 0 0;\n font-size: 12px;\n color: var(--dsh-kb-text-secondary);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.dsh-kb-timeline__item--exception .dsh-kb-timeline__summary {\n color: var(--dsh-kb-blocked);\n font-weight: 600;\n}\n.dsh-kb-timeline__author {\n display: block;\n margin-top: 2px;\n font-size: 11px;\n opacity: 0.7;\n color: var(--dsh-kb-text-tertiary);\n}\n\n.dsh-kb-spec-attachments {\n list-style: none;\n margin: 4px 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.dsh-kb-spec-attachments li {\n display: flex;\n align-items: baseline;\n gap: 6px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.dsh-kb-spec-attachment__kind {\n font-size: 11px;\n border: 1px solid var(--dsh-kb-border);\n border-radius: var(--dsh-kb-radius-sm);\n padding: 0 4px;\n opacity: 0.8;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .dsh-kb-tab * {\n transition-duration: 0ms !important;\n }\n}\n\n/* \u6574\u94FE\u5220\u9664\u6309\u94AE\uFF1A\u9ED8\u8BA4\u9690\u85CF\uFF0Chover \u9700\u6C42\u6807\u9898\u884C\u65F6\u51FA\u73B0\uFF08GUI \u4EC5 human\uFF0C\u4E8C\u6B21\u786E\u8BA4\uFF09 */\n.dsh-kb-chain__delete {\n flex: 0 0 auto;\n border: 0;\n background: transparent;\n color: currentColor;\n opacity: 0;\n font: inherit;\n line-height: 1;\n padding: 2px 4px;\n cursor: pointer;\n transition: opacity 150ms ease, color 150ms ease;\n}\n.dsh-kb-chain__title:hover .dsh-kb-chain__delete,\n.dsh-kb-chain__delete:focus-visible { opacity: 0.7; }\n.dsh-kb-chain__delete:hover { opacity: 1; color: var(--dsh-kb-blocked); }\n/* \u5220\u9664\u4E8C\u6B21\u786E\u8BA4\u5F39\u7A97\uFF08\u590D\u7528 rename \u5F39\u7A97\u5916\u58F3/\u5706\u89D2\u5951\u7EA6\uFF09 */\n.dsh-kb-delete-modal__text {\n color: var(--dsh-kb-text-secondary);\n font-size: 13px;\n line-height: 1.5;\n}\n.dsh-kb-delete-modal__error {\n color: var(--dsh-kb-blocked);\n font-size: 12px;\n}\n.dsh-kb-rename-modal__actions .dsh-kb-delete-confirm {\n border-color: transparent;\n background: var(--dsw-alias-state-error-primary);\n color: #fff;\n}\n/* T7\uFF1A\u94FE\u6807\u9898\u6539\u540D\u94C5\u7B14\u6309\u94AE + \u8F7B\u91CF\u5F39\u7A97\uFF08GUI \u4EC5 human\uFF1B\u89D2\u8272\u5361\u4E0D\u53EF\u6539\u540D\uFF09 */\n.dsh-kb-chain__rename {\n flex: 0 0 auto;\n border: 0;\n background: transparent;\n color: currentColor;\n opacity: 0.55;\n font: inherit;\n line-height: 1;\n padding: 2px 4px;\n cursor: pointer;\n}\n.dsh-kb-chain__rename:hover { opacity: 1; }\n.dsh-kb-task__status-row {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n.dsh-kb-rename-overlay {\n position: fixed;\n inset: 0;\n z-index: 1000;\n background: var(--dsh-kb-mask);\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.dsh-kb-rename-modal {\n display: flex;\n flex-direction: column;\n gap: 8px;\n min-width: 280px;\n padding: 16px;\n border: 1px solid var(--dsh-kb-border);\n border-radius: var(--dsh-kb-radius-lg);\n background: var(--dsh-kb-surface-elev);\n color: var(--dsh-kb-text-primary);\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);\n}\n.dsh-kb-rename-modal__label {\n color: var(--dsh-kb-text-primary);\n font-size: 13px;\n font-weight: 600;\n}\n.dsh-kb-rename-modal__input {\n padding: 6px 10px;\n border: 1px solid var(--dsh-kb-border);\n border-radius: var(--dsh-kb-radius-md);\n background: var(--dsh-kb-input);\n color: var(--dsh-kb-text-primary);\n font: inherit;\n}\n.dsh-kb-rename-modal__input:focus {\n outline: none;\n border-color: var(--dsh-kb-active);\n}\n.dsh-kb-rename-modal__actions {\n display: flex;\n justify-content: flex-end;\n gap: 8px;\n}\n.dsh-kb-rename-modal__actions button {\n border: 1px solid var(--dsh-kb-border);\n border-radius: var(--dsh-kb-radius-md);\n background: transparent;\n color: var(--dsh-kb-text-primary);\n font: inherit;\n padding: 4px 14px;\n cursor: pointer;\n transition: background-color 150ms ease;\n}\n.dsh-kb-rename-modal__actions button:hover {\n background: var(--dsh-kb-hover);\n}\n.dsh-kb-rename-modal__actions .dsh-kb-rename-save {\n border-color: transparent;\n background: var(--dsw-alias-button-primary-fill);\n color: var(--dsw-alias-label-primary-foreground);\n}\n";
1176
1356
 
1177
1357
  // client/index.ts
1178
1358
  var name = "kanban-board";
package/lib/config.d.ts CHANGED
@@ -1,5 +1,12 @@
1
1
  import Schema from '@deepseek-ai/schemastery';
2
2
  import type { Role } from './domain/types.js';
3
+ /** 斜杠命令前缀路由(单一事实源,决策12):plan/openspec/learning 已实现,run/changeset/archive 待落地时追加。 */
4
+ export interface PrefixRoutes {
5
+ plan: string;
6
+ openspec: string;
7
+ learning: string;
8
+ }
9
+ export declare const DEFAULT_PREFIX_ROUTES: PrefixRoutes;
3
10
  export interface KanbanConfig {
4
11
  storageDir: string;
5
12
  wikiVault: {
@@ -30,9 +37,10 @@ export interface KanbanConfig {
30
37
  dt: number;
31
38
  };
32
39
  };
33
- prefixRoutes: {
34
- plan: string;
35
- openspec: string;
40
+ prefixRoutes: PrefixRoutes;
41
+ memory: {
42
+ enabled: boolean;
43
+ maxIndexEntries: number;
36
44
  };
37
45
  ui: {
38
46
  enabled: boolean;
package/lib/config.js CHANGED
@@ -1,4 +1,9 @@
1
1
  import Schema from '@deepseek-ai/schemastery';
2
+ export const DEFAULT_PREFIX_ROUTES = {
3
+ plan: '/plan:',
4
+ openspec: '/openspec:',
5
+ learning: '/learning',
6
+ };
2
7
  const modelItemSchema = () => Schema.object({
3
8
  provider: Schema.string().required(),
4
9
  model: Schema.string().required(),
@@ -12,7 +17,7 @@ const modelItemSchema = () => Schema.object({
12
17
  export const Config = Schema.object({
13
18
  storageDir: Schema.string().default('$DSH_HOME/storages/kanban'),
14
19
  wikiVault: Schema.object({
15
- baseUrl: Schema.string().default('http://192.168.122.111:3000'),
20
+ baseUrl: Schema.string().default(''),
16
21
  pagePrefix: Schema.string().default('projects/'),
17
22
  }),
18
23
  roles: Schema.object({
@@ -31,9 +36,14 @@ export const Config = Schema.object({
31
36
  }),
32
37
  }),
33
38
  prefixRoutes: Schema.object({
34
- plan: Schema.string().default('/plan:'),
35
- openspec: Schema.string().default('/openspec:'),
39
+ plan: Schema.string().default(DEFAULT_PREFIX_ROUTES.plan),
40
+ openspec: Schema.string().default(DEFAULT_PREFIX_ROUTES.openspec),
41
+ learning: Schema.string().default(DEFAULT_PREFIX_ROUTES.learning),
36
42
  }),
43
+ memory: Schema.object({
44
+ enabled: Schema.boolean().default(true),
45
+ maxIndexEntries: Schema.number().min(1).max(20).default(8),
46
+ }).default({ enabled: true, maxIndexEntries: 8 }),
37
47
  ui: Schema.object({
38
48
  enabled: Schema.boolean().default(true),
39
49
  contentMinWidth: Schema.number().min(320).max(960).default(715), // 看板最小宽度 715px
@@ -139,7 +139,7 @@ ${task.body}`);
139
139
  sessionCwd = await resolveOrCreateWorkspace(this.ctx, null, 'task ' + task.id + ' ' + task.assignee + '/' + task.mode);
140
140
  }
141
141
  if (!sessionCwd) {
142
- await this.kanban.comment(taskId, '链未绑定工作区(Chain.workspaceDir 缺失且用户未提供工作区路径)。请重新 /plan: 绑定主 agent 工作空间后重试。', 'system');
142
+ await this.kanban.comment(taskId, `链未绑定工作区(Chain.workspaceDir 缺失且用户未提供工作区路径)。请重新 ${this.config.prefixRoutes.plan} 绑定主 agent 工作空间后重试。`, 'system');
143
143
  await this.kanban.claimTask(taskId, 'system');
144
144
  await this.kanban.blockTask(taskId, 'workspace-unknown: 链未绑定工作区(Chain.workspaceDir 缺失)', 'system');
145
145
  return;
@@ -422,7 +422,7 @@ export class VOrchestrator {
422
422
  ws = await resolveOrCreateWorkspace(this.ctx, null, 'chain ' + orch.chainId + ' V');
423
423
  }
424
424
  if (!ws)
425
- throw new Error('workspace-unknown: chain ' + orch.chainId + ' 无 workspaceDir,需重新 /plan:');
425
+ throw new Error('workspace-unknown: chain ' + orch.chainId + ` 无 workspaceDir,需重新 ${this.config.prefixRoutes.plan}`);
426
426
  const h = await this.agents.create({ sessionId: `kbn-v-${orch.chainId}`, meta: { cwd: ws }, ...opts, setup });
427
427
  orch.sessionId = `kbn-v-${orch.chainId}`;
428
428
  await attachSessionToWorkspace(this.ctx, `kbn-v-${orch.chainId}`, ws, 'chain ' + orch.chainId + ' V');
@@ -56,7 +56,7 @@ export function missingDeliveryKeys(assignee, mode, handoff, kbUrlBase) {
56
56
  missing.push(`${k} (host 前缀必须为 ${base})`);
57
57
  }
58
58
  else if (strict && k === 'page_path' && !isAllowedWikiPagePath(v)) {
59
- missing.push(`${k} (必须为 projects/checklists/、projects/ch_*/t_*.md 或 projects/ch_*/review/ 命名空间)`);
59
+ missing.push(`${k} (必须为 projects/checklists/、projects/learnings/、projects/<slug>/learnings/、projects/ch_*/learnings/、projects/ch_*/t_*.md 或 projects/ch_*/review/ 命名空间)`);
60
60
  }
61
61
  }
62
62
  }
@@ -0,0 +1,29 @@
1
+ import type { BoardState } from './types.js';
2
+ export interface LearningEntry {
3
+ title: string;
4
+ lesson: string;
5
+ evidence: string;
6
+ tags: string[];
7
+ }
8
+ export declare function validateLearning(raw: unknown): string[];
9
+ export declare function formatLearningBody(entry: LearningEntry, created?: Date): string;
10
+ export interface MemoryIndexEntry {
11
+ kind: 'learning' | 'doc';
12
+ title: string;
13
+ path: string;
14
+ }
15
+ export declare function buildMemoryIndexBlock(entries: MemoryIndexEntry[]): string | null;
16
+ export declare function weightedRank<T>(items: T[], scoreOf: (t: T) => number, timeOf: (t: T) => number): T[];
17
+ export declare function buildRepoSlug(workspaceDir: string): string;
18
+ export type { BoardState };
19
+ /** 机械提取四类信号(事件流/投影,禁 LLM 猜测),渲染紧凑 markdown。 */
20
+ export declare function buildLearningBrief(state: BoardState, chainId: string): string;
21
+ /** /learning rest → 链解析:空→最近链;精确 id→命中;子串匹配→单命中或候选列表(≤3);无→null。 */
22
+ export declare function resolveLearningChainId(state: BoardState, rest: string): {
23
+ chainId: string;
24
+ } | {
25
+ candidates: Array<{
26
+ chainId: string;
27
+ title: string;
28
+ }>;
29
+ } | null;
@@ -0,0 +1,145 @@
1
+ import { buildChecklistSlug } from '../wiki/page-path.js';
2
+ export function validateLearning(raw) {
3
+ const errors = [];
4
+ if (typeof raw !== 'object' || raw === null)
5
+ return ['learning must be an object'];
6
+ const l = raw;
7
+ for (const [label, key] of [['title', 'title'], ['lesson', 'lesson'], ['evidence', 'evidence']]) {
8
+ if (typeof l[key] !== 'string' || l[key].trim().length === 0)
9
+ errors.push(`learning.${label} must be a non-empty string`);
10
+ }
11
+ if (typeof l['title'] === 'string' && l['title'].length > 80)
12
+ errors.push('learning.title must be <= 80 chars');
13
+ if (l['tags'] !== undefined && !Array.isArray(l['tags']))
14
+ errors.push('learning.tags must be an array of strings');
15
+ if (Array.isArray(l['tags']) && l['tags'].some((v) => typeof v !== 'string'))
16
+ errors.push('learning.tags must be an array of strings');
17
+ return errors;
18
+ }
19
+ export function formatLearningBody(entry, created = new Date()) {
20
+ const date = created.toISOString().slice(0, 10);
21
+ const lines = [
22
+ '---',
23
+ `title: "${entry.title}"`,
24
+ 'type: learning',
25
+ entry.tags.length > 0 ? `tags: [${entry.tags.join(', ')}]` : 'tags: []',
26
+ `created: ${date}`,
27
+ '---',
28
+ '',
29
+ `# 【Learning】${entry.title}`,
30
+ '',
31
+ '## 教训',
32
+ entry.lesson,
33
+ '',
34
+ '## 证据',
35
+ entry.evidence,
36
+ ];
37
+ if (entry.tags.length > 0)
38
+ lines.push('', '## 适用场景', ...entry.tags.map((t) => `- ${t}`));
39
+ return lines.join('\n');
40
+ }
41
+ export function buildMemoryIndexBlock(entries) {
42
+ if (entries.length === 0)
43
+ return null;
44
+ const lines = ['## KB 记忆索引(自动注入)'];
45
+ for (const e of entries) {
46
+ const title = e.title.length > 60 ? e.title.slice(0, 60) + '…' : e.title;
47
+ lines.push(`- ${e.kind === 'learning' ? 'Learning' : '清单/计划/结果'}:[#/page/${e.path}](#/page/${e.path}) ${title}`);
48
+ }
49
+ lines.push('(需全文调 planning_memory_recall)');
50
+ return lines.join('\n');
51
+ }
52
+ export function weightedRank(items, scoreOf, timeOf) {
53
+ if (items.length === 0)
54
+ return items;
55
+ const scores = items.map(scoreOf);
56
+ const times = items.map(timeOf);
57
+ const sMin = Math.min(...scores), sMax = Math.max(...scores);
58
+ const tMin = Math.min(...times), tMax = Math.max(...times);
59
+ const sRange = sMax - sMin || 1;
60
+ const tRange = tMax - tMin || 1;
61
+ const rank = (x) => 0.7 * ((scoreOf(x) - sMin) / sRange) + 0.3 * ((timeOf(x) - tMin) / tRange);
62
+ return [...items].sort((a, b) => rank(b) - rank(a));
63
+ }
64
+ export function buildRepoSlug(workspaceDir) {
65
+ const base = workspaceDir.replace(/\/+$/, '').split('/').pop() ?? '';
66
+ return buildChecklistSlug(base);
67
+ }
68
+ // ── Task 4:证据包 + 链解析 ──────────────────────────────────────────
69
+ const truncate = (s, n) => (s.length > n ? s.slice(0, n) + '…' : s);
70
+ /** 链上下文头:链标题 + 规格卡 problem 首行。 */
71
+ function chainContext(state, chainId) {
72
+ const chain = state.chains.get(chainId);
73
+ if (!chain)
74
+ return '';
75
+ const spec = chain.specCardId ? state.specCards.get(chain.specCardId) : undefined;
76
+ const problem = spec?.sections.problem ? spec.sections.problem.split('\n')[0].slice(0, 200) : '';
77
+ return [chain.title, problem].filter(Boolean).join(' — ');
78
+ }
79
+ /** 机械提取四类信号(事件流/投影,禁 LLM 猜测),渲染紧凑 markdown。 */
80
+ export function buildLearningBrief(state, chainId) {
81
+ const header = ['## 链上下文', chainContext(state, chainId), ''];
82
+ const sections = [];
83
+ const chainEvents = state.events.filter((e) => e.chainId === chainId);
84
+ const reviewFailed = chainEvents.filter((e) => e.kind === 'review/failed');
85
+ if (reviewFailed.length > 0) {
86
+ const lines = ['### 评审失败'];
87
+ for (const e of reviewFailed.slice(-5)) {
88
+ const issues = e.payload.evidence?.issues ?? [];
89
+ for (const i of issues.slice(0, 5))
90
+ lines.push(`- [${i.severity}] ${i.title} — ${truncate(i.detail, 200)}`);
91
+ }
92
+ sections.push(lines.join('\n'));
93
+ }
94
+ const blocked = chainEvents.filter((e) => e.kind === 'task/blocked');
95
+ if (blocked.length > 0) {
96
+ const lines = ['### 任务阻塞'];
97
+ for (const e of blocked.slice(-5)) {
98
+ const task = e.taskId ? state.tasks.get(e.taskId) : undefined;
99
+ const reason = typeof e.payload.reason === 'string' ? e.payload.reason : '';
100
+ lines.push(`- ${task?.title ?? e.taskId} — ${truncate(reason, 200)}`);
101
+ }
102
+ sections.push(lines.join('\n'));
103
+ }
104
+ const reworks = [...state.tasks.values()].filter((t) => t.chainId === chainId && t.reworkOfTaskId !== null);
105
+ if (reworks.length > 0) {
106
+ const lines = ['### 返工卡'];
107
+ for (const t of reworks.slice(-5))
108
+ lines.push(`- [返工×${t.reviewAttempt}] ${t.title}(原卡 ${t.reworkOfTaskId})`);
109
+ sections.push(lines.join('\n'));
110
+ }
111
+ const audit = chainEvents.filter((e) => e.kind === 'chain/audit-warning');
112
+ if (audit.length > 0) {
113
+ const lines = ['### 审计警告'];
114
+ for (const e of audit.slice(-5)) {
115
+ const evidence = e.payload.evidence ?? [];
116
+ for (const ev of evidence.slice(0, 5))
117
+ lines.push(`- [${ev.source}] ${truncate(ev.detail, 200)}`);
118
+ }
119
+ sections.push(lines.join('\n'));
120
+ }
121
+ if (sections.length === 0)
122
+ return [...header, '(无机械信号,可基于对话观察蒸馏)'].join('\n');
123
+ return [...header, ...sections].join('\n');
124
+ }
125
+ /** /learning rest → 链解析:空→最近链;精确 id→命中;子串匹配→单命中或候选列表(≤3);无→null。 */
126
+ export function resolveLearningChainId(state, rest) {
127
+ const trimmed = rest.trim();
128
+ if (!trimmed) {
129
+ const latest = [...state.chains.values()].at(-1);
130
+ return latest ? { chainId: latest.id } : null;
131
+ }
132
+ if (state.chains.has(trimmed))
133
+ return { chainId: trimmed };
134
+ const matches = [...state.chains.values()].filter((c) => {
135
+ if (c.title.includes(trimmed))
136
+ return true;
137
+ const spec = c.specCardId ? state.specCards.get(c.specCardId) : undefined;
138
+ return spec?.sections.problem.includes(trimmed) ?? false;
139
+ }).slice(0, 3);
140
+ if (matches.length === 1)
141
+ return { chainId: matches[0].id };
142
+ if (matches.length === 0)
143
+ return null;
144
+ return { candidates: matches.map((c) => ({ chainId: c.id, title: c.title })) };
145
+ }
@@ -1,12 +1,11 @@
1
1
  import { KanbanService } from '../domain/kanban-service.js';
2
2
  import type { SpecCard, SpecCardAttachment } from '../domain/types.js';
3
- export declare const MATTPOCOCK_PLANNING_GUIDANCE = "\n# \u9636\u6BB5 0 \u89C4\u5212\u5BF9\u8BDD\uFF08v2\uFF1A\u9700\u6C42\u6F84\u6E05\u524D\u7F6E\u5316\uFF09\n1. \u9700\u6C42\u6F84\u6E05\uFF08grill-me\uFF09\uFF1A\u4E00\u6B21\u53EA\u95EE\u4E00\u4E2A\u95EE\u9898\uFF0C\u5148\u6F84\u6E05\u76EE\u7684\u3001\u7EA6\u675F\u3001\u6210\u529F\u6807\u51C6\uFF1B\u9010\u9879\u62F7\u95EE\u5047\u8BBE\u76F4\u81F3\u7528\u6237\"\u6CA1\u6709\u4EFB\u4F55\u7591\u95EE\"\u3002\n2. \u4ED3\u5E93\u4E8B\u5B9E\uFF08planning_prefetch\uFF09\uFF1A\u8C03\u53EA\u8BFB\u5B50\u4EE3\u7406\u91C7\u96C6\u76EE\u6807\u4ED3\u5E93/\u8D44\u6599/\u77E5\u8BC6\u5E93\u4E8B\u5B9E\uFF08\u672C\u5730\u8DEF\u5F84/\u5206\u652F/\u76EE\u6807\u6587\u4EF6\u57FA\u7EBF/\u65E2\u6709\u5B9E\u73B0\uFF09\uFF0C\u4E0D\u51ED\u7A7A\u5047\u8BBE\u3002\n3. \u6536\u655B\uFF08planning_checklist_save\uFF09\uFF1A\u628A\u7ED3\u8BBA\u5199\u6210\u7ED3\u6784\u5316\u9700\u6C42\u6F84\u6E05\u6E05\u5355\uFF08spec \u516D\u6BB5 + manifest repo.files + \u6F84\u6E05\u95EE\u7B54 + \u7591\u95EE\u70B9\uFF09\u5B58\u5165 KB\uFF08KB \u4E0D\u53EF\u8FBE\u81EA\u52A8\u515C\u5E95\u4E34\u65F6\u76EE\u5F55\uFF09\u3002\n4. \u6536\u5C3E\uFF1A\u63D0\u9192\u7528\u6237\u4EE5 /openspec: \u786E\u8BA4\u6267\u884C\u7ED3\u675F\u89C4\u5212\u9636\u6BB5\u2014\u2014/openspec: \u4F1A\u4ECE\u6E05\u5355\u5EFA\u94FE\u5E76\u81EA\u52A8\u4E32\u884C\u6267\u884C\u3002\n\u62A4\u680F\uFF1A\u89C4\u5212\u671F\u53EA\u8BFB\u4ED3\u5E93\uFF0C\u7981\u6B62\u4EFB\u4F55 git/\u6E90\u7801\u5199\u5165\uFF1B\u53EA\u5199 KB \u4E0E\u4E34\u65F6\u76EE\u5F55\u3002\n";
3
+ import { type PrefixRoutes } from '../config.js';
4
+ /** 阶段 0 规划引导:命令串从 config 派生(决策12),/openspec: 改名时文案自动跟随。 */
5
+ export declare function buildPlanningGuidance(routes: PrefixRoutes): string;
4
6
  export declare function validateSpecCardForApproval(card: SpecCard): string[];
5
- export declare function buildPlanningContext(chainId: string, card: SpecCard, attachments: SpecCardAttachment[]): string;
6
- export declare function approveIfReady(message: string, service: KanbanService, cfg: {
7
- plan: string;
8
- openspec: string;
9
- }, chainId: string, specCardId: string): Promise<{
7
+ export declare function buildPlanningContext(chainId: string, card: SpecCard, attachments: SpecCardAttachment[], routes?: PrefixRoutes): string;
8
+ export declare function approveIfReady(message: string, service: KanbanService, cfg: PrefixRoutes, chainId: string, specCardId: string): Promise<{
10
9
  ok: true;
11
10
  card: SpecCard;
12
11
  } | {