dsh-diff-approval 0.13.1 → 0.14.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/client.js +143 -109
- package/lib/index.js +355 -441
- package/lib/types/client/reference.d.ts +9 -4
- package/lib/types/pending.d.ts +43 -43
- package/lib/types/persist.d.ts +32 -39
- package/lib/types/types.d.ts +13 -10
- package/package.json +1 -1
|
@@ -44,8 +44,12 @@ export declare function referenceOf(path: string, workspacePath: string | undefi
|
|
|
44
44
|
/**
|
|
45
45
|
* Map one referenced line range from `oldContent` coordinates to `newContent`
|
|
46
46
|
* coordinates. Lines that survive (unchanged context) map to their new line
|
|
47
|
-
* numbers, and
|
|
48
|
-
*
|
|
47
|
+
* numbers, and a line whose content changed in place (a delete replaced by an
|
|
48
|
+
* add at the same logical position) maps to that position's new line — its text
|
|
49
|
+
* changed, but the line is not gone. Only a line that is genuinely removed
|
|
50
|
+
* (a delete with no replacement) is omitted. The surviving lines' min/max span
|
|
51
|
+
* is returned; when every line in the range is gone, returns `undefined` (the
|
|
52
|
+
* reference is expired).
|
|
49
53
|
* @param oldContent - the file content the reference was made against.
|
|
50
54
|
* @param newContent - the file content now.
|
|
51
55
|
* @param start - first referenced line (1-based, inclusive).
|
|
@@ -59,8 +63,9 @@ export declare function remapReferenceRange(oldContent: string, newContent: stri
|
|
|
59
63
|
/**
|
|
60
64
|
* Rewrite every `(referencePath:line)` / `(referencePath:start-end)` occurrence
|
|
61
65
|
* in `text`, remapping each range from `oldContent` to `newContent`. A range
|
|
62
|
-
* whose lines all survived
|
|
63
|
-
* removed becomes
|
|
66
|
+
* whose lines all survived (unchanged context or an in-place edit) becomes the
|
|
67
|
+
* new range; one whose lines were all genuinely removed becomes
|
|
68
|
+
* `(referencePath:LINE_MISSING)`.
|
|
64
69
|
* @param text - the free text (composer draft, queued message) to rewrite.
|
|
65
70
|
* @param referencePath - the file's reference path (workspace-relative or absolute).
|
|
66
71
|
* @param oldContent - the file content the references were made against.
|
package/lib/types/pending.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* In-memory pending-diff store: one entry per
|
|
3
|
-
* file's full set of unhandled changes as one cumulative span
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
2
|
+
* In-memory pending-diff store: one entry per file path, globally, holding the
|
|
3
|
+
* file's full set of unhandled changes as one cumulative span (earliest basis →
|
|
4
|
+
* latest content) across every session and workspace that touched it. Every
|
|
5
|
+
* later operation to a tracked path folds into its entry — `oldText` stays the
|
|
6
|
+
* earliest basis, `newText` takes the latest content, and the touching sessions
|
|
7
|
+
* accumulate in `sessionIds` — even when the chain breaks (an outside writer
|
|
8
|
+
* changed the file between operations). Pure state and transitions; the plugin
|
|
8
9
|
* body owns the `tools/result` observation, the RPC surface, and the
|
|
9
10
|
* filesystem I/O.
|
|
10
11
|
* @module dsh-diff-approval/src/pending
|
|
@@ -12,75 +13,74 @@
|
|
|
12
13
|
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
13
14
|
import type { PendingEntry } from './types.ts';
|
|
14
15
|
/**
|
|
15
|
-
* The pending-diff store
|
|
16
|
+
* The pending-diff store: one entry per file path across all sessions. Keep /
|
|
17
|
+
* Revert decides one whole file at a time.
|
|
16
18
|
*/
|
|
17
19
|
export declare class PendingDiffStore {
|
|
18
20
|
private readonly entries;
|
|
19
|
-
private readonly pathIndex;
|
|
20
|
-
/** Merge one operation into the store, keyed by (session, path). */
|
|
21
|
-
private merge;
|
|
22
21
|
/**
|
|
23
|
-
*
|
|
22
|
+
* Merge one captured operation into its file's entry. A no-op (equal before
|
|
24
23
|
* and after) folds nothing.
|
|
25
24
|
* @param entry - the captured operation (id assigned by the caller).
|
|
26
25
|
* @returns whether the stored entry changed.
|
|
27
26
|
*/
|
|
28
27
|
fold(entry: PendingEntry): boolean;
|
|
28
|
+
/** Fold a captured entry into the path's single global entry. */
|
|
29
|
+
private merge;
|
|
29
30
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* @param sessionId - the session the entries belong to.
|
|
35
|
-
* @param entries - persisted entries, oldest capture first, to fold in.
|
|
31
|
+
* Combine a stored entry and a new capture. `oldText` comes from the earlier
|
|
32
|
+
* capture (the file's original basis), `newText` from the later (latest
|
|
33
|
+
* content), and the session set is the union — so the entry always spans the
|
|
34
|
+
* whole pending change regardless of which session contributed which part.
|
|
36
35
|
*/
|
|
37
|
-
|
|
36
|
+
private mergeEntry;
|
|
38
37
|
/**
|
|
39
|
-
* Copy the session's
|
|
40
|
-
*
|
|
38
|
+
* Copy every entry touching a session (the session's view), oldest capture
|
|
39
|
+
* first. The file list is session-scoped: an entry appears for each session
|
|
40
|
+
* that touched it, so a session only sees files it worked on.
|
|
41
|
+
* @param sessionId - the viewing session.
|
|
41
42
|
* @returns detached entries in capture order.
|
|
42
43
|
*/
|
|
43
44
|
list(sessionId: SessionId): PendingEntry[];
|
|
45
|
+
/** Every entry (all paths, all sessions), oldest capture first. */
|
|
46
|
+
all(): PendingEntry[];
|
|
44
47
|
/**
|
|
45
|
-
* Read one entry without removing it.
|
|
46
|
-
* @param
|
|
47
|
-
* @
|
|
48
|
-
* @returns the entry, or `undefined` when the pair has none.
|
|
48
|
+
* Read one path's entry without removing it.
|
|
49
|
+
* @param path - the file path (the global entry key).
|
|
50
|
+
* @returns the entry, or `undefined` when none is tracked.
|
|
49
51
|
*/
|
|
50
|
-
get(
|
|
52
|
+
get(path: string): PendingEntry | undefined;
|
|
51
53
|
/**
|
|
52
|
-
* Remove one entry.
|
|
53
|
-
* @param
|
|
54
|
-
* @param id - the entry id.
|
|
54
|
+
* Remove one path's entry.
|
|
55
|
+
* @param path - the file path.
|
|
55
56
|
* @returns whether an entry was removed.
|
|
56
57
|
*/
|
|
57
|
-
remove(
|
|
58
|
+
remove(path: string): boolean;
|
|
58
59
|
/**
|
|
59
60
|
* Advance one entry's tracked content after a block-level keep/revert. The
|
|
60
|
-
* entry keeps its
|
|
61
|
-
*
|
|
62
|
-
* instead of updating it.
|
|
63
|
-
* @param sessionId - the owning session.
|
|
64
|
-
* @param id - the entry id.
|
|
61
|
+
* entry keeps its path; only the given side's text and capture time move.
|
|
62
|
+
* @param path - the file path.
|
|
65
63
|
* @param patch - the side to advance (`oldText` for keep, `newText` for revert).
|
|
66
64
|
* @returns whether the entry changed.
|
|
67
65
|
*/
|
|
68
|
-
update(
|
|
66
|
+
update(path: string, patch: {
|
|
69
67
|
oldText?: string;
|
|
70
68
|
newText?: string;
|
|
71
69
|
}): boolean;
|
|
72
70
|
/**
|
|
73
71
|
* Restore one entry exactly as given (an undo/redo replays a snapshot). The
|
|
74
|
-
* entry is inserted or replaced by
|
|
75
|
-
* later capture folds into the restored entry. The store keeps one entry per
|
|
76
|
-
* path: restoring an entry for a path that a DIFFERENT entry currently owns
|
|
77
|
-
* drops that occupant first, so an undo can never leave two list items for
|
|
78
|
-
* the same file (e.g. an imported entry and a replayed keep of the same path).
|
|
79
|
-
* @param sessionId - the owning session.
|
|
72
|
+
* entry is inserted or replaced by path.
|
|
80
73
|
* @param entry - the entry state to restore.
|
|
81
74
|
* @returns whether the store changed.
|
|
82
75
|
*/
|
|
83
|
-
restore(
|
|
84
|
-
/**
|
|
76
|
+
restore(entry: PendingEntry): boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Merge persisted entries into the store, one per path after folding. A live
|
|
79
|
+
* entry wins over a persisted one only when its time is newer (folders are
|
|
80
|
+
* applied in capture order, so a later persisted capture is strictly newer).
|
|
81
|
+
* @param entries - persisted entries, oldest capture first, to fold in.
|
|
82
|
+
*/
|
|
83
|
+
hydrate(entries: readonly PendingEntry[]): void;
|
|
84
|
+
/** Total entry count (one per tracked file path). */
|
|
85
85
|
get size(): number;
|
|
86
86
|
}
|
package/lib/types/persist.d.ts
CHANGED
|
@@ -1,59 +1,52 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Durable pending-entry persistence under the harness home
|
|
3
|
-
*
|
|
4
|
-
* `<storageDir
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
2
|
+
* Durable pending-entry persistence under the harness home: one global JSON
|
|
3
|
+
* file holding one entry per file path (across all sessions and workspaces), at
|
|
4
|
+
* `<storageDir>/pending.json`; the default root is `<dshHome>/diff-approval/workspaces`
|
|
5
|
+
* and the plugin's `storageDir` config relocates it. Saves rewrite the whole
|
|
6
|
+
* file (the entry set is small), staged as a sibling temp file and atomically
|
|
7
|
+
* renamed, so a crash leaves either the old or the new file. A missing file
|
|
8
|
+
* reads as empty; corrupt content and unknown versions throw.
|
|
9
|
+
*
|
|
10
|
+
* Legacy layout: the pre-global schema stored one JSON file per workspace
|
|
11
|
+
* (`<storageDir>/<workspaceId>.json`, `{ version: 2, sessions: { [sessionId]:
|
|
12
|
+
* PendingEntry[] } }`). On first load without a global file, `loadAll` scans for
|
|
13
|
+
* those files, flattens every session's entries, and reports them so the caller
|
|
14
|
+
* can fold them into the global store once; `save` then writes the global file
|
|
15
|
+
* and deletes the legacy files.
|
|
13
16
|
* @module dsh-diff-approval/src/persist
|
|
14
17
|
*/
|
|
15
18
|
import type { PendingEntry } from './types.ts';
|
|
16
19
|
/** Default persistence root: this plugin's own directory under the harness home. */
|
|
17
20
|
export declare function defaultStorageDir(): string;
|
|
18
21
|
/**
|
|
19
|
-
* File-backed pending entries keyed by
|
|
22
|
+
* File-backed pending entries, one global file keyed by path.
|
|
20
23
|
*/
|
|
21
24
|
export declare class PendingPersistence {
|
|
22
25
|
private readonly root;
|
|
23
26
|
private readonly tails;
|
|
27
|
+
private readonly globalFile;
|
|
24
28
|
/**
|
|
25
|
-
* @param root - directory holding
|
|
29
|
+
* @param root - directory holding the global `pending.json` (and, pre-migration,
|
|
30
|
+
* one JSON file per workspace).
|
|
26
31
|
*/
|
|
27
32
|
constructor(root: string);
|
|
28
|
-
private fileOf;
|
|
29
|
-
/** Read one workspace file; a missing file reads as empty. */
|
|
30
|
-
private readWorkspace;
|
|
31
|
-
/** Stage the envelope as a sibling temp file and atomically rename it into place. */
|
|
32
|
-
private writeWorkspace;
|
|
33
33
|
/**
|
|
34
|
-
* Load
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* @returns the
|
|
34
|
+
* Load every persisted entry. Returns the global file's entries, or — when the
|
|
35
|
+
* global file is absent — flattens the legacy per-workspace files so the
|
|
36
|
+
* caller can fold them once and then save (which clears the legacy files).
|
|
37
|
+
* @returns the entries plus whether they came from the legacy layout (awaiting
|
|
38
|
+
* a save to finalize the migration).
|
|
38
39
|
*/
|
|
39
|
-
|
|
40
|
+
loadAll(): Promise<{
|
|
41
|
+
entries: PendingEntry[];
|
|
42
|
+
migratedLegacy: boolean;
|
|
43
|
+
}>;
|
|
40
44
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* whole workspace, not one session.
|
|
46
|
-
* @param workspaceId - the owning workspace's stable id.
|
|
47
|
-
* @returns the persisted entries across all sessions; empty when none were saved.
|
|
48
|
-
*/
|
|
49
|
-
loadWorkspace(workspaceId: string): Promise<PendingEntry[]>;
|
|
50
|
-
/**
|
|
51
|
-
* Replace one session's entries durably. Saves to one file are serialized;
|
|
52
|
-
* a previous save's failure does not block the next one.
|
|
53
|
-
* @param workspaceId - the owning workspace's stable id.
|
|
54
|
-
* @param sessionId - the session whose entries to replace.
|
|
55
|
-
* @param entries - the session's complete entry list, possibly empty.
|
|
45
|
+
* Replace the persisted entry set durably. Saves to the one file are
|
|
46
|
+
* serialized; a previous save's failure does not block the next one. On a
|
|
47
|
+
* legacy migration this also removes the per-workspace files.
|
|
48
|
+
* @param entries - the complete entry list, possibly empty.
|
|
56
49
|
* @returns resolution after the file is durable.
|
|
57
50
|
*/
|
|
58
|
-
save(
|
|
51
|
+
save(entries: readonly PendingEntry[]): Promise<void>;
|
|
59
52
|
}
|
package/lib/types/types.d.ts
CHANGED
|
@@ -5,26 +5,29 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { SessionId } from '@deepseek-ai/dsh-session/types';
|
|
7
7
|
/**
|
|
8
|
-
* One file's pending entry
|
|
9
|
-
* changes folded into a single cumulative span
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* One file's pending entry, global and unique per `path`: the complete set of
|
|
9
|
+
* unhandled changes folded into a single cumulative span across every session
|
|
10
|
+
* and workspace that touched the file. `oldText` is the earliest captured basis
|
|
11
|
+
* and `newText` the latest captured content, so Keep/Revert decides the whole
|
|
12
|
+
* file at once.
|
|
12
13
|
*/
|
|
13
14
|
export interface PendingEntry {
|
|
14
|
-
/** Stable per-entry id
|
|
15
|
+
/** Stable per-entry id, equal to the path (the global key). */
|
|
15
16
|
id: string;
|
|
16
|
-
/** The session whose agent ran the operation. */
|
|
17
|
-
sessionId: SessionId;
|
|
18
17
|
/** Backend-resolved display path (the tool's output `path`). */
|
|
19
18
|
path: string;
|
|
20
19
|
/** What the operation did: an in-place change or a file creation. */
|
|
21
20
|
kind: PendingEntryKind;
|
|
22
|
-
/** File content before the operation (empty for a creation). */
|
|
21
|
+
/** File content before the first captured operation (empty for a creation). */
|
|
23
22
|
oldText: string;
|
|
24
|
-
/** File content after the operation. */
|
|
23
|
+
/** File content after the latest captured operation. */
|
|
25
24
|
newText: string;
|
|
26
|
-
/** Epoch milliseconds of the capture. */
|
|
25
|
+
/** Epoch milliseconds of the latest capture. */
|
|
27
26
|
updatedAt: number;
|
|
27
|
+
/** The most recent session whose agent touched the file (back-compat). */
|
|
28
|
+
sessionId: SessionId;
|
|
29
|
+
/** Every session that touched the file (drives the per-session list filter). */
|
|
30
|
+
sessionIds: SessionId[];
|
|
28
31
|
}
|
|
29
32
|
/** What one captured operation did to the file. */
|
|
30
33
|
export type PendingEntryKind = 'edit' | 'create';
|
package/package.json
CHANGED