@hheei/pi-apply-patch 0.0.0-stage → 0.1.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 (40) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +8 -2
  3. package/THIRD_PARTY_NOTICES.md +3 -0
  4. package/dist/apply-patch/executor.d.ts +15 -0
  5. package/dist/apply-patch/executor.js +407 -0
  6. package/dist/apply-patch/fs.d.ts +37 -0
  7. package/dist/apply-patch/fs.js +186 -0
  8. package/dist/apply-patch/glyph.d.ts +8 -0
  9. package/dist/apply-patch/glyph.js +23 -0
  10. package/dist/apply-patch/index.d.ts +6 -0
  11. package/dist/apply-patch/index.js +5 -0
  12. package/dist/apply-patch/jsdiff-matching.d.ts +13 -0
  13. package/dist/apply-patch/jsdiff-matching.js +613 -0
  14. package/dist/apply-patch/jsdiff-worker.d.ts +1 -0
  15. package/dist/apply-patch/jsdiff-worker.js +18 -0
  16. package/dist/apply-patch/jsdiff.d.ts +9 -0
  17. package/dist/apply-patch/jsdiff.js +219 -0
  18. package/dist/apply-patch/outcome.d.ts +84 -0
  19. package/dist/apply-patch/outcome.js +1 -0
  20. package/dist/apply-patch/parser.d.ts +85 -0
  21. package/dist/apply-patch/parser.js +486 -0
  22. package/dist/apply-patch/paths.d.ts +5 -0
  23. package/dist/apply-patch/paths.js +23 -0
  24. package/dist/apply-patch/policy.d.ts +11 -0
  25. package/dist/apply-patch/policy.js +44 -0
  26. package/dist/apply-patch/presentation.d.ts +7 -0
  27. package/dist/apply-patch/presentation.js +127 -0
  28. package/dist/apply-patch/settings.d.ts +5 -0
  29. package/dist/apply-patch/settings.js +33 -0
  30. package/dist/apply-patch/tool.d.ts +18 -0
  31. package/dist/apply-patch/tool.js +178 -0
  32. package/dist/apply-patch/view.d.ts +34 -0
  33. package/dist/apply-patch/view.js +256 -0
  34. package/dist/apply-patch-tool.d.ts +1 -0
  35. package/dist/apply-patch-tool.js +1 -0
  36. package/dist/extension.d.ts +2 -0
  37. package/dist/extension.js +22 -0
  38. package/dist/index.d.ts +1 -0
  39. package/dist/index.js +1 -0
  40. package/package.json +57 -5
