@jacklika/dsh-tool-memory-graph 0.1.0-alpha.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/README.md ADDED
@@ -0,0 +1,96 @@
1
+ ---
2
+ description: "Model-facing wiki_graph tool exposing the [[link]] graph of a Markdown vault."
3
+ kind: "package-reference"
4
+ ---
5
+
6
+ # @jacklika/dsh-tool-memory-graph
7
+
8
+ English | [中文](README.zh.md)
9
+
10
+ ## Summary
11
+
12
+ `dsh-tool-memory-graph` gives agents read-only graph queries over the Markdown vault used by [`dsh-tool-memory-filesystem`](../tool-memory-filesystem/README.md). The model sees one tool, `wiki_graph`, which returns note nodes and directed edges derived from Obsidian-style `[[link]]` references — either the whole vault or the reachable subgraph around one note. The package reuses the filesystem plugin's vault resolution and parsing helpers, so path containment, frontmatter handling, and the `indexHiddenDirs` policy match without a second parser.
13
+
14
+ ## Table of Contents
15
+
16
+ - [Use this package](#use-this-package)
17
+ - [Understand the implementation](#understand-the-implementation)
18
+ - [Model Experience](#model-experience)
19
+ - [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
20
+ - [Dev Note](#dev-note)
21
+
22
+ -----
23
+
24
+ <a id="use-this-package"></a>
25
+ ## Use this package
26
+
27
+ Mount the plugin in a profile or patch file:
28
+
29
+ ```yaml
30
+ - name: '@jacklika/dsh-tool-memory-graph'
31
+ ```
32
+
33
+ It resolves the same vault root per call as `tool-memory-filesystem`: an explicit `vaultRoot` (relative paths anchor at the session workspace), otherwise `<session cwd>/.dsh/memory/`.
34
+
35
+ ### Configure the graph
36
+
37
+ | Field | Type | Default | Description |
38
+ |---|---|---|---|
39
+ | `vaultRoot` | `string` | `''` → `<session cwd>/.dsh/memory/` | Vault root. Empty selects the per-workspace memory directory; a relative path resolves against the session workspace. |
40
+ | `extensions` | `string[]` | `['.md']` | File extensions treated as notes. |
41
+ | `indexHiddenDirs` | `boolean` | `false` | Index directories whose names start with `.` (`.git` and `node_modules` stay excluded). Enable when `vaultRoot` points at the workspace root so `.dsh/memory/` notes are included. |
42
+ | `maxDepth` | `number` | `1` | Maximum BFS depth when a center id is given. |
43
+ | `maxNodes` | `number` | `200` | Maximum nodes returned in one graph. |
44
+
45
+ ### Tools
46
+
47
+ - `wiki_graph(id?, depth?)` — without `id`, return every note and directed edge in the vault. With `id`, return the reachable subgraph within `depth` hops; edges from boundary nodes are included when both endpoints are selected.
48
+
49
+ -----
50
+
51
+ <a id="understand-the-implementation"></a>
52
+ ## Understand the implementation
53
+
54
+ The package is a single Cordis function plugin that registers `wiki_graph` on `ctx.tools`. Per call it resolves the vault root through the shared `resolveMemoryVaultRoot` helper, lists notes through the shared `listNotePaths` walker, and extracts `[[link]]` targets through `extractLinks`/`resolveLinkTarget`. A breadth-first walk selects the node set first; edges are then collected from every selected node so boundary-node outgoing edges are not dropped. Output is `{ nodes: [{ id, title, backlinks }], edges: [{ from, to }] }`.
55
+
56
+ -----
57
+
58
+ <a id="model-experience"></a>
59
+ ## Model Experience
60
+
61
+ ### Tool schema
62
+
63
+ #### What the model sees
64
+
65
+ The model sees the generated `wiki_graph` schema in the [tool catalog](../../../docs/tool-catalog.md#deepseek-aidsh-tool-memory-graph). The description states that the graph is read-only and derived from `[[link]]` references.
66
+
67
+ ##### Verbatim description for `wiki_graph`
68
+
69
+ ```markdown
70
+ Return the Obsidian-style [[link]] graph of the wiki vault: note ids, titles, and directed edges. Without an id it returns the whole vault graph (node-capped); with an id it returns the subgraph reachable from that note within the given depth.
71
+ ```
72
+
73
+ #### Token effect
74
+
75
+ A whole-vault call returns every note and edge; large vaults can add many tokens. Center-id subgraph calls bound output by `depth`.
76
+
77
+ #### KV Cache effect
78
+
79
+ Independent. The plugin only supplies tool results; it does not change the request header, system prompt, or tool list.
80
+
81
+ ## Known Limitations and Deferred Work
82
+
83
+ <a id="known-limitations-and-deferred-work"></a>
84
+
85
+ - **No visualization** — the tool returns JSON nodes and edges; rendering (Obsidian graph view, Mermaid) is left to consumers.
86
+ - **Backlinks reflect the whole vault** — node `backlinks` counts are computed over all notes, not just the selected subgraph.
87
+
88
+ <a id="dev-note"></a>
89
+ ### Dev Note
90
+
91
+ <details>
92
+ <summary>Working context for maintainers — click to expand</summary>
93
+
94
+ None.
95
+
96
+ </details>
package/README.zh.md ADDED
@@ -0,0 +1,96 @@
1
+ ---
2
+ description: "面向模型的 wiki_graph 工具,输出 Markdown 仓库的 [[link]] 链接图。"
3
+ kind: "package-reference"
4
+ ---
5
+
6
+ # @jacklika/dsh-tool-memory-graph
7
+
8
+ [English](README.md) | 中文
9
+
10
+ ## 概述
11
+
12
+ `dsh-tool-memory-graph` 让 agent 对 [`dsh-tool-memory-filesystem`](../tool-memory-filesystem/README.zh.md) 使用的 Markdown 仓库做只读图查询。模型可见一个工具 `wiki_graph`,返回由 Obsidian 风格 `[[link]]` 引用推导的笔记节点和有向边——可以是全仓库图,也可以是以某条笔记为中心的可达子图。该包复用 filesystem 插件的仓库根解析与解析辅助函数,路径约束、frontmatter 处理和 `indexHiddenDirs` 策略完全一致,不引入第二套解析器。
13
+
14
+ ## 目录
15
+
16
+ - [使用本包](#use-this-package)
17
+ - [实现说明](#understand-the-implementation)
18
+ - [Model Experience](#model-experience)
19
+ - [已知限制与后续工作](#known-limitations-and-deferred-work)
20
+ - [Dev Note](#dev-note)
21
+
22
+ -----
23
+
24
+ <a id="use-this-package"></a>
25
+ ## 使用
26
+
27
+ 在 profile 或 patch 文件中挂载插件:
28
+
29
+ ```yaml
30
+ - name: '@jacklika/dsh-tool-memory-graph'
31
+ ```
32
+
33
+ 仓库根与 `tool-memory-filesystem` 一致,每次调用时解析:显式 `vaultRoot`(相对路径锚定会话工作区),否则使用 `<session cwd>/.dsh/memory/`。
34
+
35
+ ### 配置
36
+
37
+ | 字段 | 类型 | 默认值 | 说明 |
38
+ |---|---|---|---|
39
+ | `vaultRoot` | `string` | `''` → `<session cwd>/.dsh/memory/` | 仓库根。留空时使用按工作区隔离的记忆目录;相对路径相对于会话工作区解析。 |
40
+ | `extensions` | `string[]` | `['.md']` | 视为笔记的文件扩展名。 |
41
+ | `indexHiddenDirs` | `boolean` | `false` | 是否索引以 `.` 开头的目录(`.git` 与 `node_modules` 始终排除)。当 `vaultRoot` 指向工作区根时开启,以纳入 `.dsh/memory/`。 |
42
+ | `maxDepth` | `number` | `1` | 指定中心笔记时 BFS 的最大深度。 |
43
+ | `maxNodes` | `number` | `200` | 单次图查询返回的最大节点数。 |
44
+
45
+ ### 工具
46
+
47
+ - `wiki_graph(id?, depth?)` —— 不传 `id` 时返回全仓库的节点与有向边;传 `id` 时返回 `depth` 跳内的可达子图,两个端点都被选中时边界节点的出边也会包含。
48
+
49
+ -----
50
+
51
+ <a id="understand-the-implementation"></a>
52
+ ## 实现说明
53
+
54
+ 本包是单个 Cordis 函数插件,在 `ctx.tools` 上注册 `wiki_graph`。每次调用通过共享的 `resolveMemoryVaultRoot` 解析仓库根,用 `listNotePaths` 遍历笔记,用 `extractLinks`/`resolveLinkTarget` 提取 `[[link]]` 目标。先用广度优先遍历确定节点集合,再从所有已选节点收集边,因此边界节点的出边不会丢失。输出为 `{ nodes: [{ id, title, backlinks }], edges: [{ from, to }] }`。
55
+
56
+ -----
57
+
58
+ <a id="model-experience"></a>
59
+ ## 模型体验
60
+
61
+ ### 工具 schema
62
+
63
+ #### 模型可见内容
64
+
65
+ 模型在[工具目录](../../../docs/tool-catalog.zh.md#deepseek-aidsh-tool-memory-graph)中看到生成的 `wiki_graph` schema。描述声明该图是只读的,由 `[[link]]` 引用推导。
66
+
67
+ ##### `wiki_graph` 的原文描述
68
+
69
+ ```markdown
70
+ Return the Obsidian-style [[link]] graph of the wiki vault: note ids, titles, and directed edges. Without an id it returns the whole vault graph (node-capped); with an id it returns the subgraph reachable from that note within the given depth.
71
+ ```
72
+
73
+ #### Token 影响
74
+
75
+ 全仓库调用返回所有节点与边,大仓库可能产生较多 token。指定中心 id 的子图调用通过 `depth` 限制输出规模。
76
+
77
+ #### KV Cache 影响
78
+
79
+ 独立。插件只提供工具结果,不改变请求头、system prompt 或工具列表。
80
+
81
+ ## 已知限制与待办
82
+
83
+ <a id="known-limitations-and-deferred-work"></a>
84
+
85
+ - **无可视化** —— 工具返回 JSON 节点与边;渲染(Obsidian 图谱、Mermaid)由消费方负责。
86
+ - **backlinks 基于全仓库统计** —— 节点的 `backlinks` 计数覆盖全部笔记,而非仅选中子图。
87
+
88
+ <a id="dev-note"></a>
89
+ ### 开发备注
90
+
91
+ <details>
92
+ <summary>维护者工作上下文 —— 点击展开</summary>
93
+
94
+ 无。
95
+
96
+ </details>
package/lib/index.js ADDED
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Model-facing link-graph tool over a Markdown memory vault. The package
3
+ * registers `wiki_graph`, which returns the vault's note/link graph or the
4
+ * subgraph reachable from one note, so an agent can reason about how memory
5
+ * notes connect before reading them.
6
+ * @module @jacklika/dsh-tool-memory-graph
7
+ */
8
+ import { readFile } from 'node:fs/promises';
9
+ import z from '@deepseek-ai/schemastery';
10
+ import { defineTool } from '@deepseek-ai/dsh-tools';
11
+ import { containedPathReal, extractLinks, listNotePaths, resolveLinkTarget, resolveMemoryVaultRoot, splitFrontmatter, vaultRelativeId, } from '@jacklika/dsh-tool-memory-filesystem';
12
+ /** Cordis plugin name used by loader diagnostics. */
13
+ export const name = 'tool-memory-graph';
14
+ /** Services required by the wiki graph tool. */
15
+ export const inject = ['tools'];
16
+ /** Schemastery configuration for the memory graph tool consumer. */
17
+ export const Config = z.object({
18
+ vaultRoot: z.string().default(''),
19
+ extensions: z.array(z.string()).default(['.md']),
20
+ maxDepth: z.number().default(1),
21
+ maxNodes: z.number().default(200),
22
+ indexHiddenDirs: z.boolean().default(false),
23
+ });
24
+ function assertPositiveInteger(name, value) {
25
+ if (!Number.isInteger(value) || value < 1) {
26
+ throw new Error(`tool-memory-graph: ${name} must be a positive integer`);
27
+ }
28
+ }
29
+ async function noteTitle(absolutePath, id) {
30
+ const text = await readFile(absolutePath, 'utf8');
31
+ const { body } = splitFrontmatter(text);
32
+ const heading = /^#\s+(.+)$/m.exec(body);
33
+ return heading?.[1]?.trim() ?? id.replace(/\.[^.]+$/, '');
34
+ }
35
+ /**
36
+ * Build the vault-wide link graph: every note is a node; every `[[link]]`
37
+ * that resolves to an existing note is an edge.
38
+ * @param root - vault root.
39
+ * @param resolved - applied plugin configuration.
40
+ * @returns the full graph, node-capped.
41
+ */
42
+ async function buildGraph(root, resolved) {
43
+ const paths = await listNotePaths(root, resolved.extensions, resolved.indexHiddenDirs);
44
+ const entries = paths.map(path => [vaultRelativeId(root, path), path]);
45
+ const truncated = entries.length > resolved.maxNodes;
46
+ const capped = entries.slice(0, resolved.maxNodes);
47
+ const nodes = [];
48
+ for (const [id, path] of capped)
49
+ nodes.push({ id, title: await noteTitle(path, id) });
50
+ const idSet = new Set(capped.map(([id]) => id));
51
+ const edges = [];
52
+ for (const [id, path] of capped) {
53
+ const text = await readFile(path, 'utf8');
54
+ for (const link of extractLinks(text)) {
55
+ const targetPath = await resolveLinkTarget(root, resolved.extensions, link);
56
+ if (targetPath === undefined)
57
+ continue;
58
+ const targetId = vaultRelativeId(root, targetPath);
59
+ if (idSet.has(targetId))
60
+ edges.push({ from: id, to: targetId });
61
+ }
62
+ }
63
+ return { nodes, edges, truncated };
64
+ }
65
+ /**
66
+ * Collect the subgraph reachable from one note by following `[[link]]`
67
+ * references breadth-first up to `depth`.
68
+ * @param root - vault root.
69
+ * @param resolved - applied plugin configuration.
70
+ * @param startId - vault-relative id of the center note.
71
+ * @param depth - maximum link hops from the center.
72
+ * @returns the neighborhood graph, node-capped.
73
+ */
74
+ async function buildSubgraph(root, resolved, startId, depth) {
75
+ const startPath = await containedPathReal(root, startId);
76
+ const visited = new Map([[vaultRelativeId(root, startPath), startPath]]);
77
+ let frontier = [startPath];
78
+ for (let level = 0; level < depth && frontier.length > 0; level += 1) {
79
+ const next = [];
80
+ for (const path of frontier) {
81
+ const text = await readFile(path, 'utf8');
82
+ for (const link of extractLinks(text)) {
83
+ const targetPath = await resolveLinkTarget(root, resolved.extensions, link);
84
+ if (targetPath === undefined)
85
+ continue;
86
+ const toId = vaultRelativeId(root, targetPath);
87
+ if (!visited.has(toId)) {
88
+ visited.set(toId, targetPath);
89
+ next.push(targetPath);
90
+ }
91
+ }
92
+ }
93
+ frontier = next;
94
+ }
95
+ const truncated = visited.size > resolved.maxNodes;
96
+ const ids = [...visited.keys()].slice(0, resolved.maxNodes);
97
+ const idSet = new Set(ids);
98
+ const edges = [];
99
+ const nodes = [];
100
+ for (const id of ids) {
101
+ const path = await containedPathReal(root, id);
102
+ nodes.push({ id, title: await noteTitle(path, id) });
103
+ const text = await readFile(path, 'utf8');
104
+ for (const link of extractLinks(text)) {
105
+ const targetPath = await resolveLinkTarget(root, resolved.extensions, link);
106
+ if (targetPath === undefined)
107
+ continue;
108
+ const toId = vaultRelativeId(root, targetPath);
109
+ if (idSet.has(toId))
110
+ edges.push({ from: id, to: toId });
111
+ }
112
+ }
113
+ return { nodes, edges, truncated };
114
+ }
115
+ /**
116
+ * Register the `wiki_graph` tool on `ctx.tools`.
117
+ * @param ctx - registrant context carrying the tool registry.
118
+ * @param config - deployment's explicit vault configuration.
119
+ */
120
+ export function apply(ctx, config) {
121
+ const tools = ctx.get('tools');
122
+ if (typeof tools?.register !== 'function') {
123
+ ctx.logger?.warn('tool-memory-graph: ctx.tools.register unavailable; wiki_graph tool will not register.');
124
+ return;
125
+ }
126
+ const resolved = config;
127
+ assertPositiveInteger('maxDepth', resolved.maxDepth);
128
+ assertPositiveInteger('maxNodes', resolved.maxNodes);
129
+ ctx.effect(() => ctx.tools.register(defineTool({
130
+ name: 'wiki_graph',
131
+ description: 'Return the Obsidian-style [[link]] graph of the wiki vault: note ids, titles, and directed edges. Without an id it returns the whole vault graph (node-capped); with an id it returns the subgraph reachable from that note within the given depth.',
132
+ parameters: {
133
+ id: {
134
+ type: 'string',
135
+ description: 'Optional vault-relative note id to center the subgraph on (e.g. "concepts/RAG.md").',
136
+ },
137
+ depth: {
138
+ type: 'integer',
139
+ description: 'Maximum [[link]] hops from the center note; defaults to the configured maxDepth.',
140
+ },
141
+ },
142
+ output: {
143
+ schema: { type: 'json' },
144
+ render: (_args, value) => [{ type: 'text', text: JSON.stringify(value, null, 2) }],
145
+ },
146
+ async execute(args, exec) {
147
+ const vaultRoot = resolveMemoryVaultRoot(resolved.vaultRoot, exec);
148
+ const depth = args.depth ?? resolved.maxDepth;
149
+ assertPositiveInteger('depth', depth);
150
+ const graph = args.id === undefined
151
+ ? await buildGraph(vaultRoot, resolved)
152
+ : await buildSubgraph(vaultRoot, resolved, args.id, depth);
153
+ return graph;
154
+ },
155
+ })));
156
+ }
157
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Model-facing link-graph tool over a Markdown memory vault. The package
3
+ * registers `wiki_graph`, which returns the vault's note/link graph or the
4
+ * subgraph reachable from one note, so an agent can reason about how memory
5
+ * notes connect before reading them.
6
+ * @module @jacklika/dsh-tool-memory-graph
7
+ */
8
+ import type { Context } from '@deepseek-ai/cordis';
9
+ import z from '@deepseek-ai/schemastery';
10
+ /** Cordis plugin name used by loader diagnostics. */
11
+ export declare const name = "tool-memory-graph";
12
+ /** Services required by the wiki graph tool. */
13
+ export declare const inject: string[];
14
+ /** Plugin configuration, mirroring the filesystem memory vault settings. */
15
+ export interface Config {
16
+ /**
17
+ * Explicit vault root. When omitted, each tool call resolves the memory
18
+ * directory under the calling session's workspace (`<cwd>/.dsh/memory/`).
19
+ * A relative path is resolved against the session workspace.
20
+ */
21
+ vaultRoot?: string;
22
+ /** File extensions to treat as notes. */
23
+ extensions?: string[];
24
+ /** Default traversal depth when the call omits `depth`. */
25
+ maxDepth?: number;
26
+ /** Maximum nodes returned in one graph. */
27
+ maxNodes?: number;
28
+ /** Descend into dot-directories besides the fixed exclusions while indexing. */
29
+ indexHiddenDirs?: boolean;
30
+ }
31
+ /** Schemastery configuration for the memory graph tool consumer. */
32
+ export declare const Config: z<Config>;
33
+ /**
34
+ * Register the `wiki_graph` tool on `ctx.tools`.
35
+ * @param ctx - registrant context carrying the tool registry.
36
+ * @param config - deployment's explicit vault configuration.
37
+ */
38
+ export declare function apply(ctx: Context, config: Config): void;
39
+ //# sourceMappingURL=index.d.ts.map
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@jacklika/dsh-tool-memory-graph",
3
+ "description": "Model-facing link-graph tool over a Markdown memory vault: returns note nodes and [[link]] edges, or the subgraph reachable from one note.",
4
+ "version": "0.1.0-alpha.0",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/JackLiKa/jacklika-agent-plugins-repo.git",
11
+ "directory": "packages/tool-memory-graph"
12
+ },
13
+ "type": "module",
14
+ "engines": {
15
+ "node": "^22.19.0 || >=24.0.0"
16
+ },
17
+ "main": "lib/index.js",
18
+ "types": "lib/types/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./lib/types/index.d.ts",
22
+ "default": "./lib/index.js"
23
+ },
24
+ "./package.json": "./package.json"
25
+ },
26
+ "files": [
27
+ "lib/**/*.js",
28
+ "lib/types/**/*.d.ts",
29
+ "README.md",
30
+ "README.zh.md"
31
+ ],
32
+ "license": "MIT",
33
+ "dependencies": {
34
+ "@deepseek-ai/schemastery": "3.18.4",
35
+ "@jacklika/dsh-tool-memory-filesystem": "0.1.3-alpha.1"
36
+ },
37
+ "peerDependencies": {
38
+ "@deepseek-ai/cordis": "4.0.4",
39
+ "@deepseek-ai/dsh-tools": ">=0.1.7-rc.1 <0.3.0",
40
+ "@deepseek-ai/dsh-util-values": ">=0.1.7-rc.1 <0.3.0"
41
+ }
42
+ }