dsh-code-index 0.6.1 → 0.7.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/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  All notable changes to dsh-code-index are documented here.
4
4
 
5
+ ## 0.7.0 — 2026-09-19
6
+
7
+ - **feat(context):** add deterministic task-aware `code_context` routing for change, symbol, architecture, test, exploration, and ambiguous tasks.
8
+ - **feat(context):** unify existing search, repo-map, call/import graph, change-aware context, affected tests, provenance, and structural signals behind one ranked context package.
9
+ - **feat(context):** add hard character budgeting, deterministic ordering, candidate deduplication, and low-priority trimming.
10
+ - **feat(surface):** add optional `toolSurface: compact` experiment while keeping the full v0.6 tool surface as the default.
11
+ - **bench:** add the v0.7 treatment arm to the context-economy benchmark without inventing model or token results.
12
+ - **compatibility:** retain all v0.6 tools and their existing argument/output behavior.
13
+
5
14
  ## 0.6.1 — 2026-09-18
6
15
 
7
16
  - **fix(change-context):** map pure renames to both current and baseline symbols instead of returning only a warning.
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  English | [中文](README.zh.md)
7
7
 
8
- Structural context engine for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (`dsh`): a local, no-service, no-API-key tree-sitter index with ranked symbol search, repo map, call graph, and change-aware context.
8
+ Structural context engine for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (`dsh`): a local, no-service, no-API-key tree-sitter index that gives agents the smallest useful structural context for the task at hand.
9
9
 
10
10
  Fits a niche the ecosystem took a while to fill: alongside git/voice/browser/memory plugins, several code-intelligence plugins have appeared (graph-based, embedding-based), while this one stays deliberately **dependency-free** — pure in-process tree-sitter over WASM, the aider repo-map / Cursor `@Codebase` style for dsh agents.
11
11
 
@@ -19,6 +19,7 @@ Fits a niche the ecosystem took a while to fill: alongside git/voice/browser/mem
19
19
  | `code_map` | Bounded ranked repo map (top files by symbol density + import-graph PageRank, key symbols + lines) |
20
20
  | `code_refs` | Trace a symbol through the call graph: callers (who calls it) and callees (what it calls), resolved to file:line |
21
21
  | `code_change_context` | Start from the working-tree or an explicit diff and return changed symbols, callers, import dependents, bounded impact paths, and likely tests |
22
+ | `code_context` | Task-aware unified entry point: routes a plain-language task across search, repo map, call/import graph, change context, tests, and a hard character budget |
22
23
  | `code_health` | Opt-in (`codeHealth: true`): circular dependencies (import cycles) and orphan modules |
23
24
 
24
25
  Plus an optional **auto-injected system prompt section** (`code-index:repo-map`, order 60): a compact ranked map of the default workspace, refreshed on a TTL (`mapTtlMs`, default 60s). Set `autoInject: false` to disable and rely on the `code_map` tool only.
@@ -54,6 +55,7 @@ In a workspace session, ask the agent:
54
55
  - "List the exported symbols in src/core."
55
56
  - "Rebuild the code index."
56
57
  - "What changed in the working tree, who calls it, and which tests are likely affected?"
58
+ - "Fix duplicate configuration loading during startup." (the router selects the smallest useful context automatically)
57
59
 
58
60
  No API key is needed to *index*; the model must of course be configured to call the tools.
59
61
 
@@ -89,6 +91,30 @@ The index builds lazily on first use; later calls are served from the on-disk ca
89
91
 
90
92
  `code_change_context` defaults to the current Git working tree against `HEAD`. It also accepts an inline unified diff, repo-relative `files`, or stable `symbols` IDs. Results are bounded and each inferred relationship carries a provenance label: `exact`, `import-scoped`, or `name-only`. Deletions and renames use the baseline ref when available; untracked non-ignored files are included in working-tree mode.
91
93
 
94
+ ### Task-aware context
95
+
96
+ `code_context` is the high-level entry point for agents that have a task rather than a symbol query. The deterministic router recognizes change, symbol, architecture, test, exploration, and ambiguous tasks, then ranks existing primitives into one bounded package:
97
+
98
+ ```text
99
+ code_context { task: "Fix duplicate configuration loading during startup" }
100
+
101
+ Task context — change
102
+ Primary symbols:
103
+ - [exact] function loadConfig() — src/config.ts:42
104
+ Relevant files:
105
+ - src/config.ts (current change)
106
+ - src/bootstrap.ts (entry path)
107
+ Relationships:
108
+ - bootstrap → loadConfig at src/bootstrap.ts:18 [exact; explicit-import-binding]
109
+ Current changes:
110
+ - modified function loadConfig() src/config.ts:42 [exact]
111
+ Likely affected tests:
112
+ - tests/config.spec.ts
113
+ Budget: 3720 / 5000 chars
114
+ ```
115
+
116
+ The `budgetChars`, `maxFiles`, and `maxSymbols` arguments are optional. `code_context` does not call an external model or API; `exact`, `import-scoped`, and `name-only` provenance remains explicit for inferred relationships.
117
+
92
118
  ## Configuration
93
119
 
94
120
  Options are passed as the plugin row's `config` in the profile patch (or defaults are used if absent):
@@ -101,6 +127,7 @@ Options are passed as the plugin row's `config` in the profile patch (or default
101
127
  mapTopFiles: 30
102
128
  mapMaxChars: 4000
103
129
  autoInject: true
