amalgm 0.1.235 → 0.1.237
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/package.json +1 -1
- package/runtime/scripts/amalgm-mcp/tests/workspace-reducer.test.js +18 -0
- package/runtime/scripts/amalgm-mcp/tests/workspace-wall.test.js +6 -1
- package/runtime/scripts/amalgm-mcp/workspace/checkpoint.js +4 -1
- package/runtime/scripts/amalgm-mcp/workspace/reducer.js +11 -6
- package/runtime/scripts/amalgm-mcp/workspace/transition.js +6 -1
- package/runtime/scripts/amalgm-mcp/workspace/wall.js +2 -2
package/package.json
CHANGED
|
@@ -59,6 +59,21 @@ test('a hydration failure still has a project path before a local card exists',
|
|
|
59
59
|
assert.equal(pathForRepo({ path: '/projects/local' }, { path: '/projects/fresh' }), '/projects/local');
|
|
60
60
|
});
|
|
61
61
|
|
|
62
|
+
test('a reducer cursor belongs only to the rail that created it', () => {
|
|
63
|
+
const repoKey = 'root:rail-scoped-cursor';
|
|
64
|
+
const digest = require('crypto').createHash('sha256').update(repoKey).digest('hex').slice(0, 24);
|
|
65
|
+
const file = path.join(process.env.AMALGM_DIR, 'state', 'repo-reducer', `${digest}.json`);
|
|
66
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
67
|
+
fs.writeFileSync(file, `${JSON.stringify({
|
|
68
|
+
repoKey,
|
|
69
|
+
resourceId: 'shr_old_project_rail',
|
|
70
|
+
appliedThrough: 2764,
|
|
71
|
+
})}\n`);
|
|
72
|
+
|
|
73
|
+
assert.equal(readRepoState(repoKey, 'shr_old_project_rail').appliedThrough, 2764);
|
|
74
|
+
assert.equal(readRepoState(repoKey, 'shr_new_project_rail'), null);
|
|
75
|
+
});
|
|
76
|
+
|
|
62
77
|
test.after(() => {
|
|
63
78
|
closeLocalDb();
|
|
64
79
|
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
@@ -374,6 +389,7 @@ test('an empty project folder hydrates from the winning card bundle without clon
|
|
|
374
389
|
const source = sourceRepo.repo;
|
|
375
390
|
fs.mkdirSync(path.join(source, '.amalgam', 'context'), { recursive: true });
|
|
376
391
|
fs.writeFileSync(path.join(source, '.amalgam', 'context', 'notes.md'), 'committed note\n');
|
|
392
|
+
fs.symlinkSync('notes.md', path.join(source, '.amalgam', 'context', 'current.md'));
|
|
377
393
|
await git(source, 'add', '.');
|
|
378
394
|
await git(source, 'commit', '-m', 'track project memory');
|
|
379
395
|
const oid = (await git(source, 'rev-parse', 'HEAD')).trim();
|
|
@@ -388,6 +404,7 @@ test('an empty project folder hydrates from the winning card bundle without clon
|
|
|
388
404
|
const target = path.join(tempRoot, 'hydrate-target');
|
|
389
405
|
fs.mkdirSync(path.join(target, '.amalgam', 'context'), { recursive: true });
|
|
390
406
|
fs.writeFileSync(path.join(target, '.amalgam', 'context', 'notes.md'), 'local note\n');
|
|
407
|
+
fs.symlinkSync('notes.md', path.join(target, '.amalgam', 'context', 'current.md'));
|
|
391
408
|
const winning = card('/remote', oid, 1);
|
|
392
409
|
winning.repo.remote = 'https://example.com/org/repo';
|
|
393
410
|
winning.objects = {
|
|
@@ -410,6 +427,7 @@ test('an empty project folder hydrates from the winning card bundle without clon
|
|
|
410
427
|
assert.equal((await git(target, 'symbolic-ref', '--short', 'HEAD')).trim(), 'main');
|
|
411
428
|
assert.equal(fs.readFileSync(path.join(target, 'a.txt'), 'utf8'), 'a0\n');
|
|
412
429
|
assert.equal(fs.readFileSync(path.join(target, '.amalgam', 'context', 'notes.md'), 'utf8'), 'local note\n');
|
|
430
|
+
assert.equal(fs.readlinkSync(path.join(target, '.amalgam', 'context', 'current.md')), 'notes.md');
|
|
413
431
|
assert.match(await git(target, 'status', '--porcelain=v1'), /\.amalgam\/context\/notes\.md/);
|
|
414
432
|
});
|
|
415
433
|
|
|
@@ -59,7 +59,11 @@ function markReducerApplied(version) {
|
|
|
59
59
|
const digest = crypto.createHash('sha256').update(repoKey).digest('hex').slice(0, 24);
|
|
60
60
|
const file = path.join(process.env.AMALGM_DIR, 'state', 'repo-reducer', `${digest}.json`);
|
|
61
61
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
62
|
-
fs.writeFileSync(file, `${JSON.stringify({
|
|
62
|
+
fs.writeFileSync(file, `${JSON.stringify({
|
|
63
|
+
repoKey,
|
|
64
|
+
resourceId: project.resourceId,
|
|
65
|
+
appliedThrough: version,
|
|
66
|
+
})}\n`);
|
|
63
67
|
}
|
|
64
68
|
|
|
65
69
|
function settleProject() {
|
|
@@ -450,6 +454,7 @@ test('a checkpoint base replaces history already covered by the applied rail', a
|
|
|
450
454
|
);
|
|
451
455
|
fs.writeFileSync(reducerFile, `${JSON.stringify({
|
|
452
456
|
repoKey,
|
|
457
|
+
resourceId: project.resourceId,
|
|
453
458
|
baseOid: card.head.oid,
|
|
454
459
|
branch: card.head.branch,
|
|
455
460
|
appliedThrough: 2,
|
|
@@ -383,8 +383,11 @@ async function publishPass(replicaId = null) {
|
|
|
383
383
|
continue;
|
|
384
384
|
}
|
|
385
385
|
try {
|
|
386
|
-
const reduced = require('./reducer').readRepoState(repoKey);
|
|
387
386
|
const wall = require('./wall');
|
|
387
|
+
const reduced = require('./reducer').readRepoState(
|
|
388
|
+
repoKey,
|
|
389
|
+
wall.projectForRepo(repoKey)?.resourceId,
|
|
390
|
+
);
|
|
388
391
|
if (!checkpointBaseReady(card, reduced, wall.publicationStatus(repoKey, reduced))) {
|
|
389
392
|
run.waiting += 1;
|
|
390
393
|
continue;
|
|
@@ -72,10 +72,12 @@ function stateFile(repoKey) {
|
|
|
72
72
|
return path.join(STATE_DIR, `${digest}.json`);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
function readRepoState(repoKey) {
|
|
75
|
+
function readRepoState(repoKey, resourceId = null) {
|
|
76
76
|
try {
|
|
77
77
|
const state = JSON.parse(fs.readFileSync(stateFile(repoKey), 'utf8'));
|
|
78
|
-
|
|
78
|
+
if (state?.repoKey !== repoKey) return null;
|
|
79
|
+
if (resourceId && state.resourceId !== resourceId) return null;
|
|
80
|
+
return state;
|
|
79
81
|
} catch {
|
|
80
82
|
return null;
|
|
81
83
|
}
|
|
@@ -90,7 +92,7 @@ function repoStateIdentity(state) {
|
|
|
90
92
|
function persistRepoState(repoKey, state) {
|
|
91
93
|
fs.mkdirSync(STATE_DIR, { recursive: true });
|
|
92
94
|
const next = { repoKey, ...state, updatedAt: new Date().toISOString() };
|
|
93
|
-
const previous = readRepoState(repoKey);
|
|
95
|
+
const previous = readRepoState(repoKey, state?.resourceId || null);
|
|
94
96
|
if (repoStateIdentity(previous) === repoStateIdentity(next)) {
|
|
95
97
|
return { state: previous, changed: false };
|
|
96
98
|
}
|
|
@@ -349,7 +351,8 @@ async function moveToWinningCard(localCard, targetCard) {
|
|
|
349
351
|
return { state: 'waiting', reason: 'local-state-moving' };
|
|
350
352
|
}
|
|
351
353
|
const repoKey = repoKeyOfCard(localCard);
|
|
352
|
-
const
|
|
354
|
+
const resourceId = require('./wall').projectForRepo(repoKey)?.resourceId;
|
|
355
|
+
const reducedState = readRepoState(repoKey, resourceId);
|
|
353
356
|
const capturedOnRail = reducedState?.baseOid === current.head.oid
|
|
354
357
|
&& reducedState.checkpointRevision === revisionOf(
|
|
355
358
|
current.head.oid,
|
|
@@ -590,7 +593,8 @@ async function materializeRepo(localCardInput, plan, appliedThrough = null) {
|
|
|
590
593
|
const localCard = cardResult.moved
|
|
591
594
|
? await refreshRepoCard(refreshed.replica) || refreshed
|
|
592
595
|
: refreshed;
|
|
593
|
-
const
|
|
596
|
+
const project = require('./wall').projectForRepo(plan.repoKey);
|
|
597
|
+
const previous = readRepoState(plan.repoKey, project?.resourceId);
|
|
594
598
|
const parked = parkedForState(
|
|
595
599
|
previous?.parked,
|
|
596
600
|
plan.parked,
|
|
@@ -604,6 +608,7 @@ async function materializeRepo(localCardInput, plan, appliedThrough = null) {
|
|
|
604
608
|
// bundle, so the next pass cannot replace unnumbered local work.
|
|
605
609
|
const persisted = persistRepoState(plan.repoKey, {
|
|
606
610
|
...(previous || {}),
|
|
611
|
+
resourceId: project?.resourceId || null,
|
|
607
612
|
path: localCard.path,
|
|
608
613
|
replica: localCard.replica,
|
|
609
614
|
parked,
|
|
@@ -681,7 +686,6 @@ async function materializeRepo(localCardInput, plan, appliedThrough = null) {
|
|
|
681
686
|
const captured = await captureCheckpoint(localCard.path);
|
|
682
687
|
const wall = require('./wall');
|
|
683
688
|
const localCheckpoint = readCheckpoint(machineId(), plan.repoKey);
|
|
684
|
-
const project = wall.projectForRepo(plan.repoKey);
|
|
685
689
|
const acknowledged = localCheckpoint && {
|
|
686
690
|
...localCheckpoint,
|
|
687
691
|
version: wall.mutationVersion(
|
|
@@ -716,6 +720,7 @@ async function materializeRepo(localCardInput, plan, appliedThrough = null) {
|
|
|
716
720
|
const indexResult = await advanceIndex(localCard.path, currentIndex, reducedLocalIndex.tree);
|
|
717
721
|
if (indexResult.state !== 'ready') return indexResult;
|
|
718
722
|
const persisted = persistRepoState(plan.repoKey, {
|
|
723
|
+
resourceId: project?.resourceId || null,
|
|
719
724
|
path: localCard.path,
|
|
720
725
|
replica: localCard.replica,
|
|
721
726
|
branch: stateBranch,
|
|
@@ -158,7 +158,12 @@ function holdInitialFiles(worktreeRoot) {
|
|
|
158
158
|
|
|
159
159
|
function restoreInitialFiles(worktreeRoot, holding) {
|
|
160
160
|
if (!holding) return;
|
|
161
|
-
fs.cpSync(holding, worktreeRoot, {
|
|
161
|
+
fs.cpSync(holding, worktreeRoot, {
|
|
162
|
+
recursive: true,
|
|
163
|
+
force: true,
|
|
164
|
+
dereference: false,
|
|
165
|
+
verbatimSymlinks: true,
|
|
166
|
+
});
|
|
162
167
|
fs.rmSync(holding, { recursive: true, force: true });
|
|
163
168
|
journal({ action: 'restore-initial-files', worktree: worktreeRoot, holding, ok: true });
|
|
164
169
|
}
|
|
@@ -138,7 +138,7 @@ function publicationStatus(repoKey, reduced = undefined) {
|
|
|
138
138
|
const replica = project ? wallReplica(project) : null;
|
|
139
139
|
const receivedThrough = replica?.appliedVersion ?? null;
|
|
140
140
|
const baseline = reduced === undefined
|
|
141
|
-
? require('./reducer').readRepoState(repoKey)
|
|
141
|
+
? require('./reducer').readRepoState(repoKey, project?.resourceId)
|
|
142
142
|
: reduced;
|
|
143
143
|
const appliedThrough = Number.isInteger(baseline?.appliedThrough)
|
|
144
144
|
? baseline.appliedThrough
|
|
@@ -559,7 +559,7 @@ function publishProject(project, rawCards) {
|
|
|
559
559
|
|
|
560
560
|
const reducer = require('./reducer');
|
|
561
561
|
const sharedPlan = reducer.reduceWall(readProjectWall(project)).get(project.repoKey);
|
|
562
|
-
const baseline = reducer.readRepoState(project.repoKey);
|
|
562
|
+
const baseline = reducer.readRepoState(project.repoKey, project.resourceId);
|
|
563
563
|
const previousCards = (previousEntry?.cards || [])
|
|
564
564
|
.map((card) => sealCardVersions(project, card, previousEntry));
|
|
565
565
|
const previousReplicas = new Set(previousCards.map((card) => card.replica));
|