@huanlin/dsh-plugin-codegraph-tool 0.1.8

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 CC ZHAO
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,17 @@
1
+ # @huanlin/dsh-plugin-codegraph-tool
2
+
3
+ The model-facing tools: a read-only `codegraph` tool with ten structural query operations, plus a `codegraph_index` tool that builds or refreshes the index on its own, much larger timeout budget.
4
+
5
+ Part of **[dsh-plugin-codegraph](https://github.com/CC19990113/dsh-plugin-codegraph)** — structural code intelligence for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness).
6
+
7
+ Most users should install the bundle instead, which mounts this package and its siblings in one layer:
8
+
9
+ ```sh
10
+ dsh plugin --profile <name> add dsh-plugin-codegraph
11
+ ```
12
+
13
+ See the [project README](https://github.com/CC19990113/dsh-plugin-codegraph#readme) for setup, configuration, and the full tool reference.
14
+
15
+ ## License
16
+
17
+ [MIT](LICENSE)
@@ -0,0 +1,73 @@
1
+ /**
2
+ * The two operations that are not seam queries. `explore` and `context` compose the graph primitives
3
+ * with `ctx.fs` reads, which is why they live in the consumer: a graph store returns positions and
4
+ * cannot reach a remote workspace's bytes, so aggregation belongs to the role that holds both.
5
+ * @module @huanlin/dsh-plugin-codegraph-tool/compose
6
+ */
7
+ import type { CodegraphNode, CodegraphRelation } from '@huanlin/dsh-plugin-codegraph-service';
8
+ /**
9
+ * Split a task description into the terms worth searching for.
10
+ *
11
+ * A task is prose, but a graph is indexed by identifiers, so the terms that carry signal are the
12
+ * identifier-shaped words: `camelCase` and `snake_case` names survive whole because splitting them
13
+ * would search for their fragments instead of the symbol the author meant.
14
+ * @param task - the free-text task description.
15
+ * @param maxTerms - largest number of terms to return.
16
+ * @returns the search terms, longest first so the most specific one is searched before the budget
17
+ * runs out.
18
+ */
19
+ export declare function taskTerms(task: string, maxTerms: number): string[];
20
+ /**
21
+ * Keep only the declarations among matched nodes.
22
+ * @param nodes - the matched nodes.
23
+ * @returns the nodes that declare something.
24
+ */
25
+ export declare function declarationsOnly(nodes: readonly CodegraphNode[]): CodegraphNode[];
26
+ /** A node with the evidence that it matched. */
27
+ export interface ScoredNode {
28
+ /** The matched declaration. */
29
+ readonly node: CodegraphNode;
30
+ /** How many distinct terms found it. */
31
+ readonly hits: number;
32
+ }
33
+ /**
34
+ * Merge per-term search results into one ranked list.
35
+ *
36
+ * A declaration found by several of a task's terms is more likely to be what the task is about than
37
+ * one found by a single term, so hit count leads the ranking; within an equal count the earliest
38
+ * position any single search gave it wins, preserving the store's own relevance order.
39
+ * @param batches - each term's search results, in the order the store ranked them.
40
+ * @param limit - largest number of declarations to return.
41
+ * @returns the merged declarations, most relevant first.
42
+ */
43
+ export declare function mergeByHits(batches: readonly (readonly CodegraphNode[])[], limit: number): ScoredNode[];
44
+ /** Declarations that share one file, in the order the search ranked them. */
45
+ export interface FileGroup {
46
+ /** The file, relative to the project root. */
47
+ readonly path: string;
48
+ /** The matched declarations in that file. */
49
+ readonly nodes: CodegraphNode[];
50
+ /** One-based first line spanned by {@link nodes}. */
51
+ readonly startLine: number;
52
+ /** One-based last line spanned by {@link nodes}. */
53
+ readonly endLine: number;
54
+ }
55
+ /**
56
+ * Group ranked declarations by file, preserving rank.
57
+ *
58
+ * Grouping is what makes `explore` cheaper than reading each symbol separately: several matches in
59
+ * one file share a single read and a single contiguous slice, and the file that held the top-ranked
60
+ * match is presented first.
61
+ * @param nodes - the ranked declarations.
62
+ * @param maxFiles - largest number of files to return.
63
+ * @returns the groups, best-ranked file first.
64
+ */
65
+ export declare function groupByFile(nodes: readonly CodegraphNode[], maxFiles: number): FileGroup[];
66
+ /**
67
+ * Merge relations from several symbols, keeping each related declaration once.
68
+ * @param batches - relation lists to merge.
69
+ * @param limit - largest number of relations to return.
70
+ * @returns the merged relations, in first-seen order.
71
+ */
72
+ export declare function mergeRelations(batches: readonly (readonly CodegraphRelation[])[], limit: number): CodegraphRelation[];
73
+ //# sourceMappingURL=compose.d.ts.map
package/lib/compose.js ADDED
@@ -0,0 +1,138 @@
1
+ /**
2
+ * The two operations that are not seam queries. `explore` and `context` compose the graph primitives
3
+ * with `ctx.fs` reads, which is why they live in the consumer: a graph store returns positions and
4
+ * cannot reach a remote workspace's bytes, so aggregation belongs to the role that holds both.
5
+ * @module @huanlin/dsh-plugin-codegraph-tool/compose
6
+ */
7
+ /** Words a task description contributes no search signal through. */
8
+ const STOPWORDS = new Set([
9
+ 'a', 'an', 'and', 'are', 'as', 'at', 'be', 'but', 'by', 'can', 'do', 'does', 'for', 'from',
10
+ 'how', 'in', 'into', 'is', 'it', 'its', 'not', 'of', 'on', 'or', 'that', 'the', 'their', 'then',
11
+ 'there', 'this', 'to', 'was', 'what', 'when', 'where', 'which', 'why', 'will', 'with', 'work',
12
+ 'works', 'add', 'fix', 'bug', 'code', 'file', 'files', 'need', 'make', 'use', 'used', 'using',
13
+ ]);
14
+ /**
15
+ * Split a task description into the terms worth searching for.
16
+ *
17
+ * A task is prose, but a graph is indexed by identifiers, so the terms that carry signal are the
18
+ * identifier-shaped words: `camelCase` and `snake_case` names survive whole because splitting them
19
+ * would search for their fragments instead of the symbol the author meant.
20
+ * @param task - the free-text task description.
21
+ * @param maxTerms - largest number of terms to return.
22
+ * @returns the search terms, longest first so the most specific one is searched before the budget
23
+ * runs out.
24
+ */
25
+ export function taskTerms(task, maxTerms) {
26
+ const words = task.split(/[^\p{L}\p{N}_$]+/u).filter(word => word.length > 0);
27
+ const kept = new Map();
28
+ for (const word of words) {
29
+ if (word.length < 3)
30
+ continue;
31
+ if (STOPWORDS.has(word.toLowerCase()))
32
+ continue;
33
+ const key = word.toLowerCase();
34
+ if (!kept.has(key))
35
+ kept.set(key, word);
36
+ }
37
+ return [...kept.values()]
38
+ .sort((left, right) => right.length - left.length)
39
+ .slice(0, maxTerms);
40
+ }
41
+ /**
42
+ * Node kinds that name code without declaring any. An `import` node exists once per importing file,
43
+ * so a widely used symbol contributes a dozen of them under its own name; a `file` node repeats the
44
+ * path its results are already grouped by. `search` still returns both, because a model that asked
45
+ * for a name asked for every occurrence of it — but the operations that answer "what is this task
46
+ * about" must spend their budget on declarations.
47
+ */
48
+ const STRUCTURAL_KINDS = new Set(['import', 'export', 'file', 'module']);
49
+ /**
50
+ * Keep only the declarations among matched nodes.
51
+ * @param nodes - the matched nodes.
52
+ * @returns the nodes that declare something.
53
+ */
54
+ export function declarationsOnly(nodes) {
55
+ return nodes.filter(node => !STRUCTURAL_KINDS.has(node.kind));
56
+ }
57
+ /** A stable identity for one declaration across separate searches. */
58
+ function nodeKey(node) {
59
+ return `${node.filePath}:${node.startLine}:${node.qualifiedName}`;
60
+ }
61
+ /**
62
+ * Merge per-term search results into one ranked list.
63
+ *
64
+ * A declaration found by several of a task's terms is more likely to be what the task is about than
65
+ * one found by a single term, so hit count leads the ranking; within an equal count the earliest
66
+ * position any single search gave it wins, preserving the store's own relevance order.
67
+ * @param batches - each term's search results, in the order the store ranked them.
68
+ * @param limit - largest number of declarations to return.
69
+ * @returns the merged declarations, most relevant first.
70
+ */
71
+ export function mergeByHits(batches, limit) {
72
+ const merged = new Map();
73
+ for (const batch of batches) {
74
+ batch.forEach((node, position) => {
75
+ const key = nodeKey(node);
76
+ const existing = merged.get(key);
77
+ if (existing === undefined)
78
+ merged.set(key, { node, hits: 1, best: position });
79
+ else {
80
+ existing.hits += 1;
81
+ existing.best = Math.min(existing.best, position);
82
+ }
83
+ });
84
+ }
85
+ return [...merged.values()]
86
+ .sort((left, right) => right.hits - left.hits || left.best - right.best)
87
+ .slice(0, limit)
88
+ .map(entry => ({ node: entry.node, hits: entry.hits }));
89
+ }
90
+ /**
91
+ * Group ranked declarations by file, preserving rank.
92
+ *
93
+ * Grouping is what makes `explore` cheaper than reading each symbol separately: several matches in
94
+ * one file share a single read and a single contiguous slice, and the file that held the top-ranked
95
+ * match is presented first.
96
+ * @param nodes - the ranked declarations.
97
+ * @param maxFiles - largest number of files to return.
98
+ * @returns the groups, best-ranked file first.
99
+ */
100
+ export function groupByFile(nodes, maxFiles) {
101
+ const groups = new Map();
102
+ for (const node of nodes) {
103
+ const existing = groups.get(node.filePath);
104
+ if (existing === undefined) {
105
+ groups.set(node.filePath, {
106
+ path: node.filePath,
107
+ nodes: [node],
108
+ startLine: node.startLine,
109
+ endLine: node.endLine,
110
+ });
111
+ continue;
112
+ }
113
+ existing.nodes.push(node);
114
+ existing.startLine = Math.min(existing.startLine, node.startLine);
115
+ existing.endLine = Math.max(existing.endLine, node.endLine);
116
+ }
117
+ return [...groups.values()].slice(0, maxFiles);
118
+ }
119
+ /**
120
+ * Merge relations from several symbols, keeping each related declaration once.
121
+ * @param batches - relation lists to merge.
122
+ * @param limit - largest number of relations to return.
123
+ * @returns the merged relations, in first-seen order.
124
+ */
125
+ export function mergeRelations(batches, limit) {
126
+ const merged = new Map();
127
+ for (const batch of batches) {
128
+ for (const relation of batch) {
129
+ const key = `${nodeKey(relation.node)}:${relation.edge.kind}`;
130
+ if (!merged.has(key))
131
+ merged.set(key, relation);
132
+ if (merged.size >= limit)
133
+ return [...merged.values()];
134
+ }
135
+ }
136
+ return [...merged.values()];
137
+ }
138
+ //# sourceMappingURL=compose.js.map
package/lib/index.d.ts ADDED
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Model-facing `codegraph` tool family over `ctx.codegraph` and `ctx.fs`. The `codegraph` tool is
3
+ * read-only, with ten operations: the eight the seam answers directly, plus `explore` and `context`,
4
+ * which compose graph queries with source reads because a graph store returns positions and cannot
5
+ * reach a workspace's bytes. A second tool, `codegraph_index`, builds or refreshes the graph on
6
+ * explicit request; it is separate so it can carry its own, much larger timeout budget than a query
7
+ * — `defineTool`'s `timeoutMs` is fixed per registration, not per call, so one operation cannot borrow
8
+ * a bigger budget from within a shared tool.
9
+ *
10
+ * The tools own every default the seam refuses to guess — result limits, traversal depth, source
11
+ * caps — so the seam's requests stay fully specified and a deployment can retune the model's answer
12
+ * size without touching a store. They runtime-inject only `tools`, `codegraph`, `fs`, and
13
+ * `systemPrompt`, and import no store.
14
+ *
15
+ * Namespace plugin (named exports, no default export).
16
+ * @module @huanlin/dsh-plugin-codegraph-tool
17
+ */
18
+ import type { Context } from '@deepseek-ai/cordis';
19
+ import z from '@deepseek-ai/schemastery';
20
+ import type { ToolExecution } from '@deepseek-ai/dsh-tools';
21
+ import type { CodegraphToolArgs } from './schema.ts';
22
+ export { CODEGRAPH_INDEX_PARAMETERS, CODEGRAPH_OPERATIONS, CODEGRAPH_OUTPUT_SCHEMA, CODEGRAPH_PARAMETERS, type CodegraphIndexToolArgs, type CodegraphToolArgs, type CodegraphToolOperation, type CodegraphToolValue, type CodegraphValueFor, } from './schema.ts';
23
+ export { declarationsOnly, groupByFile, mergeByHits, mergeRelations, taskTerms } from './compose.ts';
24
+ export { toAffected, toHop, toRelation, toSymbol } from './projection.ts';
25
+ export { renderCodegraph } from './render.ts';
26
+ export { readSlice } from './source.ts';
27
+ /** Cordis plugin name for loader diagnostics. */
28
+ export declare const name = "tool-codegraph";
29
+ /** Services required by this plugin. */
30
+ export declare const inject: string[];
31
+ /** Default tool-call timeout budget (ms) for the query-side `codegraph` tool. */
32
+ export declare const DEFAULT_CODEGRAPH_TOOL_TIMEOUT_MS = 30000;
33
+ /** Default timeout budget (ms) for the `codegraph_index` tool. Indexing a monorepo is a different order of work than a query. */
34
+ export declare const DEFAULT_CODEGRAPH_INDEX_TIMEOUT_MS = 300000;
35
+ /** The stable system-prompt guidance positioning the code graph against search and read. */
36
+ export declare const CODEGRAPH_PROMPT_TEXT = "Use codegraph for structural questions about code: where a symbol is declared, what calls it, what it calls, what a change to it reaches, and how one symbol reaches another. It answers from a pre-built index, so it is both faster and more precise than grepping for a name, which also matches comments, strings, and unrelated identifiers. Use search/read instead for literal text, and when codegraph reports no index for a workspace. When status reports no index, call codegraph_index once to build one \u2014 it runs on its own, longer timeout budget than a query \u2014 then retry. Results reflect the last time the workspace was indexed; a declaration added since then is absent.";
37
+ /** Plugin configuration: the defaults and caps the seam requires the consumer to own. */
38
+ export interface Config {
39
+ /** Results returned when the model names no `limit` (default 20). */
40
+ defaultLimit?: number;
41
+ /** Largest `limit` honored, whatever the model asks for (default 200). */
42
+ maxLimit?: number;
43
+ /** Hops traversed by `impact` and `trace` when the model names no `depth` (default 2). */
44
+ defaultDepth?: number;
45
+ /** Largest `depth` honored (default 6). */
46
+ maxDepth?: number;
47
+ /** Distinct paths `trace` returns (default 5). */
48
+ maxPaths?: number;
49
+ /** Files whose source `explore` and `context` return (default 5). */
50
+ maxSourceFiles?: number;
51
+ /** Lines of source carried per file (default 200). */
52
+ maxSourceLines?: number;
53
+ /** Characters of source carried per file (default 8000). */
54
+ maxSourceChars?: number;
55
+ /** Characters of documentation carried per symbol (default 400). */
56
+ maxDocstringChars?: number;
57
+ /** Characters of signature carried per symbol (default 200). */
58
+ maxSignatureChars?: number;
59
+ /** Search terms extracted from a `context` task description (default 6). */
60
+ maxContextTerms?: number;
61
+ /** Tool-call timeout budget in ms for the query-side `codegraph` tool (default 30000). */
62
+ timeoutMs?: number;
63
+ /** Tool-call timeout budget in ms for the `codegraph_index` tool (default 300000). */
64
+ indexTimeoutMs?: number;
65
+ }
66
+ export declare const Config: z<Config>;
67
+ /**
68
+ * The project root a call runs against: the model's explicit `project_path`, else the calling
69
+ * agent's session workspace. There is no process-cwd fallback — a graph query that silently answered
70
+ * about a different checkout than the session is working in would be wrong in a way the model cannot
71
+ * detect.
72
+ * @param args - the validated tool arguments.
73
+ * @param exec - the tool-execution context; only its optional `agent` is read.
74
+ * @returns the absolute project root.
75
+ */
76
+ export declare function projectRoot(args: CodegraphToolArgs, exec: ToolExecution): string;
77
+ /**
78
+ * Register the `codegraph` tool and its system-prompt guidance.
79
+ * @param ctx - the plugin context (must inject `tools`, `codegraph`, `fs`, `systemPrompt`).
80
+ * @param config - the resolved plugin configuration.
81
+ */
82
+ export declare function apply(ctx: Context, config: Config): void;
83
+ /**
84
+ * The one-line label a pending call shows.
85
+ * @param args - the validated tool arguments.
86
+ * @returns the card title naming the operation and whichever subject the operation takes.
87
+ */
88
+ export declare function callTitle(args: CodegraphToolArgs): string;
89
+ //# sourceMappingURL=index.d.ts.map