@dshtrading/knowledge 0.5.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.
@@ -1,16 +1,27 @@
1
1
  import { canonicalizeCardAuthor } from "./authors.js";
2
2
  import { readFile } from "node:fs/promises";
3
- import { writeJsonAtomic } from "@dshtrading/dsh-home";
3
+ import { SKIP_WRITE, transactStore } from "@dshtrading/dsh-home";
4
4
  //#region src/knowledge-fs.ts
5
5
  /**
6
6
  * 文件持久化版知识卡片存储(Node.js 宿主端专用)。
7
7
  *
8
- * 采用 tmp + rename 原子写入模式,并包含明确的错误日志与异常处理(逐行对齐 custom-fs.ts 先例)。
8
+ * 整表持久化经 `@dshtrading/dsh-home` 的 {@link transactStore}——跨进程排他锁 +
9
+ * 锁内新鲜读盘 + 原子写(2026-10-09 多进程互相覆盖事故修复)。读取面作者归一与坏
10
+ * 文件降级纪律不变(逐行对齐 custom-fs.ts 先例)。
9
11
  */
12
+ const LOG_PREFIX = "[dsh-trading/knowledge] failed to atomic flush knowledge cards to";
10
13
  function createFileKnowledgeCardStore(filePath) {
11
14
  let cache = null;
12
- async function load() {
13
- if (cache !== null) return cache;
15
+ /**
16
+ * 新鲜读盘(作者别名归一、坏 JSON 降级空表);供 load 与锁内重读共用。
17
+ *
18
+ * 作者别名在读取时归一(authors.ts):历史数据即刻在 GUI/检索层表现为规范名。
19
+ * 读本身不写盘,但归一结果留在缓存里,会被下一次写操作的全表回写落盘——
20
+ * 即一次静默迁移,故这里显式 log 一次,让「改别名表 = 改历史数据」可见。
21
+ * 缺 source / 缺 id 的脏行原样保留(不丢数据);结构性校验只在入库发生,
22
+ * 但这类行仍会让 getByUrl/search/graph 的解引用抛错(既有边界,未在此收口)。
23
+ */
24
+ async function readFromDisk() {
14
25
  try {
15
26
  const content = await readFile(filePath, "utf8");
16
27
  const parsed = JSON.parse(content);
@@ -24,16 +35,20 @@ function createFileKnowledgeCardStore(filePath) {
24
35
  }
25
36
  if (healed > 0) console.warn(`[dsh-trading/knowledge] 作者别名归一:读取时治愈 ${healed} 张历史卡片(${filePath});下一次写操作会把结果落盘`);
26
37
  }
27
- cache = map;
28
38
  return map;
29
39
  } catch (err) {
30
40
  if (err?.code !== "ENOENT") console.error(`[dsh-trading/knowledge] failed to read knowledge cards from ${filePath}:`, err);
31
- cache = /* @__PURE__ */ new Map();
32
- return cache;
41
+ return /* @__PURE__ */ new Map();
33
42
  }
34
43
  }
35
- async function flush(map) {
36
- await writeJsonAtomic(filePath, [...map.values()], "[dsh-trading/knowledge] failed to atomic flush knowledge cards to");
44
+ async function load() {
45
+ if (cache !== null) return cache;
46
+ cache = await readFromDisk();
47
+ return cache;
48
+ }
49
+ async function commit(mutate) {
50
+ cache = await transactStore(filePath, readFromDisk, mutate, (table) => [...table.values()], LOG_PREFIX);
51
+ return cache;
37
52
  }
38
53
  return {
39
54
  async list() {
@@ -48,14 +63,17 @@ function createFileKnowledgeCardStore(filePath) {
48
63
  for (const card of map.values()) if (card.source.url.trim() === trimmed) return card;
49
64
  },
50
65
  async save(card) {
51
- const map = await load();
52
- map.set(card.id, canonicalizeCardAuthor({ ...card }));
53
- await flush(map);
66
+ await commit((onDisk) => {
67
+ onDisk.set(card.id, canonicalizeCardAuthor({ ...card }));
68
+ return onDisk;
69
+ });
54
70
  },
55
71
  async delete(id) {
56
- const map = await load();
57
- const existed = map.delete(id);
58
- if (existed) await flush(map);
72
+ let existed = false;
73
+ await commit((onDisk) => {
74
+ existed = onDisk.delete(id);
75
+ return existed ? onDisk : SKIP_WRITE;
76
+ });
59
77
  return existed;
60
78
  }
61
79
  };
package/lib/tool.js CHANGED
@@ -30,72 +30,72 @@ function createKnowledgeIngestTool(store, options = {}) {
30
30
  const { onWritten } = options;
31
31
  return defineTool({
32
32
  name: "knowledge_ingest",
33
- description: "将经事实核查后的结构化知识卡片(Content Insight 产物)入库到本地知识库。支持自动 URL 查重(重复 URL 自动走更新且保持 ID 不变)及关联关系验证。",
33
+ description: "Ingest one fact-checked knowledge card (a Content Insight product) into the local knowledge base. Duplicate source URLs are detected automatically: an existing card with the same URL is updated in place and keeps its id. Related-card references are validated.",
34
34
  parameters: {
35
35
  title: {
36
36
  type: "string",
37
37
  required: true,
38
- description: "知识卡片主题(例如「高股息策略在低利率环境下的防御逻辑」)"
38
+ description: "Card subject, e.g. the defensive logic of high-dividend strategies in a low-rate environment."
39
39
  },
40
40
  summary: {
41
41
  type: "string",
42
42
  required: true,
43
- description: "2-4 条核心论点合并的一句话概述(图谱 hover 与快速导读展示)"
43
+ description: "One-sentence overview merging the 2-4 core claims; shown in the graph hover and quick read."
44
44
  },
45
45
  sourceType: {
46
46
  type: "string",
47
47
  required: true,
48
- description: "素材来源类型:\"bilibili\"(B站)、\"wechat\"(微信公众号)、\"manual\"(手工录入)"
48
+ description: "Source kind: \"bilibili\" | \"wechat\" | \"manual\"."
49
49
  },
50
50
  sourceUrl: {
51
51
  type: "string",
52
52
  required: true,
53
- description: "素材链接或去重标识(例如 B 站 BV 链接、公众号文章链接)"
53
+ description: "Source link or dedup key, e.g. a Bilibili BV link or a WeChat article link."
54
54
  },
55
55
  sourceAuthor: {
56
56
  type: "string",
57
57
  required: true,
58
- description: "来源作者/UP主/公众号名称"
58
+ description: "Source author, channel or account name."
59
59
  },
60
60
  publishedAt: {
61
61
  type: "string",
62
- description: "素材发布日期(ISO 格式如 2026-08-30,可选)"
62
+ description: "Optional source publish date, ISO format such as 2026-08-30."
63
63
  },
64
64
  credibility: {
65
65
  type: "string",
66
66
  required: true,
67
- description: "依据事实核查整体定级:\"high\"(事实严密证据充分)、\"medium\"(观点可取但部分有出入)、\"low\"(存在重大未核实或明显漏洞)"
67
+ description: "Overall fact-check grade: \"high\" | \"medium\" | \"low\"."
68
68
  },
69
69
  coreClaimsJson: {
70
70
  type: "string",
71
71
  required: true,
72
- description: "JSON 字符串数组,核心论点列表(保留原作者推理链条),例如 '[\"论点1\", \"论点2\"]'"
72
+ description: "JSON string array of core claims, preserving the author's reasoning chain, e.g. '[\"claim1\", \"claim2\"]'."
73
73
  },
74
74
  factCheckJson: {
75
75
  type: "string",
76
76
  required: true,
77
- description: "JSON 对象,事实核查三档分类,例如 '{\"verified\":[\"证实1\"],\"discrepancies\":[\"出入1\"],\"unverifiable\":[]}'"
77
+ description: "JSON object with the three fact-check buckets, e.g. '{\"verified\":[\"...\"],\"discrepancies\":[\"...\"],\"unverifiable\":[]}'."
78
78
  },
79
79
  takeawaysJson: {
80
80
  type: "string",
81
- description: "可选 JSON 字符串数组,可复用的分析框架与经验总结,例如 '[\"经验1\"]'"
81
+ description: "Optional JSON string array of reusable analysis frameworks and lessons."
82
82
  },
83
83
  boundariesJson: {
84
84
  type: "string",
85
- description: "可选 JSON 字符串数组,适用边界、失效情景与避坑指南,例如 '[\"边界1\"]'"
85
+ description: "Optional JSON string array of applicability boundaries, invalidation cases and pitfalls."
86
86
  },
87
87
  tagsJson: {
88
88
  type: "string",
89
89
  required: true,
90
- description: "JSON 字符串数组,受控主题标签列表,例如 '[\"宏观\", \"高股息\", \"红利策略\"]'"
90
+ description: "JSON string array of controlled topic tags, e.g. '[\"宏观\", \"高股息\", \"红利策略\"]'."
91
91
  },
92
92
  tickersJson: {
93
93
  type: "string",
94
- description: "可选 JSON 字符串数组,关联标的代码列表,例如 '[\"BTCUSDT\", \"600519.SH\"]'"
94
+ description: "Optional JSON string array of related instrument codes, e.g. '[\"BTCUSDT\", \"600519.SH\"]'."
95
95
  },
96
96
  relatedJson: {
97
97
  type: "string",
98
- description: "可选 JSON 字符串数组,显式关联的其他已入库卡片 ID 列表,例如 '[\"kc_01j...\", \"kc_01k...\"]'"
98
+ description: "Optional JSON string array of related card ids already in the library, e.g. '[\"kc_01j...\", \"kc_01k...\"]'."
99
99
  }
100
100
  },
101
101
  output: {
@@ -196,40 +196,40 @@ function createKnowledgeIngestTool(store, options = {}) {
196
196
  function createKnowledgeSearchTool(store) {
197
197
  return defineTool({
198
198
  name: "knowledge_search",
199
- description: "检索本地知识库中的知识卡片。有关键词时按字段命中相关度排序(标签 > 标题 > 核心论点 > 摘要/作者),无关键词时按更新时间倒序;支持按主体(cluster,配合 knowledge_graph 两级检索)、作者、来源类型、可信度多维过滤。detail=\"full\" 时同时返回核心论点、事实核查与可复用经验全文(上限 20 张)。",
199
+ description: "Search knowledge cards in the local library. With a keyword the results rank by field hit weight (tags > title > core claims > summary/author); without one they come newest-updated first. Filters: cluster (the graph clustering key, pair with knowledge_graph for two-stage retrieval), author, source kind and credibility. detail=\"full\" also returns core claims, the fact-check buckets and reusable lessons (capped at 20 cards).",
200
200
  parameters: {
201
201
  query: {
202
202
  type: "string",
203
- description: "搜索关键词(在标题、摘要、核心论点、标签中模糊匹配)"
203
+ description: "Search keyword, fuzzy-matched against title, summary, core claims and tags."
204
204
  },
205
205
  tags: {
206
206
  type: "string",
207
- description: "逗号分隔的标签过滤(例如 \"宏观,高股息\",匹配其中任意一个)"
207
+ description: "Comma-separated tag filter, e.g. \"宏观,高股息\"; matches any one of them."
208
208
  },
209
209
  cluster: {
210
210
  type: "string",
211
- description: "按主体精确过滤(主体 = 图谱聚类键 = 卡片首个标签)。两级检索第二级:先 knowledge_graph 看主体分布,再用本参数钻取该主体下的卡片"
211
+ description: "Exact subject filter (subject = graph clustering key = the card first tag). Second stage of a two-stage retrieval: call knowledge_graph for the subject distribution, then drill in here."
212
212
  },
213
213
  author: {
214
214
  type: "string",
215
- description: "按作者/UP主/公众号过滤(子串匹配)"
215
+ description: "Author/channel filter (substring match)."
216
216
  },
217
217
  sourceType: {
218
218
  type: "string",
219
- description: "按来源类型过滤:\"bilibili\" | \"wechat\" | \"manual\""
219
+ description: "Source kind filter: \"bilibili\" | \"wechat\" | \"manual\"."
220
220
  },
221
221
  credibility: {
222
222
  type: "string",
223
- description: "按可信度过滤:\"high\" | \"medium\" | \"low\""
223
+ description: "Credibility filter: \"high\" | \"medium\" | \"low\"."
224
224
  },
225
225
  limit: {
226
226
  type: "number",
227
- description: "最大返回数量(默认 20)",
227
+ description: "Maximum results to return (default 20).",
228
228
  default: 20
229
229
  },
230
230
  detail: {
231
231
  type: "string",
232
- description: "返回详略:\"summary\"(默认,摘要级)| \"full\"(附核心论点/事实核查/经验/边界全文)",
232
+ description: "Detail level: \"summary\" (default) | \"full\" (adds core claims, fact check, lessons and boundaries).",
233
233
  default: "summary"
234
234
  }
235
235
  },
@@ -302,11 +302,11 @@ function createKnowledgeSearchTool(store) {
302
302
  function createKnowledgeGetTool(store) {
303
303
  return defineTool({
304
304
  name: "knowledge_get",
305
- description: "按 id 读取单张知识卡片的完整内容(核心论点、事实核查三桶、可复用经验、适用边界)。id 可来自 knowledge_search 结果或历史分析中的引用标注。",
305
+ description: "Read one knowledge card in full by id: core claims, the three fact-check buckets, reusable lessons and applicability boundaries. Ids come from knowledge_search results or citation markers in earlier analyses.",
306
306
  parameters: { id: {
307
307
  type: "string",
308
308
  required: true,
309
- description: "知识卡片 id(kc_...)"
309
+ description: "Knowledge card id (kc_...)."
310
310
  } },
311
311
  output: {
312
312
  schema: { type: "string" },
@@ -337,16 +337,16 @@ function createKnowledgeDeleteTool(store, options = {}) {
337
337
  const { onWritten } = options;
338
338
  return defineTool({
339
339
  name: "knowledge_delete",
340
- description: "从本地知识库删除(下架)指定知识卡片。用于知识点/经验被事实证伪、来源撤稿或卡片重复等场景。删除时自动清理其他卡片指向本卡片的 related 引用,并在输出中回显被删卡片的标题与核心论点留痕;证伪结论本身应另行记入交易日志或对话沉淀。",
340
+ description: "Delete (retire) one knowledge card from the local library, for a claim disproved by evidence, a retracted source or a duplicate card. Other cards' related references to it are cleaned up automatically, and the deleted title and core claims are echoed as a trail. Record the disproof itself separately in the trading journal or the conversation.",
341
341
  parameters: {
342
342
  id: {
343
343
  type: "string",
344
344
  required: true,
345
- description: "要删除的知识卡片 id(kc_...)。不确定 id 时先用 knowledge_search 查询确认,不要凭模糊记忆删除。"
345
+ description: "Knowledge card id to delete (kc_...). Confirm the id with knowledge_search first; never delete from vague memory."
346
346
  },
347
347
  reason: {
348
348
  type: "string",
349
- description: "删除原因(如「核心论点被 XX 数据证伪」「来源撤稿」),回显在结果中供留痕"
349
+ description: "Deletion reason, echoed in the result as a trail."
350
350
  }
351
351
  },
352
352
  output: {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dshtrading/knowledge",
3
3
  "description": "Knowledge base data plane for dsh-trading: KnowledgeCard data model, validation, graph builder, atomic file store, and knowledge_ingest / knowledge_search agent tools.",
4
- "version": "0.5.0",
4
+ "version": "0.6.0",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "types": "./lib/index.d.ts",
@@ -27,7 +27,7 @@
27
27
  "dependencies": {
28
28
  "@deepseek-ai/dsh-tools": "0.2.0-rc.2",
29
29
  "@deepseek-ai/schemastery": "^3.18.4",
30
- "@dshtrading/dsh-home": "^0.5.0"
30
+ "@dshtrading/dsh-home": "^0.6.0"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "@deepseek-ai/cordis": ">=4.0.0"