@dshtrading/knowledge 0.1.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.
- package/LICENSE +75 -0
- package/lib/graph.d.ts +5 -0
- package/lib/graph.js +145 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.js +4 -0
- package/lib/knowledge-fs.d.ts +5 -0
- package/lib/knowledge-fs.js +77 -0
- package/lib/plugin.d.ts +34 -0
- package/lib/plugin.js +92 -0
- package/lib/store-memory.d.ts +5 -0
- package/lib/store-memory.js +25 -0
- package/lib/tool.d.ts +17 -0
- package/lib/tool.js +383 -0
- package/lib/types.d.ts +79 -0
- package/lib/validate.d.ts +11 -0
- package/lib/validate.js +127 -0
- package/package.json +44 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Copyright (c) 2026 zhu1090093659 (dsh-trading)
|
|
2
|
+
|
|
3
|
+
# PolyForm Noncommercial License 1.0.0
|
|
4
|
+
|
|
5
|
+
<https://polyformproject.org/licenses/noncommercial/1.0.0>
|
|
6
|
+
|
|
7
|
+
## Acceptance
|
|
8
|
+
|
|
9
|
+
In order to get any license under these terms, you must agree to them as both strict obligations and conditions to all your licenses.
|
|
10
|
+
|
|
11
|
+
## Copyright License
|
|
12
|
+
|
|
13
|
+
The licensor grants you a copyright license for the software to do everything you might do with the software that would otherwise infringe the licensor's copyright in it for any permitted purpose. However, you may only distribute the software according to [Distribution License](#distribution-license) and make changes or new works based on the software according to [Changes and New Works License](#changes-and-new-works-license).
|
|
14
|
+
|
|
15
|
+
## Distribution License
|
|
16
|
+
|
|
17
|
+
The licensor grants you an additional copyright license to distribute copies of the software. Your license to distribute covers distributing the software with changes and new works permitted by [Changes and New Works License](#changes-and-new-works-license).
|
|
18
|
+
|
|
19
|
+
## Notices
|
|
20
|
+
|
|
21
|
+
You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms or the URL for them above, as well as copies of any plain-text lines beginning with `Required Notice:` that the licensor provided with the software. For example:
|
|
22
|
+
|
|
23
|
+
> Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
|
|
24
|
+
|
|
25
|
+
## Changes and New Works License
|
|
26
|
+
|
|
27
|
+
The licensor grants you an additional copyright license to make changes and new works based on the software for any permitted purpose.
|
|
28
|
+
|
|
29
|
+
## Patent License
|
|
30
|
+
|
|
31
|
+
The licensor grants you a patent license for the software that covers patent claims the licensor can license, or becomes able to license, that you would infringe by using the software.
|
|
32
|
+
|
|
33
|
+
## Noncommercial Purposes
|
|
34
|
+
|
|
35
|
+
Any noncommercial purpose is a permitted purpose.
|
|
36
|
+
|
|
37
|
+
## Personal Uses
|
|
38
|
+
|
|
39
|
+
Personal use for research, experiment, and testing for the benefit of public knowledge, personal study, private entertainment, hobby projects, amateur pursuits, or religious observance, without any anticipated commercial application, is use for a permitted purpose.
|
|
40
|
+
|
|
41
|
+
## Noncommercial Organizations
|
|
42
|
+
|
|
43
|
+
Use by any charitable organization, educational institution, public research organization, public safety or health organization, environmental protection organization, or government institution is use for a permitted purpose regardless of the source of funding or obligations resulting from the funding.
|
|
44
|
+
|
|
45
|
+
## Fair Use
|
|
46
|
+
|
|
47
|
+
You may have "fair use" rights for the software under the law. These terms do not limit them.
|
|
48
|
+
|
|
49
|
+
## No Other Rights
|
|
50
|
+
|
|
51
|
+
These terms do not allow you to sublicense or transfer any of your licenses to anyone else, or prevent the licensor from granting licenses to anyone else. These terms do not imply any other licenses.
|
|
52
|
+
|
|
53
|
+
## Patent Defense
|
|
54
|
+
|
|
55
|
+
If you make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
|
|
56
|
+
|
|
57
|
+
## Violations
|
|
58
|
+
|
|
59
|
+
The first time you are notified in writing that you have violated any of these terms, or done anything with the software not covered by your licenses, your licenses can nonetheless continue if you come into full compliance with these terms, and take practical steps to correct past violations, within 32 days of receiving notice. Otherwise, all your licenses end immediately.
|
|
60
|
+
|
|
61
|
+
## No Liability
|
|
62
|
+
|
|
63
|
+
***As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.***
|
|
64
|
+
|
|
65
|
+
## Definitions
|
|
66
|
+
|
|
67
|
+
The **licensor** is the individual or entity offering these terms, and the **software** is the software the licensor makes available under these terms.
|
|
68
|
+
|
|
69
|
+
**You** refers to the individual or entity agreeing to these terms.
|
|
70
|
+
|
|
71
|
+
**Your company** is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. **Control** means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
|
|
72
|
+
|
|
73
|
+
**Your licenses** are all the licenses granted to you for the software under these terms.
|
|
74
|
+
|
|
75
|
+
**Use** means anything you do with the software requiring one of your licenses.
|
package/lib/graph.d.ts
ADDED
package/lib/graph.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
//#region src/graph.ts
|
|
2
|
+
function buildGraph(cards, options = {}) {
|
|
3
|
+
const { coTag = true, coAuthor = true, tagHubs = false } = options;
|
|
4
|
+
if (!cards || cards.length === 0) return {
|
|
5
|
+
nodes: [],
|
|
6
|
+
links: []
|
|
7
|
+
};
|
|
8
|
+
const cardMap = /* @__PURE__ */ new Map();
|
|
9
|
+
for (const c of cards) cardMap.set(c.id, c);
|
|
10
|
+
const linkMap = /* @__PURE__ */ new Map();
|
|
11
|
+
function makeLinkKey(id1, id2, kind) {
|
|
12
|
+
const [a, b] = id1 < id2 ? [id1, id2] : [id2, id1];
|
|
13
|
+
return `${kind}:${a}:${b}`;
|
|
14
|
+
}
|
|
15
|
+
if (tagHubs) {
|
|
16
|
+
for (const card of cards) {
|
|
17
|
+
if (card.related && Array.isArray(card.related)) {
|
|
18
|
+
for (const targetId of card.related) if (cardMap.has(targetId) && targetId !== card.id) {
|
|
19
|
+
const key = makeLinkKey(card.id, targetId, "related");
|
|
20
|
+
if (!linkMap.has(key)) {
|
|
21
|
+
const [source, target] = card.id < targetId ? [card.id, targetId] : [targetId, card.id];
|
|
22
|
+
linkMap.set(key, {
|
|
23
|
+
source,
|
|
24
|
+
target,
|
|
25
|
+
kind: "related",
|
|
26
|
+
weight: 2
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const seen = /* @__PURE__ */ new Set();
|
|
32
|
+
for (const tag of card.tags) {
|
|
33
|
+
const name = (tag ?? "").trim();
|
|
34
|
+
if (!name || seen.has(name)) continue;
|
|
35
|
+
seen.add(name);
|
|
36
|
+
const hubId = "tag:" + name;
|
|
37
|
+
linkMap.set(makeLinkKey(card.id, hubId, "tag-hub"), {
|
|
38
|
+
source: card.id,
|
|
39
|
+
target: hubId,
|
|
40
|
+
kind: "tag-hub",
|
|
41
|
+
weight: 1
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const hubLinks = Array.from(linkMap.values());
|
|
46
|
+
const degreeMap = /* @__PURE__ */ new Map();
|
|
47
|
+
for (const c of cards) degreeMap.set(c.id, 0);
|
|
48
|
+
for (const l of hubLinks) {
|
|
49
|
+
degreeMap.set(l.source, (degreeMap.get(l.source) ?? 0) + 1);
|
|
50
|
+
degreeMap.set(l.target, (degreeMap.get(l.target) ?? 0) + 1);
|
|
51
|
+
}
|
|
52
|
+
const cardNodes = cards.map((card) => ({
|
|
53
|
+
id: card.id,
|
|
54
|
+
label: card.title,
|
|
55
|
+
cluster: card.tags[0] ?? "未分类",
|
|
56
|
+
credibility: card.credibility,
|
|
57
|
+
degree: degreeMap.get(card.id) ?? 0,
|
|
58
|
+
type: "card",
|
|
59
|
+
raw: card
|
|
60
|
+
}));
|
|
61
|
+
const hubNodes = [...degreeMap.keys()].filter((id) => id.startsWith("tag:")).map((id) => ({
|
|
62
|
+
id,
|
|
63
|
+
label: id.slice(4),
|
|
64
|
+
cluster: id.slice(4),
|
|
65
|
+
credibility: "medium",
|
|
66
|
+
degree: degreeMap.get(id) ?? 0,
|
|
67
|
+
type: "tag"
|
|
68
|
+
}));
|
|
69
|
+
return {
|
|
70
|
+
nodes: [...cardNodes, ...hubNodes],
|
|
71
|
+
links: hubLinks
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
for (const card of cards) if (card.related && Array.isArray(card.related)) {
|
|
75
|
+
for (const targetId of card.related) if (cardMap.has(targetId) && targetId !== card.id) {
|
|
76
|
+
const key = makeLinkKey(card.id, targetId, "related");
|
|
77
|
+
if (!linkMap.has(key)) {
|
|
78
|
+
const [source, target] = card.id < targetId ? [card.id, targetId] : [targetId, card.id];
|
|
79
|
+
linkMap.set(key, {
|
|
80
|
+
source,
|
|
81
|
+
target,
|
|
82
|
+
kind: "related",
|
|
83
|
+
weight: 2
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (coTag) for (let i = 0; i < cards.length; i++) {
|
|
89
|
+
const cardA = cards[i];
|
|
90
|
+
const tagsA = new Set(cardA.tags);
|
|
91
|
+
for (let j = i + 1; j < cards.length; j++) {
|
|
92
|
+
const cardB = cards[j];
|
|
93
|
+
let sharedTagCount = 0;
|
|
94
|
+
for (const t of cardB.tags) if (tagsA.has(t)) sharedTagCount++;
|
|
95
|
+
if (sharedTagCount > 0) {
|
|
96
|
+
const key = makeLinkKey(cardA.id, cardB.id, "co-tag");
|
|
97
|
+
const [source, target] = cardA.id < cardB.id ? [cardA.id, cardB.id] : [cardB.id, cardA.id];
|
|
98
|
+
linkMap.set(key, {
|
|
99
|
+
source,
|
|
100
|
+
target,
|
|
101
|
+
kind: "co-tag",
|
|
102
|
+
weight: sharedTagCount
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (coAuthor) for (let i = 0; i < cards.length; i++) {
|
|
108
|
+
const cardA = cards[i];
|
|
109
|
+
const authorA = cardA.source.author.trim();
|
|
110
|
+
if (!authorA || authorA === "manual" || authorA === "手工") continue;
|
|
111
|
+
for (let j = i + 1; j < cards.length; j++) {
|
|
112
|
+
const cardB = cards[j];
|
|
113
|
+
if (authorA === cardB.source.author.trim()) {
|
|
114
|
+
const key = makeLinkKey(cardA.id, cardB.id, "co-author");
|
|
115
|
+
const [source, target] = cardA.id < cardB.id ? [cardA.id, cardB.id] : [cardB.id, cardA.id];
|
|
116
|
+
linkMap.set(key, {
|
|
117
|
+
source,
|
|
118
|
+
target,
|
|
119
|
+
kind: "co-author",
|
|
120
|
+
weight: 1
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const links = Array.from(linkMap.values());
|
|
126
|
+
const degreeMap = /* @__PURE__ */ new Map();
|
|
127
|
+
for (const c of cards) degreeMap.set(c.id, 0);
|
|
128
|
+
for (const l of links) {
|
|
129
|
+
degreeMap.set(l.source, (degreeMap.get(l.source) ?? 0) + 1);
|
|
130
|
+
degreeMap.set(l.target, (degreeMap.get(l.target) ?? 0) + 1);
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
nodes: cards.map((card) => ({
|
|
134
|
+
id: card.id,
|
|
135
|
+
label: card.title,
|
|
136
|
+
cluster: card.tags[0] ?? "未分类",
|
|
137
|
+
credibility: card.credibility,
|
|
138
|
+
degree: degreeMap.get(card.id) ?? 0,
|
|
139
|
+
raw: card
|
|
140
|
+
})),
|
|
141
|
+
links
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
//#endregion
|
|
145
|
+
export { buildGraph };
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { BuildGraphOptions, GraphLinkKind, KnowledgeCard, KnowledgeCardInput, KnowledgeCardStore, KnowledgeCredibility, KnowledgeFactCheck, KnowledgeGraphData, KnowledgeGraphLink, KnowledgeGraphNode, KnowledgeSource, KnowledgeSourceType } from "./types.js";
|
|
2
|
+
import { ValidationResult, generateCardId, validateKnowledgeCard } from "./validate.js";
|
|
3
|
+
import { buildGraph } from "./graph.js";
|
|
4
|
+
import { createMemoryKnowledgeCardStore } from "./store-memory.js";
|
|
5
|
+
export { type BuildGraphOptions, type GraphLinkKind, type KnowledgeCard, type KnowledgeCardInput, type KnowledgeCardStore, type KnowledgeCredibility, type KnowledgeFactCheck, type KnowledgeGraphData, type KnowledgeGraphLink, type KnowledgeGraphNode, type KnowledgeSource, type KnowledgeSourceType, type ValidationResult, buildGraph, createMemoryKnowledgeCardStore, generateCardId, validateKnowledgeCard };
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { generateCardId, validateKnowledgeCard } from "./validate.js";
|
|
2
|
+
import { buildGraph } from "./graph.js";
|
|
3
|
+
import { createMemoryKnowledgeCardStore } from "./store-memory.js";
|
|
4
|
+
export { buildGraph, createMemoryKnowledgeCardStore, generateCardId, validateKnowledgeCard };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
//#region src/knowledge-fs.ts
|
|
4
|
+
/**
|
|
5
|
+
* 文件持久化版知识卡片存储(Node.js 宿主端专用)。
|
|
6
|
+
*
|
|
7
|
+
* 采用 tmp + rename 原子写入模式,并包含明确的错误日志与异常处理(逐行对齐 custom-fs.ts 先例)。
|
|
8
|
+
*/
|
|
9
|
+
function createFileKnowledgeCardStore(filePath) {
|
|
10
|
+
let cache = null;
|
|
11
|
+
async function load() {
|
|
12
|
+
if (cache !== null) return cache;
|
|
13
|
+
try {
|
|
14
|
+
const content = await readFile(filePath, "utf8");
|
|
15
|
+
const parsed = JSON.parse(content);
|
|
16
|
+
const map = /* @__PURE__ */ new Map();
|
|
17
|
+
if (Array.isArray(parsed)) {
|
|
18
|
+
for (const item of parsed) if (item && typeof item.id === "string") map.set(item.id, item);
|
|
19
|
+
}
|
|
20
|
+
cache = map;
|
|
21
|
+
return map;
|
|
22
|
+
} catch (err) {
|
|
23
|
+
if (err?.code !== "ENOENT") console.error(`[dsh-trading/knowledge] failed to read knowledge cards from ${filePath}:`, err);
|
|
24
|
+
cache = /* @__PURE__ */ new Map();
|
|
25
|
+
return cache;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async function flush(map) {
|
|
29
|
+
const dir = dirname(filePath);
|
|
30
|
+
const tmpPath = `${filePath}.tmp.${Date.now()}.${Math.random().toString(36).slice(2, 8)}`;
|
|
31
|
+
const data = JSON.stringify([...map.values()], null, 2);
|
|
32
|
+
try {
|
|
33
|
+
await mkdir(dir, { recursive: true });
|
|
34
|
+
await writeFile(tmpPath, data, "utf8");
|
|
35
|
+
let lastError;
|
|
36
|
+
for (let attempt = 0; attempt < 3; attempt++) try {
|
|
37
|
+
await rename(tmpPath, filePath);
|
|
38
|
+
return;
|
|
39
|
+
} catch (err) {
|
|
40
|
+
if (err?.code !== "EPERM" && err?.code !== "EBUSY") throw err;
|
|
41
|
+
lastError = err;
|
|
42
|
+
await new Promise((resolve) => setTimeout(resolve, 25 * (attempt + 1)));
|
|
43
|
+
}
|
|
44
|
+
throw lastError;
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error(`[dsh-trading/knowledge] failed to atomic flush knowledge cards to ${filePath}:`, error);
|
|
47
|
+
await unlink(tmpPath).catch(() => {});
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
async list() {
|
|
53
|
+
return [...(await load()).values()];
|
|
54
|
+
},
|
|
55
|
+
async get(id) {
|
|
56
|
+
return (await load()).get(id);
|
|
57
|
+
},
|
|
58
|
+
async getByUrl(url) {
|
|
59
|
+
const map = await load();
|
|
60
|
+
const trimmed = url.trim();
|
|
61
|
+
for (const card of map.values()) if (card.source.url.trim() === trimmed) return card;
|
|
62
|
+
},
|
|
63
|
+
async save(card) {
|
|
64
|
+
const map = await load();
|
|
65
|
+
map.set(card.id, { ...card });
|
|
66
|
+
await flush(map);
|
|
67
|
+
},
|
|
68
|
+
async delete(id) {
|
|
69
|
+
const map = await load();
|
|
70
|
+
const existed = map.delete(id);
|
|
71
|
+
if (existed) await flush(map);
|
|
72
|
+
return existed;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
77
|
+
export { createFileKnowledgeCardStore };
|
package/lib/plugin.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { KnowledgeCardStore } from "./types.js";
|
|
2
|
+
import { createFileKnowledgeCardStore } from "./knowledge-fs.js";
|
|
3
|
+
import { Context, Service } from "@deepseek-ai/cordis";
|
|
4
|
+
//#region src/plugin.d.ts
|
|
5
|
+
/** Cordis 插件名 = patch 行 id(TEMPLATES §8)。 */
|
|
6
|
+
declare const name = "dsh-trading-knowledge";
|
|
7
|
+
/** 本插件不硬依赖任何服务。 */
|
|
8
|
+
declare const inject: string[];
|
|
9
|
+
/** SDK 服务键:知识卡片 store 单实例。 */
|
|
10
|
+
declare const TRADING_KNOWLEDGE_CARDS_KEY = "tradingKnowledgeCards";
|
|
11
|
+
/** tradingEvents 的最小发布面(鸭式;总线缺席时静默降级)。 */
|
|
12
|
+
interface TradingEventsPublisher {
|
|
13
|
+
emit(store: 'knowledge'): void;
|
|
14
|
+
}
|
|
15
|
+
/** 默认存储路径:~/.dsh/knowledge/cards.json(与 client-ui-trading 旧路径一致)。 */
|
|
16
|
+
declare function defaultStorePath(): string;
|
|
17
|
+
interface KnowledgeGraphToolOptions {
|
|
18
|
+
store: KnowledgeCardStore;
|
|
19
|
+
}
|
|
20
|
+
/** knowledge_graph 工厂(issue #33 新增;只读结构概要,不倾倒全量 label)。 */
|
|
21
|
+
declare function createKnowledgeGraphTool(options: KnowledgeGraphToolOptions): import("@deepseek-ai/dsh-tools").ToolDefinition;
|
|
22
|
+
interface KnowledgePluginDeps {
|
|
23
|
+
store: KnowledgeCardStore;
|
|
24
|
+
}
|
|
25
|
+
declare function registerKnowledgeTools(ctx: Context, deps: KnowledgePluginDeps): void;
|
|
26
|
+
/** 知识卡片 store 服务(桥与工具的单实例共享点)。 */
|
|
27
|
+
declare class KnowledgeCardsService extends Service {
|
|
28
|
+
readonly store: KnowledgeCardStore;
|
|
29
|
+
constructor(ctx: Context, store: KnowledgeCardStore, serviceName?: string);
|
|
30
|
+
}
|
|
31
|
+
/** Host plugin body:store 服务 provide + 工具注册。 */
|
|
32
|
+
declare function apply(ctx: Context): void;
|
|
33
|
+
//#endregion
|
|
34
|
+
export { KnowledgeCardsService, KnowledgeGraphToolOptions, KnowledgePluginDeps, TRADING_KNOWLEDGE_CARDS_KEY, TradingEventsPublisher, apply, createFileKnowledgeCardStore, createKnowledgeGraphTool, defaultStorePath, inject, name, registerKnowledgeTools };
|
package/lib/plugin.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { buildGraph } from "./graph.js";
|
|
2
|
+
import { createFileKnowledgeCardStore } from "./knowledge-fs.js";
|
|
3
|
+
import { createKnowledgeDeleteTool, createKnowledgeGetTool, createKnowledgeIngestTool, createKnowledgeSearchTool } from "./tool.js";
|
|
4
|
+
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { Service } from "@deepseek-ai/cordis";
|
|
7
|
+
import os from "node:os";
|
|
8
|
+
//#region src/plugin.ts
|
|
9
|
+
/** Cordis 插件名 = patch 行 id(TEMPLATES §8)。 */
|
|
10
|
+
const name = "dsh-trading-knowledge";
|
|
11
|
+
/** 本插件不硬依赖任何服务。 */
|
|
12
|
+
const inject = [];
|
|
13
|
+
/** SDK 服务键:知识卡片 store 单实例。 */
|
|
14
|
+
const TRADING_KNOWLEDGE_CARDS_KEY = "tradingKnowledgeCards";
|
|
15
|
+
/** 默认存储路径:~/.dsh/knowledge/cards.json(与 client-ui-trading 旧路径一致)。 */
|
|
16
|
+
function defaultStorePath() {
|
|
17
|
+
return path.join(os.homedir(), ".dsh", "knowledge", "cards.json");
|
|
18
|
+
}
|
|
19
|
+
/** knowledge_graph 工厂(issue #33 新增;只读结构概要,不倾倒全量 label)。 */
|
|
20
|
+
function createKnowledgeGraphTool(options) {
|
|
21
|
+
const { store } = options;
|
|
22
|
+
return defineTool({
|
|
23
|
+
name: "knowledge_graph",
|
|
24
|
+
description: "Level 1 of the two-level knowledge retrieval: structural overview of the persisted cards (nodes = cards, edges = shared tags/authors/related). Returns the full cluster (topic) distribution and credibility counts. Pick a cluster here, then drill down with knowledge_search { cluster } and read full cards with knowledge_get.",
|
|
25
|
+
parameters: {},
|
|
26
|
+
output: {
|
|
27
|
+
schema: { type: "string" },
|
|
28
|
+
render: (_args, value) => [{
|
|
29
|
+
type: "text",
|
|
30
|
+
text: value
|
|
31
|
+
}]
|
|
32
|
+
},
|
|
33
|
+
async execute() {
|
|
34
|
+
const cards = await store.list();
|
|
35
|
+
const data = buildGraph(cards);
|
|
36
|
+
const clusterCounts = /* @__PURE__ */ new Map();
|
|
37
|
+
const credibility = {
|
|
38
|
+
high: 0,
|
|
39
|
+
medium: 0,
|
|
40
|
+
low: 0
|
|
41
|
+
};
|
|
42
|
+
for (const card of cards) {
|
|
43
|
+
const cluster = card.tags[0] ?? "未分类";
|
|
44
|
+
clusterCounts.set(cluster, (clusterCounts.get(cluster) ?? 0) + 1);
|
|
45
|
+
credibility[card.credibility] += 1;
|
|
46
|
+
}
|
|
47
|
+
const clusters = [...clusterCounts.entries()].sort((a, b) => b[1] - a[1]).map(([cluster, count]) => ({
|
|
48
|
+
cluster,
|
|
49
|
+
count
|
|
50
|
+
}));
|
|
51
|
+
return JSON.stringify({
|
|
52
|
+
ok: true,
|
|
53
|
+
cards: cards.length,
|
|
54
|
+
nodeCount: data.nodes.length,
|
|
55
|
+
edgeCount: data.links.length,
|
|
56
|
+
clusters,
|
|
57
|
+
credibility
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function registerKnowledgeTools(ctx, deps) {
|
|
63
|
+
ctx.inject(["tools"], (toolCtx) => {
|
|
64
|
+
const tools = toolCtx.tools;
|
|
65
|
+
if (!tools || typeof tools.register !== "function") return;
|
|
66
|
+
const events = () => ctx.get?.("tradingEvents", false);
|
|
67
|
+
const register = (tool) => {
|
|
68
|
+
if (tools.get(tool.name) === void 0) tools.register(tool);
|
|
69
|
+
};
|
|
70
|
+
register(createKnowledgeIngestTool(deps.store, { onWritten: () => events()?.emit("knowledge") }));
|
|
71
|
+
register(createKnowledgeSearchTool(deps.store));
|
|
72
|
+
register(createKnowledgeGetTool(deps.store));
|
|
73
|
+
register(createKnowledgeDeleteTool(deps.store, { onWritten: () => events()?.emit("knowledge") }));
|
|
74
|
+
register(createKnowledgeGraphTool({ store: deps.store }));
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/** 知识卡片 store 服务(桥与工具的单实例共享点)。 */
|
|
78
|
+
var KnowledgeCardsService = class extends Service {
|
|
79
|
+
store;
|
|
80
|
+
constructor(ctx, store, serviceName = TRADING_KNOWLEDGE_CARDS_KEY) {
|
|
81
|
+
super(ctx, serviceName);
|
|
82
|
+
this.store = store;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
/** Host plugin body:store 服务 provide + 工具注册。 */
|
|
86
|
+
function apply(ctx) {
|
|
87
|
+
const store = createFileKnowledgeCardStore(defaultStorePath());
|
|
88
|
+
new KnowledgeCardsService(ctx, store);
|
|
89
|
+
registerKnowledgeTools(ctx, { store });
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
92
|
+
export { KnowledgeCardsService, TRADING_KNOWLEDGE_CARDS_KEY, apply, createFileKnowledgeCardStore, createKnowledgeGraphTool, defaultStorePath, inject, name, registerKnowledgeTools };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//#region src/store-memory.ts
|
|
2
|
+
function createMemoryKnowledgeCardStore(initialCards = []) {
|
|
3
|
+
const map = /* @__PURE__ */ new Map();
|
|
4
|
+
for (const c of initialCards) map.set(c.id, { ...c });
|
|
5
|
+
return {
|
|
6
|
+
async list() {
|
|
7
|
+
return Array.from(map.values());
|
|
8
|
+
},
|
|
9
|
+
async get(id) {
|
|
10
|
+
return map.get(id);
|
|
11
|
+
},
|
|
12
|
+
async getByUrl(url) {
|
|
13
|
+
const trimmed = url.trim();
|
|
14
|
+
for (const card of map.values()) if (card.source.url.trim() === trimmed) return card;
|
|
15
|
+
},
|
|
16
|
+
async save(card) {
|
|
17
|
+
map.set(card.id, { ...card });
|
|
18
|
+
},
|
|
19
|
+
async delete(id) {
|
|
20
|
+
return map.delete(id);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
export { createMemoryKnowledgeCardStore };
|
package/lib/tool.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { KnowledgeCard, KnowledgeCardStore } from "./types.js";
|
|
2
|
+
import { createFileKnowledgeCardStore } from "./knowledge-fs.js";
|
|
3
|
+
//#region src/tool.d.ts
|
|
4
|
+
interface KnowledgeIngestToolOptions {
|
|
5
|
+
/** 可选:卡片成功落盘后的回调(issue #30:事件总线 emit('knowledge') 的接线点)。 */
|
|
6
|
+
onWritten?: (card: KnowledgeCard) => void;
|
|
7
|
+
}
|
|
8
|
+
declare function createKnowledgeIngestTool(store: KnowledgeCardStore, options?: KnowledgeIngestToolOptions): import("@deepseek-ai/dsh-tools").ToolDefinition;
|
|
9
|
+
declare function createKnowledgeSearchTool(store: KnowledgeCardStore): import("@deepseek-ai/dsh-tools").ToolDefinition;
|
|
10
|
+
declare function createKnowledgeGetTool(store: KnowledgeCardStore): import("@deepseek-ai/dsh-tools").ToolDefinition;
|
|
11
|
+
interface KnowledgeDeleteToolOptions {
|
|
12
|
+
/** 可选:删除成功后的回调(与 ingest 一致,emit('knowledge') 通知 UI 刷新)。 */
|
|
13
|
+
onWritten?: () => void;
|
|
14
|
+
}
|
|
15
|
+
declare function createKnowledgeDeleteTool(store: KnowledgeCardStore, options?: KnowledgeDeleteToolOptions): import("@deepseek-ai/dsh-tools").ToolDefinition;
|
|
16
|
+
//#endregion
|
|
17
|
+
export { KnowledgeDeleteToolOptions, KnowledgeIngestToolOptions, createFileKnowledgeCardStore, createKnowledgeDeleteTool, createKnowledgeGetTool, createKnowledgeIngestTool, createKnowledgeSearchTool };
|
package/lib/tool.js
ADDED
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
import { validateKnowledgeCard } from "./validate.js";
|
|
2
|
+
import { createFileKnowledgeCardStore } from "./knowledge-fs.js";
|
|
3
|
+
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
4
|
+
//#region src/tool.ts
|
|
5
|
+
/**
|
|
6
|
+
* 知识库 Agent 工具(对齐 docs/design/knowledge-graph.md §4 与 dsh-tools 规范)。
|
|
7
|
+
*
|
|
8
|
+
* 包含:
|
|
9
|
+
* - knowledge_ingest: 结构校验 + URL 查重 Update / Create
|
|
10
|
+
* - knowledge_search: 跨字段检索(相关度排序)+ 多维过滤 + detail=full 全文返回
|
|
11
|
+
* - knowledge_get: 按 id 读取单张卡片全文
|
|
12
|
+
* - knowledge_delete: 证伪下架(删除卡片并清理 related 悬空引用)
|
|
13
|
+
*/
|
|
14
|
+
/** 卡片全文的统一文本渲染(knowledge_get 与 knowledge_search detail=full 共用)。 */
|
|
15
|
+
function renderCardFullLines(card) {
|
|
16
|
+
const lines = [];
|
|
17
|
+
if (card.coreClaims.length > 0) lines.push(` 核心论点: ${card.coreClaims.join(";")}`);
|
|
18
|
+
const fc = card.factCheck;
|
|
19
|
+
const fcParts = [];
|
|
20
|
+
if (fc.verified.length > 0) fcParts.push(`证实: ${fc.verified.join(";")}`);
|
|
21
|
+
if (fc.discrepancies.length > 0) fcParts.push(`有出入: ${fc.discrepancies.join(";")}`);
|
|
22
|
+
if (fc.unverifiable.length > 0) fcParts.push(`无法核实: ${fc.unverifiable.join(";")}`);
|
|
23
|
+
if (fcParts.length > 0) lines.push(` 事实核查: ${fcParts.join(" | ")}`);
|
|
24
|
+
if (card.takeaways.length > 0) lines.push(` 可复用经验: ${card.takeaways.join(";")}`);
|
|
25
|
+
if (card.boundaries.length > 0) lines.push(` 适用边界: ${card.boundaries.join(";")}`);
|
|
26
|
+
if (card.tickers && card.tickers.length > 0) lines.push(` 关联标的: ${card.tickers.join(", ")}`);
|
|
27
|
+
return lines;
|
|
28
|
+
}
|
|
29
|
+
function createKnowledgeIngestTool(store, options = {}) {
|
|
30
|
+
const { onWritten } = options;
|
|
31
|
+
return defineTool({
|
|
32
|
+
name: "knowledge_ingest",
|
|
33
|
+
description: "将经事实核查后的结构化知识卡片(Content Insight 产物)入库到本地知识库。支持自动 URL 查重(重复 URL 自动走更新且保持 ID 不变)及关联关系验证。",
|
|
34
|
+
parameters: {
|
|
35
|
+
title: {
|
|
36
|
+
type: "string",
|
|
37
|
+
required: true,
|
|
38
|
+
description: "知识卡片主题(例如「高股息策略在低利率环境下的防御逻辑」)"
|
|
39
|
+
},
|
|
40
|
+
summary: {
|
|
41
|
+
type: "string",
|
|
42
|
+
required: true,
|
|
43
|
+
description: "2-4 条核心论点合并的一句话概述(图谱 hover 与快速导读展示)"
|
|
44
|
+
},
|
|
45
|
+
sourceType: {
|
|
46
|
+
type: "string",
|
|
47
|
+
required: true,
|
|
48
|
+
description: "素材来源类型:\"bilibili\"(B站)、\"wechat\"(微信公众号)、\"manual\"(手工录入)"
|
|
49
|
+
},
|
|
50
|
+
sourceUrl: {
|
|
51
|
+
type: "string",
|
|
52
|
+
required: true,
|
|
53
|
+
description: "素材链接或去重标识(例如 B 站 BV 链接、公众号文章链接)"
|
|
54
|
+
},
|
|
55
|
+
sourceAuthor: {
|
|
56
|
+
type: "string",
|
|
57
|
+
required: true,
|
|
58
|
+
description: "来源作者/UP主/公众号名称"
|
|
59
|
+
},
|
|
60
|
+
publishedAt: {
|
|
61
|
+
type: "string",
|
|
62
|
+
description: "素材发布日期(ISO 格式如 2026-08-30,可选)"
|
|
63
|
+
},
|
|
64
|
+
credibility: {
|
|
65
|
+
type: "string",
|
|
66
|
+
required: true,
|
|
67
|
+
description: "依据事实核查整体定级:\"high\"(事实严密证据充分)、\"medium\"(观点可取但部分有出入)、\"low\"(存在重大未核实或明显漏洞)"
|
|
68
|
+
},
|
|
69
|
+
coreClaimsJson: {
|
|
70
|
+
type: "string",
|
|
71
|
+
required: true,
|
|
72
|
+
description: "JSON 字符串数组,核心论点列表(保留原作者推理链条),例如 '[\"论点1\", \"论点2\"]'"
|
|
73
|
+
},
|
|
74
|
+
factCheckJson: {
|
|
75
|
+
type: "string",
|
|
76
|
+
required: true,
|
|
77
|
+
description: "JSON 对象,事实核查三档分类,例如 '{\"verified\":[\"证实1\"],\"discrepancies\":[\"出入1\"],\"unverifiable\":[]}'"
|
|
78
|
+
},
|
|
79
|
+
takeawaysJson: {
|
|
80
|
+
type: "string",
|
|
81
|
+
description: "可选 JSON 字符串数组,可复用的分析框架与经验总结,例如 '[\"经验1\"]'"
|
|
82
|
+
},
|
|
83
|
+
boundariesJson: {
|
|
84
|
+
type: "string",
|
|
85
|
+
description: "可选 JSON 字符串数组,适用边界、失效情景与避坑指南,例如 '[\"边界1\"]'"
|
|
86
|
+
},
|
|
87
|
+
tagsJson: {
|
|
88
|
+
type: "string",
|
|
89
|
+
required: true,
|
|
90
|
+
description: "JSON 字符串数组,受控主题标签列表,例如 '[\"宏观\", \"高股息\", \"红利策略\"]'"
|
|
91
|
+
},
|
|
92
|
+
tickersJson: {
|
|
93
|
+
type: "string",
|
|
94
|
+
description: "可选 JSON 字符串数组,关联标的代码列表,例如 '[\"BTCUSDT\", \"600519.SH\"]'"
|
|
95
|
+
},
|
|
96
|
+
relatedJson: {
|
|
97
|
+
type: "string",
|
|
98
|
+
description: "可选 JSON 字符串数组,显式关联的其他已入库卡片 ID 列表,例如 '[\"kc_01j...\", \"kc_01k...\"]'"
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
output: {
|
|
102
|
+
schema: { type: "string" },
|
|
103
|
+
render: (_args, value) => [{
|
|
104
|
+
type: "text",
|
|
105
|
+
text: String(value)
|
|
106
|
+
}]
|
|
107
|
+
},
|
|
108
|
+
async execute(raw) {
|
|
109
|
+
const args = raw ?? {};
|
|
110
|
+
function parseJsonArray(val, fieldName, required = false) {
|
|
111
|
+
if (!val) return [];
|
|
112
|
+
if (Array.isArray(val)) return val.map(String);
|
|
113
|
+
if (typeof val === "string") try {
|
|
114
|
+
const parsed = JSON.parse(val);
|
|
115
|
+
if (Array.isArray(parsed)) return parsed.map(String);
|
|
116
|
+
} catch {
|
|
117
|
+
throw new Error(`[knowledge_ingest] 参数 ${fieldName} 不是合法的 JSON 数组: ${val}`);
|
|
118
|
+
}
|
|
119
|
+
if (required) throw new Error(`[knowledge_ingest] 参数 ${fieldName} 必须为字符串数组`);
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
function parseJsonObject(val, fieldName) {
|
|
123
|
+
if (!val) return {};
|
|
124
|
+
if (typeof val === "object" && !Array.isArray(val)) return val;
|
|
125
|
+
if (typeof val === "string") try {
|
|
126
|
+
const parsed = JSON.parse(val);
|
|
127
|
+
if (typeof parsed === "object" && parsed !== null) return parsed;
|
|
128
|
+
} catch {
|
|
129
|
+
throw new Error(`[knowledge_ingest] 参数 ${fieldName} 不是合法的 JSON 对象: ${val}`);
|
|
130
|
+
}
|
|
131
|
+
return {};
|
|
132
|
+
}
|
|
133
|
+
let coreClaims = [];
|
|
134
|
+
let factCheck = {
|
|
135
|
+
verified: [],
|
|
136
|
+
discrepancies: [],
|
|
137
|
+
unverifiable: []
|
|
138
|
+
};
|
|
139
|
+
let takeaways = [];
|
|
140
|
+
let boundaries = [];
|
|
141
|
+
let tags = [];
|
|
142
|
+
let tickers;
|
|
143
|
+
let related;
|
|
144
|
+
try {
|
|
145
|
+
coreClaims = parseJsonArray(args.coreClaimsJson, "coreClaimsJson", true);
|
|
146
|
+
factCheck = parseJsonObject(args.factCheckJson, "factCheckJson");
|
|
147
|
+
takeaways = parseJsonArray(args.takeawaysJson, "takeawaysJson");
|
|
148
|
+
boundaries = parseJsonArray(args.boundariesJson, "boundariesJson");
|
|
149
|
+
tags = parseJsonArray(args.tagsJson, "tagsJson", true);
|
|
150
|
+
tickers = args.tickersJson ? parseJsonArray(args.tickersJson, "tickersJson") : void 0;
|
|
151
|
+
related = args.relatedJson ? parseJsonArray(args.relatedJson, "relatedJson") : void 0;
|
|
152
|
+
} catch (err) {
|
|
153
|
+
return `[knowledge_ingest] 参数解析失败: ${err.message}`;
|
|
154
|
+
}
|
|
155
|
+
const cardInput = {
|
|
156
|
+
title: typeof args.title === "string" ? args.title : "",
|
|
157
|
+
summary: typeof args.summary === "string" ? args.summary : "",
|
|
158
|
+
source: {
|
|
159
|
+
type: args.sourceType || "manual",
|
|
160
|
+
url: typeof args.sourceUrl === "string" ? args.sourceUrl : "",
|
|
161
|
+
author: typeof args.sourceAuthor === "string" ? args.sourceAuthor : "",
|
|
162
|
+
publishedAt: typeof args.publishedAt === "string" ? args.publishedAt : void 0
|
|
163
|
+
},
|
|
164
|
+
credibility: args.credibility || "high",
|
|
165
|
+
coreClaims,
|
|
166
|
+
factCheck: {
|
|
167
|
+
verified: Array.isArray(factCheck.verified) ? factCheck.verified.map(String) : [],
|
|
168
|
+
discrepancies: Array.isArray(factCheck.discrepancies) ? factCheck.discrepancies.map(String) : [],
|
|
169
|
+
unverifiable: Array.isArray(factCheck.unverifiable) ? factCheck.unverifiable.map(String) : []
|
|
170
|
+
},
|
|
171
|
+
takeaways,
|
|
172
|
+
boundaries,
|
|
173
|
+
tags,
|
|
174
|
+
tickers,
|
|
175
|
+
related
|
|
176
|
+
};
|
|
177
|
+
const existingList = await store.list();
|
|
178
|
+
const existingByUrl = await store.getByUrl(cardInput.source.url);
|
|
179
|
+
const inputToValidate = {
|
|
180
|
+
...cardInput,
|
|
181
|
+
id: existingByUrl?.id,
|
|
182
|
+
createdAt: existingByUrl?.createdAt
|
|
183
|
+
};
|
|
184
|
+
const validation = validateKnowledgeCard(inputToValidate, existingList);
|
|
185
|
+
if (!validation.ok || !validation.card) return `[knowledge_ingest] 知识卡片校验失败: ${validation.error ?? "未知校验错误"}`;
|
|
186
|
+
const card = validation.card;
|
|
187
|
+
await store.save(card);
|
|
188
|
+
onWritten?.(card);
|
|
189
|
+
return `[knowledge_ingest] ${!!existingByUrl ? "成功更新已有知识卡片" : "成功创建新知识卡片"} [${card.id}] "${card.title}" (标签: ${card.tags.join(", ")}${card.related?.length ? `, 关联: ${card.related.join(", ")}` : ""})`;
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
function createKnowledgeSearchTool(store) {
|
|
194
|
+
return defineTool({
|
|
195
|
+
name: "knowledge_search",
|
|
196
|
+
description: "检索本地知识库中的知识卡片。有关键词时按字段命中相关度排序(标签 > 标题 > 核心论点 > 摘要/作者),无关键词时按更新时间倒序;支持按主体(cluster,配合 knowledge_graph 两级检索)、作者、来源类型、可信度多维过滤。detail=\"full\" 时同时返回核心论点、事实核查与可复用经验全文(上限 20 张)。",
|
|
197
|
+
parameters: {
|
|
198
|
+
query: {
|
|
199
|
+
type: "string",
|
|
200
|
+
description: "搜索关键词(在标题、摘要、核心论点、标签中模糊匹配)"
|
|
201
|
+
},
|
|
202
|
+
tags: {
|
|
203
|
+
type: "string",
|
|
204
|
+
description: "逗号分隔的标签过滤(例如 \"宏观,高股息\",匹配其中任意一个)"
|
|
205
|
+
},
|
|
206
|
+
cluster: {
|
|
207
|
+
type: "string",
|
|
208
|
+
description: "按主体精确过滤(主体 = 图谱聚类键 = 卡片首个标签)。两级检索第二级:先 knowledge_graph 看主体分布,再用本参数钻取该主体下的卡片"
|
|
209
|
+
},
|
|
210
|
+
author: {
|
|
211
|
+
type: "string",
|
|
212
|
+
description: "按作者/UP主/公众号过滤(子串匹配)"
|
|
213
|
+
},
|
|
214
|
+
sourceType: {
|
|
215
|
+
type: "string",
|
|
216
|
+
description: "按来源类型过滤:\"bilibili\" | \"wechat\" | \"manual\""
|
|
217
|
+
},
|
|
218
|
+
credibility: {
|
|
219
|
+
type: "string",
|
|
220
|
+
description: "按可信度过滤:\"high\" | \"medium\" | \"low\""
|
|
221
|
+
},
|
|
222
|
+
limit: {
|
|
223
|
+
type: "number",
|
|
224
|
+
description: "最大返回数量(默认 20)",
|
|
225
|
+
default: 20
|
|
226
|
+
},
|
|
227
|
+
detail: {
|
|
228
|
+
type: "string",
|
|
229
|
+
description: "返回详略:\"summary\"(默认,摘要级)| \"full\"(附核心论点/事实核查/经验/边界全文)",
|
|
230
|
+
default: "summary"
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
output: {
|
|
234
|
+
schema: { type: "string" },
|
|
235
|
+
render: (_args, value) => [{
|
|
236
|
+
type: "text",
|
|
237
|
+
text: String(value)
|
|
238
|
+
}]
|
|
239
|
+
},
|
|
240
|
+
async execute(raw) {
|
|
241
|
+
const params = raw ?? {};
|
|
242
|
+
const allCards = await store.list();
|
|
243
|
+
const queryLower = typeof params.query === "string" ? params.query.trim().toLowerCase() : "";
|
|
244
|
+
const authorLower = typeof params.author === "string" ? params.author.trim().toLowerCase() : "";
|
|
245
|
+
const rawTags = typeof params.tags === "string" ? params.tags.split(",").map((t) => t.trim().toLowerCase()).filter(Boolean) : [];
|
|
246
|
+
const targetCluster = typeof params.cluster === "string" ? params.cluster.trim().toLowerCase() : "";
|
|
247
|
+
const targetSourceType = typeof params.sourceType === "string" ? params.sourceType.trim() : void 0;
|
|
248
|
+
const targetCredibility = typeof params.credibility === "string" ? params.credibility.trim() : void 0;
|
|
249
|
+
const limit = typeof params.limit === "number" && Number.isFinite(params.limit) ? Math.max(1, Math.min(params.limit, 100)) : 20;
|
|
250
|
+
const detail = params.detail === "full" ? "full" : "summary";
|
|
251
|
+
const effectiveLimit = detail === "full" ? Math.min(limit, 20) : limit;
|
|
252
|
+
const matched = allCards.filter((card) => {
|
|
253
|
+
if (targetSourceType && card.source.type !== targetSourceType) return false;
|
|
254
|
+
if (targetCredibility && card.credibility !== targetCredibility) return false;
|
|
255
|
+
if (targetCluster && (card.tags[0] ?? "").trim().toLowerCase() !== targetCluster) return false;
|
|
256
|
+
if (authorLower && !card.source.author.toLowerCase().includes(authorLower)) return false;
|
|
257
|
+
if (rawTags.length > 0) {
|
|
258
|
+
const cardTagsLower = card.tags.map((t) => t.toLowerCase());
|
|
259
|
+
if (!rawTags.some((t) => cardTagsLower.includes(t))) return false;
|
|
260
|
+
}
|
|
261
|
+
if (queryLower) {
|
|
262
|
+
const titleMatch = card.title.toLowerCase().includes(queryLower);
|
|
263
|
+
const summaryMatch = card.summary.toLowerCase().includes(queryLower);
|
|
264
|
+
const claimsMatch = card.coreClaims.some((c) => c.toLowerCase().includes(queryLower));
|
|
265
|
+
const tagsMatch = card.tags.some((t) => t.toLowerCase().includes(queryLower));
|
|
266
|
+
const authorMatch = card.source.author.toLowerCase().includes(queryLower);
|
|
267
|
+
if (!titleMatch && !summaryMatch && !claimsMatch && !tagsMatch && !authorMatch) return false;
|
|
268
|
+
}
|
|
269
|
+
return true;
|
|
270
|
+
});
|
|
271
|
+
const scored = matched.map((card) => {
|
|
272
|
+
let score = 0;
|
|
273
|
+
if (queryLower) {
|
|
274
|
+
if (card.tags.some((t) => t.toLowerCase().includes(queryLower))) score += 4;
|
|
275
|
+
if (card.title.toLowerCase().includes(queryLower)) score += 3;
|
|
276
|
+
if (card.coreClaims.some((c) => c.toLowerCase().includes(queryLower))) score += 2;
|
|
277
|
+
if (card.summary.toLowerCase().includes(queryLower)) score += 1;
|
|
278
|
+
if (card.source.author.toLowerCase().includes(queryLower)) score += 1;
|
|
279
|
+
}
|
|
280
|
+
return {
|
|
281
|
+
card,
|
|
282
|
+
score
|
|
283
|
+
};
|
|
284
|
+
});
|
|
285
|
+
scored.sort((a, b) => b.score - a.score || new Date(b.card.updatedAt).getTime() - new Date(a.card.updatedAt).getTime());
|
|
286
|
+
const results = scored.slice(0, effectiveLimit).map((s) => s.card);
|
|
287
|
+
const lines = [`[knowledge_search] 匹配到 ${matched.length} 张卡片 (展示前 ${results.length} 项${detail === "full" ? ", detail=full" : ""}):`];
|
|
288
|
+
for (const card of results) {
|
|
289
|
+
lines.push(`- [${card.id}] "${card.title}" (${card.source.type} @ ${card.source.author}, 可信度: ${card.credibility})`);
|
|
290
|
+
lines.push(` 摘要: ${card.summary}`);
|
|
291
|
+
lines.push(` 标签: ${card.tags.join(", ")} | 链接: ${card.source.url}`);
|
|
292
|
+
if (detail === "full") lines.push(...renderCardFullLines(card));
|
|
293
|
+
}
|
|
294
|
+
if (results.length === 0) lines.push("(未搜索到符合条件的知识卡片)");
|
|
295
|
+
return lines.join("\n");
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
function createKnowledgeGetTool(store) {
|
|
300
|
+
return defineTool({
|
|
301
|
+
name: "knowledge_get",
|
|
302
|
+
description: "按 id 读取单张知识卡片的完整内容(核心论点、事实核查三桶、可复用经验、适用边界)。id 可来自 knowledge_search 结果或历史分析中的引用标注。",
|
|
303
|
+
parameters: { id: {
|
|
304
|
+
type: "string",
|
|
305
|
+
required: true,
|
|
306
|
+
description: "知识卡片 id(kc_...)"
|
|
307
|
+
} },
|
|
308
|
+
output: {
|
|
309
|
+
schema: { type: "string" },
|
|
310
|
+
render: (_args, value) => [{
|
|
311
|
+
type: "text",
|
|
312
|
+
text: String(value)
|
|
313
|
+
}]
|
|
314
|
+
},
|
|
315
|
+
async execute(raw) {
|
|
316
|
+
const args = raw ?? {};
|
|
317
|
+
const id = typeof args.id === "string" ? args.id.trim() : "";
|
|
318
|
+
if (!id) return "[knowledge_get] 参数 id 为必填字符串";
|
|
319
|
+
const card = await store.get(id);
|
|
320
|
+
if (!card) return `[knowledge_get] 未找到卡片 [${id}]`;
|
|
321
|
+
const lines = [
|
|
322
|
+
`[knowledge_get] [${card.id}] "${card.title}"`,
|
|
323
|
+
` 摘要: ${card.summary}`,
|
|
324
|
+
` 来源: ${card.source.type} @ ${card.source.author}${card.source.publishedAt ? ` (${card.source.publishedAt})` : ""} | 可信度: ${card.credibility} | 更新: ${card.updatedAt}`,
|
|
325
|
+
` 标签: ${card.tags.join(", ")} | 链接: ${card.source.url}`,
|
|
326
|
+
...renderCardFullLines(card)
|
|
327
|
+
];
|
|
328
|
+
if (card.related && card.related.length > 0) lines.push(` 显式关联: ${card.related.join(", ")}`);
|
|
329
|
+
return lines.join("\n");
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
function createKnowledgeDeleteTool(store, options = {}) {
|
|
334
|
+
const { onWritten } = options;
|
|
335
|
+
return defineTool({
|
|
336
|
+
name: "knowledge_delete",
|
|
337
|
+
description: "从本地知识库删除(下架)指定知识卡片。用于知识点/经验被事实证伪、来源撤稿或卡片重复等场景。删除时自动清理其他卡片指向本卡片的 related 引用,并在输出中回显被删卡片的标题与核心论点留痕;证伪结论本身应另行记入交易日志或对话沉淀。",
|
|
338
|
+
parameters: {
|
|
339
|
+
id: {
|
|
340
|
+
type: "string",
|
|
341
|
+
required: true,
|
|
342
|
+
description: "要删除的知识卡片 id(kc_...)。不确定 id 时先用 knowledge_search 查询确认,不要凭模糊记忆删除。"
|
|
343
|
+
},
|
|
344
|
+
reason: {
|
|
345
|
+
type: "string",
|
|
346
|
+
description: "删除原因(如「核心论点被 XX 数据证伪」「来源撤稿」),回显在结果中供留痕"
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
output: {
|
|
350
|
+
schema: { type: "string" },
|
|
351
|
+
render: (_args, value) => [{
|
|
352
|
+
type: "text",
|
|
353
|
+
text: String(value)
|
|
354
|
+
}]
|
|
355
|
+
},
|
|
356
|
+
async execute(raw) {
|
|
357
|
+
const args = raw ?? {};
|
|
358
|
+
const id = typeof args.id === "string" ? args.id.trim() : "";
|
|
359
|
+
const reason = typeof args.reason === "string" ? args.reason.trim() : "";
|
|
360
|
+
if (!id) return "[knowledge_delete] 参数 id 为必填字符串";
|
|
361
|
+
const target = await store.get(id);
|
|
362
|
+
if (!target) return `[knowledge_delete] 未找到卡片 [${id}],请先用 knowledge_search 确认卡片 id`;
|
|
363
|
+
const referencing = [];
|
|
364
|
+
for (const card of await store.list()) if (card.related && card.related.includes(id)) {
|
|
365
|
+
const remaining = card.related.filter((r) => r !== id);
|
|
366
|
+
referencing.push(card.id);
|
|
367
|
+
await store.save({
|
|
368
|
+
...card,
|
|
369
|
+
related: remaining.length > 0 ? remaining : void 0,
|
|
370
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
if (!await store.delete(id)) return `[knowledge_delete] 删除失败:卡片 [${id}] 在删除时已不存在`;
|
|
374
|
+
onWritten?.();
|
|
375
|
+
const lines = [`[knowledge_delete] 已删除卡片 [${target.id}] "${target.title}"`, ` 核心论点(留痕回显): ${target.coreClaims.join(";")}`];
|
|
376
|
+
if (reason) lines.push(` 删除原因: ${reason}`);
|
|
377
|
+
lines.push(referencing.length > 0 ? ` 已清理 ${referencing.length} 张卡片的 related 引用: ${referencing.join(", ")}` : " 无其他卡片引用本卡片");
|
|
378
|
+
return lines.join("\n");
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
//#endregion
|
|
383
|
+
export { createFileKnowledgeCardStore, createKnowledgeDeleteTool, createKnowledgeGetTool, createKnowledgeIngestTool, createKnowledgeSearchTool };
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* 知识库核心契约类型(对齐 docs/design/knowledge-graph.md §2)。
|
|
4
|
+
*/
|
|
5
|
+
type KnowledgeSourceType = 'bilibili' | 'wechat' | 'manual';
|
|
6
|
+
type KnowledgeCredibility = 'high' | 'medium' | 'low';
|
|
7
|
+
interface KnowledgeFactCheck {
|
|
8
|
+
readonly verified: readonly string[];
|
|
9
|
+
readonly discrepancies: readonly string[];
|
|
10
|
+
readonly unverifiable: readonly string[];
|
|
11
|
+
}
|
|
12
|
+
interface KnowledgeSource {
|
|
13
|
+
readonly type: KnowledgeSourceType;
|
|
14
|
+
readonly url: string;
|
|
15
|
+
readonly author: string;
|
|
16
|
+
readonly publishedAt?: string;
|
|
17
|
+
}
|
|
18
|
+
interface KnowledgeCard {
|
|
19
|
+
readonly id: string;
|
|
20
|
+
readonly title: string;
|
|
21
|
+
readonly summary: string;
|
|
22
|
+
readonly source: KnowledgeSource;
|
|
23
|
+
readonly credibility: KnowledgeCredibility;
|
|
24
|
+
readonly coreClaims: readonly string[];
|
|
25
|
+
readonly factCheck: KnowledgeFactCheck;
|
|
26
|
+
readonly takeaways: readonly string[];
|
|
27
|
+
readonly boundaries: readonly string[];
|
|
28
|
+
readonly tags: readonly string[];
|
|
29
|
+
readonly tickers?: readonly string[];
|
|
30
|
+
readonly related?: readonly string[];
|
|
31
|
+
readonly createdAt: string;
|
|
32
|
+
readonly updatedAt: string;
|
|
33
|
+
}
|
|
34
|
+
type KnowledgeCardInput = Omit<KnowledgeCard, 'id' | 'createdAt' | 'updatedAt'> & {
|
|
35
|
+
readonly id?: string;
|
|
36
|
+
readonly createdAt?: string;
|
|
37
|
+
readonly updatedAt?: string;
|
|
38
|
+
};
|
|
39
|
+
type GraphLinkKind = 'related' | 'co-tag' | 'co-author' | 'tag-hub';
|
|
40
|
+
interface KnowledgeGraphNode {
|
|
41
|
+
readonly id: string;
|
|
42
|
+
readonly label: string;
|
|
43
|
+
readonly cluster: string;
|
|
44
|
+
readonly credibility: KnowledgeCredibility;
|
|
45
|
+
readonly degree: number;
|
|
46
|
+
/** 节点类别:'card' = 知识卡片(默认,向后兼容),'tag' = 标签 hub 节点(Obsidian 式)。 */
|
|
47
|
+
readonly type?: 'card' | 'tag';
|
|
48
|
+
readonly raw?: KnowledgeCard;
|
|
49
|
+
}
|
|
50
|
+
interface KnowledgeGraphLink {
|
|
51
|
+
readonly source: string;
|
|
52
|
+
readonly target: string;
|
|
53
|
+
readonly kind: GraphLinkKind;
|
|
54
|
+
readonly weight: number;
|
|
55
|
+
}
|
|
56
|
+
interface KnowledgeGraphData {
|
|
57
|
+
readonly nodes: readonly KnowledgeGraphNode[];
|
|
58
|
+
readonly links: readonly KnowledgeGraphLink[];
|
|
59
|
+
}
|
|
60
|
+
interface BuildGraphOptions {
|
|
61
|
+
readonly coTag?: boolean;
|
|
62
|
+
readonly coAuthor?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Obsidian 式标签 hub 模式(2026-09-01 起 UI 默认开启):标签聚合为独立 hub 节点,
|
|
65
|
+
* 卡片仅与自己的标签建边(kind='tag-hub')+ 显式 related 建边;关闭 co-tag/co-author
|
|
66
|
+
* 全配对边。大规模单作者/单标签库下全配对是 O(n²) 边爆炸(215 卡 ≈ 2 万+边),
|
|
67
|
+
* 力导模拟发散、画布糊成一团——hub 模式把边数降为 O(卡片数 × 平均标签数)。
|
|
68
|
+
*/
|
|
69
|
+
readonly tagHubs?: boolean;
|
|
70
|
+
}
|
|
71
|
+
interface KnowledgeCardStore {
|
|
72
|
+
list(): Promise<readonly KnowledgeCard[]>;
|
|
73
|
+
get(id: string): Promise<KnowledgeCard | undefined>;
|
|
74
|
+
getByUrl(url: string): Promise<KnowledgeCard | undefined>;
|
|
75
|
+
save(card: KnowledgeCard): Promise<void>;
|
|
76
|
+
delete(id: string): Promise<boolean>;
|
|
77
|
+
}
|
|
78
|
+
//#endregion
|
|
79
|
+
export { BuildGraphOptions, GraphLinkKind, KnowledgeCard, KnowledgeCardInput, KnowledgeCardStore, KnowledgeCredibility, KnowledgeFactCheck, KnowledgeGraphData, KnowledgeGraphLink, KnowledgeGraphNode, KnowledgeSource, KnowledgeSourceType };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { KnowledgeCard, KnowledgeCardInput } from "./types.js";
|
|
2
|
+
//#region src/validate.d.ts
|
|
3
|
+
interface ValidationResult {
|
|
4
|
+
ok: boolean;
|
|
5
|
+
card?: KnowledgeCard;
|
|
6
|
+
error?: string;
|
|
7
|
+
}
|
|
8
|
+
declare function generateCardId(): string;
|
|
9
|
+
declare function validateKnowledgeCard(input: KnowledgeCardInput, existingCards?: readonly KnowledgeCard[]): ValidationResult;
|
|
10
|
+
//#endregion
|
|
11
|
+
export { ValidationResult, generateCardId, validateKnowledgeCard };
|
package/lib/validate.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
//#region src/validate.ts
|
|
2
|
+
const ALLOWED_CREDIBILITY = /* @__PURE__ */ new Set([
|
|
3
|
+
"high",
|
|
4
|
+
"medium",
|
|
5
|
+
"low"
|
|
6
|
+
]);
|
|
7
|
+
const ALLOWED_SOURCE_TYPES = /* @__PURE__ */ new Set([
|
|
8
|
+
"bilibili",
|
|
9
|
+
"wechat",
|
|
10
|
+
"manual"
|
|
11
|
+
]);
|
|
12
|
+
function isValidSourceUrl(url, type) {
|
|
13
|
+
if (!url || typeof url !== "string") return false;
|
|
14
|
+
const trimmed = url.trim();
|
|
15
|
+
if (type === "manual") return trimmed === "manual" || trimmed.startsWith("manual:") || trimmed.startsWith("http://") || trimmed.startsWith("https://");
|
|
16
|
+
if (type === "bilibili") return trimmed.includes("bilibili.com") || trimmed.includes("b23.tv") || trimmed.startsWith("BV");
|
|
17
|
+
if (type === "wechat") return trimmed.includes("mp.weixin.qq.com") || trimmed.includes("weixin.qq.com");
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
function generateCardId() {
|
|
21
|
+
return `kc_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
|
|
22
|
+
}
|
|
23
|
+
function validateKnowledgeCard(input, existingCards = []) {
|
|
24
|
+
if (!input || typeof input !== "object") return {
|
|
25
|
+
ok: false,
|
|
26
|
+
error: "输入卡片数据必须是非空对象"
|
|
27
|
+
};
|
|
28
|
+
if (!input.title || typeof input.title !== "string" || input.title.trim().length === 0) return {
|
|
29
|
+
ok: false,
|
|
30
|
+
error: "卡片标题 (title) 为必填字段且不能为空"
|
|
31
|
+
};
|
|
32
|
+
if (!input.summary || typeof input.summary !== "string" || input.summary.trim().length === 0) return {
|
|
33
|
+
ok: false,
|
|
34
|
+
error: "卡片概述 (summary) 为必填字段且不能为空"
|
|
35
|
+
};
|
|
36
|
+
if (!input.source || typeof input.source !== "object") return {
|
|
37
|
+
ok: false,
|
|
38
|
+
error: "卡片来源 (source) 为必填对象"
|
|
39
|
+
};
|
|
40
|
+
const { type, url, author } = input.source;
|
|
41
|
+
if (!type || !ALLOWED_SOURCE_TYPES.has(type)) return {
|
|
42
|
+
ok: false,
|
|
43
|
+
error: `来源类型 (source.type) 非法,允许值为: ${Array.from(ALLOWED_SOURCE_TYPES).join(", ")}`
|
|
44
|
+
};
|
|
45
|
+
if (!url || typeof url !== "string" || url.trim().length === 0) return {
|
|
46
|
+
ok: false,
|
|
47
|
+
error: "来源链接 (source.url) 为必填字段且不能为空"
|
|
48
|
+
};
|
|
49
|
+
if (!isValidSourceUrl(url, type)) return {
|
|
50
|
+
ok: false,
|
|
51
|
+
error: `来源链接 (source.url) 与来源类型 (${type}) 不匹配或不符合白名单规范`
|
|
52
|
+
};
|
|
53
|
+
if (!author || typeof author !== "string" || author.trim().length === 0) return {
|
|
54
|
+
ok: false,
|
|
55
|
+
error: "来源作者/UP主/公众号 (source.author) 为必填字段"
|
|
56
|
+
};
|
|
57
|
+
if (!input.credibility || !ALLOWED_CREDIBILITY.has(input.credibility)) return {
|
|
58
|
+
ok: false,
|
|
59
|
+
error: `内容可信度 (credibility) 非法,允许值为: ${Array.from(ALLOWED_CREDIBILITY).join(", ")}`
|
|
60
|
+
};
|
|
61
|
+
if (!Array.isArray(input.coreClaims) || input.coreClaims.length === 0) return {
|
|
62
|
+
ok: false,
|
|
63
|
+
error: "核心论点 (coreClaims) 必须是非空字符串数组"
|
|
64
|
+
};
|
|
65
|
+
if (!input.factCheck || typeof input.factCheck !== "object") return {
|
|
66
|
+
ok: false,
|
|
67
|
+
error: "事实核查三桶 (factCheck) 为必填对象,包含 verified, discrepancies, unverifiable"
|
|
68
|
+
};
|
|
69
|
+
const { verified = [], discrepancies = [], unverifiable = [] } = input.factCheck;
|
|
70
|
+
if (!Array.isArray(verified) || !Array.isArray(discrepancies) || !Array.isArray(unverifiable)) return {
|
|
71
|
+
ok: false,
|
|
72
|
+
error: "事实核查分类项 (verified / discrepancies / unverifiable) 必须为数组"
|
|
73
|
+
};
|
|
74
|
+
if (!Array.isArray(input.takeaways)) return {
|
|
75
|
+
ok: false,
|
|
76
|
+
error: "可复用经验 (takeaways) 必须为字符串数组"
|
|
77
|
+
};
|
|
78
|
+
if (!Array.isArray(input.boundaries)) return {
|
|
79
|
+
ok: false,
|
|
80
|
+
error: "适用边界与避坑 (boundaries) 必须为字符串数组"
|
|
81
|
+
};
|
|
82
|
+
if (!Array.isArray(input.tags) || input.tags.length === 0) return {
|
|
83
|
+
ok: false,
|
|
84
|
+
error: "主题关键词标签 (tags) 必须包含至少一个标签"
|
|
85
|
+
};
|
|
86
|
+
if (input.related && Array.isArray(input.related) && input.related.length > 0) {
|
|
87
|
+
const existingIds = new Set(existingCards.map((c) => c.id));
|
|
88
|
+
for (const relId of input.related) if (!existingIds.has(relId)) return {
|
|
89
|
+
ok: false,
|
|
90
|
+
error: `显式关联卡片 id [${relId}] 在知识库中不存在(拒绝悬空关联)`
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
94
|
+
const id = input.id && input.id.startsWith("kc_") ? input.id : generateCardId();
|
|
95
|
+
const createdAt = input.createdAt || now;
|
|
96
|
+
const updatedAt = now;
|
|
97
|
+
return {
|
|
98
|
+
ok: true,
|
|
99
|
+
card: {
|
|
100
|
+
id,
|
|
101
|
+
title: input.title.trim(),
|
|
102
|
+
summary: input.summary.trim(),
|
|
103
|
+
source: {
|
|
104
|
+
type: input.source.type,
|
|
105
|
+
url: input.source.url.trim(),
|
|
106
|
+
author: input.source.author.trim(),
|
|
107
|
+
publishedAt: input.source.publishedAt
|
|
108
|
+
},
|
|
109
|
+
credibility: input.credibility,
|
|
110
|
+
coreClaims: input.coreClaims.map((c) => String(c).trim()).filter(Boolean),
|
|
111
|
+
factCheck: {
|
|
112
|
+
verified: verified.map((c) => String(c).trim()).filter(Boolean),
|
|
113
|
+
discrepancies: discrepancies.map((c) => String(c).trim()).filter(Boolean),
|
|
114
|
+
unverifiable: unverifiable.map((c) => String(c).trim()).filter(Boolean)
|
|
115
|
+
},
|
|
116
|
+
takeaways: input.takeaways.map((c) => String(c).trim()).filter(Boolean),
|
|
117
|
+
boundaries: input.boundaries.map((c) => String(c).trim()).filter(Boolean),
|
|
118
|
+
tags: input.tags.map((t) => String(t).trim()).filter(Boolean),
|
|
119
|
+
tickers: input.tickers ? input.tickers.map((t) => String(t).trim().toUpperCase()).filter(Boolean) : void 0,
|
|
120
|
+
related: input.related ? Array.from(new Set(input.related)) : void 0,
|
|
121
|
+
createdAt,
|
|
122
|
+
updatedAt
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
//#endregion
|
|
127
|
+
export { generateCardId, validateKnowledgeCard };
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dshtrading/knowledge",
|
|
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.1.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"types": "./lib/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./lib/index.d.ts",
|
|
11
|
+
"default": "./lib/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./tool": {
|
|
14
|
+
"types": "./lib/tool.d.ts",
|
|
15
|
+
"default": "./lib/tool.js"
|
|
16
|
+
},
|
|
17
|
+
"./plugin": {
|
|
18
|
+
"types": "./lib/plugin.d.ts",
|
|
19
|
+
"default": "./lib/plugin.js"
|
|
20
|
+
},
|
|
21
|
+
"./package.json": "./package.json"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"lib"
|
|
25
|
+
],
|
|
26
|
+
"license": "PolyForm-Noncommercial-1.0.0",
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@deepseek-ai/dsh-tools": "^0.1.2-alpha.2",
|
|
29
|
+
"@deepseek-ai/schemastery": "^3.18.2"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"@deepseek-ai/cordis": ">=4.0.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^24.0.0",
|
|
36
|
+
"tsdown": "^0.22.0",
|
|
37
|
+
"typescript": "^5.9.0",
|
|
38
|
+
"vitest": "^3.0.0"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsdown",
|
|
42
|
+
"test": "vitest run"
|
|
43
|
+
}
|
|
44
|
+
}
|