dsh-diff-approval 0.24.0 → 0.25.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/lib/index.js CHANGED
@@ -3021,6 +3021,14 @@ function apply(ctx, config) {
3021
3021
  }
3022
3022
  };
3023
3023
  const isDirectory = info.type === "directory";
3024
+ if (target.exact && isDirectory) return {
3025
+ ok: true,
3026
+ value: {
3027
+ outcome: "not-a-file",
3028
+ added: 0,
3029
+ duplicates: 0
3030
+ }
3031
+ };
3024
3032
  const root = detectVcsRoot(workspace.path);
3025
3033
  if (root === void 0) return {
3026
3034
  ok: true,
@@ -3098,17 +3106,15 @@ function apply(ctx, config) {
3098
3106
  const duplicates = candidates.length - fresh.length;
3099
3107
  const added = await foldBatch(target.sessionId, fresh, true);
3100
3108
  const outcome = added > 0 ? "added" : duplicates > 0 ? "duplicate" : isDirectory ? "empty" : "unchanged";
3109
+ const single = isDirectory ? void 0 : fresh[0] ?? store.list(target.sessionId).find((entry) => pathIdentity(entry.path) === pathIdentity(absolute));
3101
3110
  return {
3102
3111
  ok: true,
3103
- value: truncated ? {
3112
+ value: {
3104
3113
  outcome,
3105
3114
  added,
3106
3115
  duplicates,
3107
- truncated
3108
- } : {
3109
- outcome,
3110
- added,
3111
- duplicates
3116
+ ...single === void 0 ? {} : { id: single.id },
3117
+ ...truncated ? { truncated: true } : {}
3112
3118
  }
3113
3119
  };
3114
3120
  }
@@ -3282,10 +3288,12 @@ function addTargetOf(payload) {
3282
3288
  if (sessionId === void 0) return void 0;
3283
3289
  const path = pathFieldOf(payload)?.trim();
3284
3290
  if (path === void 0 || path === "") return void 0;
3291
+ const record = payload;
3285
3292
  return {
3286
3293
  sessionId,
3287
3294
  path,
3288
- includeUnchanged: payload.includeUnchanged === true
3295
+ includeUnchanged: record.includeUnchanged === true,
3296
+ exact: record.exact === true
3289
3297
  };
3290
3298
  }
3291
3299
  /** Narrow a wire payload to one open target: the keep/revert pair plus the action. */
@@ -37,6 +37,14 @@ export interface DiscussionQuoteLine {
37
37
  old: number | undefined;
38
38
  /** New-file number — the second gutter — `undefined` on a removed line. */
39
39
  new: number | undefined;
40
+ /**
41
+ * Which side of the change the row was on when the thread quoted it: an added line, a removed
42
+ * one, or context. Kept so the quote can wear the diff's own green/red washes — the reader
43
+ * recognised those rows by their colour, and a quote that renders them as plain code loses the
44
+ * thing they were looking at. Absent on a thread quoted before this was recorded, which then
45
+ * simply draws no wash.
46
+ */
47
+ kind?: 'add' | 'del' | 'context';
40
48
  }
41
49
  /** One discussion bound to a row range. */
42
50
  export interface Discussion {
@@ -105,7 +113,9 @@ export interface Discussion {
105
113
  /**
106
114
  * How many rows the block's body occupies, measured by the caller from what it
107
115
  * actually renders (the thread plus the compose row). It is what keeps a
108
- * discussion block's height an exact multiple of the code row.
116
+ * discussion block's height an exact multiple of the thread's own row — which is fixed, not
117
+ * the code's (see `THREAD_ROW_PX`): a thread is prose and keeps its size whatever the reader
118
+ * sets the code's line height to.
109
119
  */
110
120
  bodyRows?: number;
111
121
  /** How many older messages the row cap left out of the render. */
@@ -113,32 +123,58 @@ export interface Discussion {
113
123
  }
114
124
  /** Rows a folded block occupies: its header line. */
115
125
  export declare const DISCUSSION_HEADER_ROWS = 1;
116
- /** Rows the compose area occupies: 0.3 + 1.4 + 0.3 of a code row. */
126
+ /** Rows the compose area occupies: 0.3 + 1.4 + 0.3 of the thread's own row. */
117
127
  export declare const DISCUSSION_COMPOSE_ROWS = 2;
118
128
  /**
119
- * The body budget for the turns a block shows: how many rows of older turns it is worth
120
- * carrying before the diff it annotates is swamped. It is spent in WHOLE turns — see
121
- * `discussionTail` — so a single turn that needs more than this is shown in full rather
122
- * than sliced in half (which is what a hard cap on the body's row count did).
123
- */
124
- export declare const DISCUSSION_MAX_BODY_ROWS = 12;
125
- /**
126
- * The newest turns that fit a size budget, and how many older ones were left out.
127
- * A thread grows without bound, so the block shows its tail and says what it is
128
- * hiding rather than clipping silently; the hidden note costs one row of the
129
- * budget itself. Sizes are in whatever unit the caller budgets in (the panel uses
130
- * code rows, fractions included, and rounds the total up once).
129
+ * The newest rounds of a thread, and how many older turns were left out.
130
+ *
131
+ * A thread grows without bound, so the block shows its tail and says what it is hiding. The unit
132
+ * is the round rather than a row budget: a block keeps the last `rounds` questions and
133
+ * everything said after them, however long those turns are (the count is a preference). That is possible
134
+ * because the block reserves exactly what it draws (see `discussionRows`), so a long answer is
135
+ * shown in full instead of being sliced — the row budget this replaces existed to stop an old
136
+ * thread from swamping the diff, and a round cap does that without ever cutting a turn in half.
131
137
  *
132
138
  * @param messages - the whole thread, oldest first.
139
+ * @param rounds - how many rounds to keep (at least one: something has to be visible).
133
140
  * @param sizeOf - the size one message needs when rendered (text plus its chrome).
134
- * @param capRows - the body budget.
135
141
  * @returns the messages to render, the count left out, and the size they need.
136
142
  */
137
- export declare function discussionTail(messages: readonly DiscussionMessage[], sizeOf: (message: DiscussionMessage) => number, capRows: number): {
143
+ export declare function discussionRounds(messages: readonly DiscussionMessage[], rounds: number, sizeOf: (message: DiscussionMessage) => number): {
138
144
  messages: readonly DiscussionMessage[];
139
145
  hidden: number;
140
146
  rows: number;
141
147
  };
148
+ /** One run of a turn's prose, after its inline Markdown is read. */
149
+ export interface DiscussionRun {
150
+ /** A literal run, inline `code`, or **strong** emphasis. */
151
+ kind: 'text' | 'code' | 'strong';
152
+ /** What the run draws: for `code` and `strong`, the text with its markers gone. */
153
+ text: string;
154
+ }
155
+ /**
156
+ * Read one turn's inline Markdown: `code` and **strong**.
157
+ *
158
+ * Deliberately tiny. The thread is laid out on the diff's own row grid — one line box per code
159
+ * row — so nothing here may become a block: no headings, lists, quotes or fenced code. A marker
160
+ * with no partner, or one spanning a line break, is left exactly as typed, and so is everything
161
+ * this does not know about.
162
+ *
163
+ * @param text - the turn's text.
164
+ * @returns its runs, in order.
165
+ */
166
+ export declare function discussionRuns(text: string): DiscussionRun[];
167
+ /**
168
+ * A turn's text with its inline markers gone: what the runs draw, character for character.
169
+ *
170
+ * The row measurement uses this, so the rows a block reserves and the text it draws agree. The
171
+ * markers are not drawn, and counting them would reserve room for characters nobody sees — which
172
+ * is exactly the kind of whole-row slack that leaves a blank row in the block.
173
+ *
174
+ * @param text - the turn's text.
175
+ * @returns the same text without its `code` and **strong** markers.
176
+ */
177
+ export declare function discussionPlainText(text: string): string;
142
178
  /**
143
179
  * An answer with its blank lines dropped.
144
180
  *
@@ -50,10 +50,14 @@ export declare const zh: {
50
50
  'panel.quickSummon': string;
51
51
  'panel.commentMode': string;
52
52
  'panel.commentModeDesc': string;
53
+ 'panel.discussionRounds': string;
54
+ 'panel.discussionRoundsDesc': string;
53
55
  'panel.quickSummonDesc': string;
54
56
  'panel.recordShortcut': string;
55
57
  'settings.keybindings': string;
56
58
  'settings.keybindingsDesc': string;
59
+ 'settings.comment': string;
60
+ 'settings.commentDesc': string;
57
61
  'settings.diffView': string;
58
62
  'settings.diffViewDesc': string;
59
63
  'settings.diffPreview': string;
@@ -89,7 +93,6 @@ export declare const zh: {
89
93
  'action.discussionExpand': string;
90
94
  'action.discussionCollapse': string;
91
95
  'discussion.placeholder': string;
92
- 'discussion.placeholderOutdated': string;
93
96
  'discussion.marker': string;
94
97
  'discussion.promptRule': string;
95
98
  'discussion.promptRuleSkill': string;
@@ -106,6 +109,7 @@ export declare const zh: {
106
109
  'action.keepAll': string;
107
110
  'action.revertAll': string;
108
111
  'action.openFile': string;
112
+ 'action.pathField': string;
109
113
  'action.revealFile': string;
110
114
  'action.busy': string;
111
115
  'action.prevDiff': string;
@@ -265,10 +269,14 @@ export declare const en: {
265
269
  'panel.quickSummon': string;
266
270
  'panel.commentMode': string;
267
271
  'panel.commentModeDesc': string;
272
+ 'panel.discussionRounds': string;
273
+ 'panel.discussionRoundsDesc': string;
268
274
  'panel.quickSummonDesc': string;
269
275
  'panel.recordShortcut': string;
270
276
  'settings.keybindings': string;
271
277
  'settings.keybindingsDesc': string;
278
+ 'settings.comment': string;
279
+ 'settings.commentDesc': string;
272
280
  'settings.diffView': string;
273
281
  'settings.diffViewDesc': string;
274
282
  'settings.diffPreview': string;
@@ -304,7 +312,6 @@ export declare const en: {
304
312
  'action.discussionExpand': string;
305
313
  'action.discussionCollapse': string;
306
314
  'discussion.placeholder': string;
307
- 'discussion.placeholderOutdated': string;
308
315
  'discussion.marker': string;
309
316
  'discussion.promptRule': string;
310
317
  'discussion.promptRuleSkill': string;
@@ -321,6 +328,7 @@ export declare const en: {
321
328
  'action.keepAll': string;
322
329
  'action.revertAll': string;
323
330
  'action.openFile': string;
331
+ 'action.pathField': string;
324
332
  'action.revealFile': string;
325
333
  'action.busy': string;
326
334
  'action.prevDiff': string;
@@ -30,8 +30,11 @@ export interface DiffApprovalPort {
30
30
  refreshVcs(sessionId: SessionId, id: string, includeUntracked: boolean): Promise<DiffApprovalRefreshValue>;
31
31
  /** List one workspace directory level (workspace-relative; `''` is the root). */
32
32
  browse(sessionId: SessionId, path?: string): Promise<DiffApprovalBrowseValue>;
33
- /** Add one named path (a file, or a directory's whole subtree) to the list. */
34
- addPath(sessionId: SessionId, path: string, includeUnchanged: boolean): Promise<DiffApprovalAddValue>;
33
+ /**
34
+ * Add one named path (a file, or a directory's whole subtree) to the list. With `exact`, the
35
+ * path must be one regular file: a directory is refused instead of being scanned.
36
+ */
37
+ addPath(sessionId: SessionId, path: string, includeUnchanged: boolean, exact?: boolean): Promise<DiffApprovalAddValue>;
35
38
  /** Open one file with its default application or reveal it in the folder. */
36
39
  open(sessionId: SessionId, id: string, action: DiffApprovalOpenAction): Promise<DiffApprovalOpenValue>;
37
40
  /** Keep every pending entry of one session in a single host call (one batch). */
@@ -20,6 +20,12 @@ export interface DiffApprovalCover {
20
20
  * composer. */
21
21
  export declare const FLOAT_COVER_DEFAULT: DiffApprovalCover;
22
22
  /** Default lead rows above a jumped-to diff block (kept small and bounded). */
23
+ /** Rounds of a comment thread a block keeps before it hides the older ones. A round is one
24
+ * question and the answer to it. One is the floor (something has to be visible) and the
25
+ * ceiling keeps a long thread from swamping the diff it annotates. */
26
+ export declare const DISCUSSION_ROUNDS_DEFAULT = 2;
27
+ export declare const DISCUSSION_ROUNDS_MIN = 1;
28
+ export declare const DISCUSSION_ROUNDS_MAX = 10;
23
29
  export declare const NAV_LEAD_ROWS_DEFAULT = 2;
24
30
  export declare const NAV_LEAD_ROWS_MIN = 0;
25
31
  export declare const NAV_LEAD_ROWS_MAX = 10;
@@ -227,6 +233,13 @@ export declare function setMdMaxWidth(value: number): void;
227
233
  * @returns the lead row count.
228
234
  */
229
235
  export declare function navLeadRows(): number;
236
+ /**
237
+ * How many rounds of a comment thread the panel keeps.
238
+ * @returns the configured round count, or the default when nothing valid is stored.
239
+ */
240
+ export declare function discussionRoundLimit(): number;
241
+ /** Persist the comment thread's round limit. */
242
+ export declare function setDiscussionRoundLimit(value: number): void;
230
243
  /** Persist the block-jump lead row count. */
231
244
  export declare function setNavLeadRows(value: number): void;
232
245
  /** Default quick-summon chord (toggle the review panel open/closed). */
@@ -90,7 +90,7 @@ export interface PendingPanelFace {
90
90
  /** List one workspace directory level for the add-path dialog. */
91
91
  onBrowse: (sessionId: SessionId, path?: string) => Promise<DiffApprovalBrowseValue>;
92
92
  /** Add one named path (a file, or a directory's whole subtree) to the list. */
93
- onAddPath: (sessionId: SessionId, path: string, includeUnchanged: boolean) => Promise<DiffApprovalAddValue>;
93
+ onAddPath: (sessionId: SessionId, path: string, includeUnchanged: boolean, exact?: boolean) => Promise<DiffApprovalAddValue>;
94
94
  /** Keep every pending entry of one session in a single host call (bulk). */
95
95
  onKeepAll: (sessionId: SessionId) => Promise<void>;
96
96
  /** Revert every pending entry of one session in a single host call (bulk). */
@@ -34,7 +34,7 @@ export interface PendingDiffStore extends HostObservable<PendingDiffSnapshot> {
34
34
  /** List one workspace directory level for the add-path dialog. */
35
35
  browse: (sessionId: SessionId, path?: string) => Promise<DiffApprovalBrowseValue>;
36
36
  /** Add one named path to the list, then refresh; resolves to what the scan did. */
37
- addPath: (sessionId: SessionId, path: string, includeUnchanged: boolean) => Promise<DiffApprovalAddValue>;
37
+ addPath: (sessionId: SessionId, path: string, includeUnchanged: boolean, exact?: boolean) => Promise<DiffApprovalAddValue>;
38
38
  /** Open one file with its default application or reveal it in the folder. */
39
39
  open: (sessionId: SessionId, id: string, action: DiffApprovalOpenAction) => Promise<void>;
40
40
  /** Keep every pending entry of one session in a single host call, then refresh. */
@@ -188,6 +188,11 @@ export type DiffApprovalAddOutcome =
188
188
  | 'empty'
189
189
  /** The path does not exist (or is neither a file nor a directory). */
190
190
  | 'missing'
191
+ /**
192
+ * The caller named one exact file and the path is a directory. Refused before anything is
193
+ * scanned, so a mistyped path cannot add a whole subtree.
194
+ */
195
+ | 'not-a-file'
191
196
  /** The path lies outside the session's workspace. */
192
197
  | 'outside'
193
198
  /** The workspace is not inside a git/svn/p4 checkout. */
@@ -201,6 +206,12 @@ export interface DiffApprovalAddValue {
201
206
  added: number;
202
207
  /** How many scanned paths were already listed (left untouched). */
203
208
  duplicates: number;
209
+ /**
210
+ * The entry a single-file add landed as (the one already listed, for a duplicate). Absent
211
+ * for a directory add, which is about many files. It is what lets a caller that named one
212
+ * path select the file without waiting for the next list poll.
213
+ */
214
+ id?: string | undefined;
204
215
  /** Whether the no-change walk hit its file cap, so some files were not added. */
205
216
  truncated?: boolean | undefined;
206
217
  /** Failure detail for `outcome: 'failed'`. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-diff-approval",
3
- "version": "0.24.0",
3
+ "version": "0.25.0",
4
4
  "description": "DeepSeek Harness plugin: pending-edit review with whole-file diff and Keep/Revert",
5
5
  "packageManager": "pnpm@11.21.0",
6
6
  "author": "Wu Zhiwei",