@hilariouhiss/dsh-colgrep 1.0.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 +21 -0
- package/README.md +93 -0
- package/cordis.patch.yml +12 -0
- package/lib/index.js +153 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dsh-colgrep contributors
|
|
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,93 @@
|
|
|
1
|
+
# @hilariouhiss/dsh-colgrep
|
|
2
|
+
|
|
3
|
+
把 [colgrep](https://github.com/lightonai/next-plaid/tree/main/colgrep)(LightOn 的语义代码检索工具:ColBERT 多向量搜索,按“含义”而非“精确文本”找代码)接入 DeepSeek Harness。
|
|
4
|
+
|
|
5
|
+
本插件以 DSH **bundle 插件**形式提供一个宿主侧模型工具 `colgrep`,安装后对 `web`、`headless` 等任意 profile 的所有会话生效。
|
|
6
|
+
|
|
7
|
+
## 功能
|
|
8
|
+
|
|
9
|
+
注册一个 `colgrep` 工具,智能体可用自然语言检索工作区代码,例如:
|
|
10
|
+
|
|
11
|
+
- `colgrep "error handling for database connections"`
|
|
12
|
+
- `colgrep "auth" -e "async fn" --include "*.rs"`
|
|
13
|
+
- `colgrep command:"init"` / `command:"status"` / `command:"clear"`
|
|
14
|
+
|
|
15
|
+
工具参数:
|
|
16
|
+
|
|
17
|
+
| 参数 | 类型 | 说明 |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| `query` | string(必填) | 自然语言查询 |
|
|
20
|
+
| `command` | enum `search`(默认)/`init`/`status`/`clear` | 检索 / 建索引 / 查状态 / 清索引 |
|
|
21
|
+
| `path` | string | 待检索/索引的文件或目录(默认工作区根) |
|
|
22
|
+
| `top_k` | integer | 结果数(`-k`,默认 15) |
|
|
23
|
+
| `pattern` | string | 正则预筛(`-e`,hybrid:先 grep 再语义排序) |
|
|
24
|
+
| `include` | string | 只检索匹配 glob 的文件(`--include`,如 `"*.rs"`) |
|
|
25
|
+
| `code_only` | boolean | 只检索代码文件,跳过 md/txt/yaml/json 等 |
|
|
26
|
+
| `no_update` | boolean | 跳过自动重建索引,直接检索现有索引 |
|
|
27
|
+
|
|
28
|
+
行为要点:
|
|
29
|
+
|
|
30
|
+
- 结果以 `--json` 解析后折叠为紧凑可读文本(`文件:行-行 [语言, 类型, 名称] (score)` + 首行签名),不倾倒整段代码;模型可再 `read` 具体文件。
|
|
31
|
+
- **索引重定位**:DSH 文件沙箱禁止写工作区之外,而 colgrep 检索需要写索引锁文件。插件把索引目录重定位到 `<工作区>/.colgrep-data`(通过 `COLGREP_DATA_DIR`),使全部写入落在沙箱允许范围内。ColBERT 模型与 ONNX runtime 从 `~/.cache` 只读加载,无需联网。
|
|
32
|
+
- 首次检索会自动建立索引(CPU 上大仓库可能需数分钟);可用 `command:"init"` 显式建索引,`no_update:true` 走现有索引。
|
|
33
|
+
|
|
34
|
+
## 前置要求
|
|
35
|
+
|
|
36
|
+
- 已安装 [colgrep](https://github.com/lightonai/next-plaid/tree/main/colgrep) CLI(`cargo install colgrep` 或官方预编译二进制),且 `colgrep` 在 PATH 上。
|
|
37
|
+
- 首次使用前,colgrep 需已下载默认模型 `lightonai/LateOn-Code-edge`(`colgrep "任意查询"` 会自动下载;私有模型需 `HF_TOKEN`,可用 `colgrep set-model` 切换)。
|
|
38
|
+
|
|
39
|
+
## 安装
|
|
40
|
+
|
|
41
|
+
```powershell
|
|
42
|
+
dsh plugin --profile web add @hilariouhiss/dsh-colgrep
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
把 `web` 换成你的 profile 名(例如 `headless`)。然后**重启 dsh**。
|
|
46
|
+
|
|
47
|
+
`dsh plugin` 会把本包安装进 profile 的 `node_modules`;因为 `package.json` 声明了 `dsh.bundle.patch`,profile 的 bundle 列表会自动追加 `@hilariouhiss/dsh-colgrep`,重启后插件行自动激活。
|
|
48
|
+
|
|
49
|
+
## 验证
|
|
50
|
+
|
|
51
|
+
1. 重启后在会话中让模型调用 `colgrep`,例如“用 colgrep 搜索 error handling 相关代码”。
|
|
52
|
+
2. 或让模型执行 `colgrep command:"status"`,应看到当前工作区的索引状态。
|
|
53
|
+
|
|
54
|
+
## 卸载
|
|
55
|
+
|
|
56
|
+
```powershell
|
|
57
|
+
dsh plugin --profile web remove @hilariouhiss/dsh-colgrep
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
然后重启 dsh。
|
|
61
|
+
|
|
62
|
+
## 开发
|
|
63
|
+
|
|
64
|
+
```powershell
|
|
65
|
+
pnpm install
|
|
66
|
+
pnpm test
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
结构:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
lib/index.js 插件入口:apply(ctx) 注册 colgrep 工具(defineTool)
|
|
73
|
+
cordis.patch.yml bundle patch:插入本插件的宿主行
|
|
74
|
+
test/ node:test 单元测试(argv 组装、渲染、execute 管道)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## 发布(给维护者)
|
|
78
|
+
|
|
79
|
+
```powershell
|
|
80
|
+
cd dsh-colgrep
|
|
81
|
+
pnpm install
|
|
82
|
+
pnpm test
|
|
83
|
+
npm login # 首次发布前登录 npm
|
|
84
|
+
npm publish # publishConfig.access 已设为 public
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`prepublishOnly` 会在每次发布前自动运行 `npm test`;发布新版本前记得递增 `package.json` 的 `version`。
|
|
88
|
+
|
|
89
|
+
## 许可与来源
|
|
90
|
+
|
|
91
|
+
本插件为 MIT 许可。它只调用外部的 `colgrep` CLI,未内置 colgrep 源码;colgrep 本身来自 [lightonai/next-plaid](https://github.com/lightonai/next-plaid)(© LightOn,其自身许可见上游仓库)。
|
|
92
|
+
|
|
93
|
+
> 提示:若日后升级 dsh 后 `@deepseek-ai/dsh-tools` 的公开 API 发生变化,需同步更新本插件的依赖版本。
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# @hilariouhiss/dsh-colgrep bundle patch: one self-referencing host row.
|
|
2
|
+
#
|
|
3
|
+
# `dsh plugin --profile <name> add @hilariouhiss/dsh-colgrep` installs this
|
|
4
|
+
# package into the profile; because it declares `dsh.bundle.patch`, the profile
|
|
5
|
+
# manifest appends it to the bundle list, and this patch is applied as a layer
|
|
6
|
+
# on boot. The row resolves `@hilariouhiss/dsh-colgrep` from the profile's own
|
|
7
|
+
# node_modules, so the plugin's `apply(ctx)` runs in the host plane: it
|
|
8
|
+
# registers the `colgrep` model tool into the global `tools` layer (visible to
|
|
9
|
+
# every agent preset).
|
|
10
|
+
- insert:
|
|
11
|
+
- id: colgrep
|
|
12
|
+
name: '@hilariouhiss/dsh-colgrep'
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
2
|
+
|
|
3
|
+
export const name = "colgrep";
|
|
4
|
+
export const inject = ["tools", "shell", "sandboxPolicy"];
|
|
5
|
+
|
|
6
|
+
const LONG_PATH_PREFIX = String.fromCharCode(92, 92, 63, 92); // "\\?\"
|
|
7
|
+
|
|
8
|
+
const TOOL_DESCRIPTION = `Semantic code search over the workspace using the local colgrep CLI (ColBERT multi-vector search). Finds code by meaning, not exact text. Use a natural-language \`query\` such as "error handling for database connections". Defaults to \`search\`; the first search auto-builds/updates the index (can take a while). Use \`command:"status"\` to inspect the index, \`command:"init"\` to (re)build it explicitly, and \`no_update:true\` for a fast search over an existing index. Use \`pattern\` to pre-filter by regex, \`include\` to limit file types (e.g. "*.rs"), and \`code_only\` to skip text/config files.`;
|
|
9
|
+
|
|
10
|
+
export function stripPathPrefix(path) {
|
|
11
|
+
const value = String(path);
|
|
12
|
+
return value.startsWith(LONG_PATH_PREFIX) ? value.slice(4) : value;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function quoteArg(value) {
|
|
16
|
+
return "'" + String(value).replace(/'/g, "''") + "'";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function buildArgs(args) {
|
|
20
|
+
const command = args.command || "search";
|
|
21
|
+
const argv = ["colgrep"];
|
|
22
|
+
if (command === "search") {
|
|
23
|
+
argv.push("--json", "--color", "never", "-y");
|
|
24
|
+
if (args.no_update) argv.push("--no-update");
|
|
25
|
+
argv.push("-k", String(args.top_k ? Number(args.top_k) : 15));
|
|
26
|
+
if (args.pattern) argv.push("-e", args.pattern);
|
|
27
|
+
if (args.include) argv.push("--include", args.include);
|
|
28
|
+
if (args.code_only) argv.push("--code-only");
|
|
29
|
+
argv.push(String(args.query || ""));
|
|
30
|
+
if (args.path) argv.push(args.path);
|
|
31
|
+
} else if (command === "init") {
|
|
32
|
+
argv.push("init", "-y");
|
|
33
|
+
if (args.path) argv.push(args.path);
|
|
34
|
+
} else if (command === "status") {
|
|
35
|
+
argv.push("status");
|
|
36
|
+
} else if (command === "clear") {
|
|
37
|
+
argv.push("clear");
|
|
38
|
+
} else {
|
|
39
|
+
throw new Error("command must be one of: search, init, status, clear");
|
|
40
|
+
}
|
|
41
|
+
return argv;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function normalizeResults(parsed) {
|
|
45
|
+
return parsed.map((item) => {
|
|
46
|
+
const unit = item && item.unit ? item.unit : {};
|
|
47
|
+
return {
|
|
48
|
+
file: stripPathPrefix(unit.file),
|
|
49
|
+
name: unit.name || "",
|
|
50
|
+
qualified_name: unit.qualified_name || "",
|
|
51
|
+
line: unit.line,
|
|
52
|
+
end_line: unit.end_line,
|
|
53
|
+
language: unit.language || "",
|
|
54
|
+
unit_type: unit.unit_type || "",
|
|
55
|
+
signature: unit.signature || "",
|
|
56
|
+
score: item ? item.score : null,
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function renderOutput(value) {
|
|
62
|
+
if (!value || typeof value !== "object") return String(value);
|
|
63
|
+
const lines = [];
|
|
64
|
+
if (value.command === "search" && Array.isArray(value.results)) {
|
|
65
|
+
lines.push(`colgrep: ${value.results.length} result(s) for ${JSON.stringify(value.query)}`);
|
|
66
|
+
value.results.forEach((result, index) => {
|
|
67
|
+
const file = result.file || result.qualified_name || "?";
|
|
68
|
+
const location = typeof result.line === "number" && typeof result.end_line === "number"
|
|
69
|
+
? `:${result.line}-${result.end_line}`
|
|
70
|
+
: "";
|
|
71
|
+
const meta = [result.language, result.unit_type, result.name].filter(Boolean).join(", ");
|
|
72
|
+
const score = typeof result.score === "number" ? `score ${result.score.toFixed(2)}` : "";
|
|
73
|
+
lines.push("");
|
|
74
|
+
lines.push(`${index + 1}. ${file}${location}${meta ? ` [${meta}]` : ""}${score ? ` (${score})` : ""}`);
|
|
75
|
+
if (result.signature) {
|
|
76
|
+
lines.push(` ${String(result.signature).split("\n")[0].slice(0, 200)}`);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
} else if (value.command === "search") {
|
|
80
|
+
lines.push(`colgrep search failed (ok=${value.ok})`);
|
|
81
|
+
if (value.stderr) lines.push(`[stderr]\n${value.stderr}`);
|
|
82
|
+
else if (value.stdout) lines.push(`[stdout]\n${value.stdout}`);
|
|
83
|
+
} else {
|
|
84
|
+
lines.push(`colgrep ${value.command} (exit ${value.exitCode})`);
|
|
85
|
+
if (value.stdout) lines.push(value.stdout);
|
|
86
|
+
if (!value.ok && value.stderr) lines.push(`[stderr]\n${value.stderr}`);
|
|
87
|
+
}
|
|
88
|
+
if (value.timedOut) lines.push("[timed out]");
|
|
89
|
+
if (value.aborted) lines.push("[aborted]");
|
|
90
|
+
return lines.join("\n");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function apply(ctx) {
|
|
94
|
+
ctx.tools.register(defineTool({
|
|
95
|
+
name: "colgrep",
|
|
96
|
+
description: TOOL_DESCRIPTION,
|
|
97
|
+
parameters: {
|
|
98
|
+
query: { type: "string", description: "Natural-language query (used by search).", required: true },
|
|
99
|
+
command: { type: "string", enum: ["search", "init", "status", "clear"], description: "Operation. search (default) finds code; init builds/updates the index; status reports index state; clear removes the index." },
|
|
100
|
+
path: { type: "string", description: "File or directory to search/index (default: workspace root)." },
|
|
101
|
+
top_k: { type: "integer", description: "Number of results to return (-k). Default 15." },
|
|
102
|
+
pattern: { type: "string", description: "Regex pre-filter (-e): grep first, then rank semantically (hybrid)." },
|
|
103
|
+
include: { type: "string", description: "Only search files matching this glob (--include), e.g. \"*.rs\"." },
|
|
104
|
+
code_only: { type: "boolean", description: "Only search code files; skip text/config files such as md/txt/yaml/json (--code-only)." },
|
|
105
|
+
no_update: { type: "boolean", description: "Skip automatic index update and search the existing index as-is (--no-update)." },
|
|
106
|
+
},
|
|
107
|
+
output: {
|
|
108
|
+
schema: { type: "json" },
|
|
109
|
+
render(_args, value) {
|
|
110
|
+
return [{ type: "text", text: renderOutput(value) }];
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
async execute(args, exec) {
|
|
114
|
+
const policy = ctx.sandboxPolicy.resolve({ session: exec.agent && exec.agent.session });
|
|
115
|
+
const root = policy && typeof policy.workspaceRoot === "string" ? policy.workspaceRoot : "";
|
|
116
|
+
const base = root.replace(/[\\/]+$/, "");
|
|
117
|
+
const dataDir = base + "/.colgrep-data";
|
|
118
|
+
const command = buildArgs(args).map((part, index) => (index === 0 ? part : quoteArg(part))).join(" ");
|
|
119
|
+
const spec = ctx.shell.resolve({
|
|
120
|
+
command,
|
|
121
|
+
timeoutMs: 300000,
|
|
122
|
+
stdoutMaxBytes: 2 * 1024 * 1024,
|
|
123
|
+
signal: exec.signal,
|
|
124
|
+
sandboxPolicy: policy,
|
|
125
|
+
...(base ? { workdir: base, env: { COLGREP_DATA_DIR: dataDir } } : {}),
|
|
126
|
+
});
|
|
127
|
+
const run = await ctx.shell.run(spec);
|
|
128
|
+
const stdout = run.stdout && run.stdout.text ? run.stdout.text : "";
|
|
129
|
+
const stderr = run.stderr && run.stderr.text ? run.stderr.text : "";
|
|
130
|
+
const commandName = args.command || "search";
|
|
131
|
+
const common = {
|
|
132
|
+
ok: run.exitCode === 0,
|
|
133
|
+
command: commandName,
|
|
134
|
+
exitCode: run.exitCode,
|
|
135
|
+
...(run.timedOut ? { timedOut: true } : {}),
|
|
136
|
+
...(run.aborted ? { aborted: true } : {}),
|
|
137
|
+
};
|
|
138
|
+
if (commandName !== "search") {
|
|
139
|
+
return { ...common, stdout, stderr };
|
|
140
|
+
}
|
|
141
|
+
let parsed = null;
|
|
142
|
+
try {
|
|
143
|
+
parsed = JSON.parse(stdout);
|
|
144
|
+
} catch {
|
|
145
|
+
parsed = null;
|
|
146
|
+
}
|
|
147
|
+
if (Array.isArray(parsed)) {
|
|
148
|
+
return { ...common, query: args.query, count: parsed.length, results: normalizeResults(parsed) };
|
|
149
|
+
}
|
|
150
|
+
return { ...common, query: args.query, results: null, stdout, stderr };
|
|
151
|
+
},
|
|
152
|
+
}));
|
|
153
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hilariouhiss/dsh-colgrep",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "colgrep — semantic code search (ColBERT multi-vector) for DeepSeek Harness. Adds a `colgrep` tool that finds code by meaning, not exact text.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"dsh",
|
|
7
|
+
"deepseek-harness",
|
|
8
|
+
"dsh-plugin",
|
|
9
|
+
"colgrep",
|
|
10
|
+
"semantic-search",
|
|
11
|
+
"code-search",
|
|
12
|
+
"colbert"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": {
|
|
16
|
+
"name": "dsh-colgrep contributors"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://github.com/hilariouhiss/dsh-hiss-plugins/tree/main/dsh-colgrep",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/hilariouhiss/dsh-hiss-plugins.git",
|
|
22
|
+
"directory": "dsh-colgrep"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"type": "module",
|
|
28
|
+
"main": "./lib/index.js",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": "./lib/index.js",
|
|
31
|
+
"./package.json": "./package.json"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"lib/",
|
|
35
|
+
"cordis.patch.yml",
|
|
36
|
+
"README.md",
|
|
37
|
+
"LICENSE"
|
|
38
|
+
],
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@deepseek-ai/dsh-tools": "0.1.1-rc.2"
|
|
41
|
+
},
|
|
42
|
+
"dsh": {
|
|
43
|
+
"bundle": {
|
|
44
|
+
"patch": "./cordis.patch.yml"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=20"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"test": "node --test"
|
|
52
|
+
}
|
|
53
|
+
}
|