pushwork 1.0.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 (184) hide show
  1. package/README.md +460 -0
  2. package/dist/browser/browser-sync-engine.d.ts +64 -0
  3. package/dist/browser/browser-sync-engine.d.ts.map +1 -0
  4. package/dist/browser/browser-sync-engine.js +303 -0
  5. package/dist/browser/browser-sync-engine.js.map +1 -0
  6. package/dist/browser/filesystem-adapter.d.ts +84 -0
  7. package/dist/browser/filesystem-adapter.d.ts.map +1 -0
  8. package/dist/browser/filesystem-adapter.js +413 -0
  9. package/dist/browser/filesystem-adapter.js.map +1 -0
  10. package/dist/browser/index.d.ts +36 -0
  11. package/dist/browser/index.d.ts.map +1 -0
  12. package/dist/browser/index.js +90 -0
  13. package/dist/browser/index.js.map +1 -0
  14. package/dist/browser/types.d.ts +70 -0
  15. package/dist/browser/types.d.ts.map +1 -0
  16. package/dist/browser/types.js +6 -0
  17. package/dist/browser/types.js.map +1 -0
  18. package/dist/cli/commands.d.ts +71 -0
  19. package/dist/cli/commands.d.ts.map +1 -0
  20. package/dist/cli/commands.js +794 -0
  21. package/dist/cli/commands.js.map +1 -0
  22. package/dist/cli/index.d.ts +2 -0
  23. package/dist/cli/index.d.ts.map +1 -0
  24. package/dist/cli/index.js +19 -0
  25. package/dist/cli/index.js.map +1 -0
  26. package/dist/cli.d.ts +3 -0
  27. package/dist/cli.d.ts.map +1 -0
  28. package/dist/cli.js +199 -0
  29. package/dist/cli.js.map +1 -0
  30. package/dist/config/index.d.ts +71 -0
  31. package/dist/config/index.d.ts.map +1 -0
  32. package/dist/config/index.js +314 -0
  33. package/dist/config/index.js.map +1 -0
  34. package/dist/core/change-detection.d.ts +78 -0
  35. package/dist/core/change-detection.d.ts.map +1 -0
  36. package/dist/core/change-detection.js +370 -0
  37. package/dist/core/change-detection.js.map +1 -0
  38. package/dist/core/index.d.ts +5 -0
  39. package/dist/core/index.d.ts.map +1 -0
  40. package/dist/core/index.js +22 -0
  41. package/dist/core/index.js.map +1 -0
  42. package/dist/core/isomorphic-snapshot.d.ts +58 -0
  43. package/dist/core/isomorphic-snapshot.d.ts.map +1 -0
  44. package/dist/core/isomorphic-snapshot.js +204 -0
  45. package/dist/core/isomorphic-snapshot.js.map +1 -0
  46. package/dist/core/move-detection.d.ts +72 -0
  47. package/dist/core/move-detection.d.ts.map +1 -0
  48. package/dist/core/move-detection.js +200 -0
  49. package/dist/core/move-detection.js.map +1 -0
  50. package/dist/core/snapshot.d.ts +109 -0
  51. package/dist/core/snapshot.d.ts.map +1 -0
  52. package/dist/core/snapshot.js +263 -0
  53. package/dist/core/snapshot.js.map +1 -0
  54. package/dist/core/sync-engine.d.ts +110 -0
  55. package/dist/core/sync-engine.d.ts.map +1 -0
  56. package/dist/core/sync-engine.js +817 -0
  57. package/dist/core/sync-engine.js.map +1 -0
  58. package/dist/index.d.ts +6 -0
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/index.js +27 -0
  61. package/dist/index.js.map +1 -0
  62. package/dist/platform/browser-filesystem.d.ts +26 -0
  63. package/dist/platform/browser-filesystem.d.ts.map +1 -0
  64. package/dist/platform/browser-filesystem.js +91 -0
  65. package/dist/platform/browser-filesystem.js.map +1 -0
  66. package/dist/platform/filesystem.d.ts +29 -0
  67. package/dist/platform/filesystem.d.ts.map +1 -0
  68. package/dist/platform/filesystem.js +65 -0
  69. package/dist/platform/filesystem.js.map +1 -0
  70. package/dist/platform/node-filesystem.d.ts +21 -0
  71. package/dist/platform/node-filesystem.d.ts.map +1 -0
  72. package/dist/platform/node-filesystem.js +93 -0
  73. package/dist/platform/node-filesystem.js.map +1 -0
  74. package/dist/types/config.d.ts +119 -0
  75. package/dist/types/config.d.ts.map +1 -0
  76. package/dist/types/config.js +3 -0
  77. package/dist/types/config.js.map +1 -0
  78. package/dist/types/documents.d.ts +70 -0
  79. package/dist/types/documents.d.ts.map +1 -0
  80. package/dist/types/documents.js +23 -0
  81. package/dist/types/documents.js.map +1 -0
  82. package/dist/types/index.d.ts +4 -0
  83. package/dist/types/index.d.ts.map +1 -0
  84. package/dist/types/index.js +23 -0
  85. package/dist/types/index.js.map +1 -0
  86. package/dist/types/snapshot.d.ts +81 -0
  87. package/dist/types/snapshot.d.ts.map +1 -0
  88. package/dist/types/snapshot.js +17 -0
  89. package/dist/types/snapshot.js.map +1 -0
  90. package/dist/utils/content-similarity.d.ts +53 -0
  91. package/dist/utils/content-similarity.d.ts.map +1 -0
  92. package/dist/utils/content-similarity.js +155 -0
  93. package/dist/utils/content-similarity.js.map +1 -0
  94. package/dist/utils/content.d.ts +5 -0
  95. package/dist/utils/content.d.ts.map +1 -0
  96. package/dist/utils/content.js +30 -0
  97. package/dist/utils/content.js.map +1 -0
  98. package/dist/utils/fs-browser.d.ts +57 -0
  99. package/dist/utils/fs-browser.d.ts.map +1 -0
  100. package/dist/utils/fs-browser.js +311 -0
  101. package/dist/utils/fs-browser.js.map +1 -0
  102. package/dist/utils/fs-node.d.ts +53 -0
  103. package/dist/utils/fs-node.d.ts.map +1 -0
  104. package/dist/utils/fs-node.js +220 -0
  105. package/dist/utils/fs-node.js.map +1 -0
  106. package/dist/utils/fs.d.ts +62 -0
  107. package/dist/utils/fs.d.ts.map +1 -0
  108. package/dist/utils/fs.js +293 -0
  109. package/dist/utils/fs.js.map +1 -0
  110. package/dist/utils/index.d.ts +4 -0
  111. package/dist/utils/index.d.ts.map +1 -0
  112. package/dist/utils/index.js +23 -0
  113. package/dist/utils/index.js.map +1 -0
  114. package/dist/utils/isomorphic.d.ts +29 -0
  115. package/dist/utils/isomorphic.d.ts.map +1 -0
  116. package/dist/utils/isomorphic.js +139 -0
  117. package/dist/utils/isomorphic.js.map +1 -0
  118. package/dist/utils/mime-types.d.ts +13 -0
  119. package/dist/utils/mime-types.d.ts.map +1 -0
  120. package/dist/utils/mime-types.js +240 -0
  121. package/dist/utils/mime-types.js.map +1 -0
  122. package/dist/utils/network-sync.d.ts +12 -0
  123. package/dist/utils/network-sync.d.ts.map +1 -0
  124. package/dist/utils/network-sync.js +149 -0
  125. package/dist/utils/network-sync.js.map +1 -0
  126. package/dist/utils/pure.d.ts +25 -0
  127. package/dist/utils/pure.d.ts.map +1 -0
  128. package/dist/utils/pure.js +112 -0
  129. package/dist/utils/pure.js.map +1 -0
  130. package/dist/utils/repo-factory.d.ts +11 -0
  131. package/dist/utils/repo-factory.d.ts.map +1 -0
  132. package/dist/utils/repo-factory.js +77 -0
  133. package/dist/utils/repo-factory.js.map +1 -0
  134. package/package.json +83 -0
  135. package/src/cli/commands.ts +1053 -0
  136. package/src/cli/index.ts +2 -0
  137. package/src/cli.ts +287 -0
  138. package/src/config/index.ts +334 -0
  139. package/src/core/change-detection.ts +484 -0
  140. package/src/core/index.ts +5 -0
  141. package/src/core/move-detection.ts +269 -0
  142. package/src/core/snapshot.ts +285 -0
  143. package/src/core/sync-engine.ts +1167 -0
  144. package/src/index.ts +14 -0
  145. package/src/types/config.ts +130 -0
  146. package/src/types/documents.ts +72 -0
  147. package/src/types/index.ts +8 -0
  148. package/src/types/snapshot.ts +88 -0
  149. package/src/utils/content-similarity.ts +194 -0
  150. package/src/utils/content.ts +28 -0
  151. package/src/utils/fs.ts +289 -0
  152. package/src/utils/index.ts +8 -0
  153. package/src/utils/mime-types.ts +236 -0
  154. package/src/utils/network-sync.ts +153 -0
  155. package/src/utils/repo-factory.ts +58 -0
  156. package/test/README-TESTING-GAPS.md +174 -0
  157. package/test/integration/README.md +328 -0
  158. package/test/integration/clone-test.sh +310 -0
  159. package/test/integration/conflict-resolution-test.sh +309 -0
  160. package/test/integration/deletion-behavior-test.sh +487 -0
  161. package/test/integration/deletion-sync-test-simple.sh +193 -0
  162. package/test/integration/deletion-sync-test.sh +297 -0
  163. package/test/integration/exclude-patterns.test.ts +152 -0
  164. package/test/integration/full-integration-test.sh +363 -0
  165. package/test/integration/sync-deletion.test.ts +339 -0
  166. package/test/integration/sync-flow.test.ts +309 -0
  167. package/test/run-tests.sh +225 -0
  168. package/test/unit/content-similarity.test.ts +236 -0
  169. package/test/unit/deletion-behavior.test.ts +260 -0
  170. package/test/unit/enhanced-mime-detection.test.ts +266 -0
  171. package/test/unit/snapshot.test.ts +431 -0
  172. package/test/unit/sync-timing.test.ts +178 -0
  173. package/test/unit/utils.test.ts +368 -0
  174. package/tools/browser-sync/README.md +116 -0
  175. package/tools/browser-sync/package.json +44 -0
  176. package/tools/browser-sync/patchwork.json +1 -0
  177. package/tools/browser-sync/pnpm-lock.yaml +4202 -0
  178. package/tools/browser-sync/src/components/BrowserSyncTool.tsx +599 -0
  179. package/tools/browser-sync/src/index.ts +20 -0
  180. package/tools/browser-sync/src/polyfills.ts +31 -0
  181. package/tools/browser-sync/src/styles.css +290 -0
  182. package/tools/browser-sync/src/types.ts +27 -0
  183. package/tools/browser-sync/vite.config.ts +25 -0
  184. package/tsconfig.json +22 -0
