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