dsh-vscode-mode 0.21.3 → 0.22.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/README.md +51 -3
- package/lib/client.js +254 -174
- package/lib/client.js.map +2 -2
- package/lib/index.js +2419 -32
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/addToConversation.ts +79 -32
- package/src/client/sidebar/panels/SvnPanel.ts +38 -30
- package/src/client/state/diffSummary.ts +43 -4
- package/src/client/ui/DiffLauncher.ts +1 -1
- package/src/client/ui/EditorView.ts +10 -2
- package/src/client/ui/OfficialDiffBar.tsx +1 -12
- package/src/client/ui/OfficialReview.ts +13 -3
- package/src/client/ui/SidecarDiffBar.tsx +0 -22
- package/src/index.ts +9 -0
- package/src/missingReview.ts +36 -14
- package/src/pending/capture.ts +136 -0
- package/src/pending/decide.ts +213 -0
- package/src/pending/hunks.ts +241 -0
- package/src/pending/kernel.ts +233 -0
- package/src/pending/persist.ts +324 -0
- package/src/pending/store.ts +467 -0
- package/src/pending/vcs.ts +444 -0
- package/src/pendingCompat.ts +189 -0
- package/src/pendingRpc.ts +128 -0
- package/src/reviewDb.ts +3 -0
- package/src/reviewRpc.ts +52 -1
- package/src/rpc.ts +29 -2
- package/src/shared/pendingReview.ts +105 -0
- package/src/shared/rpc.ts +18 -0
- package/src/store.ts +19 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 待审投影的持久化:与官方历史共用工作区库,但只新增 pending_* 表,绝不改写 review_* 事实。
|
|
3
|
+
* @author ddj 2026年10月29号
|
|
4
|
+
*/
|
|
5
|
+
import type { DatabaseSync } from 'node:sqlite'
|
|
6
|
+
import { activeReady, dbFor } from '../archiveDb.js'
|
|
7
|
+
import type { PendingLineage } from '../shared/pendingReview.js'
|
|
8
|
+
import type { PendingEntry } from './kernel.js'
|
|
9
|
+
|
|
10
|
+
/** 数据库行。 */
|
|
11
|
+
type Row = Record<string, string | number | null>
|
|
12
|
+
|
|
13
|
+
/** 条目上除两侧全文之外的持久标记。 */
|
|
14
|
+
export interface PendingFlags {
|
|
15
|
+
missing: boolean
|
|
16
|
+
diverged: boolean
|
|
17
|
+
oversized: boolean
|
|
18
|
+
revision: number
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** 一条已落库的条目。 */
|
|
22
|
+
export interface StoredEntry {
|
|
23
|
+
entry: PendingEntry
|
|
24
|
+
flags: PendingFlags
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** 待审库版本号,写进 metadata 便于日后迁移。v3 给评论表加了引文与上下文列。 */
|
|
28
|
+
const SCHEMA_VERSION = '3'
|
|
29
|
+
/** 迁移前的备份后缀。 */
|
|
30
|
+
const BACKUP_SUFFIX = '.before-pending.bak'
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @author ddj 2026年10月29号
|
|
34
|
+
* @description: 打开工作区库并确保待审表存在;DDL 与版本标记在同一事务里提交。
|
|
35
|
+
* @param cwd 已迁移工作区
|
|
36
|
+
* @returns 可读写的连接
|
|
37
|
+
* @throws 未完成活跃库迁移时拒绝
|
|
38
|
+
*/
|
|
39
|
+
export function pendingDb(cwd: string): DatabaseSync {
|
|
40
|
+
if (!activeReady(cwd)) throw new Error('待审投影要求先完成活跃库迁移')
|
|
41
|
+
const db = dbFor(cwd)
|
|
42
|
+
const stamp = db.prepare("SELECT value FROM metadata WHERE name='pending_schema'").get() as Row | undefined
|
|
43
|
+
if (stamp?.value === SCHEMA_VERSION) return db
|
|
44
|
+
db.exec('BEGIN IMMEDIATE')
|
|
45
|
+
try {
|
|
46
|
+
db.exec(DDL)
|
|
47
|
+
migrateHistory(db)
|
|
48
|
+
db.prepare("INSERT INTO metadata(name,value) VALUES('pending_schema',?) ON CONFLICT(name) DO UPDATE SET value=excluded.value")
|
|
49
|
+
.run(SCHEMA_VERSION)
|
|
50
|
+
db.exec('COMMIT')
|
|
51
|
+
} catch (error) {
|
|
52
|
+
if (db.isTransaction) db.exec('ROLLBACK')
|
|
53
|
+
throw error
|
|
54
|
+
}
|
|
55
|
+
return db
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** 待审表结构:条目、块决定、幂等操作、撤销栈。 */
|
|
59
|
+
const DDL = `
|
|
60
|
+
CREATE TABLE IF NOT EXISTS pending_entries (
|
|
61
|
+
id TEXT PRIMARY KEY, path TEXT NOT NULL, earlier TEXT NOT NULL,
|
|
62
|
+
old_text TEXT NOT NULL, new_text TEXT NOT NULL, updated_at INTEGER NOT NULL,
|
|
63
|
+
unseen INTEGER NOT NULL DEFAULT 0, missing INTEGER NOT NULL DEFAULT 0,
|
|
64
|
+
diverged INTEGER NOT NULL DEFAULT 0, oversized INTEGER NOT NULL DEFAULT 0,
|
|
65
|
+
revision INTEGER NOT NULL DEFAULT 0, session_id TEXT NOT NULL,
|
|
66
|
+
session_ids TEXT NOT NULL, lineage TEXT);
|
|
67
|
+
CREATE TABLE IF NOT EXISTS pending_blocks (
|
|
68
|
+
entry_id TEXT NOT NULL, block_id TEXT NOT NULL, decision TEXT NOT NULL,
|
|
69
|
+
PRIMARY KEY(entry_id,block_id));
|
|
70
|
+
CREATE TABLE IF NOT EXISTS pending_ops (
|
|
71
|
+
operation_id TEXT PRIMARY KEY, request TEXT NOT NULL, state TEXT NOT NULL,
|
|
72
|
+
result TEXT, undo TEXT);
|
|
73
|
+
CREATE TABLE IF NOT EXISTS pending_history (
|
|
74
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT, kind TEXT NOT NULL, entry_id TEXT,
|
|
75
|
+
snapshot TEXT NOT NULL, created_at INTEGER NOT NULL,
|
|
76
|
+
state TEXT NOT NULL DEFAULT 'applied');
|
|
77
|
+
`
|
|
78
|
+
|
|
79
|
+
/** v1 历史表补 state 列(rebuild 成本高于一次 ALTER)。 */
|
|
80
|
+
function migrateHistory(db: DatabaseSync): void {
|
|
81
|
+
const columns = db.prepare('PRAGMA table_info(pending_history)').all() as Row[]
|
|
82
|
+
if (columns.some((column) => column.name === 'state')) return
|
|
83
|
+
db.exec("ALTER TABLE pending_history ADD COLUMN state TEXT NOT NULL DEFAULT 'applied'")
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @author ddj 2026年10月29号
|
|
88
|
+
* @description: 读出全部待审条目,按更新时间升序。
|
|
89
|
+
* @param cwd 工作区
|
|
90
|
+
* @returns 条目与标记
|
|
91
|
+
*/
|
|
92
|
+
export function loadEntries(cwd: string): StoredEntry[] {
|
|
93
|
+
const rows = pendingDb(cwd).prepare('SELECT * FROM pending_entries ORDER BY updated_at ASC').all() as Row[]
|
|
94
|
+
return rows.map((row) => ({ entry: entryOfRow(row), flags: flagsOfRow(row) }))
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @author ddj 2026年10月29号
|
|
99
|
+
* @description: 写入或更新一条条目(含标记),单条原子完成。
|
|
100
|
+
* @param cwd 工作区
|
|
101
|
+
* @param stored 条目与标记
|
|
102
|
+
* @returns 无
|
|
103
|
+
*/
|
|
104
|
+
export function saveEntry(cwd: string, stored: StoredEntry): void {
|
|
105
|
+
const { entry, flags } = stored
|
|
106
|
+
pendingDb(cwd).prepare(`INSERT INTO pending_entries
|
|
107
|
+
(id,path,earlier,old_text,new_text,updated_at,unseen,missing,diverged,oversized,revision,session_id,session_ids,lineage)
|
|
108
|
+
VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
|
109
|
+
ON CONFLICT(id) DO UPDATE SET path=excluded.path,earlier=excluded.earlier,old_text=excluded.old_text,
|
|
110
|
+
new_text=excluded.new_text,updated_at=excluded.updated_at,unseen=excluded.unseen,missing=excluded.missing,
|
|
111
|
+
diverged=excluded.diverged,oversized=excluded.oversized,revision=excluded.revision,
|
|
112
|
+
session_id=excluded.session_id,session_ids=excluded.session_ids,lineage=excluded.lineage`)
|
|
113
|
+
.run(entry.id, entry.path, entry.earlierVersion, entry.oldText, entry.newText, entry.updatedAt,
|
|
114
|
+
entry.unseen === true ? 1 : 0, flags.missing ? 1 : 0, flags.diverged ? 1 : 0, flags.oversized ? 1 : 0,
|
|
115
|
+
flags.revision, entry.sessionId, JSON.stringify(entry.sessionIds), entry.lineage ? JSON.stringify(entry.lineage) : null)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* @author ddj 2026年10月29号
|
|
120
|
+
* @description: 删除条目及其块决定,用于"已解决条目移除"。
|
|
121
|
+
* @param cwd 工作区
|
|
122
|
+
* @param id 条目身份
|
|
123
|
+
* @returns 无
|
|
124
|
+
*/
|
|
125
|
+
export function dropEntry(cwd: string, id: string): void {
|
|
126
|
+
const db = pendingDb(cwd)
|
|
127
|
+
db.exec('BEGIN IMMEDIATE')
|
|
128
|
+
try {
|
|
129
|
+
db.prepare('DELETE FROM pending_entries WHERE id=?').run(id)
|
|
130
|
+
db.prepare('DELETE FROM pending_blocks WHERE entry_id=?').run(id)
|
|
131
|
+
db.exec('COMMIT')
|
|
132
|
+
} catch (error) {
|
|
133
|
+
if (db.isTransaction) db.exec('ROLLBACK')
|
|
134
|
+
throw error
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* @author ddj 2026年10月29号
|
|
140
|
+
* @description: 读出某条目的块决定。
|
|
141
|
+
* @param cwd 工作区
|
|
142
|
+
* @param entryId 条目身份
|
|
143
|
+
* @returns 块决定列表
|
|
144
|
+
*/
|
|
145
|
+
export function loadBlocks(cwd: string, entryId: string): { id: string; decision: string }[] {
|
|
146
|
+
const rows = pendingDb(cwd).prepare('SELECT block_id AS id,decision FROM pending_blocks WHERE entry_id=? ORDER BY rowid')
|
|
147
|
+
.all(entryId) as Row[]
|
|
148
|
+
return rows.map((row) => ({ id: String(row.id), decision: String(row.decision) }))
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* @author ddj 2026年10月29号
|
|
153
|
+
* @description: 覆盖某条目的块决定集合。
|
|
154
|
+
* @param cwd 工作区
|
|
155
|
+
* @param entryId 条目身份
|
|
156
|
+
* @param blocks 块决定
|
|
157
|
+
* @returns 无
|
|
158
|
+
*/
|
|
159
|
+
export function saveBlocks(cwd: string, entryId: string, blocks: { id: string; decision: string }[]): void {
|
|
160
|
+
const db = pendingDb(cwd)
|
|
161
|
+
db.exec('BEGIN IMMEDIATE')
|
|
162
|
+
try {
|
|
163
|
+
db.prepare('DELETE FROM pending_blocks WHERE entry_id=?').run(entryId)
|
|
164
|
+
const insert = db.prepare('INSERT INTO pending_blocks VALUES(?,?,?)')
|
|
165
|
+
for (const block of blocks) insert.run(entryId, block.id, block.decision)
|
|
166
|
+
db.exec('COMMIT')
|
|
167
|
+
} catch (error) {
|
|
168
|
+
if (db.isTransaction) db.exec('ROLLBACK')
|
|
169
|
+
throw error
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* @author ddj 2026年10月29号
|
|
176
|
+
* @description: 读取幂等操作记录;同 operationId 的重复请求必须复用结果。
|
|
177
|
+
* @param cwd 工作区
|
|
178
|
+
* @param operationId 操作身份
|
|
179
|
+
* @returns 请求原文、状态与结果
|
|
180
|
+
*/
|
|
181
|
+
export function readOp(cwd: string, operationId: string): { request: string; state: string; result: string | null } | undefined {
|
|
182
|
+
const row = pendingDb(cwd).prepare('SELECT request,state,result FROM pending_ops WHERE operation_id=?').get(operationId) as Row | undefined
|
|
183
|
+
return row ? { request: String(row.request), state: String(row.state), result: row.result === null ? null : String(row.result) } : undefined
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* @author ddj 2026年10月29号
|
|
188
|
+
* @description: 记录一次操作的阶段与撤销快照。
|
|
189
|
+
* @param cwd 工作区
|
|
190
|
+
* @param operationId 操作身份
|
|
191
|
+
* @param request 请求原文
|
|
192
|
+
* @param state prepared / committed
|
|
193
|
+
* @param payload 结果与撤销快照
|
|
194
|
+
* @returns 无
|
|
195
|
+
*/
|
|
196
|
+
export function writeOp(
|
|
197
|
+
cwd: string,
|
|
198
|
+
operationId: string,
|
|
199
|
+
request: string,
|
|
200
|
+
state: 'prepared' | 'committed',
|
|
201
|
+
payload: { result?: string; undo?: string } = {},
|
|
202
|
+
): void {
|
|
203
|
+
pendingDb(cwd).prepare(`INSERT INTO pending_ops VALUES(?,?,?,?,?)
|
|
204
|
+
ON CONFLICT(operation_id) DO UPDATE SET state=excluded.state,
|
|
205
|
+
result=COALESCE(excluded.result,pending_ops.result), undo=COALESCE(excluded.undo,pending_ops.undo)`)
|
|
206
|
+
.run(operationId, request, state, payload.result ?? null, payload.undo ?? null)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* @author ddj 2026年10月29号
|
|
211
|
+
* @description: 追加一条撤销栈记录,并截断重做分支(新决定之后旧的"已撤销"记录不能再重做)。
|
|
212
|
+
* @param cwd 工作区
|
|
213
|
+
* @param kind 操作种类
|
|
214
|
+
* @param entryId 涉及的条目
|
|
215
|
+
* @param snapshot 可重放快照
|
|
216
|
+
* @param now 时间
|
|
217
|
+
* @returns 无
|
|
218
|
+
*/
|
|
219
|
+
export function pushHistory(cwd: string, kind: string, entryId: string, snapshot: string, now: number): void {
|
|
220
|
+
const db = pendingDb(cwd)
|
|
221
|
+
db.prepare("DELETE FROM pending_history WHERE state='undone'").run()
|
|
222
|
+
db.prepare("INSERT INTO pending_history(kind,entry_id,snapshot,created_at,state) VALUES(?,?,?,?,'applied')")
|
|
223
|
+
.run(kind, entryId, snapshot, now)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* @author ddj 2026年10月30号
|
|
228
|
+
* @description: 取最近一条仍生效的历史记录,撤销就作用在它上面。
|
|
229
|
+
* @param cwd 工作区
|
|
230
|
+
* @returns 记录或 undefined
|
|
231
|
+
*/
|
|
232
|
+
export function latestApplied(cwd: string): HistoryRow | undefined {
|
|
233
|
+
const row = pendingDb(cwd).prepare(
|
|
234
|
+
"SELECT id,kind,entry_id,snapshot FROM pending_history WHERE state='applied' ORDER BY id DESC LIMIT 1",
|
|
235
|
+
).get() as Row | undefined
|
|
236
|
+
return row ? rowOfHistory(row) : undefined
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* @author ddj 2026年10月30号
|
|
241
|
+
* @description: 取最早一条被撤销的记录,重做按"撤销的逆序"重新生效。
|
|
242
|
+
* @param cwd 工作区
|
|
243
|
+
* @returns 记录或 undefined
|
|
244
|
+
*/
|
|
245
|
+
export function earliestUndone(cwd: string): HistoryRow | undefined {
|
|
246
|
+
const row = pendingDb(cwd).prepare(
|
|
247
|
+
"SELECT id,kind,entry_id,snapshot FROM pending_history WHERE state='undone' ORDER BY id ASC LIMIT 1",
|
|
248
|
+
).get() as Row | undefined
|
|
249
|
+
return row ? rowOfHistory(row) : undefined
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* @author ddj 2026年10月30号
|
|
254
|
+
* @description: 翻转一条历史记录的生效状态。
|
|
255
|
+
* @param cwd 工作区
|
|
256
|
+
* @param id 记录主键
|
|
257
|
+
* @param state applied / undone
|
|
258
|
+
* @returns 无
|
|
259
|
+
*/
|
|
260
|
+
export function setHistoryState(cwd: string, id: number, state: 'applied' | 'undone'): void {
|
|
261
|
+
pendingDb(cwd).prepare('UPDATE pending_history SET state=? WHERE id=?').run(state, id)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* @author ddj 2026年10月30号
|
|
266
|
+
* @description: 把数据库行还原成历史记录。
|
|
267
|
+
* @param row 数据库行
|
|
268
|
+
* @returns 记录
|
|
269
|
+
*/
|
|
270
|
+
function rowOfHistory(row: Row): HistoryRow {
|
|
271
|
+
return { id: Number(row.id), kind: String(row.kind), entryId: String(row.entry_id), snapshot: String(row.snapshot) }
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** 历史记录行形态(快照为 JSON 文本)。 */
|
|
275
|
+
export interface HistoryRow {
|
|
276
|
+
id: number
|
|
277
|
+
kind: string
|
|
278
|
+
entryId: string
|
|
279
|
+
snapshot: string
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* @author ddj 2026年10月29号
|
|
284
|
+
* @description: 迁移前备份库文件路径,便于回退只读旧视图。
|
|
285
|
+
* @param dbFile 工作区库文件
|
|
286
|
+
* @returns 备份路径
|
|
287
|
+
*/
|
|
288
|
+
export function backupPath(dbFile: string): string {
|
|
289
|
+
return dbFile + BACKUP_SUFFIX
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* @author ddj 2026年10月29号
|
|
294
|
+
* @description: 由数据库行还原条目。
|
|
295
|
+
* @param row 数据库行
|
|
296
|
+
* @returns 条目
|
|
297
|
+
*/
|
|
298
|
+
function entryOfRow(row: Row): PendingEntry {
|
|
299
|
+
const lineage = row.lineage === null ? undefined : JSON.parse(String(row.lineage)) as PendingLineage
|
|
300
|
+
const entry: PendingEntry = {
|
|
301
|
+
id: String(row.id), path: String(row.path),
|
|
302
|
+
earlierVersion: String(row.earlier) === 'none' ? 'none' : 'file',
|
|
303
|
+
oldText: String(row.old_text), newText: String(row.new_text), updatedAt: Number(row.updated_at),
|
|
304
|
+
sessionId: String(row.session_id), sessionIds: JSON.parse(String(row.session_ids)) as string[],
|
|
305
|
+
}
|
|
306
|
+
if (Number(row.unseen) === 1) entry.unseen = true
|
|
307
|
+
if (lineage !== undefined) entry.lineage = lineage
|
|
308
|
+
return entry
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* @author ddj 2026年10月29号
|
|
313
|
+
* @description: 由数据库行还原标记。
|
|
314
|
+
* @param row 数据库行
|
|
315
|
+
* @returns 标记
|
|
316
|
+
*/
|
|
317
|
+
function flagsOfRow(row: Row): PendingFlags {
|
|
318
|
+
return {
|
|
319
|
+
missing: Number(row.missing) === 1,
|
|
320
|
+
diverged: Number(row.diverged) === 1,
|
|
321
|
+
oversized: Number(row.oversized) === 1,
|
|
322
|
+
revision: Number(row.revision),
|
|
323
|
+
}
|
|
324
|
+
}
|