pushwork 2.0.0-a.sub.1 → 2.0.0-preview.2
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/dist/branches.d.ts +20 -0
- package/dist/branches.d.ts.map +1 -0
- package/dist/branches.js +111 -0
- package/dist/branches.js.map +1 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +245 -270
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +17 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +84 -0
- package/dist/config.js.map +1 -0
- package/dist/fs-tree.d.ts +6 -0
- package/dist/fs-tree.d.ts.map +1 -0
- package/dist/fs-tree.js +99 -0
- package/dist/fs-tree.js.map +1 -0
- package/dist/ignore.d.ts +6 -0
- package/dist/ignore.d.ts.map +1 -0
- package/dist/ignore.js +74 -0
- package/dist/ignore.js.map +1 -0
- package/dist/index.d.ts +8 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -4
- package/dist/index.js.map +1 -1
- package/dist/log.d.ts +3 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +14 -0
- package/dist/log.js.map +1 -0
- package/dist/pushwork.d.ts +129 -0
- package/dist/pushwork.d.ts.map +1 -0
- package/dist/pushwork.js +1062 -0
- package/dist/pushwork.js.map +1 -0
- package/dist/repo.d.ts +14 -0
- package/dist/repo.d.ts.map +1 -0
- package/dist/repo.js +60 -0
- package/dist/repo.js.map +1 -0
- package/dist/shapes/custom.d.ts +3 -0
- package/dist/shapes/custom.d.ts.map +1 -0
- package/dist/shapes/custom.js +57 -0
- package/dist/shapes/custom.js.map +1 -0
- package/dist/shapes/file.d.ts +20 -0
- package/dist/shapes/file.d.ts.map +1 -0
- package/dist/shapes/file.js +140 -0
- package/dist/shapes/file.js.map +1 -0
- package/dist/shapes/index.d.ts +10 -0
- package/dist/shapes/index.d.ts.map +1 -0
- package/dist/shapes/index.js +35 -0
- package/dist/shapes/index.js.map +1 -0
- package/dist/shapes/patchwork-folder.d.ts +3 -0
- package/dist/shapes/patchwork-folder.d.ts.map +1 -0
- package/dist/shapes/patchwork-folder.js +160 -0
- package/dist/shapes/patchwork-folder.js.map +1 -0
- package/dist/shapes/types.d.ts +38 -0
- package/dist/shapes/types.d.ts.map +1 -0
- package/dist/shapes/types.js +52 -0
- package/dist/shapes/types.js.map +1 -0
- package/dist/shapes/vfs.d.ts +3 -0
- package/dist/shapes/vfs.d.ts.map +1 -0
- package/dist/shapes/vfs.js +92 -0
- package/dist/shapes/vfs.js.map +1 -0
- package/dist/stash.d.ts +23 -0
- package/dist/stash.d.ts.map +1 -0
- package/dist/stash.js +118 -0
- package/dist/stash.js.map +1 -0
- package/dist/version.d.ts +11 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +93 -0
- package/dist/version.js.map +1 -0
- package/package.json +19 -48
- package/patches/@automerge__automerge-repo@2.6.0-subduction.15.patch +26 -0
- package/.prettierrc +0 -9
- package/ARCHITECTURE-ACCORDING-TO-CLAUDE.md +0 -248
- package/CLAUDE.md +0 -141
- package/README.md +0 -221
- package/babel.config.js +0 -5
- package/dist/cli/commands.d.ts +0 -71
- package/dist/cli/commands.d.ts.map +0 -1
- package/dist/cli/commands.js +0 -794
- package/dist/cli/commands.js.map +0 -1
- package/dist/cli/index.d.ts +0 -2
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -19
- package/dist/cli/index.js.map +0 -1
- package/dist/commands.d.ts +0 -61
- package/dist/commands.d.ts.map +0 -1
- package/dist/commands.js +0 -861
- package/dist/commands.js.map +0 -1
- package/dist/config/index.d.ts +0 -71
- package/dist/config/index.d.ts.map +0 -1
- package/dist/config/index.js +0 -314
- package/dist/config/index.js.map +0 -1
- package/dist/core/change-detection.d.ts +0 -80
- package/dist/core/change-detection.d.ts.map +0 -1
- package/dist/core/change-detection.js +0 -523
- package/dist/core/change-detection.js.map +0 -1
- package/dist/core/config.d.ts +0 -81
- package/dist/core/config.d.ts.map +0 -1
- package/dist/core/config.js +0 -258
- package/dist/core/config.js.map +0 -1
- package/dist/core/index.d.ts +0 -6
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/index.js +0 -6
- package/dist/core/index.js.map +0 -1
- package/dist/core/move-detection.d.ts +0 -34
- package/dist/core/move-detection.d.ts.map +0 -1
- package/dist/core/move-detection.js +0 -121
- package/dist/core/move-detection.js.map +0 -1
- package/dist/core/snapshot.d.ts +0 -105
- package/dist/core/snapshot.d.ts.map +0 -1
- package/dist/core/snapshot.js +0 -217
- package/dist/core/snapshot.js.map +0 -1
- package/dist/core/sync-engine.d.ts +0 -157
- package/dist/core/sync-engine.d.ts.map +0 -1
- package/dist/core/sync-engine.js +0 -1379
- package/dist/core/sync-engine.js.map +0 -1
- package/dist/types/config.d.ts +0 -99
- package/dist/types/config.d.ts.map +0 -1
- package/dist/types/config.js +0 -5
- package/dist/types/config.js.map +0 -1
- package/dist/types/documents.d.ts +0 -88
- package/dist/types/documents.d.ts.map +0 -1
- package/dist/types/documents.js +0 -20
- package/dist/types/documents.js.map +0 -1
- package/dist/types/index.d.ts +0 -4
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -4
- package/dist/types/index.js.map +0 -1
- package/dist/types/snapshot.d.ts +0 -64
- package/dist/types/snapshot.d.ts.map +0 -1
- package/dist/types/snapshot.js +0 -2
- package/dist/types/snapshot.js.map +0 -1
- package/dist/utils/content-similarity.d.ts +0 -53
- package/dist/utils/content-similarity.d.ts.map +0 -1
- package/dist/utils/content-similarity.js +0 -155
- package/dist/utils/content-similarity.js.map +0 -1
- package/dist/utils/content.d.ts +0 -10
- package/dist/utils/content.d.ts.map +0 -1
- package/dist/utils/content.js +0 -31
- package/dist/utils/content.js.map +0 -1
- package/dist/utils/directory.d.ts +0 -24
- package/dist/utils/directory.d.ts.map +0 -1
- package/dist/utils/directory.js +0 -52
- package/dist/utils/directory.js.map +0 -1
- package/dist/utils/fs.d.ts +0 -74
- package/dist/utils/fs.d.ts.map +0 -1
- package/dist/utils/fs.js +0 -248
- package/dist/utils/fs.js.map +0 -1
- package/dist/utils/index.d.ts +0 -5
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -5
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/mime-types.d.ts +0 -13
- package/dist/utils/mime-types.d.ts.map +0 -1
- package/dist/utils/mime-types.js +0 -209
- package/dist/utils/mime-types.js.map +0 -1
- package/dist/utils/network-sync.d.ts +0 -36
- package/dist/utils/network-sync.d.ts.map +0 -1
- package/dist/utils/network-sync.js +0 -250
- package/dist/utils/network-sync.js.map +0 -1
- package/dist/utils/node-polyfills.d.ts +0 -9
- package/dist/utils/node-polyfills.d.ts.map +0 -1
- package/dist/utils/node-polyfills.js +0 -9
- package/dist/utils/node-polyfills.js.map +0 -1
- package/dist/utils/output.d.ts +0 -129
- package/dist/utils/output.d.ts.map +0 -1
- package/dist/utils/output.js +0 -368
- package/dist/utils/output.js.map +0 -1
- package/dist/utils/repo-factory.d.ts +0 -13
- package/dist/utils/repo-factory.d.ts.map +0 -1
- package/dist/utils/repo-factory.js +0 -46
- package/dist/utils/repo-factory.js.map +0 -1
- package/dist/utils/string-similarity.d.ts +0 -14
- package/dist/utils/string-similarity.d.ts.map +0 -1
- package/dist/utils/string-similarity.js +0 -39
- package/dist/utils/string-similarity.js.map +0 -1
- package/dist/utils/text-diff.d.ts +0 -37
- package/dist/utils/text-diff.d.ts.map +0 -1
- package/dist/utils/text-diff.js +0 -93
- package/dist/utils/text-diff.js.map +0 -1
- package/dist/utils/trace.d.ts +0 -19
- package/dist/utils/trace.d.ts.map +0 -1
- package/dist/utils/trace.js +0 -63
- package/dist/utils/trace.js.map +0 -1
- package/src/cli.ts +0 -442
- package/src/commands.ts +0 -1134
- package/src/core/change-detection.ts +0 -712
- package/src/core/config.ts +0 -313
- package/src/core/index.ts +0 -5
- package/src/core/move-detection.ts +0 -169
- package/src/core/snapshot.ts +0 -275
- package/src/core/sync-engine.ts +0 -1795
- package/src/index.ts +0 -4
- package/src/types/config.ts +0 -111
- package/src/types/documents.ts +0 -91
- package/src/types/index.ts +0 -3
- package/src/types/snapshot.ts +0 -67
- package/src/utils/content.ts +0 -34
- package/src/utils/directory.ts +0 -73
- package/src/utils/fs.ts +0 -297
- package/src/utils/index.ts +0 -4
- package/src/utils/mime-types.ts +0 -244
- package/src/utils/network-sync.ts +0 -319
- package/src/utils/node-polyfills.ts +0 -8
- package/src/utils/output.ts +0 -450
- package/src/utils/repo-factory.ts +0 -73
- package/src/utils/string-similarity.ts +0 -54
- package/src/utils/text-diff.ts +0 -101
- package/src/utils/trace.ts +0 -70
- package/test/integration/README.md +0 -328
- package/test/integration/clone-test.sh +0 -310
- package/test/integration/conflict-resolution-test.sh +0 -309
- package/test/integration/debug-both-nested.sh +0 -74
- package/test/integration/debug-concurrent-nested.sh +0 -87
- package/test/integration/debug-nested.sh +0 -73
- package/test/integration/deletion-behavior-test.sh +0 -487
- package/test/integration/deletion-sync-test-simple.sh +0 -193
- package/test/integration/deletion-sync-test.sh +0 -297
- package/test/integration/exclude-patterns.test.ts +0 -144
- package/test/integration/full-integration-test.sh +0 -363
- package/test/integration/fuzzer.test.ts +0 -818
- package/test/integration/in-memory-sync.test.ts +0 -830
- package/test/integration/init-sync.test.ts +0 -89
- package/test/integration/manual-sync-test.sh +0 -84
- package/test/integration/sync-deletion.test.ts +0 -280
- package/test/integration/sync-flow.test.ts +0 -291
- package/test/jest.setup.ts +0 -34
- package/test/run-tests.sh +0 -225
- package/test/unit/deletion-behavior.test.ts +0 -249
- package/test/unit/enhanced-mime-detection.test.ts +0 -244
- package/test/unit/snapshot.test.ts +0 -404
- package/test/unit/sync-convergence.test.ts +0 -298
- package/test/unit/sync-timing.test.ts +0 -134
- package/test/unit/utils.test.ts +0 -366
- package/tsconfig.json +0 -23
package/src/core/config.ts
DELETED
|
@@ -1,313 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs/promises";
|
|
2
|
-
import * as path from "path";
|
|
3
|
-
import * as os from "os";
|
|
4
|
-
import {
|
|
5
|
-
GlobalConfig,
|
|
6
|
-
DirectoryConfig,
|
|
7
|
-
DEFAULT_SYNC_SERVER,
|
|
8
|
-
} from "../types/index.js";
|
|
9
|
-
import { pathExists, ensureDirectoryExists } from "../utils/index.js";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Configuration manager for pushwork
|
|
13
|
-
*/
|
|
14
|
-
export class ConfigManager {
|
|
15
|
-
private static readonly GLOBAL_CONFIG_DIR = ".pushwork";
|
|
16
|
-
private static readonly CONFIG_FILENAME = "config.json";
|
|
17
|
-
|
|
18
|
-
static readonly CONFIG_DIR = ".pushwork";
|
|
19
|
-
|
|
20
|
-
constructor(private workingDir?: string) {}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Get global configuration path
|
|
24
|
-
*/
|
|
25
|
-
private getGlobalConfigPath(): string {
|
|
26
|
-
return path.join(
|
|
27
|
-
os.homedir(),
|
|
28
|
-
ConfigManager.GLOBAL_CONFIG_DIR,
|
|
29
|
-
ConfigManager.CONFIG_FILENAME
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Get local configuration path
|
|
35
|
-
*/
|
|
36
|
-
private getLocalConfigPath(): string {
|
|
37
|
-
if (!this.workingDir) {
|
|
38
|
-
throw new Error("Working directory not set for local config");
|
|
39
|
-
}
|
|
40
|
-
return path.join(
|
|
41
|
-
this.workingDir,
|
|
42
|
-
ConfigManager.CONFIG_DIR,
|
|
43
|
-
ConfigManager.CONFIG_FILENAME
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Load global configuration
|
|
49
|
-
*/
|
|
50
|
-
async loadGlobal(): Promise<GlobalConfig | null> {
|
|
51
|
-
try {
|
|
52
|
-
const configPath = this.getGlobalConfigPath();
|
|
53
|
-
if (!(await pathExists(configPath))) {
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const content = await fs.readFile(configPath, "utf8");
|
|
58
|
-
return JSON.parse(content) as GlobalConfig;
|
|
59
|
-
} catch (error) {
|
|
60
|
-
// Failed to load global config
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Save global configuration
|
|
67
|
-
*/
|
|
68
|
-
async saveGlobal(config: GlobalConfig): Promise<void> {
|
|
69
|
-
try {
|
|
70
|
-
const configPath = this.getGlobalConfigPath();
|
|
71
|
-
await ensureDirectoryExists(path.dirname(configPath));
|
|
72
|
-
|
|
73
|
-
const content = JSON.stringify(config, null, 2);
|
|
74
|
-
await fs.writeFile(configPath, content, "utf8");
|
|
75
|
-
} catch (error) {
|
|
76
|
-
throw new Error(`Failed to save global config: ${error}`);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Load local/directory configuration
|
|
82
|
-
*/
|
|
83
|
-
async load(): Promise<DirectoryConfig | null> {
|
|
84
|
-
if (!this.workingDir) {
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
try {
|
|
89
|
-
const configPath = this.getLocalConfigPath();
|
|
90
|
-
if (!(await pathExists(configPath))) {
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const content = await fs.readFile(configPath, "utf8");
|
|
95
|
-
return JSON.parse(content) as DirectoryConfig;
|
|
96
|
-
} catch (error) {
|
|
97
|
-
// Failed to load local config
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Save local/directory configuration
|
|
104
|
-
*/
|
|
105
|
-
async save(config: DirectoryConfig): Promise<void> {
|
|
106
|
-
if (!this.workingDir) {
|
|
107
|
-
throw new Error("Working directory not set for local config");
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
try {
|
|
111
|
-
const configPath = this.getLocalConfigPath();
|
|
112
|
-
await ensureDirectoryExists(path.dirname(configPath));
|
|
113
|
-
|
|
114
|
-
const content = JSON.stringify(config, null, 2);
|
|
115
|
-
await fs.writeFile(configPath, content, "utf8");
|
|
116
|
-
} catch (error) {
|
|
117
|
-
throw new Error(`Failed to save local config: ${error}`);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
private getDefaultGlobalConfig(): GlobalConfig {
|
|
122
|
-
return {
|
|
123
|
-
exclude_patterns: [
|
|
124
|
-
".git",
|
|
125
|
-
"node_modules",
|
|
126
|
-
"*.tmp",
|
|
127
|
-
".DS_Store",
|
|
128
|
-
".pushwork",
|
|
129
|
-
],
|
|
130
|
-
artifact_directories: ["dist"],
|
|
131
|
-
sync_server: DEFAULT_SYNC_SERVER,
|
|
132
|
-
sync: {
|
|
133
|
-
move_detection_threshold: 0.7,
|
|
134
|
-
},
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Get default configuration
|
|
140
|
-
*/
|
|
141
|
-
getDefaultDirectoryConfig(): DirectoryConfig {
|
|
142
|
-
return {
|
|
143
|
-
sync_enabled: true,
|
|
144
|
-
sync_server: DEFAULT_SYNC_SERVER,
|
|
145
|
-
exclude_patterns: [
|
|
146
|
-
".git",
|
|
147
|
-
"node_modules",
|
|
148
|
-
"*.tmp",
|
|
149
|
-
".pushwork",
|
|
150
|
-
".DS_Store",
|
|
151
|
-
],
|
|
152
|
-
artifact_directories: ["dist"],
|
|
153
|
-
sync: {
|
|
154
|
-
move_detection_threshold: 0.7,
|
|
155
|
-
},
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Get merged configuration (global + local)
|
|
161
|
-
*/
|
|
162
|
-
async getMerged(): Promise<DirectoryConfig> {
|
|
163
|
-
const globalConfig = await this.loadGlobal();
|
|
164
|
-
const localConfig = await this.load();
|
|
165
|
-
|
|
166
|
-
// Merge configurations: default < global < local
|
|
167
|
-
let merged = this.getDefaultDirectoryConfig();
|
|
168
|
-
|
|
169
|
-
if (globalConfig) {
|
|
170
|
-
merged = this.mergeConfigs(merged, globalConfig);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (localConfig) {
|
|
174
|
-
merged = this.mergeConfigs(merged, localConfig);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
return merged;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Initialize with CLI option overrides
|
|
182
|
-
* Creates a new config with defaults + CLI overrides and saves it
|
|
183
|
-
*/
|
|
184
|
-
async initializeWithOverrides(
|
|
185
|
-
overrides: Partial<DirectoryConfig> = {}
|
|
186
|
-
): Promise<DirectoryConfig> {
|
|
187
|
-
const config = this.mergeConfigs(this.getDefaultDirectoryConfig(), overrides);
|
|
188
|
-
await this.save(config);
|
|
189
|
-
return config;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Merge two configuration objects
|
|
194
|
-
*/
|
|
195
|
-
private mergeConfigs(
|
|
196
|
-
base: DirectoryConfig,
|
|
197
|
-
override: Partial<DirectoryConfig> | GlobalConfig
|
|
198
|
-
): DirectoryConfig {
|
|
199
|
-
const merged = { ...base };
|
|
200
|
-
|
|
201
|
-
if ("sync_server" in override && override.sync_server !== undefined) {
|
|
202
|
-
merged.sync_server = override.sync_server;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
if ("sync_enabled" in override && override.sync_enabled !== undefined) {
|
|
206
|
-
merged.sync_enabled = override.sync_enabled;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Handle GlobalConfig structure
|
|
210
|
-
if ("exclude_patterns" in override && override.exclude_patterns) {
|
|
211
|
-
merged.exclude_patterns = override.exclude_patterns;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
if ("artifact_directories" in override && override.artifact_directories) {
|
|
215
|
-
merged.artifact_directories = override.artifact_directories;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if ("sync" in override && override.sync) {
|
|
219
|
-
merged.sync = { ...merged.sync, ...override.sync };
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
return merged;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* Create default global configuration
|
|
227
|
-
*/
|
|
228
|
-
async createDefaultGlobal(): Promise<void> {
|
|
229
|
-
const defaultGlobal = this.getDefaultGlobalConfig();
|
|
230
|
-
await this.saveGlobal(defaultGlobal);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Check if global configuration exists
|
|
235
|
-
*/
|
|
236
|
-
async globalConfigExists(): Promise<boolean> {
|
|
237
|
-
return await pathExists(this.getGlobalConfigPath());
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Check if local configuration exists
|
|
242
|
-
*/
|
|
243
|
-
async localConfigExists(): Promise<boolean> {
|
|
244
|
-
if (!this.workingDir) return false;
|
|
245
|
-
return await pathExists(this.getLocalConfigPath());
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Get configuration value by path (e.g., 'sync.move_detection_threshold')
|
|
250
|
-
*/
|
|
251
|
-
async getValue(keyPath: string): Promise<any> {
|
|
252
|
-
const config = await this.getMerged();
|
|
253
|
-
|
|
254
|
-
const keys = keyPath.split(".");
|
|
255
|
-
let value: any = config;
|
|
256
|
-
|
|
257
|
-
for (const key of keys) {
|
|
258
|
-
if (value && typeof value === "object" && key in value) {
|
|
259
|
-
value = value[key];
|
|
260
|
-
} else {
|
|
261
|
-
return undefined;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
return value;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Set configuration value by path
|
|
270
|
-
*/
|
|
271
|
-
async setValue(keyPath: string, value: any): Promise<void> {
|
|
272
|
-
const config = (await this.load()) || ({} as DirectoryConfig);
|
|
273
|
-
|
|
274
|
-
const keys = keyPath.split(".");
|
|
275
|
-
let current: any = config;
|
|
276
|
-
|
|
277
|
-
// Navigate to the parent of the target key
|
|
278
|
-
for (let i = 0; i < keys.length - 1; i++) {
|
|
279
|
-
const key = keys[i];
|
|
280
|
-
if (!(key in current) || typeof current[key] !== "object") {
|
|
281
|
-
current[key] = {};
|
|
282
|
-
}
|
|
283
|
-
current = current[key];
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// Set the value
|
|
287
|
-
const finalKey = keys[keys.length - 1];
|
|
288
|
-
current[finalKey] = value;
|
|
289
|
-
|
|
290
|
-
await this.save(config);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Validate configuration
|
|
295
|
-
*/
|
|
296
|
-
validate(config: DirectoryConfig): { valid: boolean; errors: string[] } {
|
|
297
|
-
const errors: string[] = [];
|
|
298
|
-
|
|
299
|
-
if (config.sync?.move_detection_threshold !== undefined) {
|
|
300
|
-
if (
|
|
301
|
-
config.sync.move_detection_threshold < 0 ||
|
|
302
|
-
config.sync.move_detection_threshold > 1
|
|
303
|
-
) {
|
|
304
|
-
errors.push("move_detection_threshold must be between 0 and 1");
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
return {
|
|
309
|
-
valid: errors.length === 0,
|
|
310
|
-
errors,
|
|
311
|
-
};
|
|
312
|
-
}
|
|
313
|
-
}
|
package/src/core/index.ts
DELETED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import { SyncSnapshot, MoveCandidate } from "../types/index.js";
|
|
2
|
-
import { isTextFile } from "../utils/index.js";
|
|
3
|
-
import { stringSimilarity } from "../utils/string-similarity.js";
|
|
4
|
-
import { ChangeType, DetectedChange } from "../types/index.js";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Simplified move detection engine
|
|
8
|
-
*/
|
|
9
|
-
export class MoveDetector {
|
|
10
|
-
private readonly moveThreshold: number;
|
|
11
|
-
|
|
12
|
-
constructor(moveThreshold: number = 0.7) {
|
|
13
|
-
this.moveThreshold = moveThreshold;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Detect file moves by analyzing deleted and created files
|
|
18
|
-
*/
|
|
19
|
-
async detectMoves(
|
|
20
|
-
changes: DetectedChange[],
|
|
21
|
-
snapshot: SyncSnapshot
|
|
22
|
-
): Promise<{ moves: MoveCandidate[]; remainingChanges: DetectedChange[] }> {
|
|
23
|
-
const deletedFiles = changes.filter(
|
|
24
|
-
(c) => !c.localContent && c.changeType === ChangeType.LOCAL_ONLY
|
|
25
|
-
);
|
|
26
|
-
const createdFiles = changes.filter(
|
|
27
|
-
(c) =>
|
|
28
|
-
c.localContent &&
|
|
29
|
-
c.changeType === ChangeType.LOCAL_ONLY &&
|
|
30
|
-
!snapshot.files.has(c.path)
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
if (deletedFiles.length === 0 || createdFiles.length === 0) {
|
|
34
|
-
return { moves: [], remainingChanges: changes };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const moves: MoveCandidate[] = [];
|
|
38
|
-
const usedCreations = new Set<string>();
|
|
39
|
-
const usedDeletions = new Set<string>();
|
|
40
|
-
|
|
41
|
-
// Find potential moves by comparing content
|
|
42
|
-
for (const deletedFile of deletedFiles) {
|
|
43
|
-
const deletedContent = deletedFile.remoteContent;
|
|
44
|
-
if (deletedContent === null) continue;
|
|
45
|
-
|
|
46
|
-
let bestMatch: { file: DetectedChange; similarity: number } | null = null;
|
|
47
|
-
|
|
48
|
-
for (const createdFile of createdFiles) {
|
|
49
|
-
if (usedCreations.has(createdFile.path)) continue;
|
|
50
|
-
if (createdFile.localContent === null) continue;
|
|
51
|
-
|
|
52
|
-
const similarity = await this.calculateSimilarity(
|
|
53
|
-
deletedContent,
|
|
54
|
-
createdFile.localContent,
|
|
55
|
-
deletedFile.path
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
if (similarity >= this.moveThreshold) {
|
|
59
|
-
if (!bestMatch || similarity > bestMatch.similarity) {
|
|
60
|
-
bestMatch = { file: createdFile, similarity };
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (bestMatch) {
|
|
66
|
-
// If we detected a move above threshold, we apply it
|
|
67
|
-
moves.push({
|
|
68
|
-
fromPath: deletedFile.path,
|
|
69
|
-
toPath: bestMatch.file.path,
|
|
70
|
-
similarity: bestMatch.similarity,
|
|
71
|
-
newContent: bestMatch.file.localContent || undefined,
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
// Consume the deletion and creation (move replaces both)
|
|
75
|
-
usedCreations.add(bestMatch.file.path);
|
|
76
|
-
usedDeletions.add(deletedFile.path);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const remainingChanges = changes.filter(
|
|
81
|
-
(change) =>
|
|
82
|
-
!usedCreations.has(change.path) && !usedDeletions.has(change.path)
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
return { moves, remainingChanges };
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Calculate similarity between two content pieces
|
|
90
|
-
* Optimized for speed while maintaining accuracy
|
|
91
|
-
*/
|
|
92
|
-
private async calculateSimilarity(
|
|
93
|
-
content1: string | Uint8Array,
|
|
94
|
-
content2: string | Uint8Array,
|
|
95
|
-
path: string
|
|
96
|
-
): Promise<number> {
|
|
97
|
-
if (content1 === content2) return 1.0;
|
|
98
|
-
|
|
99
|
-
// Early exit: size difference too large
|
|
100
|
-
const size1 =
|
|
101
|
-
typeof content1 === "string" ? content1.length : content1.length;
|
|
102
|
-
const size2 =
|
|
103
|
-
typeof content2 === "string" ? content2.length : content2.length;
|
|
104
|
-
const sizeDiff = Math.abs(size1 - size2) / Math.max(size1, size2);
|
|
105
|
-
if (sizeDiff > 0.5) return 0.0;
|
|
106
|
-
|
|
107
|
-
// Binary files: hash mismatch = not a move
|
|
108
|
-
const isText = await isTextFile(path);
|
|
109
|
-
if (!isText) return 0.0;
|
|
110
|
-
|
|
111
|
-
// Text files: use string similarity
|
|
112
|
-
const str1 =
|
|
113
|
-
typeof content1 === "string" ? content1 : this.bufferToString(content1);
|
|
114
|
-
const str2 =
|
|
115
|
-
typeof content2 === "string" ? content2 : this.bufferToString(content2);
|
|
116
|
-
|
|
117
|
-
// For small files (<4KB), compare full content
|
|
118
|
-
if (size1 < 4096 && size2 < 4096) {
|
|
119
|
-
return stringSimilarity(str1, str2);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// For large files, sample 3 locations
|
|
123
|
-
const samples1 = this.getSamples(str1);
|
|
124
|
-
const samples2 = this.getSamples(str2);
|
|
125
|
-
|
|
126
|
-
let totalSimilarity = 0;
|
|
127
|
-
for (let i = 0; i < Math.min(samples1.length, samples2.length); i++) {
|
|
128
|
-
totalSimilarity += stringSimilarity(samples1[i], samples2[i]);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return totalSimilarity / Math.min(samples1.length, samples2.length);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Get representative samples from content (beginning, middle, end)
|
|
136
|
-
*/
|
|
137
|
-
private getSamples(str: string): string[] {
|
|
138
|
-
const CHUNK_SIZE = 1024;
|
|
139
|
-
const length = str.length;
|
|
140
|
-
|
|
141
|
-
if (length <= CHUNK_SIZE) {
|
|
142
|
-
return [str];
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return [
|
|
146
|
-
str.slice(0, CHUNK_SIZE), // Beginning
|
|
147
|
-
str.slice(
|
|
148
|
-
Math.floor(length / 2) - Math.floor(CHUNK_SIZE / 2),
|
|
149
|
-
Math.floor(length / 2) + Math.floor(CHUNK_SIZE / 2)
|
|
150
|
-
), // Middle
|
|
151
|
-
str.slice(-CHUNK_SIZE), // End
|
|
152
|
-
];
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Convert buffer to string (for text comparison)
|
|
157
|
-
*/
|
|
158
|
-
private bufferToString(buffer: Uint8Array): string {
|
|
159
|
-
return new TextDecoder().decode(buffer);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Format move for display
|
|
164
|
-
*/
|
|
165
|
-
formatMove(move: MoveCandidate): string {
|
|
166
|
-
const percentage = Math.round(move.similarity * 100);
|
|
167
|
-
return `${move.fromPath} → ${move.toPath} (${percentage}% similar)`;
|
|
168
|
-
}
|
|
169
|
-
}
|