dsh-vscode-mode 0.23.4 → 0.23.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/lib/client.js +1 -0
- package/lib/client.js.map +2 -2
- package/lib/index.js +60 -16
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/state/diffSummary.ts +4 -0
- package/src/pending/decide.ts +9 -2
- package/src/pending/store.ts +13 -9
- package/src/pendingCompat.ts +7 -1
- package/src/pendingRpc.ts +5 -2
- package/src/reviewDb.ts +25 -0
- package/src/reviewRpc.ts +7 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-vscode-mode",
|
|
3
|
-
"version": "0.23.
|
|
3
|
+
"version": "0.23.5",
|
|
4
4
|
"description": "DSH 上的类 VSCode 编码体验:Monaco 编辑器(文件页签/QuickOpen/状态栏,可驻 DSH 0.1.5+ 官方右侧 Sidebar 与对话同屏)+ 命令栏(Ctrl+Shift+P)与可扩展指令系统 + VS Code 兼容代码片段(.code-snippets 全局/项目,IntelliSense 展开)+ Agent 编辑差异审查(整文件差异/采纳/拒绝/归档/回滚)+ SVN 集成(侧栏变更面板/日志弹窗/并排差异)+ 语言服务器(LSP)智能(跳转定义/引用/hover/大纲 + VSIX 扩展市场安装),状态持久化到工作区旁车,界面状态(页签/光标/侧栏/编辑 Tab/导航历史)镜像到 DSH home 跨重启恢复",
|
|
5
5
|
"icon": "assets/icon.svg",
|
|
6
6
|
"keywords": [
|
|
@@ -44,7 +44,11 @@ function pendingRowsOf(page: ReviewPage | null): { rows: DiffFile[]; covered: Se
|
|
|
44
44
|
for (const item of page?.items ?? []) {
|
|
45
45
|
if (!item.id.startsWith(PENDING_PREFIX)) continue
|
|
46
46
|
const path = item.currentPath || item.path
|
|
47
|
+
// 待审条目的 path 取自写入时的原始写法(agent 常用工作区相对路径),官方导航用的却是绝对身份:
|
|
48
|
+
// 只按 path 建覆盖集时,同一文件的官方汇总行不会被压掉,差异栏把 3 个文件算成 6 条。
|
|
49
|
+
// 身份(id 去掉待审前缀)与官方 identity 同源,必须一起入集。
|
|
47
50
|
covered.add(diffPathKey(path))
|
|
51
|
+
covered.add(diffPathKey(item.id.slice(PENDING_PREFIX.length)))
|
|
48
52
|
rows.push({
|
|
49
53
|
path,
|
|
50
54
|
label: item.display || path,
|
package/src/pending/decide.ts
CHANGED
|
@@ -206,8 +206,15 @@ export function capabilityOf(entry: PendingEntry, flags: PendingFlags): { keep:
|
|
|
206
206
|
* @author ddj 2026年10月29号
|
|
207
207
|
* @description: 供详情接口复用的差异构造。
|
|
208
208
|
* @param entry 待审条目
|
|
209
|
+
* @param baselineEmpty 基线置空:文件由本次会话新建,旧基线只是会话自己的中间产物
|
|
209
210
|
* @returns 官方差异
|
|
210
211
|
*/
|
|
211
|
-
export function diffOf(entry: PendingEntry): OfficialDiff {
|
|
212
|
-
return textDiff(entry.path, entry.path, entry.oldText, entry.newText)
|
|
212
|
+
export function diffOf(entry: PendingEntry, baselineEmpty = false): OfficialDiff {
|
|
213
|
+
if (!baselineEmpty) return textDiff(entry.path, entry.path, entry.oldText, entry.newText)
|
|
214
|
+
// 空基线在按行切分时会留下一个空行,差异里会多出一条「删空行」;新增文件应当只有新增行,
|
|
215
|
+
// 与官方整文件新增块(oldLines=0、全是 + 行)保持同一形状。
|
|
216
|
+
if (entry.newText === '') return textDiff(entry.path, entry.path, '', '')
|
|
217
|
+
const diff = textDiff(entry.path, entry.path, '', entry.newText)
|
|
218
|
+
if (diff.kind !== 'text') return diff
|
|
219
|
+
return { ...diff, hunks: diff.hunks.map((hunk) => ({ ...hunk, oldLines: 0, lines: hunk.lines.filter((line) => line !== '-') })) }
|
|
213
220
|
}
|
package/src/pending/store.ts
CHANGED
|
@@ -25,10 +25,11 @@ import {
|
|
|
25
25
|
* @author ddj 2026年10月29号
|
|
26
26
|
* @description: 统计一次差异的增删行数;非文本差异统一记 0/0。
|
|
27
27
|
* @param entry 待审条目
|
|
28
|
+
* @param baselineEmpty 基线置空(新建文件按全量新增计)
|
|
28
29
|
* @returns 增删行数
|
|
29
30
|
*/
|
|
30
|
-
function countsOf(entry: PendingEntry): { added: number; deleted: number } {
|
|
31
|
-
const diff = diffOf(entry)
|
|
31
|
+
function countsOf(entry: PendingEntry, baselineEmpty = false): { added: number; deleted: number } {
|
|
32
|
+
const diff = diffOf(entry, baselineEmpty)
|
|
32
33
|
if (diff.kind !== 'text') return { added: 0, deleted: 0 }
|
|
33
34
|
let added = 0
|
|
34
35
|
let deleted = 0
|
|
@@ -93,11 +94,12 @@ export function foldWrite(stored: StoredEntry | undefined, write: PendingWrite):
|
|
|
93
94
|
* @author ddj 2026年10月29号
|
|
94
95
|
* @description: 条目转列表投影,已解决条目仍保留但不可再决定。
|
|
95
96
|
* @param stored 条目与标记
|
|
97
|
+
* @param baselineEmpty 基线置空(新建文件按全量新增计)
|
|
96
98
|
* @returns 列表项
|
|
97
99
|
*/
|
|
98
|
-
export function itemOf(stored: StoredEntry): PendingItem {
|
|
100
|
+
export function itemOf(stored: StoredEntry, baselineEmpty = false): PendingItem {
|
|
99
101
|
const { entry, flags } = stored
|
|
100
|
-
const counts = countsOf(entry)
|
|
102
|
+
const counts = countsOf(entry, baselineEmpty)
|
|
101
103
|
const item: PendingItem = {
|
|
102
104
|
id: entry.id,
|
|
103
105
|
path: entry.path,
|
|
@@ -123,12 +125,13 @@ export function itemOf(stored: StoredEntry): PendingItem {
|
|
|
123
125
|
* @description: 读一页待审条目,按更新时间升序;会话过滤走血缘集合。
|
|
124
126
|
* @param cwd 工作区
|
|
125
127
|
* @param sessionId 当前会话
|
|
128
|
+
* @param wholeAdd 本次会话新建的文件身份集合,命中时基线置空
|
|
126
129
|
* @returns 列表
|
|
127
130
|
*/
|
|
128
|
-
export function loadPage(cwd: string, sessionId?: string): PendingPage {
|
|
131
|
+
export function loadPage(cwd: string, sessionId?: string, wholeAdd?: ReadonlySet<string>): PendingPage {
|
|
129
132
|
const all = loadEntries(cwd)
|
|
130
133
|
const visible = sessionId ? all.filter((stored) => stored.entry.sessionIds.includes(sessionId)) : all
|
|
131
|
-
const items = visible.map(itemOf)
|
|
134
|
+
const items = visible.map((stored) => itemOf(stored, wholeAdd?.has(stored.entry.id) === true))
|
|
132
135
|
const gaps: string[] = []
|
|
133
136
|
for (const stored of visible) {
|
|
134
137
|
if (stored.flags.oversized) gaps.push(`${stored.entry.path}: 文本超出上限,仅保留标记`)
|
|
@@ -141,14 +144,15 @@ export function loadPage(cwd: string, sessionId?: string): PendingPage {
|
|
|
141
144
|
* @description: 读单条详情,包含可交给现有面板渲染的差异与块决定。
|
|
142
145
|
* @param cwd 工作区
|
|
143
146
|
* @param id 条目身份
|
|
147
|
+
* @param baselineEmpty 基线置空(新建文件按全量新增渲染)
|
|
144
148
|
* @returns 详情,条目不存在时 null
|
|
145
149
|
*/
|
|
146
|
-
export function loadDetail(cwd: string, id: string): PendingDetail | null {
|
|
150
|
+
export function loadDetail(cwd: string, id: string, baselineEmpty = false): PendingDetail | null {
|
|
147
151
|
const stored = loadEntries(cwd).find((item) => item.entry.id === id)
|
|
148
152
|
if (!stored) return null
|
|
149
|
-
const diff = diffOf(stored.entry)
|
|
153
|
+
const diff = diffOf(stored.entry, baselineEmpty)
|
|
150
154
|
return {
|
|
151
|
-
item: itemOf(stored),
|
|
155
|
+
item: itemOf(stored, baselineEmpty),
|
|
152
156
|
diff,
|
|
153
157
|
blocks: blockStatesOf(stored.entry),
|
|
154
158
|
capability: capabilityOf(stored.entry, stored.flags),
|
package/src/pendingCompat.ts
CHANGED
|
@@ -171,7 +171,13 @@ export function pendingCovers(cwd: string, path: string): boolean {
|
|
|
171
171
|
* @returns 合并后的页
|
|
172
172
|
*/
|
|
173
173
|
export function mergedPage(official: ReviewPage, pending: PendingPage, cwd?: string): ReviewPage {
|
|
174
|
-
const covered = new Set(
|
|
174
|
+
const covered = new Set<string>()
|
|
175
|
+
for (const item of pending.items) {
|
|
176
|
+
// 待审条目的 path 是写入时的原始写法(常为工作区相对路径),官方条目用绝对身份:
|
|
177
|
+
// 只按 path 建覆盖集时同一文件会同时留下待审行与官方行,差异栏把一份改动算成两条。
|
|
178
|
+
covered.add(pathKey(item.path))
|
|
179
|
+
covered.add(pathKey(item.id))
|
|
180
|
+
}
|
|
175
181
|
const kept = official.items.filter((item) => !covered.has(pathKey(item.currentPath)) && !covered.has(pathKey(item.path)))
|
|
176
182
|
const items = [...pending.items.map((item) => pendingItemOf(item, cwd)), ...kept]
|
|
177
183
|
return {
|
package/src/pendingRpc.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { readText } from './pending/capture.js'
|
|
|
8
8
|
import { pendingRawId, trackPendingCover } from './pendingCompat.js'
|
|
9
9
|
import { loadEntries, saveEntry } from './pending/persist.js'
|
|
10
10
|
import { applyDecide, importFromVcs, itemOf, loadDetail, loadPage, refreshed, removeEntry, undoDecide } from './pending/store.js'
|
|
11
|
+
import { addedFiles } from './reviewDb.js'
|
|
11
12
|
import type { ShellLike } from './pending/vcs.js'
|
|
12
13
|
import type { Ctx } from './store.js'
|
|
13
14
|
import { policyOf } from './store.js'
|
|
@@ -82,14 +83,16 @@ function readHandlers(kit: ScopeKit): Partial<RpcHandlerMap> {
|
|
|
82
83
|
return {
|
|
83
84
|
'edrv.pending.list': async (args: PendingRequest) => {
|
|
84
85
|
const cwd = await kit.read(args.sessionId)
|
|
85
|
-
|
|
86
|
+
// 与官方入口同口径:本会话新建的文件基线置空,列表里的增删行数才等于整文件行数
|
|
87
|
+
const page = loadPage(cwd, args.sessionId, addedFiles(cwd, args.sessionId))
|
|
86
88
|
// 列表即登记接管关系;旧写入口守卫之后只读内存集合,不再产生数据库读
|
|
87
89
|
for (const item of page.items) trackPendingCover(cwd, item.path, true)
|
|
88
90
|
return { ok: true, page }
|
|
89
91
|
},
|
|
90
92
|
'edrv.pending.detail': async (args: PendingRequest & { id: string }) => {
|
|
91
93
|
const cwd = await kit.read(args.sessionId)
|
|
92
|
-
const
|
|
94
|
+
const rawId = pendingRawId(args.id)
|
|
95
|
+
const detail = loadDetail(cwd, rawId, addedFiles(cwd, args.sessionId).has(rawId))
|
|
93
96
|
return detail ? { ok: true, detail } : { ok: false, error: '待审条目不存在' }
|
|
94
97
|
},
|
|
95
98
|
}
|
package/src/reviewDb.ts
CHANGED
|
@@ -205,6 +205,31 @@ export function reviewPage(cwd: string, cursor = 0, history = false, path?: stri
|
|
|
205
205
|
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
/**
|
|
209
|
+
* @public
|
|
210
|
+
* @author ddj 2026年10月31号
|
|
211
|
+
* @description: 该会话里由官方记录判为「整文件新增」的文件身份集合。待审条目命中时基线要置空:
|
|
212
|
+
* 文件是本会话新建的,累计基线只是会话自己的中间产物,拿它做旧侧会把新增文件渲染成一堆修改块。
|
|
213
|
+
* @param cwd 迁移后的工作区
|
|
214
|
+
* @param sessionId 会话
|
|
215
|
+
* @returns 整文件新增的文件身份集合
|
|
216
|
+
*/
|
|
217
|
+
export function addedFiles(cwd: string, sessionId: string): Set<string> {
|
|
218
|
+
try {
|
|
219
|
+
const db = dbFor(cwd)
|
|
220
|
+
if (!db.prepare("SELECT 1 FROM sqlite_master WHERE name='review_files' AND type='table'").get()) return new Set()
|
|
221
|
+
// 先按会话收窄再取每个文件最早的那条官方记录:首个块是整文件新增(before=false)才说明文件
|
|
222
|
+
// 由本次会话创建,后续修改块不能反过来把新建成改写成「有旧基线的修改」。
|
|
223
|
+
const rows = db.prepare(`SELECT identity FROM (SELECT identity,
|
|
224
|
+
json_extract(diff,'$.before') AS before, ROW_NUMBER() OVER (PARTITION BY identity ORDER BY turn, seq) AS rank
|
|
225
|
+
FROM review_files WHERE session_id=?) WHERE rank=1 AND before=0`).all(sessionId) as Row[]
|
|
226
|
+
return new Set(rows.map((row) => String(row.identity)))
|
|
227
|
+
} catch {
|
|
228
|
+
// 判定失败只退回旧的累计基线口径,不影响列表与详情可用
|
|
229
|
+
return new Set()
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
208
233
|
/**
|
|
209
234
|
* @public
|
|
210
235
|
* @author ddj 2026年10月31号
|
package/src/reviewRpc.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { RpcHandlerMap } from './shared/rpc.js'
|
|
3
3
|
import type { ReviewDetail, ReviewItem, ReviewPage } from './shared/review.js'
|
|
4
4
|
import type { PendingPage } from './shared/pendingReview.js'
|
|
5
|
-
import { decideReview, reviewDetail, reviewPage } from './reviewDb.js'
|
|
5
|
+
import { addedFiles, decideReview, reviewDetail, reviewPage } from './reviewDb.js'
|
|
6
6
|
import { undoReview } from './reviewUndo.js'
|
|
7
7
|
import { applyDecide, loadDetail, loadPage } from './pending/store.js'
|
|
8
8
|
import { PENDING_PREFIX, mergedPage, pendingDetailOf, pendingItemOf, pendingSamePath } from './pendingCompat.js'
|
|
@@ -153,7 +153,9 @@ export function reviewHandlers(ctx: Ctx, registry: Registry): Partial<RpcHandler
|
|
|
153
153
|
*/
|
|
154
154
|
function pendingPageOf(cwd: string, sessionId: string): PendingPage {
|
|
155
155
|
try {
|
|
156
|
-
|
|
156
|
+
// 本会话新建的文件由官方首个块判为整文件新增,待审基线必须跟着置空:
|
|
157
|
+
// 否则同一次新建会同时存在「官方说全加 516 行」与「待审说改 8 处」两套相反口径。
|
|
158
|
+
const page = loadPage(cwd, sessionId, addedFiles(cwd, sessionId))
|
|
157
159
|
// 已结算(老文本与新文本一致)的投影没有净变化可审:留着会顶掉同路径的官方待审记录,
|
|
158
160
|
// 让差异栏选中一条 Keep 不可用的条目,表现为「按钮亮着却点不动」。
|
|
159
161
|
return page.items.some((item) => item.settled === true)
|
|
@@ -192,7 +194,9 @@ export function reviewHandlers(ctx: Ctx, registry: Registry): Partial<RpcHandler
|
|
|
192
194
|
const scope = await scopeOf(args.sessionId)
|
|
193
195
|
const cwd = scope.cwd
|
|
194
196
|
if (args.id.startsWith(PENDING_PREFIX)) {
|
|
195
|
-
const
|
|
197
|
+
const entryId = args.id.slice(PENDING_PREFIX.length)
|
|
198
|
+
// 详情侧与列表侧必须同一口径:整文件新增的文件用空基线渲染,差异块才是全绿一整块
|
|
199
|
+
const detail = loadDetail(cwd, entryId, addedFiles(cwd, args.sessionId).has(entryId))
|
|
196
200
|
if (!detail) return { ok: false, error: '待审条目不存在' }
|
|
197
201
|
|
|
198
202
|
const mapped = pendingDetailOf(detail, new Date(detail.item.updatedAt).toISOString(), cwd)
|