130
+ toolSurface: full
104
131
  ```
105
132
 
106
133
  | Key | Default | Meaning |
@@ -111,6 +138,7 @@ Options are passed as the plugin row's `config` in the profile patch (or default
111
138
  | `mapTtlMs` | `60000` | Refresh interval for the auto-injected map (ms, min 1000) |
112
139
  | `autoInject` | `true` | Register the system prompt section |
113
140
  | `codeHealth` | `false` | Register the `code_health` tool (cycles / orphan modules) |
141
+ | `toolSurface` | `full` | Experimental `compact` mode exposes `code_index`, `code_context`, and enabled `code_health`; `full` preserves all tools |
114
142
 
115
143
  ## Supported languages
116
144
 
@@ -123,6 +151,7 @@ TypeScript, JavaScript, Python, Go, Rust, Java, C++ and C (`.ts .tsx .mts .cts .
123
151
  - **Repo map** (`src/repomap.ts`): personalized PageRank over the import graph (teleport = per-file density share, so hub files that are themselves imported by other hubs rise above flat in-degree counting), seeded by the density-aware file score (class/interface/function weighted, test paths damped), top-N files, per-file symbol cap, hard char truncation.
124
152
  - **Call graph** (`src/refgraph.ts`): call sites extracted per file (per language, with their enclosing function) are resolved by name into callers and callees — `code_refs` exposes this directly, and `code_search` uses call fan-in as a ranking tie-break.
125
153
  - **Change context** (`src/change-context.ts`): maps Git hunks to stable symbols, then follows bounded provenance-labeled callers, import dependents, entry paths, impact, and likely affected tests without returning the whole repository.
154
+ - **Task-aware context** (`src/context.ts`): deterministically routes a task across the existing search, map, call graph, change context, and test signals, then deduplicates and trims them to a hard character budget.
126
155
  - **Health** (`src/health.ts`): Tarjan SCC over the import graph yields circular dependencies; orphan-module detection lists symbol-bearing files with no inbound or outbound imports (entry points and tests excluded).
127
156
  - **Workspace resolution**: each tool resolves the session cwd (`agent.session.header.cwd`) and walks up to the nearest `.git` (bounded — a directory without a repo marker is never indexed).
128
157
 
@@ -143,7 +172,7 @@ pnpm build # tsup → dist/index.js (ESM, external deps)
143
172
  pnpm build && pnpm release:smoke # pack, clean-install the tarball, boot the plugin, and exercise core tools
144
173
  ```
145
174
 
146
- The benchmark harness under `bench/` compares stock DSH, the published 0.5 baseline, and the local 0.6 treatment. It records task completion, input tokens, tool calls, turns, and wall time; it does not invent missing provider usage. The repository contains infrastructure and sample tasks, not measured performance claims.
175
+ The benchmark harness under `bench/` compares stock DSH, the published 0.5 baseline, the v0.6 change-aware treatment, and the v0.7 task-aware treatment. It records task completion, input tokens, tool calls, turns, and wall time; it does not invent missing provider usage. The repository contains infrastructure and sample tasks, not measured performance claims.
147
176
 
148
177
  **WSL → Windows checkouts:** running `pnpm install` from WSL against a checkout on `/mnt/c` leaves Linux-style symlinks that Windows Node cannot traverse (`Cannot find package 'web-tree-sitter'`, `EACCES`). Repair without a reinstall from the Windows side:
149
178
 
package/README.zh.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [English](README.md) | 中文
4
4
 
5
- 面向 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(`dsh`) 的结构化上下文引擎:本地运行、无需外部服务和 API key,基于 tree-sitter 提供符号索引、排名搜索、仓库地图、调用图和变更感知上下文。
5
+ 面向 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(`dsh`) 的结构化上下文引擎:本地运行、无需外部服务和 API key,为当前代码任务自动选择最少但足够有用的结构化上下文。
6
6
 
7
7
  在一个生态姗姗来迟的细分领域占位:git/语音/浏览器/记忆类插件之外,代码智能方向的插件已陆续出现(图谱路线、向量嵌入路线),而本插件刻意保持**零外部依赖**——纯进程内 tree-sitter WASM,把 aider repo-map / Cursor `@Codebase` 的同类能力带给 dsh agent。
8
8
 
@@ -16,6 +16,7 @@
16
16
  | `code_map` | 限量排名仓库地图(按符号密度 + import 图 PageRank 取核心文件 + 关键符号与行号) |
17
17
  | `code_refs` | 沿调用图追踪符号:callers(谁调用了它)与 callees(它调用了谁),解析到 file:line |
18
18
  | `code_change_context` | 从工作区或显式 diff 出发,返回变更符号、调用者、import 依赖、有限影响路径和可能受影响的测试 |
19
+ | `code_context` | 任务感知的统一入口:根据自然语言任务组合搜索、仓库地图、调用/import 图、变更上下文、测试和字符预算 |
19
20
  | `code_health` | 可选开启(`codeHealth: true`):环依赖(import 环)与孤儿模块 |
20
21
 
21
22
  外加一个可选的**自动注入系统提示词段**(`code-index:repo-map`,序 60):默认工作区的精简排名地图,按 TTL 自动刷新(`mapTtlMs`,默认 60 秒)。将 `autoInject: false` 可关闭,只依赖 `code_map` 工具。
@@ -51,6 +52,7 @@ npx @deepseek-ai/dsh plugin --profile web add ./dsh-code-index
51
52
  - "列出 src/core 里的导出符号。"
52
53
  - "重建代码索引。"
53
54
  - "工作区改了什么、谁调用了它、哪些测试可能受影响?"
55
+ - "修复启动时重复加载配置。"(路由器会自动选择最有用的最小上下文)
54
56
 
55
57
  *索引*本身不需要 API key;模型当然要配置好才能调用这些工具。
56
58
 
@@ -86,6 +88,10 @@ export function extractSymbols(code, id) — src/extract.ts:121
86
88
 
87
89
  `code_change_context` 默认分析相对于 `HEAD` 的当前 Git 工作区,也支持内联 unified diff、仓库相对 `files` 或稳定的 `symbols` ID。结果有数量和字符预算,每条推导关系都会标注 `exact`、`import-scoped` 或 `name-only` 来源。删除和重命名在可用时读取 baseline;工作区模式也会包含未被忽略的未跟踪源码文件。
88
90
 
91
+ ### 任务感知上下文
92
+
93
+ `code_context` 是 Agent 面对“代码任务”而不是单个符号时的高级入口。确定性的路由器识别修改、符号、架构、测试、探索和模糊任务,然后把已有能力合并、排序、去重并限制在字符预算内。只传 `task` 即可,也可以覆盖 `budgetChars`、`maxFiles`、`maxSymbols`。它不调用外部模型/API,推导关系的 `exact`、`import-scoped`、`name-only` provenance 会继续保留。
94
+
89
95
  ## 配置
90
96
 
91
97
  选项通过插件行的 `config` 在 profile 补丁中传入(缺省时使用默认值):
@@ -98,6 +104,7 @@ export function extractSymbols(code, id) — src/extract.ts:121
98
104
  mapTopFiles: 30
99
105
  mapMaxChars: 4000
100
106
  autoInject: true
107
+ toolSurface: full
101
108
  ```