package/src/index.ts ADDED
@@ -0,0 +1,14 @@
1
+ // Core sync functionality
2
+ export * from "./core";
3
+
4
+ // Utilities
5
+ export * from "./utils";
6
+
7
+ // Configuration
8
+ export * from "./config";
9
+
10
+ // Types
11
+ export * from "./types";
12
+
13
+ // CLI commands (for programmatic use)
14
+ export * from "./cli";
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Global configuration options
3
+ */
4
+ export interface GlobalConfig {
5
+ sync_server?: string;
6
+ sync_server_storage_id?: string;
7
+ exclude_patterns?: string[];
8
+ large_file_threshold?: string;
9
+ diff?: {
10
+ external_tool?: string;
11
+ show_binary?: boolean;
12
+ };
13
+ sync?: {
14
+ move_detection_threshold?: number;
15
+ prompt_threshold?: number;
16
+ auto_sync?: boolean;
17
+ parallel_operations?: number;
18
+ };
19
+ }
20
+
21
+ /**
22
+ * Default settings
23
+ */
24
+ export interface DefaultSettings {
25
+ remote_repo?: string;
26
+ exclude_patterns: string[];
27
+ large_file_threshold: string;
28
+ }
29
+
30
+ /**
31
+ * Diff tool settings
32
+ */
33
+ export interface DiffSettings {
34
+ external_tool?: string;
35
+ show_binary: boolean;
36
+ }
37
+
38
+ /**
39
+ * Sync behavior settings
40
+ */
41
+ export interface SyncSettings {
42
+ move_detection_threshold: number;
43
+ prompt_threshold: number;
44
+ auto_sync: boolean;
45
+ parallel_operations: number;
46
+ }
47
+
48
+ /**
49
+ * Per-directory configuration
50
+ */
51
+ export interface DirectoryConfig {
52
+ sync_server?: string;
53
+ sync_server_storage_id?: string;
54
+ sync_enabled: boolean;
55
+ root_directory_url?: string; // AutomergeUrl of the root directory document
56
+ defaults: {
57
+ exclude_patterns: string[];
58
+ large_file_threshold: string;
59
+ };
60
+ diff: {
61
+ external_tool?: string;
62
+ show_binary: boolean;
63
+ };
64
+ sync: {
65
+ move_detection_threshold: number;
66
+ prompt_threshold: number;
67
+ auto_sync: boolean;
68
+ parallel_operations: number;
69
+ };
70
+ }
71
+
72
+ /**
73
+ * CLI command options
74
+ */
75
+ export interface CommandOptions {
76
+ dryRun?: boolean;
77
+ verbose?: boolean;
78
+ tool?: string;
79
+ nameOnly?: boolean;
80
+ oneline?: boolean;
81
+ remote?: string;
82
+ }
83
+
84
+ /**
85
+ * Init command specific options
86
+ */
87
+ export interface InitOptions extends CommandOptions {
88
+ // No additional options needed - init creates a new sync directory
89
+ }
90
+
91
+ /**
92
+ * Clone command specific options
93
+ */
94
+ export interface CloneOptions extends CommandOptions {
95
+ force?: boolean; // Overwrite existing directory
96
+ syncServer?: string; // Custom sync server URL
97
+ syncServerStorageId?: string; // Custom sync server storage ID
98
+ }
99
+
100
+ /**
101
+ * Sync command specific options
102
+ */
103
+ export interface SyncOptions extends CommandOptions {
104
+ dryRun: boolean;
105
+ force?: boolean;
106
+ }
107
+
108
+ /**
109
+ * Diff command specific options
110
+ */
111
+ export interface DiffOptions extends CommandOptions {
112
+ tool?: string;
113
+ nameOnly: boolean;
114
+ }
115
+
116
+ /**
117
+ * Log command specific options
118
+ */
119
+ export interface LogOptions extends CommandOptions {
120
+ oneline: boolean;
121
+ since?: string;
122
+ limit?: number;
123
+ }
124
+
125
+ /**
126
+ * Checkout command specific options
127
+ */
128
+ export interface CheckoutOptions extends CommandOptions {
129
+ force?: boolean;
130
+ }
@@ -0,0 +1,72 @@
1
+ import { AutomergeUrl } from "@automerge/automerge-repo";
2
+
3
+ /**
4
+ * Entry in a directory document
5
+ */
6
+ export interface DirectoryEntry {
7
+ name: string;
8
+ type: "file" | "folder";
9
+ url: AutomergeUrl;
10
+ }
11
+
12
+ /**
13
+ * Directory document structure
14
+ */
15
+ export interface DirectoryDocument {
16
+ "@patchwork": { type: "folder" };
17
+ docs: DirectoryEntry[];
18
+ }
19
+
20
+ /**
21
+ * File document structure
22
+ */
23
+ export interface FileDocument {
24
+ "@patchwork": { type: "file" };
25
+ name: string;
26
+ extension: string;
27
+ mimeType: string;
28
+ content: string | Uint8Array;
29
+ metadata: {
30
+ permissions: number;
31
+ };
32
+ }
33
+
34
+ /**
35
+ * File type classification
36
+ */
37
+ export enum FileType {
38
+ TEXT = "text",
39
+ BINARY = "binary",
40
+ DIRECTORY = "directory",
41
+ }
42
+
43
+ /**
44
+ * Change type classification for sync operations
45
+ */
46
+ export enum ChangeType {
47
+ NO_CHANGE = "no_change",
48
+ LOCAL_ONLY = "local_only",
49
+ REMOTE_ONLY = "remote_only",
50
+ BOTH_CHANGED = "both_changed",
51
+ }
52
+
53
+ /**
54
+ * File system entry metadata
55
+ */
56
+ export interface FileSystemEntry {
57
+ path: string;
58
+ type: FileType;
59
+ size: number;
60
+ mtime: Date;
61
+ permissions: number;
62
+ }
63
+
64
+ /**
65
+ * Move detection result
66
+ */
67
+ export interface MoveCandidate {
68
+ fromPath: string;
69
+ toPath: string;
70
+ similarity: number;
71
+ confidence: "auto" | "prompt" | "low";
72
+ }
@@ -0,0 +1,8 @@
1
+ // Document types
2
+ export * from "./documents";
3
+
4
+ // Snapshot and sync types
5
+ export * from "./snapshot";
6
+
7
+ // Configuration types
8
+ export * from "./config";
@@ -0,0 +1,88 @@
1
+ import { AutomergeUrl, UrlHeads } from "@automerge/automerge-repo";
2
+
3
+ /**
4
+ * Tracked file entry in the sync snapshot
5
+ */
6
+ export interface SnapshotFileEntry {
7
+ path: string; // Full filesystem path for mapping
8
+ url: AutomergeUrl; // Automerge document URL
9
+ head: UrlHeads; // Document head at last sync
10
+ extension: string; // File extension
11
+ mimeType: string; // MIME type
12
+ }
13
+
14
+ /**
15
+ * Tracked directory entry in the sync snapshot
16
+ */
17
+ export interface SnapshotDirectoryEntry {
18
+ path: string; // Full filesystem path for mapping
19
+ url: AutomergeUrl; // Automerge document URL
20
+ head: UrlHeads; // Document head at last sync
21
+ entries: string[]; // List of child entry names
22
+ }
23
+
24
+ /**
25
+ * Sync snapshot for local state management
26
+ */
27
+ export interface SyncSnapshot {
28
+ timestamp: number;
29
+ rootPath: string;
30
+ rootDirectoryUrl?: AutomergeUrl; // URL of the root directory document
31
+ files: Map<string, SnapshotFileEntry>;
32
+ directories: Map<string, SnapshotDirectoryEntry>;
33
+ }
34
+
35
+ /**
36
+ * Serializable version of sync snapshot for storage
37
+ */
38
+ export interface SerializableSyncSnapshot {
39
+ timestamp: number;
40
+ rootPath: string;
41
+ rootDirectoryUrl?: AutomergeUrl; // URL of the root directory document
42
+ files: Array<[string, SnapshotFileEntry]>;
43
+ directories: Array<[string, SnapshotDirectoryEntry]>;
44
+ }
45
+
46
+ /**
47
+ * Sync operation result
48
+ */
49
+ export interface SyncResult {
50
+ success: boolean;
51
+ filesChanged: number;
52
+ directoriesChanged: number;
53
+ errors: SyncError[];
54
+ warnings: string[];
55
+ }
56
+
57
+ /**
58
+ * Sync error details
59
+ */
60
+ export interface SyncError {
61
+ path: string;
62
+ operation: string;
63
+ error: Error;
64
+ recoverable: boolean;
65
+ }
66
+
67
+ /**
68
+ * Sync operation type
69
+ */
70
+ export enum SyncOperation {
71
+ CREATE_FILE = "create_file",
72
+ UPDATE_FILE = "update_file",
73
+ DELETE_FILE = "delete_file",
74
+ MOVE_FILE = "move_file",
75
+ CREATE_DIRECTORY = "create_directory",
76
+ DELETE_DIRECTORY = "delete_directory",
77
+ MOVE_DIRECTORY = "move_directory",
78
+ }
79
+
80
+ /**
81
+ * Pending sync operation
82
+ */
83
+ export interface PendingSyncOperation {
84
+ operation: SyncOperation;
85
+ path: string;
86
+ newPath?: string;
87
+ priority: number;
88
+ }
@@ -0,0 +1,194 @@
1
+ import { calculateContentHash } from "./fs";
2
+
3
+ /**
4
+ * Content similarity calculation for move detection
5
+ */
6
+ export class ContentSimilarity {
7
+ private static readonly CHUNK_SIZE = 1024; // 1KB chunks for sampling
8
+ private static readonly AUTO_THRESHOLD = 0.8;
9
+ private static readonly PROMPT_THRESHOLD = 0.5;
10
+
11
+ /**
12
+ * Calculate similarity between two content pieces
13
+ */
14
+ static async calculateSimilarity(
15
+ content1: string | Uint8Array,
16
+ content2: string | Uint8Array
17
+ ): Promise<number> {
18
+ // Quick early exit for identical content
19
+ if (await this.areIdentical(content1, content2)) {
20
+ return 1.0;
21
+ }
22
+
23
+ // Size-based quick rejection
24
+ const size1 =
25
+ typeof content1 === "string" ? content1.length : content1.length;
26
+ const size2 =
27
+ typeof content2 === "string" ? content2.length : content2.length;
28
+ const sizeDiff = Math.abs(size1 - size2) / Math.max(size1, size2);
29
+
30
+ if (sizeDiff > 0.5) {
31
+ return 0.0; // Too different in size
32
+ }
33
+
34
+ // For small files, use full content comparison
35
+ if (size1 < this.CHUNK_SIZE * 4 && size2 < this.CHUNK_SIZE * 4) {
36
+ return this.calculateFullSimilarity(content1, content2);
37
+ }
38
+
39
+ // For large files, use sampling
40
+ return this.calculateSampledSimilarity(content1, content2);
41
+ }
42
+
43
+ /**
44
+ * Check if two content pieces are identical
45
+ */
46
+ private static async areIdentical(
47
+ content1: string | Uint8Array,
48
+ content2: string | Uint8Array
49
+ ): Promise<boolean> {
50
+ const hash1 = await calculateContentHash(content1);
51
+ const hash2 = await calculateContentHash(content2);
52
+ return hash1 === hash2;
53
+ }
54
+
55
+ /**
56
+ * Calculate similarity for small files using full content
57
+ */
58
+ private static calculateFullSimilarity(
59
+ content1: string | Uint8Array,
60
+ content2: string | Uint8Array
61
+ ): number {
62
+ const str1 =
63
+ typeof content1 === "string" ? content1 : this.bufferToString(content1);
64
+ const str2 =
65
+ typeof content2 === "string" ? content2 : this.bufferToString(content2);
66
+
67
+ return this.levenshteinSimilarity(str1, str2);
68
+ }
69
+
70
+ /**
71
+ * Calculate similarity for large files using sampling
72
+ */
73
+ private static calculateSampledSimilarity(
74
+ content1: string | Uint8Array,
75
+ content2: string | Uint8Array
76
+ ): number {
77
+ const samples1 = this.getSamples(content1);
78
+ const samples2 = this.getSamples(content2);
79
+
80
+ let totalSimilarity = 0;
81
+ let comparisons = 0;
82
+
83
+ for (let i = 0; i < Math.min(samples1.length, samples2.length); i++) {
84
+ totalSimilarity += this.levenshteinSimilarity(samples1[i], samples2[i]);
85
+ comparisons++;
86
+ }
87
+
88
+ return comparisons > 0 ? totalSimilarity / comparisons : 0;
89
+ }
90
+
91
+ /**
92
+ * Get representative samples from content
93
+ */
94
+ private static getSamples(content: string | Uint8Array): string[] {
95
+ const str =
96
+ typeof content === "string" ? content : this.bufferToString(content);
97
+ const length = str.length;
98
+ const samples: string[] = [];
99
+
100
+ if (length <= this.CHUNK_SIZE) {
101
+ samples.push(str);
102
+ return samples;
103
+ }
104
+
105
+ // Beginning
106
+ samples.push(str.slice(0, this.CHUNK_SIZE));
107
+
108
+ // Middle
109
+ const midStart = Math.floor(length / 2) - Math.floor(this.CHUNK_SIZE / 2);
110
+ samples.push(str.slice(midStart, midStart + this.CHUNK_SIZE));
111
+
112
+ // End
113
+ samples.push(str.slice(-this.CHUNK_SIZE));
114
+
115
+ return samples;
116
+ }
117
+
118
+ /**
119
+ * Calculate Levenshtein similarity (0-1 scale)
120
+ */
121
+ private static levenshteinSimilarity(str1: string, str2: string): number {
122
+ if (str1 === str2) return 1.0;
123
+ if (str1.length === 0 || str2.length === 0) return 0.0;
124
+
125
+ const distance = this.levenshteinDistance(str1, str2);
126
+ const maxLength = Math.max(str1.length, str2.length);
127
+
128
+ return 1 - distance / maxLength;
129
+ }
130
+
131
+ /**
132
+ * Calculate Levenshtein distance
133
+ */
134
+ private static levenshteinDistance(str1: string, str2: string): number {
135
+ const matrix = Array(str2.length + 1)
136
+ .fill(null)
137
+ .map(() => Array(str1.length + 1).fill(null));
138
+
139
+ for (let i = 0; i <= str1.length; i++) matrix[0][i] = i;
140
+ for (let j = 0; j <= str2.length; j++) matrix[j][0] = j;
141
+
142
+ for (let j = 1; j <= str2.length; j++) {
143
+ for (let i = 1; i <= str1.length; i++) {
144
+ const indicator = str1[i - 1] === str2[j - 1] ? 0 : 1;
145
+ matrix[j][i] = Math.min(
146
+ matrix[j][i - 1] + 1, // deletion
147
+ matrix[j - 1][i] + 1, // insertion
148
+ matrix[j - 1][i - 1] + indicator // substitution
149
+ );
150
+ }
151
+ }
152
+
153
+ return matrix[str2.length][str1.length];
154
+ }
155
+
156
+ /**
157
+ * Convert buffer to string for comparison
158
+ */
159
+ private static bufferToString(buffer: Uint8Array): string {
160
+ // For binary content, use hex representation for comparison
161
+ return Array.from(buffer)
162
+ .map((b) => b.toString(16).padStart(2, "0"))
163
+ .join("");
164
+ }
165
+
166
+ /**
167
+ * Determine confidence level based on similarity score
168
+ */
169
+ static getConfidenceLevel(similarity: number): "auto" | "prompt" | "low" {
170
+ if (similarity >= this.AUTO_THRESHOLD) {
171
+ return "auto";
172
+ } else if (similarity >= this.PROMPT_THRESHOLD) {
173
+ return "prompt";
174
+ } else {
175
+ return "low";
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Should auto-apply move based on similarity
181
+ */
182
+ static shouldAutoApply(similarity: number): boolean {
183
+ return similarity >= this.AUTO_THRESHOLD;
184
+ }
185
+
186
+ /**
187
+ * Should prompt user for move confirmation
188
+ */
189
+ static shouldPromptUser(similarity: number): boolean {
190
+ return (
191
+ similarity >= this.PROMPT_THRESHOLD && similarity < this.AUTO_THRESHOLD
192
+ );
193
+ }
194
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Compare two content pieces for equality
3
+ */
4
+ export function isContentEqual(
5
+ content1: string | Uint8Array | null,
6
+ content2: string | Uint8Array | null
7
+ ): boolean {
8
+ if (content1 === content2) return true;
9
+ if (!content1 || !content2) return false;
10
+
11
+ if (typeof content1 !== typeof content2) return false;
12
+
13
+ if (typeof content1 === "string") {
14
+ return content1 === content2;
15
+ } else {
16
+ // Compare Uint8Array
17
+ const buf1 = content1 as Uint8Array;
18
+ const buf2 = content2 as Uint8Array;
19
+
20
+ if (buf1.length !== buf2.length) return false;
21
+
22
+ for (let i = 0; i < buf1.length; i++) {
23
+ if (buf1[i] !== buf2[i]) return false;
24
+ }
25
+
26
+ return true;
27
+ }
28
+ }