dsh-code-index 0.9.0 → 0.9.1

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/CHANGELOG.md CHANGED
@@ -2,7 +2,13 @@
2
2
 
3
3
  All notable changes to dsh-code-index are documented here.
4
4
 
5
- ## 0.9.0 — Edit-ready Context Packs (unreleased)
5
+ ## 0.9.1 — Gitignore directory whitelist fix (2026-10-10)
6
+
7
+ - Fix Gitignore directory whitelist rules (`!dir/`) silently excluding source trees: directory probes now retain their trailing slash in both the scanner and watcher. Thanks to @bbskye5008 for the detailed report and reproduction in [Issue #2](https://github.com/lemonxiny55/dsh-code-index/issues/2).
8
+ - Apply nested `.gitignore` precedence consistently, while preserving Git's rule that files inside an ignored parent directory cannot be re-included. Add regressions for source/context retrieval, affected tests, and external add/change/delete freshness.
9
+ - Verified on Node 22/24 and with a real DSH Desktop Agent using the packaged candidate. See `RELEASE_EVIDENCE_v0.9.1.md` for methods, results and publication status.
10
+
11
+ ## 0.9.0 — Edit-ready Context Packs (2026-10-05)
6
12
 
7
13
  - `code_context` now includes bounded, whole-line source excerpts for primary declarations, strong callers/callees, imports, likely tests and changes. Overlapping current/base ranges are deduplicated independently. Large declarations degrade to changed-line windows, then signature-only metadata.
8
14
  - One final ContextPack DTO selects candidates before presentation; both text and serialized pack respect the normalized hard character budget. Default canonical output stays a string; explicit `outputFormat: pack` enables structured output.
@@ -11,7 +17,8 @@ All notable changes to dsh-code-index are documented here.
11
17
  - Deterministic camel/snake, filename/path/signature terms, bounded graph neighborhoods, and clean-tree test seeds improve task selection. No BM25 or architecture rewrite.
12
18
  - Added executable context-quality contracts for task recall, source evidence, noise exclusion, budget, isolation, worktrees, freshness, overlap and classification.
13
19
  - Development target moves to matching DSH/dsh-tools `0.2.0-rc.2`. Full/compact surfaces, v0.8 configuration, Node 22/24, local-first behavior, and text calls remain supported. Public declaration files are now included in the package.
14
- - Synced English/Chinese product docs and added a reproducible 30-second recording of real local tool calls. Web Context Card is deferred pending real Agent/replay/navigation validation; no Card screenshot or benchmark claim is made.
20
+ - Synced English/Chinese product docs and added a reproducible 30-second recording of real local tool calls. Web Context Card is deferred; Card replay/navigation remain unverified. No Card screenshot or benchmark claim is made.
21
+ - Final DSH Desktop `0.2.0-rc.2` real Agent verification passed: default text and explicit pack calls, bounded source/reasons/provenance, related tests, change classification and external freshness. The npm package was published manually on 2026-10-05.
15
22
 
16
23
  See `RELEASE_EVIDENCE_v0.9.0.md` for verified checks and release boundaries.
17
24
 
package/README.md CHANGED
@@ -14,7 +14,7 @@ Give your [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) ag
14
14
  - **Evidence you can inspect:** inclusion reasons are separate from `exact` / `import-scoped` / `name-only` relationship provenance. No probability or confidence estimate is invented.
15
15
  - **Local and current:** isolated repo/worktree indexes, external add/change/delete freshness, no external indexing API key. Existing full/compact surfaces and v0.8 configuration remain supported.
16
16
 
17
- This checkout contains the v0.9 release candidate. It has not been published; an npm install still receives the latest published version. See [release evidence](RELEASE_EVIDENCE_v0.9.0.md).
17
+ v0.9.0 is published on npm and passed real Agent verification in DSH Desktop `0.2.0-rc.2`. See [release evidence](RELEASE_EVIDENCE_v0.9.0.md).
18
18
 
19
19
  ## On this page
20
20
 
@@ -30,16 +30,14 @@ This checkout contains the v0.9 release candidate. It has not been published; an
30
30
 
31
31
  ## Quick start
32
32
 
33
- Requires Node 22/24 and a matching DSH `0.2.0-rc.2` host group. For this unreleased checkout, build and install locally from the repository root:
33
+ Requires Node 22/24 and a matching DSH `0.2.0-rc.2` host group. Install the published version into a Web profile:
34
34
 
35
35
  ```sh
36
- pnpm install
37
- pnpm build
38
- npx @deepseek-ai/dsh@0.2.0-rc.2 plugin --profile web add .
36
+ npx @deepseek-ai/dsh@0.2.0-rc.2 plugin --profile web add dsh-code-index@0.9.0
39
37
  npx @deepseek-ai/dsh@0.2.0-rc.2 web
40
38
  ```
41
39
 
42
- Use the published-package command in [Install](#install) only when you intend to install the registry version, with its matching host.
40
+ For local development, run `pnpm install` and `pnpm build` at the repository root, then use `plugin --profile web add .`. See [Install](#install) for profile options.
43
41
 
44
42
  ## Project isolation and live updates
45
43
 
@@ -54,22 +52,19 @@ Each Git worktree gets its own index and change state. The plugin watches projec
54
52
 
55
53
  ## See it in action
56
54
 
57
- [30-second recorded demo](assets/context-pack-demo.html) · [terminal recording](assets/context-pack-demo.cast)
55
+ **42 seconds in DSH Desktop: why did loading `' input '` produce `'INPUT'`?**
58
56
 
59
- Download the HTML replay and open it locally, or play the cast with an asciinema-compatible player. Reproduce with `pnpm build`, then `node scripts/context-pack-demo.mjs`. The recording uses actual local tool calls in a tiny Git repository; it does not claim an Agent task-success or performance benchmark.
57
+ ![Actual DSH Desktop Agent calling code_context, with a bilingual task and explanation](assets/desktop-agent-v090.gif)
60
58
 
61
- `code_context({ task: "Explain loadConfig", budgetChars: 5000 })` returns source immediately. An excerpt from the recorded call:
59
+ One real `code_context` call returned `loadConfig` at `src/config.ts:1–3`, its callers, Git additions/modifications/deletions, and a likely test, with inclusion reasons and relationship provenance: **2347/5000 characters**. The Agent used that context to identify `.toUpperCase()` as the cause.
62
60
 
63
- ```text
64
- primary: loadConfig src/config.ts:1-3 [current]
65
- Reason: task names this symbol
66
- Source (complete):
67
- export function loadConfig(input: string) {
68
- return input.trim()
69
- }
70
- ```
61
+ This is **actual desktop footage recorded on 2026-10-08**, using the installed v0.9.0 plugin and a tiny demonstration repository. Only DSH is shown; waiting was cut and audio removed. No files were edited or tests run. The clip is not a latency measurement or a claim that a fix/tests succeeded. [MP4](assets/desktop-agent-v090.mp4) · [Recording notes and provenance](assets/desktop-agent-v090.md) · [Actual tool-card evidence](assets/desktop-agent-v090.evidence.json).
62
+
63
+ The earlier [bilingual terminal replay](assets/agent-context-demo.md) remains available as a separate, clearly labeled replay of audited 2026-10-05 Agent records. It also shows external-edit freshness and a 500-character signature-only fallback.
64
+
65
+ Try asking: **“Use code_context to locate a config-loading bug, show the relevant source and callers, and identify likely tests.”** Tell us what helped or what context was missing in the [DSH discussion](https://github.com/deepseek-ai/deepseek-harness/discussions/5623).
71
66
 
72
- The demo then makes an external edit, shows added/modified/deleted declarations with separate current/base source, and requests a 500-character pack. Web Context Card is deferred until real Agent execution, replay, and file navigation can be verified on the matching RC. The existing text presentation remains usable.
67
+ The earlier [30-second local tool demo](assets/context-pack-demo.html) remains available and can be reproduced with `pnpm build`, then `node scripts/context-pack-demo.mjs`. Web Context Card remains deferred; Card replay and file navigation have not been verified.
73
68
 
74
69
  ## Tools
75
70
 
@@ -96,13 +91,13 @@ v0.9 requires the matching `0.2.0-rc.2` tool runtime; the peer pin prevents fres
96
91
 
97
92
  ```sh
98
93
  # from npm (prebuilt)
99
- npx @deepseek-ai/dsh plugin --profile web add dsh-code-index
94
+ npx @deepseek-ai/dsh@0.2.0-rc.2 plugin --profile web add dsh-code-index@0.9.0
100
95
 
101
96
  # or from a directory containing this checkout
102
- npx @deepseek-ai/dsh plugin --profile web add ./dsh-code-index
97
+ npx @deepseek-ai/dsh@0.2.0-rc.2 plugin --profile web add ./dsh-code-index
103
98
  ```
104
99
 
105
- Restart the Web UI (`npx @deepseek-ai/dsh web`) — startup logs confirm each tool:
100
+ Restart the Web UI (`npx @deepseek-ai/dsh@0.2.0-rc.2 web`) — startup logs confirm each tool:
106
101
 
107
102
  ```
108
103
  [dsh-code-index] plugin loaded
package/README.zh.md CHANGED
@@ -11,7 +11,7 @@
11
11
  - **可检查的证据:**入选 reason 与关系的 `exact` / `import-scoped` / `name-only` provenance 分开,不编造概率或 confidence。
12
12
  - **本地、隔离、实时:**repo/worktree 独立,外部增改删自动刷新,不需要外部索引 API key;保留 full/compact surface 和 v0.8 配置。
13
13
 
14
- 当前 checkout 是 v0.9 发布候选,尚未发布;npm 安装得到的仍是最新已发布版本。验证边界见[release evidence](RELEASE_EVIDENCE_v0.9.0.md)。
14
+ v0.9.0 已发布到 npm,并通过 DSH Desktop `0.2.0-rc.2` 真实 Agent 验证。验证边界见[release evidence](RELEASE_EVIDENCE_v0.9.0.md)。
15
15
 
16
16
  ## 本页导航
17
17
 
@@ -27,16 +27,14 @@
27
27
 
28
28
  ## 快速开始
29
29
 
30
- 需要 Node 22/24 与匹配的 DSH `0.2.0-rc.2` 宿主版本组。当前版本尚未发布,在仓库根目录构建并本地安装:
30
+ 需要 Node 22/24 与匹配的 DSH `0.2.0-rc.2` 宿主版本组。将已发布版本安装到 Web profile:
31
31
 
32
32
  ```sh
33
- pnpm install
34
- pnpm build
35
- npx @deepseek-ai/dsh@0.2.0-rc.2 plugin --profile web add .
33
+ npx @deepseek-ai/dsh@0.2.0-rc.2 plugin --profile web add dsh-code-index@0.9.0
36
34
  npx @deepseek-ai/dsh@0.2.0-rc.2 web
37
35
  ```
38
36
 
39
- 只有想安装 registry 已发布版本时,才使用[安装说明](#安装)中的 npm 包命令,并匹配该版本的宿主。
37
+ 本地开发时,在仓库根目录运行 `pnpm install`、`pnpm build`,然后使用 `plugin --profile web add .`。其他 profile 选项见[安装说明](#安装)。
40
38
 
41
39
  ## 项目隔离与实时更新
42
40
 
@@ -51,22 +49,19 @@ npx @deepseek-ai/dsh@0.2.0-rc.2 web
51
49
 
52
50
  ## 实际效果
53
51
 
54
- [30 秒真实调用回放](assets/context-pack-demo.html) · [终端录制](assets/context-pack-demo.cast)
52
+ **42 秒真实 DSH 桌面演示:加载 `' input '` 为什么变成了 `'INPUT'`?**
55
53
 
56
- 下载 HTML 后本地打开,或使用兼容 asciinema 的播放器。复现方式:`pnpm build`,再运行 `node scripts/context-pack-demo.mjs`。录制内容是在小型 Git 仓库中的实际本地工具调用,不代表 Agent 任务成功率或性能 benchmark。
54
+ ![真实 DSH Desktop Agent 调用 code_context,含中英文任务与解释](assets/desktop-agent-v090.gif)
57
55
 
58
- `code_context({ task: "Explain loadConfig", budgetChars: 5000 })` 直接返回源码。以下摘自实际录制输出:
56
+ 一次真实 `code_context` 调用返回 `src/config.ts:1–3` 的 `loadConfig`、调用方、Git 增改删和相关测试线索,附入选理由与关系来源,共 **2347/5000 字符**。Agent 据此定位到改变大小写的 `.toUpperCase()`。
59
57
 
60
- ```text
61
- primary: loadConfig src/config.ts:1-3 [current]
62
- Reason: task names this symbol
63
- Source (complete):
64
- export function loadConfig(input: string) {
65
- return input.trim()
66
- }
67
- ```
58
+ 这是 **2026-10-08 新录制的真实桌面画面**,使用已安装的 v0.9.0 与一个小演示仓库。只展示 DSH,剪去等待并移除音轨;没有修改文件或运行测试,不代表实际耗时,也不宣称修复或测试成功。[MP4 视频](assets/desktop-agent-v090.mp4) · [录制与来源说明](assets/desktop-agent-v090.md) · [实际工具卡片证据](assets/desktop-agent-v090.evidence.json)。
59
+
60
+ 先前的[中英终端回放](assets/agent-context-demo.md)继续保留,单独标明来自 2026-10-05 的真实 Agent 记录,补充展示外部编辑刷新及 500 字符下的签名降级。
61
+
62
+ 可以先试一句:**“用 code_context 定位一个配置加载 Bug,给出相关源码、调用者和可能受影响的测试。”** 欢迎在 [DSH Discussion](https://github.com/deepseek-ai/deepseek-harness/discussions/5623) 分享哪些上下文有帮助、哪些被漏掉。
68
63
 
69
- 随后 demo 从外部修改文件,展示 added/modified/deleted 及独立的 current/base 源码,再请求 500 字符预算。Web Context Card 延期,等待匹配 RC 上真实 Agent 调用、replay 和文件跳转验证;既有文本展示继续可用。
64
+ 早期的 [30 秒本地工具演示](assets/context-pack-demo.html) 仍然保留,可用 `pnpm build` 后运行 `node scripts/context-pack-demo.mjs` 复现。Web Context Card 继续延期,Card replay 和文件跳转尚未验证。
70
65
 
71
66
  ## 工具一览
72
67
 
@@ -93,13 +88,13 @@ v0.9 要求匹配的 `0.2.0-rc.2` tools runtime;精确 peer pin 防止 npm 新
93
88
 
94
89
  ```sh
95
90
  # 从 npm(预编译)
96
- npx @deepseek-ai/dsh plugin --profile web add dsh-code-index
91
+ npx @deepseek-ai/dsh@0.2.0-rc.2 plugin --profile web add dsh-code-index@0.9.0
97
92
 
98
93
  # 或从包含本仓库检查副本的目录
99
- npx @deepseek-ai/dsh plugin --profile web add ./dsh-code-index
94
+ npx @deepseek-ai/dsh@0.2.0-rc.2 plugin --profile web add ./dsh-code-index
100
95
  ```
101
96
 
102
- 重启 Web UI(`npx @deepseek-ai/dsh web`)——启动日志会确认每个工具:
97
+ 重启 Web UI(`npx @deepseek-ai/dsh@0.2.0-rc.2 web`)——启动日志会确认每个工具:
103
98
 
104
99
  ```
105
100
  [dsh-code-index] plugin loaded
package/dist/index.js CHANGED
@@ -1056,32 +1056,38 @@ var SUPPORTED_EXTS = /* @__PURE__ */ new Set([
1056
1056
  ".inl",
1057
1057
  ".c"
1058
1058
  ]);
1059
- function isGitIgnoredPath(root, relativePath, cache = /* @__PURE__ */ new Map()) {
1060
- const rel = relativePath.replace(/\\/g, "/").replace(/^\.\//, "");
1059
+ function ignoredByGitLayers(layers, relativePath, isDirectory) {
1060
+ let ignored = false;
1061
+ for (const { base, matcher } of layers) {
1062
+ const scoped = base ? path2.posix.relative(base, relativePath) : relativePath;
1063
+ if (!scoped || scoped === ".." || scoped.startsWith("../")) continue;
1064
+ const result = matcher.test(isDirectory ? `${scoped.replace(/\/$/, "")}/` : scoped);
1065
+ if (result.ignored || result.unignored) ignored = result.ignored;
1066
+ }
1067
+ return ignored;
1068
+ }
1069
+ function isGitIgnoredPath(root, relativePath, cache = /* @__PURE__ */ new Map(), isDirectory = relativePath.endsWith("/")) {
1070
+ const rel = relativePath.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
1061
1071
  if (!rel) return false;
1062
- const parentParts = rel.split("/").slice(0, -1);
1063
- for (let depth = 0; depth <= parentParts.length; depth += 1) {
1064
- const base = parentParts.slice(0, depth).join("/");
1072
+ const parts = rel.split("/");
1073
+ const layers = [];
1074
+ for (let depth = 0; depth < parts.length; depth += 1) {
1075
+ const base = parts.slice(0, depth).join("/");
1065
1076
  const ignoreFile = path2.join(root, base, ".gitignore");
1066
- let fileStat;
1067
1077
  try {
1068
- fileStat = statSync(ignoreFile);
1069
- } catch {
1070
- continue;
1071
- }
1072
- const previous = cache.get(ignoreFile);
1073
- let matcher = previous?.matcher;
1074
- if (!previous || previous.mtimeMs !== fileStat.mtimeMs || previous.size !== fileStat.size) {
1075
- try {
1078
+ const fileStat = statSync(ignoreFile);
1079
+ const previous = cache.get(ignoreFile);
1080
+ let matcher = previous?.matcher;
1081
+ if (!previous || previous.mtimeMs !== fileStat.mtimeMs || previous.size !== fileStat.size) {
1076
1082
  matcher = ignore().add(readFileSync(ignoreFile, "utf8"));
1077
1083
  cache.set(ignoreFile, { mtimeMs: fileStat.mtimeMs, size: fileStat.size, matcher });
1078
- } catch {
1079
- cache.delete(ignoreFile);
1080
- continue;
1081
1084
  }
1085
+ if (matcher) layers.push({ base, matcher });
1086
+ } catch {
1087
+ cache.delete(ignoreFile);
1082
1088
  }
1083
- const scoped = base ? path2.posix.relative(base, rel) : rel;
1084
- if (matcher?.ignores(scoped)) return true;
1089
+ const probe = parts.slice(0, depth + 1).join("/");
1090
+ if (ignoredByGitLayers(layers, probe, depth < parts.length - 1 || isDirectory)) return true;
1085
1091
  }
1086
1092
  return false;
1087
1093
  }
@@ -1097,10 +1103,6 @@ async function scanRepo(root, options = {}) {
1097
1103
  layers = [...inherited, { base: relDir, matcher: ignore().add(rules) }];
1098
1104
  } catch {
1099
1105
  }
1100
- const ignoredByGit = (relativePath) => layers.some(({ base, matcher }) => {
1101
- const scoped = base ? path2.posix.relative(base, relativePath) : relativePath;
1102
- return scoped !== ".." && !scoped.startsWith("../") && matcher.ignores(scoped);
1103
- });
1104
1106
  let entries;
1105
1107
  try {
1106
1108
  entries = await readdir(absDir, { withFileTypes: true });
@@ -1112,8 +1114,8 @@ async function scanRepo(root, options = {}) {
1112
1114
  const rel = relDir ? `${relDir}/${entry.name}` : entry.name;
1113
1115
  if (entry.isDirectory()) {
1114
1116
  if (excluded.has(entry.name)) continue;
1115
- if (!ignoredByGit(rel)) queue.push([abs, rel, layers]);
1116
- } else if (entry.isFile() && !ignoredByGit(rel) && SUPPORTED_EXTS.has(path2.extname(entry.name).toLowerCase())) {
1117
+ if (!ignoredByGitLayers(layers, rel, true)) queue.push([abs, rel, layers]);
1118
+ } else if (entry.isFile() && !ignoredByGitLayers(layers, rel, false) && SUPPORTED_EXTS.has(path2.extname(entry.name).toLowerCase())) {
1117
1119
  try {
1118
1120
  const st = await stat(abs);
1119
1121
  results.push({ abs, rel, mtimeMs: st.mtimeMs });
@@ -3645,9 +3647,9 @@ var RepoContextManager = class {
3645
3647
  attachWatcher(context) {
3646
3648
  if (!this.options.watch) return;
3647
3649
  const options = this.options.indexOptions();
3648
- const ignored = (absolute) => {
3650
+ const ignored = (absolute, stats) => {
3649
3651
  const rel = path9.relative(context.root, absolute);
3650
- return rel === ".." || rel.startsWith(`..${path9.sep}`) || path9.isAbsolute(rel) || ignoredRelativePath(rel, options) || isGitIgnoredPath(context.root, rel, context.ignoreMatcherCache);
3652
+ return rel === ".." || rel.startsWith(`..${path9.sep}`) || path9.isAbsolute(rel) || ignoredRelativePath(rel, options) || stats !== void 0 && isGitIgnoredPath(context.root, rel, context.ignoreMatcherCache, stats.isDirectory());
3651
3653
  };
3652
3654
  const watcher = chokidar.watch(context.root, {
3653
3655
  ignored,