102
109
 
103
110
  | 键 | 默认 | 含义 |
@@ -108,6 +115,7 @@ export function extractSymbols(code, id) — src/extract.ts:121
108
115
  | `mapTtlMs` | `60000` | 自动注入地图的刷新间隔(毫秒,最小 1000) |
109
116
  | `autoInject` | `true` | 是否注册系统提示词段 |
110
117
  | `codeHealth` | `false` | 是否注册 `code_health` 工具(环/孤儿模块) |
118
+ | `toolSurface` | `full` | 实验性的 `compact` 模式只暴露 `code_index`、`code_context` 和已启用的 `code_health`; `full` 保持全部工具 |
111
119
 
112
120
  ## 支持的语言
113
121
 
@@ -120,6 +128,7 @@ TypeScript、JavaScript、Python、Go、Rust、Java、C++、C(`.ts .tsx .mts .ct
120
128
  - **仓库地图**(`src/repomap.ts`):import 图上的个性化 PageRank(传送向量 = 各文件密度份额,被其他枢纽文件引用的枢纽会比平铺入度统计排得更靠前),以密度感知的文件打分为底(class/interface/function 加权,测试路径衰减),取 Top-N 文件,每文件符号上限,硬截断。
121
129
  - **调用图**(`src/refgraph.ts`):逐文件提取调用点(按语言,并记录其所属函数),按名称解析成 callers 与 callees——`code_refs` 直接暴露,`code_search` 也把调用热度作为排名平局裁决。
122
130
  - **变更上下文**(`src/change-context.ts`):把 Git hunk 映射到稳定符号,再在有限深度内追踪带来源标签的调用者、import 依赖、入口路径、影响范围和可能受影响的测试,避免返回整个仓库。
131
+ - **任务感知上下文**(`src/context.ts`):确定性地把任务路由到已有的搜索、地图、调用图、变更上下文和测试信号,再按统一优先级去重并限制字符预算。
123
132
  - **健康检查**(`src/health.ts`):对 import 图跑 Tarjan SCC 得到环依赖;孤儿模块检测列出既不被 import、也不 import 任何文件的含符号文件(排除入口与测试)。
124
133
  - **工作区解析**:每个工具解析会话 cwd(`agent.session.header.cwd`)并向上查找最近的 `.git`(有界——没有仓库标记的目录绝不会被索引)。
125
134
 
@@ -140,7 +149,7 @@ pnpm build # tsup → dist/index.js(ESM,外部依赖)
140
149
  pnpm build && pnpm release:smoke # 打包、干净安装 tarball、启动插件并调用核心工具
141
150
  ```
142
151
 
143
- `bench/` 下的 benchmark 对比 stock DSH、已发布的 0.5 基线和本地 0.6 处理组,记录任务完成、输入 token、工具调用、轮次和墙钟时间; provider 未提供的 usage 会保留为 null,不会伪造性能数据。当前仓库提供基础设施和示例任务,不宣称已测得性能提升。
152
+ `bench/` 下的 benchmark 对比 stock DSH、已发布的 0.5 基线、0.6 变更感知处理组和 0.7 任务感知处理组,记录任务完成、输入 token、工具调用、轮次和墙钟时间; provider 未提供的 usage 会保留为 null,不会伪造性能数据。当前仓库提供基础设施和示例任务,不宣称已测得性能提升。
144
153
 
145
154
  **WSL → Windows 检出**:从 WSL 对 `/mnt/c` 下的检出跑 `pnpm install`,会留下 Windows 侧 Node 无法穿透的 Linux 风格符号链接(`Cannot find package 'web-tree-sitter'`、`EACCES`)。无需重装,在 Windows 侧跑一次修复:
146
155
 
package/dist/client.js CHANGED
@@ -53,7 +53,7 @@ function parseArgs(raw) {
53
53
  }
54
54
  function summarizeArgs(args) {
55
55
  const parts = [];
56
- for (const key of ["query", "symbol", "action", "direction", "kind"]) {
56
+ for (const key of ["task", "query", "symbol", "action", "direction", "kind", "budgetChars"]) {
57
57
  const value = args[key];
58
58
  if (typeof value === "string" && value) parts.push(`${key}=${value}`);
59
59
  }
@@ -264,6 +264,7 @@ var TOOLVIEW_KEYS = [
264
264
  "code_map",
265
265
  "code_refs",
266
266
  "code_change_context",
267
+ "code_context",
267
268
  "code_health"
268
269
  ];
269
270
  function apply(ctx) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client/index.tsx"],"sourcesContent":["/**\n * Browser half of dsh-code-index: structured result cards for the code_* tools\n * plus a settings card for the `code-index` namespace.\n *\n * Deliberately imports no harness client package: this bundle only needs\n * `react` (a shell platform module) plus the slot registry and settings scope\n * services reached through the plugin context. Keeping the surface local avoids\n * a dependency on a synchronized client-package version.\n */\n\nimport { useEffect, useState, type ReactNode } from 'react'\n\n/** Minimal structural view of the client plugin context this bundle uses. */\ninterface ClientContextLike {\n slots: {\n inject(name: string, run: () => Iterable<unknown>): void\n register(entry: Record<string, unknown>, component: (props: never) => ReactNode): () => void\n }\n inject(deps: string[], run: (ctx: ClientContextLike) => void): unknown\n settingsScope?: { bind(spec: { namespace: string }): SettingsScopeLike<CodeIndexSettings> }\n}\n\ninterface SettingsSnapshot<T> {\n status: 'loading' | 'ready' | 'unavailable'\n value: T | undefined\n writable: boolean\n}\n\ninterface SettingsScopeLike<T> {\n getSnapshot(): SettingsSnapshot<T>\n subscribe(listener: () => void): () => void\n set(field: string, value: unknown): Promise<void>\n unset(field: string): Promise<void>\n}\n\ninterface CodeIndexSettings {\n excludeDirs?: string[]\n mapTopFiles?: number\n mapMaxChars?: number\n mapTtlMs?: number\n autoInject?: boolean\n codeHealth?: boolean\n}\n\ninterface ToolCallHead {\n name?: string\n argsRaw?: string\n}\n\ninterface TextContent {\n type?: string\n text?: string\n}\n\ninterface ToolResultLike {\n call?: ToolCallHead | null\n content?: readonly TextContent[]\n isError?: boolean\n resultView?: unknown\n}\n\ninterface SearchView {\n card: 'search'\n shape?: 'matches' | 'paths'\n files?: Array<{ path: string; matches?: Array<{ lineNumber: number; line: string }> }>\n paths?: string[]\n truncated?: boolean\n total?: number\n}\n\ninterface ToolViewProps {\n toolName: string\n block: ToolResultLike & ToolCallHead\n openFile?: (path: string, options?: unknown) => void\n}\n\nconst CARD_STYLE: Record<string, string | number> = {\n border: '1px solid var(--dsh-border, #333)',\n borderRadius: 6,\n padding: '6px 8px',\n fontFamily: 'var(--dsh-mono-font, monospace)',\n fontSize: 12,\n margin: '4px 0',\n}\n\nconst ROW_STYLE: Record<string, string | number> = {\n display: 'flex',\n gap: 8,\n alignItems: 'baseline',\n}\n\nfunction parseArgs(raw: string | undefined): Record<string, unknown> {\n if (!raw) return {}\n try {\n const value: unknown = JSON.parse(raw)\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : {}\n } catch {\n return {}\n }\n}\n\nfunction summarizeArgs(args: Record<string, unknown>): string {\n const parts: string[] = []\n for (const key of ['query', 'symbol', 'action', 'direction', 'kind']) {\n const value = args[key]\n if (typeof value === 'string' && value) parts.push(`${key}=${value}`)\n }\n return parts.join(' ')\n}\n\nfunction asSearchView(view: unknown): SearchView | undefined {\n if (typeof view !== 'object' || view === null || Array.isArray(view)) return undefined\n const record = view as Record<string, unknown>\n return record.card === 'search' ? (record as unknown as SearchView) : undefined\n}\n\nfunction resultText(block: ToolResultLike): string {\n return (block.content ?? [])\n .filter((part) => part.type === 'text' && typeof part.text === 'string')\n .map((part) => part.text ?? '')\n .join('\\n')\n}\n\n/** One code tool's card: argument summary, structured search matches, or text. */\nfunction CodeToolCard(props: ToolViewProps): ReactNode {\n const { toolName, block, openFile } = props\n const head: ToolCallHead = block.call ?? block\n const summary = summarizeArgs(parseArgs(head.argsRaw))\n const search = asSearchView(block.resultView)\n const isError = block.isError === true\n\n return (\n <div style={CARD_STYLE}>\n <div style={ROW_STYLE}>\n <strong>{toolName}</strong>\n {summary ? <span style={{ opacity: 0.75 }}>{summary}</span> : null}\n {isError ? <span style={{ color: '#e5534b' }}>error</span> : null}\n </div>\n {search?.shape === 'matches' && search.files ? (\n <div style={{ marginTop: 4 }}>\n {search.files.map((file) => (\n <div key={file.path}>\n <div style={{ opacity: 0.85 }}>{file.path}</div>\n {(file.matches ?? []).map((match, index) => (\n <div\n key={`${match.lineNumber}-${index}`}\n style={{ paddingLeft: 12, cursor: openFile ? 'pointer' : 'default' }}\n onClick={() => openFile?.(file.path, { line: match.lineNumber })}\n >\n <span style={{ opacity: 0.6 }}>{match.lineNumber}</span> {match.line}\n </div>\n ))}\n </div>\n ))}\n {search.truncated ? <div style={{ opacity: 0.6 }}>… truncated ({search.total} total)</div> : null}\n </div>\n ) : (\n <pre style={{ margin: '4px 0 0', whiteSpace: 'pre-wrap', maxHeight: 220, overflow: 'auto' }}>\n {resultText(block)}\n </pre>\n )}\n </div>\n )\n}\n\n/** The scope is captured at apply() time; the card reads it through React state. */\nlet activeScope: SettingsScopeLike<CodeIndexSettings> | null = null\n\n/** Mirrors the first-party plugin card + field CSS modules, under our own class names. */\nconst CARD_CSS = `\n.dci-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}\n.dci-card:hover{border-color:var(--dsw-alias-label-dimmed)}\n.dci-cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}\n.dci-header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}\n.dci-header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}\n.dci-headText{flex-direction:column;flex:1 1 0%;gap:4px;min-width:0;display:flex}\n.dci-name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}\n.dci-description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}\n.dci-chevron{color:var(--dsw-alias-label-tertiary);flex:0 0 auto;transition:transform .16s}\n.dci-chevronOpen{transform:rotate(180deg)}\n.dci-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}\n.dci-field{flex-direction:column;gap:6px;padding:12px 0;display:flex}\n.dci-field+.dci-field{border-top:1px solid var(--dsw-alias-border-l2)}\n.dci-head{align-items:center;gap:8px;display:flex}\n.dci-label{min-width:0;color:var(--dsw-alias-label-primary);flex:1 1 0%;font-size:13px;font-weight:500;line-height:1.5}\n.dci-input{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font:inherit;font-size:13px;line-height:1.5}\n.dci-input:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}\n.dci-input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}\n.dci-check{width:16px;height:16px;flex:0 0 auto;accent-color:var(--dsw-alias-brand-primary)}\n.dci-hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}\n.dci-footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}\n.dci-discard,.dci-save{appearance:none;font:inherit;cursor:pointer;border:1px solid transparent;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}\n.dci-discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}\n.dci-discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}\n.dci-save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}\n.dci-discard:disabled,.dci-save:disabled{opacity:.4;cursor:default}\n`\n\nconst CHEVRON_PATH =\n 'M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z'\n\nconst SETTINGS_DEFAULTS = { autoInject: true, codeHealth: false, mapTopFiles: 24 }\nconst SETTINGS_FIELDS = ['autoInject', 'codeHealth', 'mapTopFiles'] as const\n\n/** Settings card for `code-index`, laid out like the first-party plugin cards. */\nfunction CodeIndexSettingsCard(): ReactNode {\n const scope = activeScope\n const [open, setOpen] = useState(false)\n const [snapshot, setSnapshot] = useState<SettingsSnapshot<CodeIndexSettings> | null>(\n scope ? scope.getSnapshot() : null,\n )\n const [draft, setDraft] = useState<CodeIndexSettings | null>(null)\n\n useEffect(() => {\n if (!scope) return\n setSnapshot(scope.getSnapshot())\n return scope.subscribe(() => setSnapshot(scope.getSnapshot()))\n }, [scope])\n\n const resolved = snapshot?.value ?? {}\n const form: CodeIndexSettings = { ...SETTINGS_DEFAULTS, ...resolved, ...draft }\n const writable = snapshot?.writable ?? false\n const changed = (key: (typeof SETTINGS_FIELDS)[number]): boolean =>\n form[key] !== (resolved[key] ?? SETTINGS_DEFAULTS[key])\n const dirty = draft !== null && SETTINGS_FIELDS.some(changed)\n\n const edit = (key: (typeof SETTINGS_FIELDS)[number], value: string | number | boolean): void => {\n setDraft({ ...form, [key]: value })\n }\n const save = (): void => {\n if (!scope) return\n for (const key of SETTINGS_FIELDS) {\n if (changed(key)) {\n scope.set(key, form[key]).catch((error: unknown) => {\n console.error('[dsh-code-index] settings save failed', error)\n })\n }\n }\n setDraft(null)\n }\n\n return (\n <li className={open ? 'dci-card dci-cardOpen' : 'dci-card'}>\n <style>{CARD_CSS}</style>\n <button\n type=\"button\"\n className=\"dci-header\"\n aria-expanded={open}\n aria-label={`${open ? '收起设置' : '展开设置'}: dsh-code-index`}\n onClick={() => { setOpen(!open) }}\n >\n <span className=\"dci-headText\">\n <span className=\"dci-name\">dsh-code-index</span>\n <span className=\"dci-description\">结构化仓库索引:符号搜索、仓库地图与调用图。</span>\n </span>\n <svg className={open ? 'dci-chevron dci-chevronOpen' : 'dci-chevron'} width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d={CHEVRON_PATH} fill=\"currentColor\" />\n </svg>\n </button>\n {open ? (\n <div className=\"dci-body\">\n <div className=\"dci-field\">\n <div className=\"dci-head\">\n <label className=\"dci-label\" htmlFor=\"dci-auto-inject\">自动注入仓库地图</label>\n <input\n id=\"dci-auto-inject\"\n className=\"dci-check\"\n type=\"checkbox\"\n checked={form.autoInject}\n disabled={!writable}\n onChange={(event) => { edit('autoInject', event.target.checked) }}\n />\n </div>\n <p className=\"dci-hint\">把精简的排名仓库地图注入系统提示词。</p>\n </div>\n <div className=\"dci-field\">\n <div className=\"dci-head\">\n <label className=\"dci-label\" htmlFor=\"dci-code-health\">代码健康工具</label>\n <input\n id=\"dci-code-health\"\n className=\"dci-check\"\n type=\"checkbox\"\n checked={form.codeHealth}\n disabled={!writable}\n onChange={(event) => { edit('codeHealth', event.target.checked) }}\n />\n </div>\n <p className=\"dci-hint\">注册 code_health 工具,报告环依赖与孤儿模块(重启后生效)。</p>\n </div>\n <div className=\"dci-field\">\n <div className=\"dci-head\">\n <label className=\"dci-label\" htmlFor=\"dci-map-files\">地图文件数</label>\n </div>\n <input\n id=\"dci-map-files\"\n className=\"dci-input\"\n type=\"text\"\n inputMode=\"numeric\"\n value={String(form.mapTopFiles)}\n disabled={!writable}\n onChange={(event) => { edit('mapTopFiles', Number(event.target.value)) }}\n />\n <p className=\"dci-hint\">仓库地图中最多展示的文件数。</p>\n </div>\n <div className=\"dci-footer\">\n <button type=\"button\" className=\"dci-discard\" disabled={!dirty || !writable} onClick={() => { setDraft(null) }}>\n 放弃修改\n </button>\n <button type=\"button\" className=\"dci-save\" disabled={!dirty || !writable} onClick={save}>保存</button>\n </div>\n </div>\n ) : null}\n {scope ? null : <p className=\"dci-hint\" style={{ padding: '0 16px 12px' }}>设置服务不可用。</p>}\n </li>\n )\n}\n\nexport const name = 'dsh-code-index'\n\n/** `slots` is required for direct access; `settingsScope` binds lazily below. */\nexport const inject = ['slots']\n\nconst TOOLVIEW_KEYS = [\n 'code_index',\n 'code_symbols',\n 'code_search',\n 'code_map',\n 'code_refs',\n 'code_change_context',\n 'code_health',\n]\n\nexport function apply(ctx: ClientContextLike): void {\n const card = CodeToolCard as unknown as (props: never) => ReactNode\n ctx.slots.inject('tool.call.toolview', function* () {\n for (const key of TOOLVIEW_KEYS) {\n yield ctx.slots.register({ name: 'tool.call.toolview', key }, card)\n }\n })\n\n ctx.inject(['settingsScope'], (scoped: ClientContextLike) => {\n activeScope = scoped.settingsScope?.bind({ namespace: 'code-index' }) ?? null\n ctx.slots.inject('settings.plugin.item', function* () {\n yield ctx.slots.register(\n { name: 'settings.plugin.item', key: 'code-index' },\n CodeIndexSettingsCard as unknown as (props: never) => ReactNode,\n )\n })\n })\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/client/index.tsx"],"sourcesContent":["/**\n * Browser half of dsh-code-index: structured result cards for the code_* tools\n * plus a settings card for the `code-index` namespace.\n *\n * Deliberately imports no harness client package: this bundle only needs\n * `react` (a shell platform module) plus the slot registry and settings scope\n * services reached through the plugin context. Keeping the surface local avoids\n * a dependency on a synchronized client-package version.\n */\n\nimport { useEffect, useState, type ReactNode } from 'react'\n\n/** Minimal structural view of the client plugin context this bundle uses. */\ninterface ClientContextLike {\n slots: {\n inject(name: string, run: () => Iterable<unknown>): void\n register(entry: Record<string, unknown>, component: (props: never) => ReactNode): () => void\n }\n inject(deps: string[], run: (ctx: ClientContextLike) => void): unknown\n settingsScope?: { bind(spec: { namespace: string }): SettingsScopeLike<CodeIndexSettings> }\n}\n\ninterface SettingsSnapshot<T> {\n status: 'loading' | 'ready' | 'unavailable'\n value: T | undefined\n writable: boolean\n}\n\ninterface SettingsScopeLike<T> {\n getSnapshot(): SettingsSnapshot<T>\n subscribe(listener: () => void): () => void\n set(field: string, value: unknown): Promise<void>\n unset(field: string): Promise<void>\n}\n\ninterface CodeIndexSettings {\n excludeDirs?: string[]\n mapTopFiles?: number\n mapMaxChars?: number\n mapTtlMs?: number\n autoInject?: boolean\n codeHealth?: boolean\n toolSurface?: 'full' | 'compact'\n}\n\ninterface ToolCallHead {\n name?: string\n argsRaw?: string\n}\n\ninterface TextContent {\n type?: string\n text?: string\n}\n\ninterface ToolResultLike {\n call?: ToolCallHead | null\n content?: readonly TextContent[]\n isError?: boolean\n resultView?: unknown\n}\n\ninterface SearchView {\n card: 'search'\n shape?: 'matches' | 'paths'\n files?: Array<{ path: string; matches?: Array<{ lineNumber: number; line: string }> }>\n paths?: string[]\n truncated?: boolean\n total?: number\n}\n\ninterface ToolViewProps {\n toolName: string\n block: ToolResultLike & ToolCallHead\n openFile?: (path: string, options?: unknown) => void\n}\n\nconst CARD_STYLE: Record<string, string | number> = {\n border: '1px solid var(--dsh-border, #333)',\n borderRadius: 6,\n padding: '6px 8px',\n fontFamily: 'var(--dsh-mono-font, monospace)',\n fontSize: 12,\n margin: '4px 0',\n}\n\nconst ROW_STYLE: Record<string, string | number> = {\n display: 'flex',\n gap: 8,\n alignItems: 'baseline',\n}\n\nfunction parseArgs(raw: string | undefined): Record<string, unknown> {\n if (!raw) return {}\n try {\n const value: unknown = JSON.parse(raw)\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : {}\n } catch {\n return {}\n }\n}\n\nfunction summarizeArgs(args: Record<string, unknown>): string {\n const parts: string[] = []\n for (const key of ['task', 'query', 'symbol', 'action', 'direction', 'kind', 'budgetChars']) {\n const value = args[key]\n if (typeof value === 'string' && value) parts.push(`${key}=${value}`)\n }\n return parts.join(' ')\n}\n\nfunction asSearchView(view: unknown): SearchView | undefined {\n if (typeof view !== 'object' || view === null || Array.isArray(view)) return undefined\n const record = view as Record<string, unknown>\n return record.card === 'search' ? (record as unknown as SearchView) : undefined\n}\n\nfunction resultText(block: ToolResultLike): string {\n return (block.content ?? [])\n .filter((part) => part.type === 'text' && typeof part.text === 'string')\n .map((part) => part.text ?? '')\n .join('\\n')\n}\n\n/** One code tool's card: argument summary, structured search matches, or text. */\nfunction CodeToolCard(props: ToolViewProps): ReactNode {\n const { toolName, block, openFile } = props\n const head: ToolCallHead = block.call ?? block\n const summary = summarizeArgs(parseArgs(head.argsRaw))\n const search = asSearchView(block.resultView)\n const isError = block.isError === true\n\n return (\n <div style={CARD_STYLE}>\n <div style={ROW_STYLE}>\n <strong>{toolName}</strong>\n {summary ? <span style={{ opacity: 0.75 }}>{summary}</span> : null}\n {isError ? <span style={{ color: '#e5534b' }}>error</span> : null}\n </div>\n {search?.shape === 'matches' && search.files ? (\n <div style={{ marginTop: 4 }}>\n {search.files.map((file) => (\n <div key={file.path}>\n <div style={{ opacity: 0.85 }}>{file.path}</div>\n {(file.matches ?? []).map((match, index) => (\n <div\n key={`${match.lineNumber}-${index}`}\n style={{ paddingLeft: 12, cursor: openFile ? 'pointer' : 'default' }}\n onClick={() => openFile?.(file.path, { line: match.lineNumber })}\n >\n <span style={{ opacity: 0.6 }}>{match.lineNumber}</span> {match.line}\n </div>\n ))}\n </div>\n ))}\n {search.truncated ? <div style={{ opacity: 0.6 }}>… truncated ({search.total} total)</div> : null}\n </div>\n ) : (\n <pre style={{ margin: '4px 0 0', whiteSpace: 'pre-wrap', maxHeight: 220, overflow: 'auto' }}>\n {resultText(block)}\n </pre>\n )}\n </div>\n )\n}\n\n/** The scope is captured at apply() time; the card reads it through React state. */\nlet activeScope: SettingsScopeLike<CodeIndexSettings> | null = null\n\n/** Mirrors the first-party plugin card + field CSS modules, under our own class names. */\nconst CARD_CSS = `\n.dci-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}\n.dci-card:hover{border-color:var(--dsw-alias-label-dimmed)}\n.dci-cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}\n.dci-header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}\n.dci-header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}\n.dci-headText{flex-direction:column;flex:1 1 0%;gap:4px;min-width:0;display:flex}\n.dci-name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}\n.dci-description{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}\n.dci-chevron{color:var(--dsw-alias-label-tertiary);flex:0 0 auto;transition:transform .16s}\n.dci-chevronOpen{transform:rotate(180deg)}\n.dci-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding-bottom:8px}\n.dci-field{flex-direction:column;gap:6px;padding:12px 0;display:flex}\n.dci-field+.dci-field{border-top:1px solid var(--dsw-alias-border-l2)}\n.dci-head{align-items:center;gap:8px;display:flex}\n.dci-label{min-width:0;color:var(--dsw-alias-label-primary);flex:1 1 0%;font-size:13px;font-weight:500;line-height:1.5}\n.dci-input{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);height:34px;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font:inherit;font-size:13px;line-height:1.5}\n.dci-input:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}\n.dci-input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}\n.dci-check{width:16px;height:16px;flex:0 0 auto;accent-color:var(--dsw-alias-brand-primary)}\n.dci-hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}\n.dci-footer{border-top:1px solid var(--dsw-alias-border-l2);justify-content:flex-end;align-items:center;gap:8px;padding:12px 0 4px;display:flex}\n.dci-discard,.dci-save{appearance:none;font:inherit;cursor:pointer;border:1px solid transparent;border-radius:8px;padding:5px 14px;font-size:13px;line-height:1.5}\n.dci-discard{border-color:var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:0 0}\n.dci-discard:hover:not(:disabled){color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-dimmed)}\n.dci-save{background:var(--dsw-alias-label-primary);color:var(--dsw-alias-bg-layer-3)}\n.dci-discard:disabled,.dci-save:disabled{opacity:.4;cursor:default}\n`\n\nconst CHEVRON_PATH =\n 'M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z'\n\nconst SETTINGS_DEFAULTS = { autoInject: true, codeHealth: false, mapTopFiles: 24 }\nconst SETTINGS_FIELDS = ['autoInject', 'codeHealth', 'mapTopFiles'] as const\n\n/** Settings card for `code-index`, laid out like the first-party plugin cards. */\nfunction CodeIndexSettingsCard(): ReactNode {\n const scope = activeScope\n const [open, setOpen] = useState(false)\n const [snapshot, setSnapshot] = useState<SettingsSnapshot<CodeIndexSettings> | null>(\n scope ? scope.getSnapshot() : null,\n )\n const [draft, setDraft] = useState<CodeIndexSettings | null>(null)\n\n useEffect(() => {\n if (!scope) return\n setSnapshot(scope.getSnapshot())\n return scope.subscribe(() => setSnapshot(scope.getSnapshot()))\n }, [scope])\n\n const resolved = snapshot?.value ?? {}\n const form: CodeIndexSettings = { ...SETTINGS_DEFAULTS, ...resolved, ...draft }\n const writable = snapshot?.writable ?? false\n const changed = (key: (typeof SETTINGS_FIELDS)[number]): boolean =>\n form[key] !== (resolved[key] ?? SETTINGS_DEFAULTS[key])\n const dirty = draft !== null && SETTINGS_FIELDS.some(changed)\n\n const edit = (key: (typeof SETTINGS_FIELDS)[number], value: string | number | boolean): void => {\n setDraft({ ...form, [key]: value })\n }\n const save = (): void => {\n if (!scope) return\n for (const key of SETTINGS_FIELDS) {\n if (changed(key)) {\n scope.set(key, form[key]).catch((error: unknown) => {\n console.error('[dsh-code-index] settings save failed', error)\n })\n }\n }\n setDraft(null)\n }\n\n return (\n <li className={open ? 'dci-card dci-cardOpen' : 'dci-card'}>\n <style>{CARD_CSS}</style>\n <button\n type=\"button\"\n className=\"dci-header\"\n aria-expanded={open}\n aria-label={`${open ? '收起设置' : '展开设置'}: dsh-code-index`}\n onClick={() => { setOpen(!open) }}\n >\n <span className=\"dci-headText\">\n <span className=\"dci-name\">dsh-code-index</span>\n <span className=\"dci-description\">结构化仓库索引:符号搜索、仓库地图与调用图。</span>\n </span>\n <svg className={open ? 'dci-chevron dci-chevronOpen' : 'dci-chevron'} width=\"14\" height=\"14\" viewBox=\"0 0 14 14\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n <path d={CHEVRON_PATH} fill=\"currentColor\" />\n </svg>\n </button>\n {open ? (\n <div className=\"dci-body\">\n <div className=\"dci-field\">\n <div className=\"dci-head\">\n <label className=\"dci-label\" htmlFor=\"dci-auto-inject\">自动注入仓库地图</label>\n <input\n id=\"dci-auto-inject\"\n className=\"dci-check\"\n type=\"checkbox\"\n checked={form.autoInject}\n disabled={!writable}\n onChange={(event) => { edit('autoInject', event.target.checked) }}\n />\n </div>\n <p className=\"dci-hint\">把精简的排名仓库地图注入系统提示词。</p>\n </div>\n <div className=\"dci-field\">\n <div className=\"dci-head\">\n <label className=\"dci-label\" htmlFor=\"dci-code-health\">代码健康工具</label>\n <input\n id=\"dci-code-health\"\n className=\"dci-check\"\n type=\"checkbox\"\n checked={form.codeHealth}\n disabled={!writable}\n onChange={(event) => { edit('codeHealth', event.target.checked) }}\n />\n </div>\n <p className=\"dci-hint\">注册 code_health 工具,报告环依赖与孤儿模块(重启后生效)。</p>\n </div>\n <div className=\"dci-field\">\n <div className=\"dci-head\">\n <label className=\"dci-label\" htmlFor=\"dci-map-files\">地图文件数</label>\n </div>\n <input\n id=\"dci-map-files\"\n className=\"dci-input\"\n type=\"text\"\n inputMode=\"numeric\"\n value={String(form.mapTopFiles)}\n disabled={!writable}\n onChange={(event) => { edit('mapTopFiles', Number(event.target.value)) }}\n />\n <p className=\"dci-hint\">仓库地图中最多展示的文件数。</p>\n </div>\n <div className=\"dci-footer\">\n <button type=\"button\" className=\"dci-discard\" disabled={!dirty || !writable} onClick={() => { setDraft(null) }}>\n 放弃修改\n </button>\n <button type=\"button\" className=\"dci-save\" disabled={!dirty || !writable} onClick={save}>保存</button>\n </div>\n </div>\n ) : null}\n {scope ? null : <p className=\"dci-hint\" style={{ padding: '0 16px 12px' }}>设置服务不可用。</p>}\n </li>\n )\n}\n\nexport const name = 'dsh-code-index'\n\n/** `slots` is required for direct access; `settingsScope` binds lazily below. */\nexport const inject = ['slots']\n\nconst TOOLVIEW_KEYS = [\n 'code_index',\n 'code_symbols',\n 'code_search',\n 'code_map',\n 'code_refs',\n 'code_change_context',\n 'code_context',\n 'code_health',\n]\n\nexport function apply(ctx: ClientContextLike): void {\n const card = CodeToolCard as unknown as (props: never) => ReactNode\n ctx.slots.inject('tool.call.toolview', function* () {\n for (const key of TOOLVIEW_KEYS) {\n yield ctx.slots.register({ name: 'tool.call.toolview', key }, card)\n }\n })\n\n ctx.inject(['settingsScope'], (scoped: ClientContextLike) => {\n activeScope = scoped.settingsScope?.bind({ namespace: 'code-index' }) ?? null\n ctx.slots.inject('settings.plugin.item', function* () {\n yield ctx.slots.register(\n { name: 'settings.plugin.item', key: 'code-index' },\n CodeIndexSettingsCard as unknown as (props: never) => ReactNode,\n )\n })\n })\n}\n"],"mappings":"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","names":[]}