amalgm 0.1.245 → 0.1.246
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/README.md +12 -0
- package/lib/cli.js +146 -8
- package/lib/layout.js +89 -4
- package/lib/shared-realtime-tunnel.js +384 -43
- package/lib/supervisor.js +0 -3
- package/package.json +2 -2
- package/runtime/scripts/amalgm-mcp/adapters/contract.js +197 -0
- package/runtime/scripts/amalgm-mcp/adapters/filesystem.js +135 -0
- package/runtime/scripts/amalgm-mcp/adapters/git.js +84 -0
- package/runtime/scripts/amalgm-mcp/adapters/index.js +43 -0
- package/runtime/scripts/amalgm-mcp/adapters/reference.js +31 -0
- package/runtime/scripts/amalgm-mcp/adapters/truth.js +19 -0
- package/runtime/scripts/amalgm-mcp/browser/cookie-jar.js +20 -68
- package/runtime/scripts/amalgm-mcp/index.js +4 -0
- package/runtime/scripts/amalgm-mcp/lib/layout.js +89 -4
- package/runtime/scripts/amalgm-mcp/observer/README.md +259 -0
- package/runtime/scripts/amalgm-mcp/observer/apply.js +150 -0
- package/runtime/scripts/amalgm-mcp/observer/continuity.js +111 -0
- package/runtime/scripts/amalgm-mcp/observer/edges.js +111 -0
- package/runtime/scripts/amalgm-mcp/observer/index.js +1146 -0
- package/runtime/scripts/amalgm-mcp/observer/scan.js +387 -0
- package/runtime/scripts/amalgm-mcp/observer/store.js +262 -0
- package/runtime/scripts/amalgm-mcp/observer/verify.js +200 -0
- package/runtime/scripts/amalgm-mcp/observer/watch.js +62 -0
- package/runtime/scripts/amalgm-mcp/project-context/store.js +19 -87
- package/runtime/scripts/amalgm-mcp/registration/classify.js +33 -0
- package/runtime/scripts/amalgm-mcp/registration/entity-cloud.js +456 -0
- package/runtime/scripts/amalgm-mcp/registration/entity-content.js +345 -0
- package/runtime/scripts/amalgm-mcp/registration/index.js +752 -0
- package/runtime/scripts/amalgm-mcp/registration/refusal.js +42 -0
- package/runtime/scripts/amalgm-mcp/registration/repo-followers.js +127 -0
- package/runtime/scripts/amalgm-mcp/registration/repo-states.js +109 -0
- package/runtime/scripts/amalgm-mcp/registration/service.js +310 -0
- package/runtime/scripts/amalgm-mcp/registration/tree.js +178 -0
- package/runtime/scripts/amalgm-mcp/registry/evidence.js +826 -0
- package/runtime/scripts/amalgm-mcp/registry/index.js +666 -0
- package/runtime/scripts/amalgm-mcp/registry/store.js +290 -0
- package/runtime/scripts/amalgm-mcp/repocard/README.md +103 -0
- package/runtime/scripts/amalgm-mcp/repocard/apply.js +111 -0
- package/runtime/scripts/amalgm-mcp/repocard/capture-worker.js +94 -0
- package/runtime/scripts/amalgm-mcp/repocard/capture.js +149 -0
- package/runtime/scripts/amalgm-mcp/repocard/follow.js +310 -0
- package/runtime/scripts/amalgm-mcp/repocard/git.js +48 -0
- package/runtime/scripts/amalgm-mcp/repocard/index.js +48 -0
- package/runtime/scripts/amalgm-mcp/server/local-service-router.js +2 -0
- package/runtime/scripts/amalgm-mcp/server/routes/entities.js +287 -0
- package/runtime/scripts/amalgm-mcp/server/routes/state.js +0 -13
- package/runtime/scripts/amalgm-mcp/state/attachments.js +11 -45
- package/runtime/scripts/amalgm-mcp/state/db.js +46 -215
- package/runtime/scripts/amalgm-mcp/state/docs.js +301 -1101
- package/runtime/scripts/amalgm-mcp/state/mutation-contracts.js +0 -99
- package/runtime/scripts/amalgm-mcp/state/mutations.js +130 -507
- package/runtime/scripts/amalgm-mcp/state/promotions.js +1 -21
- package/runtime/scripts/amalgm-mcp/state/replicas.js +13 -134
- package/runtime/scripts/amalgm-mcp/state/shared-rest.js +3 -46
- package/runtime/scripts/amalgm-mcp/tests/adapters.test.js +379 -0
- package/runtime/scripts/amalgm-mcp/tests/browser-cookie-cloud.test.js +0 -193
- package/runtime/scripts/amalgm-mcp/tests/doorbell.matrix.life.test.js +449 -0
- package/runtime/scripts/amalgm-mcp/tests/doorbell.matrix.rig.js +107 -0
- package/runtime/scripts/amalgm-mcp/tests/doorbell.matrix.watch.test.js +213 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/adapter.js +72 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/assert.js +497 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/bind.js +91 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/graph.js +1031 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/ledger.js +234 -0
- package/runtime/scripts/amalgm-mcp/tests/entity-oracle/referee.js +118 -0
- package/runtime/scripts/amalgm-mcp/tests/entity.oracle.test.js +1365 -0
- package/runtime/scripts/amalgm-mcp/tests/entity.registry.test.js +967 -0
- package/runtime/scripts/amalgm-mcp/tests/entity.rig.js +239 -0
- package/runtime/scripts/amalgm-mcp/tests/entity.storm.test.js +486 -0
- package/runtime/scripts/amalgm-mcp/tests/fake-watch.js +35 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.links.test.js +289 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.rig.js +103 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.roots.test.js +505 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.storm.test.js +389 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.territory.test.js +845 -0
- package/runtime/scripts/amalgm-mcp/tests/observer.test.js +657 -0
- package/runtime/scripts/amalgm-mcp/tests/project-context.test.js +17 -32
- package/runtime/scripts/amalgm-mcp/tests/registration.service.test.js +256 -0
- package/runtime/scripts/amalgm-mcp/tests/registration.test.js +931 -0
- package/runtime/scripts/amalgm-mcp/tests/repocard.storm.test.js +284 -0
- package/runtime/scripts/amalgm-mcp/tests/repocard.test.js +371 -0
- package/runtime/scripts/amalgm-mcp/tests/repofollow.storm.test.js +435 -0
- package/runtime/scripts/amalgm-mcp/tests/repofollow.test.js +675 -0
- package/runtime/scripts/amalgm-mcp/tests/shared-tunnel-rehydration.test.js +0 -84
- package/runtime/scripts/amalgm-mcp/tests/state-docs.test.js +13 -376
- package/runtime/scripts/amalgm-mcp/tests/state-mutations.test.js +0 -309
- package/runtime/scripts/amalgm-mcp/tests/state-shared-replica.test.js +30 -758
- package/runtime/scripts/amalgm-mcp/tests/workspace-cards-store.test.js +3 -72
- package/runtime/scripts/amalgm-mcp/tests/workspace-cards.test.js +1 -89
- package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint-reducer.test.js +24 -55
- package/runtime/scripts/amalgm-mcp/tests/workspace-checkpoint.test.js +141 -424
- package/runtime/scripts/amalgm-mcp/tests/workspace-object-tunnel.test.js +3 -171
- package/runtime/scripts/amalgm-mcp/tests/workspace-objects.test.js +7 -220
- package/runtime/scripts/amalgm-mcp/tests/workspace-publication-invariants.test.js +236 -179
- package/runtime/scripts/amalgm-mcp/tests/workspace-reducer.test.js +326 -2228
- package/runtime/scripts/amalgm-mcp/tests/workspace-tree-cloud.test.js +3 -5319
- package/runtime/scripts/amalgm-mcp/tests/workspace-tree-core.test.js +1 -191
- package/runtime/scripts/amalgm-mcp/tests/workspace-tree-store.test.js +17 -3140
- package/runtime/scripts/amalgm-mcp/tests/workspace-wall.test.js +740 -137
- package/runtime/scripts/amalgm-mcp/workspace/access-store.js +1 -5
- package/runtime/scripts/amalgm-mcp/workspace/card.js +11 -35
- package/runtime/scripts/amalgm-mcp/workspace/cards.js +14 -39
- package/runtime/scripts/amalgm-mcp/workspace/checkpoint.js +147 -587
- package/runtime/scripts/amalgm-mcp/workspace/git.js +34 -406
- package/runtime/scripts/amalgm-mcp/workspace/merge.js +181 -102
- package/runtime/scripts/amalgm-mcp/workspace/objects.js +56 -221
- package/runtime/scripts/amalgm-mcp/workspace/reducer.js +538 -1964
- package/runtime/scripts/amalgm-mcp/workspace/rest.js +28 -49
- package/runtime/scripts/amalgm-mcp/workspace/store.js +4 -77
- package/runtime/scripts/amalgm-mcp/workspace/transition.js +192 -1734
- package/runtime/scripts/amalgm-mcp/workspace/tree/inclusion.js +1 -7
- package/runtime/scripts/amalgm-mcp/workspace/tree/local-state.js +61 -73
- package/runtime/scripts/amalgm-mcp/workspace/tree/reducer.js +6 -51
- package/runtime/scripts/amalgm-mcp/workspace/tree/snapshot.js +8 -79
- package/runtime/scripts/amalgm-mcp/workspace/tree-cloud.js +146 -3461
- package/runtime/scripts/amalgm-mcp/workspace/tree-store.js +71 -899
- package/runtime/scripts/amalgm-mcp/workspace/wall.js +538 -370
- package/runtime/scripts/local-gateway.js +1 -0
- package/runtime/scripts/amalgm-mcp/state/doc-disk.js +0 -550
- package/runtime/scripts/amalgm-mcp/tests/code-project-stream.test.js +0 -399
- package/runtime/scripts/amalgm-mcp/tests/fixtures/code-project-stream-v2.json +0 -1
- package/runtime/scripts/amalgm-mcp/tests/workspace-durable.test.js +0 -24
- package/runtime/scripts/amalgm-mcp/tests/workspace-local-intent.test.js +0 -1999
- package/runtime/scripts/amalgm-mcp/tests/workspace-native-file-transaction.test.js +0 -364
- package/runtime/scripts/amalgm-mcp/tests/workspace-provenance.test.js +0 -185
- package/runtime/scripts/amalgm-mcp/tests/workspace-reducer-liveness.test.js +0 -26
- package/runtime/scripts/amalgm-mcp/tests/workspace-sync-stats.test.js +0 -119
- package/runtime/scripts/amalgm-mcp/tests/workspace-transition.test.js +0 -1446
- package/runtime/scripts/amalgm-mcp/workspace/content.js +0 -32
- package/runtime/scripts/amalgm-mcp/workspace/durable.js +0 -45
- package/runtime/scripts/amalgm-mcp/workspace/intent.js +0 -535
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/BUILD.md +0 -31
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/bin/darwin-arm64/amalgm-file-transaction +0 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/bin/linux-x64/amalgm-file-transaction +0 -0
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/build.mjs +0 -72
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/client.js +0 -165
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/go.mod +0 -5
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/go.sum +0 -2
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/main.go +0 -2201
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/manifest.json +0 -19
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/move_darwin.go +0 -18
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/move_linux.go +0 -18
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/process_darwin.go +0 -33
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/process_linux.go +0 -47
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/verify.mjs +0 -43
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/writers_darwin.go +0 -114
- package/runtime/scripts/amalgm-mcp/workspace/native-file-transaction/writers_linux.go +0 -84
- package/runtime/scripts/amalgm-mcp/workspace/project-stream.js +0 -771
- package/runtime/scripts/amalgm-mcp/workspace/provenance.js +0 -240
- package/runtime/scripts/amalgm-mcp/workspace/sync-stats.js +0 -240
- package/runtime/scripts/amalgm-mcp/workspace/tree/floor.js +0 -100
- package/runtime/scripts/amalgm-mcp/workspace/tree/observation-holds.js +0 -127
- package/runtime/scripts/amalgm-mcp/workspace/tree/path-state.js +0 -12
- package/runtime/scripts/amalgm-mcp/workspace/tree/root-binding.js +0 -125
- package/runtime/scripts/amalgm-mcp/workspace/tree/symlinks.js +0 -76
- package/runtime/scripts/amalgm-mcp/workspace/tree-materialization.js +0 -3766
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
const fs = require('fs');
|
|
21
|
-
const os = require('os');
|
|
22
21
|
const path = require('path');
|
|
23
22
|
const { execFile } = require('child_process');
|
|
24
23
|
const { promisify } = require('util');
|
|
@@ -47,15 +46,14 @@ class GitError extends Error {
|
|
|
47
46
|
}
|
|
48
47
|
}
|
|
49
48
|
|
|
50
|
-
async function git(args, cwd, { okCodes = [0]
|
|
49
|
+
async function git(args, cwd, { okCodes = [0] } = {}) {
|
|
51
50
|
try {
|
|
52
|
-
const
|
|
51
|
+
const { stdout } = await execFileAsync('git', args, {
|
|
53
52
|
cwd,
|
|
54
|
-
env: { ...process.env, ...GIT_ENV
|
|
53
|
+
env: { ...process.env, ...GIT_ENV },
|
|
55
54
|
timeout: 15_000,
|
|
56
55
|
maxBuffer: 128 * 1024 * 1024,
|
|
57
|
-
};
|
|
58
|
-
const { stdout } = await execFileAsync('git', args, options);
|
|
56
|
+
});
|
|
59
57
|
return { stdout, exitCode: 0 };
|
|
60
58
|
} catch (error) {
|
|
61
59
|
const exitCode = typeof error.code === 'number' ? error.code : 1;
|
|
@@ -66,189 +64,6 @@ async function git(args, cwd, { okCodes = [0], env = {} } = {}) {
|
|
|
66
64
|
}
|
|
67
65
|
}
|
|
68
66
|
|
|
69
|
-
function changeIdentity(file) {
|
|
70
|
-
try {
|
|
71
|
-
const stat = fs.lstatSync(file, { bigint: true });
|
|
72
|
-
return {
|
|
73
|
-
dev: stat.dev.toString(),
|
|
74
|
-
ino: stat.ino.toString(),
|
|
75
|
-
mode: stat.mode.toString(),
|
|
76
|
-
size: stat.size.toString(),
|
|
77
|
-
mtimeNs: stat.mtimeNs.toString(),
|
|
78
|
-
ctimeNs: stat.ctimeNs.toString(),
|
|
79
|
-
};
|
|
80
|
-
} catch (error) {
|
|
81
|
-
if (error?.code === 'ENOENT') return null;
|
|
82
|
-
throw error;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function sameIdentity(left, right) {
|
|
87
|
-
return JSON.stringify(left) === JSON.stringify(right);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async function indexPath(worktreeRoot) {
|
|
91
|
-
const { stdout } = await git(
|
|
92
|
-
['-C', worktreeRoot, 'rev-parse', '--git-path', 'index'],
|
|
93
|
-
worktreeRoot,
|
|
94
|
-
);
|
|
95
|
-
const value = stdout.trim();
|
|
96
|
-
return path.isAbsolute(value) ? value : path.resolve(worktreeRoot, value);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function worktreePathIdentity(worktreeRoot, relativePath) {
|
|
100
|
-
const parts = relativePath.split('/');
|
|
101
|
-
let current = worktreeRoot;
|
|
102
|
-
for (let index = 0; index < parts.length; index += 1) {
|
|
103
|
-
current = path.join(current, parts[index]);
|
|
104
|
-
let stat;
|
|
105
|
-
try {
|
|
106
|
-
stat = fs.lstatSync(current, { bigint: true });
|
|
107
|
-
} catch (error) {
|
|
108
|
-
if (error?.code === 'ENOENT') return { kind: 'absent' };
|
|
109
|
-
throw error;
|
|
110
|
-
}
|
|
111
|
-
if (index < parts.length - 1 && !stat.isDirectory()) {
|
|
112
|
-
return { kind: 'unsafe-parent' };
|
|
113
|
-
}
|
|
114
|
-
if (index === parts.length - 1) {
|
|
115
|
-
return {
|
|
116
|
-
kind: stat.isSymbolicLink()
|
|
117
|
-
? 'symlink'
|
|
118
|
-
: stat.isFile()
|
|
119
|
-
? 'file'
|
|
120
|
-
: 'other',
|
|
121
|
-
dev: stat.dev.toString(),
|
|
122
|
-
ino: stat.ino.toString(),
|
|
123
|
-
mode: stat.mode.toString(),
|
|
124
|
-
size: stat.size.toString(),
|
|
125
|
-
mtimeNs: stat.mtimeNs.toString(),
|
|
126
|
-
ctimeNs: stat.ctimeNs.toString(),
|
|
127
|
-
...(stat.isSymbolicLink() ? { target: fs.readlinkSync(current) } : {}),
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
return { kind: 'absent' };
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function worktreeIdentity(worktreeRoot, paths) {
|
|
135
|
-
return Object.fromEntries(
|
|
136
|
-
paths.map((relativePath) => [
|
|
137
|
-
relativePath,
|
|
138
|
-
worktreePathIdentity(worktreeRoot, relativePath),
|
|
139
|
-
]),
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
async function ignoredNamespacePaths(worktreeRoot) {
|
|
144
|
-
const { stdout } = await git(
|
|
145
|
-
[
|
|
146
|
-
'-C', worktreeRoot,
|
|
147
|
-
'ls-files',
|
|
148
|
-
'--others',
|
|
149
|
-
'--ignored',
|
|
150
|
-
'--exclude-standard',
|
|
151
|
-
'--directory',
|
|
152
|
-
'-z',
|
|
153
|
-
],
|
|
154
|
-
worktreeRoot,
|
|
155
|
-
);
|
|
156
|
-
return stdout.split('\0').filter(Boolean).map((entry) => (
|
|
157
|
-
entry.endsWith('/') ? entry.slice(0, -1) : entry
|
|
158
|
-
));
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function namespacePathIgnored(relativePath, ignoredPaths) {
|
|
162
|
-
return ignoredPaths.some((ignored) => (
|
|
163
|
-
relativePath === ignored || relativePath.startsWith(`${ignored}/`)
|
|
164
|
-
));
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function directoryIdentity(absolutePath) {
|
|
168
|
-
const stat = fs.lstatSync(absolutePath, { bigint: true });
|
|
169
|
-
if (!stat.isDirectory()) return null;
|
|
170
|
-
return {
|
|
171
|
-
dev: stat.dev.toString(),
|
|
172
|
-
ino: stat.ino.toString(),
|
|
173
|
-
mode: stat.mode.toString(),
|
|
174
|
-
mtimeNs: stat.mtimeNs.toString(),
|
|
175
|
-
ctimeNs: stat.ctimeNs.toString(),
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Git's untracked listing names files, not the namespace in which new files
|
|
181
|
-
* can appear. Capture the complete non-ignored directory membership before
|
|
182
|
-
* and after the byte reads so delete/list/recreate ABA cannot hide a file.
|
|
183
|
-
* Ignored subtrees are pruned at their boundary, so dependency churn never
|
|
184
|
-
* blocks publication.
|
|
185
|
-
*/
|
|
186
|
-
async function visibleNamespaceIdentity(worktreeRoot) {
|
|
187
|
-
const ignored = await ignoredNamespacePaths(worktreeRoot);
|
|
188
|
-
const identity = {};
|
|
189
|
-
let directories = [{ absolute: worktreeRoot, relative: '' }];
|
|
190
|
-
while (directories.length > 0) {
|
|
191
|
-
const next = [];
|
|
192
|
-
for (const directory of directories) {
|
|
193
|
-
const entries = [];
|
|
194
|
-
try {
|
|
195
|
-
const before = directoryIdentity(directory.absolute);
|
|
196
|
-
if (!before) return { state: 'moving' };
|
|
197
|
-
for (const dirent of fs.readdirSync(directory.absolute, { withFileTypes: true })) {
|
|
198
|
-
if (directory.relative === '' && dirent.name === '.git') continue;
|
|
199
|
-
const relative = directory.relative
|
|
200
|
-
? `${directory.relative}/${dirent.name}`
|
|
201
|
-
: dirent.name;
|
|
202
|
-
if (namespacePathIgnored(relative, ignored)) continue;
|
|
203
|
-
const absolute = path.join(directory.absolute, dirent.name);
|
|
204
|
-
const stat = fs.lstatSync(absolute, { bigint: true });
|
|
205
|
-
const isDirectory = stat.isDirectory();
|
|
206
|
-
const kind = isDirectory
|
|
207
|
-
? 'directory'
|
|
208
|
-
: stat.isSymbolicLink()
|
|
209
|
-
? 'symlink'
|
|
210
|
-
: stat.isFile()
|
|
211
|
-
? 'file'
|
|
212
|
-
: 'other';
|
|
213
|
-
entries.push({
|
|
214
|
-
name: dirent.name,
|
|
215
|
-
kind,
|
|
216
|
-
dev: stat.dev.toString(),
|
|
217
|
-
ino: stat.ino.toString(),
|
|
218
|
-
...(isDirectory ? {
|
|
219
|
-
mode: stat.mode.toString(),
|
|
220
|
-
mtimeNs: stat.mtimeNs.toString(),
|
|
221
|
-
ctimeNs: stat.ctimeNs.toString(),
|
|
222
|
-
} : {
|
|
223
|
-
mode: stat.mode.toString(),
|
|
224
|
-
size: stat.size.toString(),
|
|
225
|
-
mtimeNs: stat.mtimeNs.toString(),
|
|
226
|
-
ctimeNs: stat.ctimeNs.toString(),
|
|
227
|
-
...(stat.isSymbolicLink() ? { target: fs.readlinkSync(absolute) } : {}),
|
|
228
|
-
}),
|
|
229
|
-
});
|
|
230
|
-
if (isDirectory && !fs.existsSync(path.join(absolute, '.git'))) {
|
|
231
|
-
next.push({ absolute, relative });
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
const after = directoryIdentity(directory.absolute);
|
|
235
|
-
if (!after || !sameIdentity(before, after)) return { state: 'moving' };
|
|
236
|
-
identity[directory.relative] = {
|
|
237
|
-
directory: after,
|
|
238
|
-
entries: entries.sort((left, right) => left.name.localeCompare(right.name)),
|
|
239
|
-
};
|
|
240
|
-
} catch (error) {
|
|
241
|
-
if (error?.code === 'ENOENT' || error?.code === 'ENOTDIR') {
|
|
242
|
-
return { state: 'moving' };
|
|
243
|
-
}
|
|
244
|
-
throw error;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
directories = next.sort((left, right) => left.relative.localeCompare(right.relative));
|
|
248
|
-
}
|
|
249
|
-
return { state: 'ready', value: identity };
|
|
250
|
-
}
|
|
251
|
-
|
|
252
67
|
const NOT_A_REPO_RE = /not a git repository/i;
|
|
253
68
|
|
|
254
69
|
/**
|
|
@@ -524,16 +339,7 @@ async function captureWorktreeDiff(worktreeRoot, { maxBytes = Infinity } = {}) {
|
|
|
524
339
|
['-C', worktreeRoot, 'ls-files', '--others', '--exclude-standard', '-z'],
|
|
525
340
|
worktreeRoot,
|
|
526
341
|
);
|
|
527
|
-
const
|
|
528
|
-
if (untrackedPaths.some((file) => file.endsWith('/'))) {
|
|
529
|
-
return {
|
|
530
|
-
baseOid,
|
|
531
|
-
diff: null,
|
|
532
|
-
reason: 'unsupported-nested-repository',
|
|
533
|
-
bytes,
|
|
534
|
-
};
|
|
535
|
-
}
|
|
536
|
-
for (const file of untrackedPaths) {
|
|
342
|
+
for (const file of untracked.stdout.split('\0').filter(Boolean).sort()) {
|
|
537
343
|
if (bytes > maxBytes) break;
|
|
538
344
|
const creation = await git(
|
|
539
345
|
['-C', worktreeRoot, ...PATCH_ARGS, 'diff', '--no-index', '--binary', '--full-index', '--', '/dev/null', file],
|
|
@@ -554,224 +360,46 @@ async function captureWorktreeDiff(worktreeRoot, { maxBytes = Infinity } = {}) {
|
|
|
554
360
|
* Untracked, non-ignored files belong to the working-tree layer. Keeping
|
|
555
361
|
* these deltas separate preserves what the user deliberately staged.
|
|
556
362
|
*/
|
|
557
|
-
function
|
|
558
|
-
baseOid,
|
|
559
|
-
observedOid,
|
|
560
|
-
indexDiff,
|
|
561
|
-
worktreeDiff,
|
|
562
|
-
bytes,
|
|
563
|
-
maxBytes,
|
|
564
|
-
}) {
|
|
565
|
-
if (observedOid !== baseOid) {
|
|
566
|
-
return { baseOid, indexDiff: null, worktreeDiff: null, reason: 'head-moved', bytes };
|
|
567
|
-
}
|
|
568
|
-
if (bytes > maxBytes) {
|
|
569
|
-
return { baseOid, indexDiff: null, worktreeDiff: null, reason: 'too-large', bytes };
|
|
570
|
-
}
|
|
571
|
-
return { baseOid, indexDiff, worktreeDiff, bytes };
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
async function captureCheckpointOnce(worktreeRoot, { maxBytes = Infinity } = {}) {
|
|
363
|
+
async function captureCheckpoint(worktreeRoot, { maxBytes = Infinity } = {}) {
|
|
575
364
|
const baseOid = await headOid(worktreeRoot);
|
|
576
365
|
if (!baseOid) return { baseOid: null, indexDiff: null, worktreeDiff: null, reason: 'unborn' };
|
|
577
|
-
const sourceIndex = await indexPath(worktreeRoot);
|
|
578
|
-
const snapshotRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'amalgm-index-snapshot-'));
|
|
579
|
-
const snapshotIndex = path.join(snapshotRoot, 'index');
|
|
580
|
-
try {
|
|
581
|
-
const initialIndexIdentity = changeIdentity(sourceIndex);
|
|
582
|
-
if (!initialIndexIdentity) {
|
|
583
|
-
return {
|
|
584
|
-
baseOid,
|
|
585
|
-
indexDiff: null,
|
|
586
|
-
worktreeDiff: null,
|
|
587
|
-
reason: 'index-missing',
|
|
588
|
-
bytes: 0,
|
|
589
|
-
};
|
|
590
|
-
}
|
|
591
|
-
fs.copyFileSync(sourceIndex, snapshotIndex);
|
|
592
|
-
if (!sameIdentity(initialIndexIdentity, changeIdentity(sourceIndex))) {
|
|
593
|
-
return {
|
|
594
|
-
baseOid,
|
|
595
|
-
indexDiff: null,
|
|
596
|
-
worktreeDiff: null,
|
|
597
|
-
reason: 'observation-moved',
|
|
598
|
-
bytes: 0,
|
|
599
|
-
};
|
|
600
|
-
}
|
|
601
|
-
const snapshotEnv = { GIT_INDEX_FILE: snapshotIndex };
|
|
602
|
-
let stagedTree;
|
|
603
|
-
try {
|
|
604
|
-
stagedTree = (await git(
|
|
605
|
-
['-C', worktreeRoot, 'write-tree'],
|
|
606
|
-
worktreeRoot,
|
|
607
|
-
{ env: snapshotEnv },
|
|
608
|
-
)).stdout.trim();
|
|
609
|
-
} catch (error) {
|
|
610
|
-
if (
|
|
611
|
-
error instanceof GitError
|
|
612
|
-
&& /unmerged|not uptodate|cannot write.*tree/i.test(error.stderr)
|
|
613
|
-
) {
|
|
614
|
-
return {
|
|
615
|
-
baseOid,
|
|
616
|
-
indexDiff: null,
|
|
617
|
-
worktreeDiff: null,
|
|
618
|
-
reason: 'index-unmerged',
|
|
619
|
-
bytes: 0,
|
|
620
|
-
};
|
|
621
|
-
}
|
|
622
|
-
throw error;
|
|
623
|
-
}
|
|
624
|
-
const normalizedIndex = path.join(snapshotRoot, 'normalized-index');
|
|
625
|
-
const normalizedEnv = { GIT_INDEX_FILE: normalizedIndex };
|
|
626
|
-
await git(
|
|
627
|
-
['-C', worktreeRoot, 'read-tree', stagedTree],
|
|
628
|
-
worktreeRoot,
|
|
629
|
-
{ env: normalizedEnv },
|
|
630
|
-
);
|
|
631
|
-
const tracked = await git(
|
|
632
|
-
['-C', worktreeRoot, 'ls-files', '-z'],
|
|
633
|
-
worktreeRoot,
|
|
634
|
-
{ env: normalizedEnv },
|
|
635
|
-
);
|
|
636
|
-
const trackedPaths = tracked.stdout.split('\0').filter(Boolean);
|
|
637
|
-
const initialNamespace = await visibleNamespaceIdentity(worktreeRoot);
|
|
638
|
-
if (initialNamespace.state !== 'ready') {
|
|
639
|
-
return {
|
|
640
|
-
baseOid,
|
|
641
|
-
indexDiff: null,
|
|
642
|
-
worktreeDiff: null,
|
|
643
|
-
reason: 'observation-moved',
|
|
644
|
-
bytes: 0,
|
|
645
|
-
};
|
|
646
|
-
}
|
|
647
|
-
const untracked = await git(
|
|
648
|
-
['-C', worktreeRoot, 'ls-files', '--others', '--exclude-standard', '-z'],
|
|
649
|
-
worktreeRoot,
|
|
650
|
-
{ env: normalizedEnv },
|
|
651
|
-
);
|
|
652
|
-
const untrackedPaths = untracked.stdout.split('\0').filter(Boolean).sort();
|
|
653
|
-
if (untrackedPaths.some((file) => file.endsWith('/'))) {
|
|
654
|
-
return {
|
|
655
|
-
baseOid,
|
|
656
|
-
indexDiff: null,
|
|
657
|
-
worktreeDiff: null,
|
|
658
|
-
reason: 'unsupported-nested-repository',
|
|
659
|
-
bytes: 0,
|
|
660
|
-
};
|
|
661
|
-
}
|
|
662
|
-
const observedPaths = [...new Set([...trackedPaths, ...untrackedPaths])].sort();
|
|
663
|
-
const initialWorktreeIdentity = worktreeIdentity(worktreeRoot, observedPaths);
|
|
664
366
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
],
|
|
684
|
-
worktreeRoot,
|
|
685
|
-
{ okCodes: [0, 1], env: normalizedEnv },
|
|
686
|
-
);
|
|
687
|
-
worktreeParts.push(creation.stdout);
|
|
688
|
-
bytes += Buffer.byteLength(creation.stdout);
|
|
689
|
-
}
|
|
690
|
-
const observedOid = await headOid(worktreeRoot);
|
|
691
|
-
const finalWorktreeIdentity = worktreeIdentity(worktreeRoot, observedPaths);
|
|
692
|
-
const finalUntracked = await git(
|
|
693
|
-
['-C', worktreeRoot, 'ls-files', '--others', '--exclude-standard', '-z'],
|
|
367
|
+
const staged = await git(
|
|
368
|
+
['-C', worktreeRoot, ...PATCH_ARGS, 'diff', '--cached', 'HEAD', '--binary', '--full-index'],
|
|
369
|
+
worktreeRoot,
|
|
370
|
+
);
|
|
371
|
+
const unstaged = await git(
|
|
372
|
+
['-C', worktreeRoot, ...PATCH_ARGS, 'diff', '--binary', '--full-index'],
|
|
373
|
+
worktreeRoot,
|
|
374
|
+
);
|
|
375
|
+
const worktreeParts = [unstaged.stdout];
|
|
376
|
+
let bytes = Buffer.byteLength(staged.stdout) + Buffer.byteLength(unstaged.stdout);
|
|
377
|
+
const untracked = await git(
|
|
378
|
+
['-C', worktreeRoot, 'ls-files', '--others', '--exclude-standard', '-z'],
|
|
379
|
+
worktreeRoot,
|
|
380
|
+
);
|
|
381
|
+
for (const file of untracked.stdout.split('\0').filter(Boolean).sort()) {
|
|
382
|
+
if (bytes > maxBytes) break;
|
|
383
|
+
const creation = await git(
|
|
384
|
+
['-C', worktreeRoot, ...PATCH_ARGS, 'diff', '--no-index', '--binary', '--full-index', '--', '/dev/null', file],
|
|
694
385
|
worktreeRoot,
|
|
695
|
-
{
|
|
386
|
+
{ okCodes: [0, 1] },
|
|
696
387
|
);
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|| finalNamespace.state !== 'ready'
|
|
703
|
-
|| !sameIdentity(initialNamespace.value, finalNamespace.value)
|
|
704
|
-
) {
|
|
705
|
-
return {
|
|
706
|
-
baseOid,
|
|
707
|
-
indexDiff: null,
|
|
708
|
-
worktreeDiff: null,
|
|
709
|
-
reason: 'observation-moved',
|
|
710
|
-
bytes,
|
|
711
|
-
};
|
|
712
|
-
}
|
|
713
|
-
return checkpointCaptureResult({
|
|
714
|
-
baseOid,
|
|
715
|
-
observedOid,
|
|
716
|
-
indexDiff: staged.stdout,
|
|
717
|
-
worktreeDiff: worktreeParts.join(''),
|
|
718
|
-
bytes,
|
|
719
|
-
maxBytes,
|
|
720
|
-
});
|
|
721
|
-
} finally {
|
|
722
|
-
fs.rmSync(snapshotRoot, { recursive: true, force: true });
|
|
388
|
+
worktreeParts.push(creation.stdout);
|
|
389
|
+
bytes += Buffer.byteLength(creation.stdout);
|
|
390
|
+
}
|
|
391
|
+
if (bytes > maxBytes) {
|
|
392
|
+
return { baseOid, indexDiff: null, worktreeDiff: null, reason: 'too-large', bytes };
|
|
723
393
|
}
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
function stableCheckpointCapture(first, second) {
|
|
727
|
-
if (JSON.stringify(first) === JSON.stringify(second)) return second;
|
|
728
394
|
return {
|
|
729
|
-
baseOid
|
|
730
|
-
indexDiff:
|
|
731
|
-
worktreeDiff:
|
|
732
|
-
|
|
733
|
-
bytes: Math.max(
|
|
734
|
-
Number.isFinite(first?.bytes) ? first.bytes : 0,
|
|
735
|
-
Number.isFinite(second?.bytes) ? second.bytes : 0,
|
|
736
|
-
),
|
|
395
|
+
baseOid,
|
|
396
|
+
indexDiff: staged.stdout,
|
|
397
|
+
worktreeDiff: worktreeParts.join(''),
|
|
398
|
+
bytes,
|
|
737
399
|
};
|
|
738
400
|
}
|
|
739
401
|
|
|
740
|
-
async function captureStableCheckpoint(read, attempts = 4) {
|
|
741
|
-
if (!Number.isInteger(attempts) || attempts < 2) {
|
|
742
|
-
throw new Error('checkpoint capture requires at least two observations');
|
|
743
|
-
}
|
|
744
|
-
let previous = await read();
|
|
745
|
-
let compared = previous;
|
|
746
|
-
for (let attempt = 1; attempt < attempts; attempt += 1) {
|
|
747
|
-
const current = await read();
|
|
748
|
-
compared = stableCheckpointCapture(previous, current);
|
|
749
|
-
if (compared.reason !== 'observation-moved') return compared;
|
|
750
|
-
previous = current;
|
|
751
|
-
}
|
|
752
|
-
return compared;
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
/**
|
|
756
|
-
* A checkpoint is a fact about HEAD, index, and worktree at one stable
|
|
757
|
-
* instant. Individual Git reads cannot lock all three layers together, so a
|
|
758
|
-
* capture is usable only after two consecutive complete captures are
|
|
759
|
-
* byte-identical. A bounded retry absorbs filesystem metadata settling after
|
|
760
|
-
* a save; alternating or continuously moving state still returns "moving."
|
|
761
|
-
* Mixed layers are never promoted to provenance.
|
|
762
|
-
*/
|
|
763
|
-
async function captureCheckpoint(worktreeRoot, options = {}) {
|
|
764
|
-
return captureStableCheckpoint(
|
|
765
|
-
() => captureCheckpointOnce(worktreeRoot, options),
|
|
766
|
-
);
|
|
767
|
-
}
|
|
768
|
-
|
|
769
402
|
module.exports = {
|
|
770
|
-
_test: {
|
|
771
|
-
captureStableCheckpoint,
|
|
772
|
-
checkpointCaptureResult,
|
|
773
|
-
stableCheckpointCapture,
|
|
774
|
-
},
|
|
775
403
|
GitError,
|
|
776
404
|
captureCheckpoint,
|
|
777
405
|
captureWorktreeDiff,
|