@@ -0,0 +1,219 @@
1
+ import { Worker } from "node:worker_threads";
2
+ import { isRecord } from "@hheei/pi-ext-core";
3
+ import { compileV4aUpdateToUnifiedDiff } from "./parser.js";
4
+ const INTERNAL_PATH = "__apply_patch_target__";
5
+ const WORKER_URL = new URL(import.meta.url.endsWith(".ts") ? "./jsdiff-worker.ts" : "./jsdiff-worker.js", import.meta.url);
6
+ function isFiniteInteger(value) {
7
+ return typeof value === "number" && Number.isInteger(value);
8
+ }
9
+ function parseRejected(value) {
10
+ if (!isRecord(value) || !isFiniteInteger(value.hunkIndex) || value.hunkIndex < 1)
11
+ return undefined;
12
+ if (value.kind === "context_not_found") {
13
+ const hint = typeof value.hint === "string" ? value.hint : undefined;
14
+ return {
15
+ kind: "context_not_found",
16
+ hunkIndex: value.hunkIndex,
17
+ ...(hint !== undefined ? { hint } : {}),
18
+ };
19
+ }
20
+ if (value.kind !== "ambiguous_exact" || !Array.isArray(value.candidateStartLines))
21
+ return undefined;
22
+ const candidateStartLines = [];
23
+ for (const line of value.candidateStartLines) {
24
+ if (!isFiniteInteger(line) || line < 1)
25
+ return undefined;
26
+ candidateStartLines.push(line);
27
+ }
28
+ return {
29
+ kind: "ambiguous_exact",
30
+ hunkIndex: value.hunkIndex,
31
+ candidateStartLines: Object.freeze(candidateStartLines),
32
+ };
33
+ }
34
+ function parseStringList(value) {
35
+ if (!Array.isArray(value) || value.some((line) => typeof line !== "string"))
36
+ return undefined;
37
+ return Object.freeze([...value]);
38
+ }
39
+ function parsePrepared(value, expectedHunks, snapshotPath) {
40
+ if (!isRecord(value) ||
41
+ !(value.after instanceof Uint8Array) ||
42
+ value.after.byteLength > 32 * 1024 * 1024)
43
+ return undefined;
44
+ if (value.mode !== undefined && value.mode !== "exact" && value.mode !== "fuzzy")
45
+ return undefined;
46
+ if (!isFiniteInteger(value.addedLines) ||
47
+ !isFiniteInteger(value.removedLines) ||
48
+ value.addedLines < 0 ||
49
+ value.removedLines < 0 ||
50
+ !Array.isArray(value.outcomes) ||
51
+ !Array.isArray(value.rejected) ||
52
+ !Array.isArray(value.snapshots))
53
+ return undefined;
54
+ const seen = new Set();
55
+ const outcomes = [];
56
+ for (const outcome of value.outcomes) {
57
+ if (!isRecord(outcome) ||
58
+ outcome.kind !== "applied" ||
59
+ !isFiniteInteger(outcome.hunkIndex) ||
60
+ !isFiniteInteger(outcome.startLine) ||
61
+ !isFiniteInteger(outcome.length) ||
62
+ outcome.hunkIndex < 1 ||
63
+ outcome.hunkIndex > expectedHunks ||
64
+ outcome.startLine < 1 ||
65
+ outcome.length < 0 ||
66
+ (outcome.match !== "exact" && outcome.match !== "fuzzy") ||
67
+ seen.has(outcome.hunkIndex))
68
+ return undefined;
69
+ seen.add(outcome.hunkIndex);
70
+ outcomes.push({
71
+ kind: "applied",
72
+ hunkIndex: outcome.hunkIndex,
73
+ startLine: outcome.startLine,
74
+ length: outcome.length,
75
+ match: outcome.match,
76
+ });
77
+ }
78
+ const rejected = [];
79
+ for (const item of value.rejected) {
80
+ const parsed = parseRejected(item);
81
+ if (parsed === undefined || parsed.hunkIndex > expectedHunks || seen.has(parsed.hunkIndex))
82
+ return undefined;
83
+ seen.add(parsed.hunkIndex);
84
+ rejected.push(parsed);
85
+ }
86
+ if (seen.size !== expectedHunks)
87
+ return undefined;
88
+ const snapshots = [];
89
+ for (const snapshot of value.snapshots) {
90
+ if (!isRecord(snapshot) || snapshot.path !== snapshotPath)
91
+ return undefined;
92
+ if (!isFiniteInteger(snapshot.hunkIndex) ||
93
+ !isFiniteInteger(snapshot.startLine) ||
94
+ !isFiniteInteger(snapshot.afterStartLine) ||
95
+ snapshot.hunkIndex < 1 ||
96
+ snapshot.hunkIndex > expectedHunks ||
97
+ snapshot.startLine < 1 ||
98
+ snapshot.afterStartLine < 1)
99
+ return undefined;
100
+ const before = parseStringList(snapshot.before);
101
+ const after = parseStringList(snapshot.after);
102
+ if (before === undefined || after === undefined)
103
+ return undefined;
104
+ snapshots.push({
105
+ path: snapshot.path,
106
+ hunkIndex: snapshot.hunkIndex,
107
+ startLine: snapshot.startLine,
108
+ afterStartLine: snapshot.afterStartLine,
109
+ before,
110
+ after,
111
+ });
112
+ }
113
+ return {
114
+ after: value.after,
115
+ mode: value.mode,
116
+ addedLines: value.addedLines,
117
+ removedLines: value.removedLines,
118
+ outcomes: Object.freeze(outcomes),
119
+ rejected: Object.freeze(rejected),
120
+ snapshots: Object.freeze(snapshots),
121
+ };
122
+ }
123
+ export async function runJsDiffUpdate(options) {
124
+ options.signal?.throwIfAborted();
125
+ if (options.operation.hunks.length === 0)
126
+ return {
127
+ after: options.before,
128
+ mode: undefined,
129
+ addedLines: 0,
130
+ removedLines: 0,
131
+ outcomes: Object.freeze([]),
132
+ rejected: Object.freeze([]),
133
+ snapshots: Object.freeze([]),
134
+ };
135
+ const workerData = {
136
+ before: options.before,
137
+ hunks: options.operation.hunks.map((hunk) => ({
138
+ unifiedDiff: compileV4aUpdateToUnifiedDiff({
139
+ kind: "update",
140
+ path: INTERNAL_PATH,
141
+ hunks: [hunk],
142
+ }),
143
+ hunk,
144
+ })),
145
+ fuzzFactor: options.fuzzFactor,
146
+ snapshotPath: options.operation.moveTo ?? options.operation.path,
147
+ };
148
+ options.signal?.throwIfAborted();
149
+ const worker = new Worker(WORKER_URL, { workerData });
150
+ let settled = false;
151
+ let abortHandler;
152
+ const cleanup = () => {
153
+ if (abortHandler !== undefined)
154
+ options.signal?.removeEventListener("abort", abortHandler);
155
+ worker.removeAllListeners();
156
+ };
157
+ try {
158
+ return await new Promise((resolve, reject) => {
159
+ let message;
160
+ const fail = (error) => {
161
+ if (settled)
162
+ return;
163
+ settled = true;
164
+ cleanup();
165
+ reject(error);
166
+ };
167
+ const succeed = (value) => {
168
+ if (settled)
169
+ return;
170
+ settled = true;
171
+ cleanup();
172
+ resolve(value);
173
+ };
174
+ abortHandler = () => {
175
+ void worker.terminate().then(() => fail(options.signal?.reason ?? new Error("aborted")), (error) => fail(options.signal?.reason ?? error));
176
+ };
177
+ worker.once("message", (value) => {
178
+ message = value;
179
+ });
180
+ worker.once("error", (error) => fail(error));
181
+ worker.once("exit", (code) => {
182
+ if (settled)
183
+ return;
184
+ if (options.signal?.aborted) {
185
+ fail(options.signal.reason ?? new Error("aborted"));
186
+ return;
187
+ }
188
+ if (code !== 0 && code !== null) {
189
+ fail(new Error(`jsdiff worker exited with code ${code}`));
190
+ return;
191
+ }
192
+ if (!isRecord(message) || (message.kind !== "result" && message.kind !== "error")) {
193
+ fail(new Error("jsdiff worker returned an invalid result"));
194
+ return;
195
+ }
196
+ if (message.kind === "error") {
197
+ fail(new Error(typeof message.message === "string" ? message.message : "jsdiff worker failed"));
198
+ return;
199
+ }
200
+ const prepared = parsePrepared(message.value, options.operation.hunks.length, options.operation.moveTo ?? options.operation.path);
201
+ if (prepared === undefined) {
202
+ fail(new Error("jsdiff worker returned an invalid result"));
203
+ return;
204
+ }
205
+ succeed(prepared);
206
+ });
207
+ options.signal?.addEventListener("abort", abortHandler, { once: true });
208
+ if (options.signal?.aborted)
209
+ abortHandler();
210
+ });
211
+ }
212
+ finally {
213
+ if (!settled) {
214
+ settled = true;
215
+ cleanup();
216
+ await worker.terminate();
217
+ }
218
+ }
219
+ }
@@ -0,0 +1,84 @@
1
+ export type PatchHunkOutcome = {
2
+ readonly kind: "applied";
3
+ readonly hunkIndex: number;
4
+ readonly startLine: number;
5
+ readonly length: number;
6
+ readonly match: "exact" | "fuzzy";
7
+ } | {
8
+ readonly kind: "context_not_found";
9
+ readonly hunkIndex: number;
10
+ readonly hint?: string;
11
+ } | {
12
+ readonly kind: "ambiguous_exact";
13
+ readonly hunkIndex: number;
14
+ readonly candidateStartLines: readonly number[];
15
+ };
16
+ export type AppliedPatchHunk = Extract<PatchHunkOutcome, {
17
+ readonly kind: "applied";
18
+ }>;
19
+ export type RejectedPatchHunk = Exclude<PatchHunkOutcome, {
20
+ readonly kind: "applied";
21
+ }>;
22
+ export interface ApplyPatchHunkSnapshot {
23
+ readonly path: string;
24
+ readonly hunkIndex: number;
25
+ readonly startLine: number;
26
+ readonly afterStartLine: number;
27
+ readonly before: readonly string[];
28
+ readonly after: readonly string[];
29
+ }
30
+ export interface PreparedPatchUpdate {
31
+ readonly after: Uint8Array;
32
+ readonly mode: "exact" | "fuzzy" | undefined;
33
+ readonly addedLines: number;
34
+ readonly removedLines: number;
35
+ readonly outcomes: readonly AppliedPatchHunk[];
36
+ readonly rejected: readonly RejectedPatchHunk[];
37
+ readonly snapshots: readonly ApplyPatchHunkSnapshot[];
38
+ }
39
+ export interface ApplyPatchAppliedOperation {
40
+ readonly operationIndex: number;
41
+ readonly kind: "add" | "delete" | "update";
42
+ readonly paths: readonly string[];
43
+ readonly outcomes: readonly AppliedPatchHunk[];
44
+ readonly snapshots: readonly ApplyPatchHunkSnapshot[];
45
+ }
46
+ export interface ApplyPatchRejection {
47
+ readonly operationIndices: readonly number[];
48
+ readonly paths: readonly string[];
49
+ readonly error: string;
50
+ readonly diagnostics: readonly RejectedPatchHunk[];
51
+ }
52
+ export type ApplyPatchProgressStage = "parsed" | "publishing" | "done";
53
+ export type ApplyPatchOperationStatus = "pending" | "applied" | "partial" | "fuzzy" | "rejected" | "unconfirmed" | "not_applied";
54
+ export type ApplyPatchOperationProgress = {
55
+ readonly operationIndex: number;
56
+ readonly kind: "add" | "delete" | "update";
57
+ readonly path: string;
58
+ readonly addedLines: number;
59
+ readonly removedLines: number;
60
+ readonly status: ApplyPatchOperationStatus;
61
+ readonly appliedHunks?: number;
62
+ readonly totalHunks?: number;
63
+ readonly partialReason?: string;
64
+ };
65
+ export interface ApplyPatchProgress {
66
+ readonly stage: ApplyPatchProgressStage;
67
+ readonly files: number;
68
+ readonly addedLines: number;
69
+ readonly removedLines: number;
70
+ readonly operations: readonly ApplyPatchOperationProgress[];
71
+ }
72
+ export interface ApplyPatchInWorkspaceResult {
73
+ readonly changedPaths: readonly string[];
74
+ readonly addedLines: number;
75
+ readonly removedLines: number;
76
+ readonly operations: readonly ApplyPatchOperationProgress[];
77
+ readonly operationCount: number;
78
+ readonly exactUpdateCount: number;
79
+ readonly fuzzyUpdateCount: number;
80
+ readonly applied: readonly ApplyPatchAppliedOperation[];
81
+ readonly rejected: readonly ApplyPatchRejection[];
82
+ readonly unconfirmed: readonly ApplyPatchRejection[];
83
+ readonly notApplied: readonly ApplyPatchRejection[];
84
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,85 @@
1
+ export type V4aPatchOperation = V4aAddOperation | V4aDeleteOperation | V4aUpdateOperation;
2
+ export interface V4aAddOperation {
3
+ readonly kind: "add";
4
+ readonly path: string;
5
+ readonly content: string;
6
+ }
7
+ export interface V4aDeleteOperation {
8
+ readonly kind: "delete";
9
+ readonly path: string;
10
+ }
11
+ export interface V4aUpdateOperation {
12
+ readonly kind: "update";
13
+ readonly path: string;
14
+ readonly moveTo?: string;
15
+ readonly hunks: readonly V4aUpdateHunk[];
16
+ }
17
+ export interface V4aUpdateHunk {
18
+ readonly anchor?: string;
19
+ readonly anchors?: readonly string[];
20
+ readonly endOfFile?: true;
21
+ readonly lines: readonly V4aUpdateLine[];
22
+ }
23
+ export type V4aUpdateLine = V4aContextLine | V4aAddedLine | V4aRemovedLine;
24
+ export interface V4aContextLine {
25
+ readonly kind: "context";
26
+ readonly text: string;
27
+ readonly noNewline?: true;
28
+ }
29
+ export interface V4aAddedLine {
30
+ readonly kind: "add";
31
+ readonly text: string;
32
+ readonly noNewline?: true;
33
+ }
34
+ export interface V4aRemovedLine {
35
+ readonly kind: "remove";
36
+ readonly text: string;
37
+ readonly noNewline?: true;
38
+ }
39
+ export interface V4aPatch {
40
+ readonly operations: readonly V4aPatchOperation[];
41
+ }
42
+ export interface V4aPatchConflict {
43
+ readonly path: string;
44
+ readonly operationIndices: readonly number[];
45
+ readonly message: string;
46
+ }
47
+ export declare const MAX_V4A_PATCH_BYTES = 1048576;
48
+ export declare const MAX_V4A_OPERATIONS = 128;
49
+ export declare const MAX_V4A_HUNKS_PER_UPDATE = 128;
50
+ export declare const MAX_V4A_HUNK_LINES = 4096;
51
+ export declare const MAX_V4A_PATH_BYTES = 4096;
52
+ export declare const MAX_V4A_PATH_SEGMENT_BYTES = 255;
53
+ export interface V4aPreviewOperation {
54
+ readonly kind: "add" | "delete" | "update";
55
+ readonly path: string;
56
+ readonly addedLines: number;
57
+ readonly removedLines: number;
58
+ }
59
+ type MutablePreviewOperation = {
60
+ kind: V4aPreviewOperation["kind"];
61
+ path: string;
62
+ addedLines: number;
63
+ removedLines: number;
64
+ };
65
+ export interface V4aPreviewCursor {
66
+ consumed: string;
67
+ bytes: number;
68
+ operations: MutablePreviewOperation[];
69
+ stopped: boolean;
70
+ }
71
+ export declare function createV4aPreviewCursor(): V4aPreviewCursor;
72
+ export declare function previewV4aPatchPrefix(input: string, argsComplete?: boolean, cursor?: V4aPreviewCursor): readonly V4aPreviewOperation[];
73
+ /** Counts unique completed operation paths. Ignores payload lines and partial last lines. */
74
+ export declare function previewV4aPatchFileCount(input: string, cursor?: V4aPreviewCursor): number;
75
+ export declare function parseV4aPatch(input: string): V4aPatch;
76
+ /**
77
+ * Parses a V4A patch one complete operation at a time. Callers may present
78
+ * parsed operations before workspace validation or mutation begins.
79
+ */
80
+ export declare function parseV4aPatchProgressively(input: string, onOperation: (operation: V4aPatchOperation, index: number) => void | Promise<void>): Promise<V4aPatch>;
81
+ export declare function operationTouchedPaths(operation: V4aPatchOperation): readonly string[];
82
+ /** Finds operation graph conflicts without reading or mutating the workspace. */
83
+ export declare function findV4aPatchConflicts(patch: V4aPatch): readonly V4aPatchConflict[];
84
+ export declare function compileV4aUpdateToUnifiedDiff(operation: V4aUpdateOperation): string;
85
+ export {};