@pellux/goodvibes-sdk 0.37.0 → 0.37.1
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/contracts/artifacts/operator-contract.json +1 -1
- package/dist/platform/version.js +1 -1
- package/dist/platform/workspace/checkpoint/manager.d.ts +51 -0
- package/dist/platform/workspace/checkpoint/manager.d.ts.map +1 -1
- package/dist/platform/workspace/checkpoint/manager.js +72 -2
- package/dist/platform/workspace/checkpoint/side-git.d.ts +17 -4
- package/dist/platform/workspace/checkpoint/side-git.d.ts.map +1 -1
- package/dist/platform/workspace/checkpoint/side-git.js +18 -8
- package/package.json +9 -9
package/dist/platform/version.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
let version = '0.37.
|
|
3
|
+
let version = '0.37.1';
|
|
4
4
|
try {
|
|
5
5
|
const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', '..', 'package.json'), 'utf-8'));
|
|
6
6
|
version = pkg.version ?? version;
|
|
@@ -75,6 +75,30 @@ export declare class WorkspaceCheckpointManager {
|
|
|
75
75
|
private checkpoints;
|
|
76
76
|
private initialized;
|
|
77
77
|
private initPromise;
|
|
78
|
+
/**
|
|
79
|
+
* Promise-chain mutex serializing every public operation that touches the
|
|
80
|
+
* side repo's shared index or shared object store: `create`, `restore`,
|
|
81
|
+
* `gc`, and `diff` (see each method's `*Internal` body below). Without
|
|
82
|
+
* this, an auto-snapshot `create()` firing on a bus event (TURN_COMPLETED,
|
|
83
|
+
* TURN_ERROR, TURN_CANCEL, or AGENT_COMPLETED) could run its `git add -A`
|
|
84
|
+
* in between `restore()`'s
|
|
85
|
+
* `read-tree --reset` and `checkout-index -a -f`, silently corrupting the
|
|
86
|
+
* restore; two concurrent `create()` calls share the same hazard on the
|
|
87
|
+
* index, and a same-tick `gc()` could treat a not-yet-ref'd loose commit
|
|
88
|
+
* from an in-flight `create()` as unreachable and prune it out from under
|
|
89
|
+
* it. `diff()` is included too: the single-argument `git diff <tree-ish>`
|
|
90
|
+
* form (diffing a checkpoint against the live working tree) refreshes the
|
|
91
|
+
* index's stat cache as a side effect, which is itself a write.
|
|
92
|
+
*
|
|
93
|
+
* Each public method below only does `await this.init()` (idempotent, safe
|
|
94
|
+
* to race) before calling `withLock`; the actual git-touching work lives in
|
|
95
|
+
* a same-named `*Internal` method. Internal callers that need another
|
|
96
|
+
* locked operation's behavior (e.g. `restore()`'s safety checkpoint) call
|
|
97
|
+
* the `*Internal` method directly — never the public wrapper — so a single
|
|
98
|
+
* logical operation never tries to re-enter its own lock.
|
|
99
|
+
*/
|
|
100
|
+
private lockChain;
|
|
101
|
+
private withLock;
|
|
78
102
|
constructor(opts: WorkspaceCheckpointManagerOptions);
|
|
79
103
|
/**
|
|
80
104
|
* Idempotent setup: init the side repo, load the manifest (re-hydrating the
|
|
@@ -103,15 +127,25 @@ export declare class WorkspaceCheckpointManager {
|
|
|
103
127
|
* Create a new checkpoint. Returns `null` (a cheap no-op) when the current
|
|
104
128
|
* workspace tree is identical to the parent checkpoint's tree — no commit,
|
|
105
129
|
* no ref, no manifest entry is created in that case.
|
|
130
|
+
*
|
|
131
|
+
* Serialized against every other index-touching operation on this manager
|
|
132
|
+
* — see `withLock`.
|
|
106
133
|
*/
|
|
107
134
|
create(opts: CreateCheckpointOptions): Promise<WorkspaceCheckpoint | null>;
|
|
135
|
+
private createInternal;
|
|
108
136
|
/** List checkpoints, newest-first. `list()[0]` is always the most recent checkpoint. */
|
|
109
137
|
list(filter?: ListCheckpointsFilter): Promise<WorkspaceCheckpoint[]>;
|
|
110
138
|
/**
|
|
111
139
|
* Diff two checkpoints, or a checkpoint against the live working tree when
|
|
112
140
|
* `b` is omitted.
|
|
141
|
+
*
|
|
142
|
+
* Serialized against every other index-touching operation on this manager
|
|
143
|
+
* — see `withLock`. The single-argument form (`b` omitted, diffing against
|
|
144
|
+
* the live working tree) refreshes the side index's stat cache as a side
|
|
145
|
+
* effect, so it is not purely read-only.
|
|
113
146
|
*/
|
|
114
147
|
diff(a: string, b?: string): Promise<CheckpointDiff>;
|
|
148
|
+
private diffInternal;
|
|
115
149
|
/**
|
|
116
150
|
* Restore the workspace to the state captured by checkpoint `id`.
|
|
117
151
|
*
|
|
@@ -133,8 +167,14 @@ export declare class WorkspaceCheckpointManager {
|
|
|
133
167
|
*
|
|
134
168
|
* Scoped restore (`opts.paths` provided) only checks out those paths from
|
|
135
169
|
* the target tree; it never removes files outside the given paths.
|
|
170
|
+
*
|
|
171
|
+
* Serialized against every other index-touching operation on this manager
|
|
172
|
+
* — see `withLock`. Without this, an auto-snapshot `create()` firing on a
|
|
173
|
+
* bus event could run its `git add -A` in between the `read-tree --reset`
|
|
174
|
+
* and `checkout-index -a -f` calls below, silently corrupting the restore.
|
|
136
175
|
*/
|
|
137
176
|
restore(id: string, opts?: RestoreOptions): Promise<RestoreResult>;
|
|
177
|
+
private restoreInternal;
|
|
138
178
|
/**
|
|
139
179
|
* Apply retention limits: `RetentionPolicy` selects prune candidates,
|
|
140
180
|
* `WorkspaceCheckpointPruner` deletes their refs, then (only if anything
|
|
@@ -142,8 +182,19 @@ export declare class WorkspaceCheckpointManager {
|
|
|
142
182
|
* unreachable objects. This never touches compaction's boundary commits —
|
|
143
183
|
* they are tracked in an entirely separate `RetentionPolicy` instance
|
|
144
184
|
* (../../runtime/compaction) with no shared state.
|
|
185
|
+
*
|
|
186
|
+
* Reclamation only works because checkpoint commits are parentless (see
|
|
187
|
+
* `SideGitRunner.commitTree`): a pruned ref's commit has no descendant
|
|
188
|
+
* keeping it reachable via a git parent pointer, so once its ref is
|
|
189
|
+
* deleted it is genuinely unreachable and `--prune=now` frees it.
|
|
190
|
+
*
|
|
191
|
+
* Serialized against every other index/object-store-touching operation on
|
|
192
|
+
* this manager — see `withLock`. Without this, a `create()` racing this
|
|
193
|
+
* method could write a loose commit object that isn't ref'd yet at the
|
|
194
|
+
* moment `--prune=now` runs, and lose it.
|
|
145
195
|
*/
|
|
146
196
|
gc(): Promise<PruneResult>;
|
|
197
|
+
private gcInternal;
|
|
147
198
|
/** Unsubscribe from the runtime bus. Does not touch anything on disk. */
|
|
148
199
|
dispose(): void;
|
|
149
200
|
private mostRecentCheckpoint;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../../../src/platform/workspace/checkpoint/manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AASH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAGrE,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,cAAc,EAGnB,KAAK,WAAW,EAEjB,MAAM,kCAAkC,CAAC;AAE1C,OAAO,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAuFrG,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;IACrD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,iFAAiF;IACjF,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;CACvC;AAED,MAAM,WAAW,cAAc;IAC7B,sEAAsE;IACtE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IACtC,wFAAwF;IACxF,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACjD;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,IAAI,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;IAC3C,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACrC;AAED,MAAM,WAAW,iCAAiC;IAChD,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,kGAAkG;IAClG,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C,8HAA8H;IAC9H,QAAQ,CAAC,UAAU,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC;IAClD,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,SAAS,CAAC;IAC1D,8CAA8C;IAC9C,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC;CAC3C;AAED;;;;GAIG;AACH,qBAAa,0BAA0B;IACrC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAS;IAC3C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgB;IACxC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA0B;IACxD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAClD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA8B;IACzD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAsB;IAEpD,OAAO,CAAC,WAAW,CAA0C;IAC7D,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAA8B;
|
|
1
|
+
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../../../src/platform/workspace/checkpoint/manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -130,6 +130,39 @@ export class WorkspaceCheckpointManager {
|
|
|
130
130
|
checkpoints = new Map();
|
|
131
131
|
initialized = false;
|
|
132
132
|
initPromise = null;
|
|
133
|
+
/**
|
|
134
|
+
* Promise-chain mutex serializing every public operation that touches the
|
|
135
|
+
* side repo's shared index or shared object store: `create`, `restore`,
|
|
136
|
+
* `gc`, and `diff` (see each method's `*Internal` body below). Without
|
|
137
|
+
* this, an auto-snapshot `create()` firing on a bus event (TURN_COMPLETED,
|
|
138
|
+
* TURN_ERROR, TURN_CANCEL, or AGENT_COMPLETED) could run its `git add -A`
|
|
139
|
+
* in between `restore()`'s
|
|
140
|
+
* `read-tree --reset` and `checkout-index -a -f`, silently corrupting the
|
|
141
|
+
* restore; two concurrent `create()` calls share the same hazard on the
|
|
142
|
+
* index, and a same-tick `gc()` could treat a not-yet-ref'd loose commit
|
|
143
|
+
* from an in-flight `create()` as unreachable and prune it out from under
|
|
144
|
+
* it. `diff()` is included too: the single-argument `git diff <tree-ish>`
|
|
145
|
+
* form (diffing a checkpoint against the live working tree) refreshes the
|
|
146
|
+
* index's stat cache as a side effect, which is itself a write.
|
|
147
|
+
*
|
|
148
|
+
* Each public method below only does `await this.init()` (idempotent, safe
|
|
149
|
+
* to race) before calling `withLock`; the actual git-touching work lives in
|
|
150
|
+
* a same-named `*Internal` method. Internal callers that need another
|
|
151
|
+
* locked operation's behavior (e.g. `restore()`'s safety checkpoint) call
|
|
152
|
+
* the `*Internal` method directly — never the public wrapper — so a single
|
|
153
|
+
* logical operation never tries to re-enter its own lock.
|
|
154
|
+
*/
|
|
155
|
+
lockChain = Promise.resolve();
|
|
156
|
+
withLock(fn) {
|
|
157
|
+
// `lockChain` is constructed below so it never itself rejects (its
|
|
158
|
+
// continuation always swallows both outcomes) — chaining with a single
|
|
159
|
+
// `.then(fn)` is enough to guarantee `fn` only starts after every
|
|
160
|
+
// previously-queued operation has settled, regardless of whether that
|
|
161
|
+
// prior operation resolved or rejected.
|
|
162
|
+
const result = this.lockChain.then(fn);
|
|
163
|
+
this.lockChain = result.then(() => undefined, () => undefined);
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
133
166
|
constructor(opts) {
|
|
134
167
|
this.workspaceRoot = opts.workspaceRoot;
|
|
135
168
|
this.checkpointRootDir = opts.checkpointDir ?? join(opts.workspaceRoot, '.goodvibes', 'checkpoints');
|
|
@@ -226,9 +259,15 @@ export class WorkspaceCheckpointManager {
|
|
|
226
259
|
* Create a new checkpoint. Returns `null` (a cheap no-op) when the current
|
|
227
260
|
* workspace tree is identical to the parent checkpoint's tree — no commit,
|
|
228
261
|
* no ref, no manifest entry is created in that case.
|
|
262
|
+
*
|
|
263
|
+
* Serialized against every other index-touching operation on this manager
|
|
264
|
+
* — see `withLock`.
|
|
229
265
|
*/
|
|
230
266
|
async create(opts) {
|
|
231
267
|
await this.init();
|
|
268
|
+
return this.withLock(() => this.createInternal(opts));
|
|
269
|
+
}
|
|
270
|
+
async createInternal(opts) {
|
|
232
271
|
await this.sideGit.stageAll(opts.paths);
|
|
233
272
|
const treeHash = await this.sideGit.writeTree();
|
|
234
273
|
const parent = this.mostRecentCheckpoint();
|
|
@@ -245,7 +284,7 @@ export class WorkspaceCheckpointManager {
|
|
|
245
284
|
const retentionClass = opts.retentionClass ?? defaultRetentionClassFor(kind);
|
|
246
285
|
const label = opts.label ?? this.defaultLabel(kind, id);
|
|
247
286
|
const message = `wcp: ${kind} ${label}`.trim();
|
|
248
|
-
const commit = await this.sideGit.commitTree(treeHash, message
|
|
287
|
+
const commit = await this.sideGit.commitTree(treeHash, message);
|
|
249
288
|
await this.sideGit.updateRef(`${CHECKPOINT_REF_PREFIX}${id}`, commit);
|
|
250
289
|
const changedPaths = parent
|
|
251
290
|
? await this.sideGit.diffNameOnly(parent.commit, commit)
|
|
@@ -290,9 +329,17 @@ export class WorkspaceCheckpointManager {
|
|
|
290
329
|
/**
|
|
291
330
|
* Diff two checkpoints, or a checkpoint against the live working tree when
|
|
292
331
|
* `b` is omitted.
|
|
332
|
+
*
|
|
333
|
+
* Serialized against every other index-touching operation on this manager
|
|
334
|
+
* — see `withLock`. The single-argument form (`b` omitted, diffing against
|
|
335
|
+
* the live working tree) refreshes the side index's stat cache as a side
|
|
336
|
+
* effect, so it is not purely read-only.
|
|
293
337
|
*/
|
|
294
338
|
async diff(a, b) {
|
|
295
339
|
await this.init();
|
|
340
|
+
return this.withLock(() => this.diffInternal(a, b));
|
|
341
|
+
}
|
|
342
|
+
async diffInternal(a, b) {
|
|
296
343
|
const fromCheckpoint = this.requireCheckpoint(a);
|
|
297
344
|
const toCheckpoint = b ? this.requireCheckpoint(b) : undefined;
|
|
298
345
|
const [unifiedDiff, stat_, files] = await Promise.all([
|
|
@@ -329,15 +376,25 @@ export class WorkspaceCheckpointManager {
|
|
|
329
376
|
*
|
|
330
377
|
* Scoped restore (`opts.paths` provided) only checks out those paths from
|
|
331
378
|
* the target tree; it never removes files outside the given paths.
|
|
379
|
+
*
|
|
380
|
+
* Serialized against every other index-touching operation on this manager
|
|
381
|
+
* — see `withLock`. Without this, an auto-snapshot `create()` firing on a
|
|
382
|
+
* bus event could run its `git add -A` in between the `read-tree --reset`
|
|
383
|
+
* and `checkout-index -a -f` calls below, silently corrupting the restore.
|
|
332
384
|
*/
|
|
333
385
|
async restore(id, opts) {
|
|
334
386
|
await this.init();
|
|
387
|
+
return this.withLock(() => this.restoreInternal(id, opts));
|
|
388
|
+
}
|
|
389
|
+
async restoreInternal(id, opts) {
|
|
335
390
|
const target = this.requireCheckpoint(id);
|
|
336
391
|
const wantSafety = opts?.safetyCheckpoint ?? true;
|
|
337
392
|
let beforeFiles;
|
|
338
393
|
let safetyCheckpointId = null;
|
|
339
394
|
if (wantSafety) {
|
|
340
|
-
|
|
395
|
+
// Calls createInternal directly (not the public, lock-acquiring
|
|
396
|
+
// `create()`) — this whole method already holds the lock.
|
|
397
|
+
const safety = await this.createInternal({ kind: 'manual', label: `pre-restore safety (before ${id})`, retentionClass: 'forensic' });
|
|
341
398
|
safetyCheckpointId = safety?.id ?? null;
|
|
342
399
|
const current = safety ?? this.mostRecentCheckpoint();
|
|
343
400
|
beforeFiles = current ? await this.sideGit.listTrackedFiles(current.commit) : [];
|
|
@@ -396,9 +453,22 @@ export class WorkspaceCheckpointManager {
|
|
|
396
453
|
* unreachable objects. This never touches compaction's boundary commits —
|
|
397
454
|
* they are tracked in an entirely separate `RetentionPolicy` instance
|
|
398
455
|
* (../../runtime/compaction) with no shared state.
|
|
456
|
+
*
|
|
457
|
+
* Reclamation only works because checkpoint commits are parentless (see
|
|
458
|
+
* `SideGitRunner.commitTree`): a pruned ref's commit has no descendant
|
|
459
|
+
* keeping it reachable via a git parent pointer, so once its ref is
|
|
460
|
+
* deleted it is genuinely unreachable and `--prune=now` frees it.
|
|
461
|
+
*
|
|
462
|
+
* Serialized against every other index/object-store-touching operation on
|
|
463
|
+
* this manager — see `withLock`. Without this, a `create()` racing this
|
|
464
|
+
* method could write a loose commit object that isn't ref'd yet at the
|
|
465
|
+
* moment `--prune=now` runs, and lose it.
|
|
399
466
|
*/
|
|
400
467
|
async gc() {
|
|
401
468
|
await this.init();
|
|
469
|
+
return this.withLock(() => this.gcInternal());
|
|
470
|
+
}
|
|
471
|
+
async gcInternal() {
|
|
402
472
|
const result = await this.retentionPolicy.prune();
|
|
403
473
|
if (result.deletedCount > 0) {
|
|
404
474
|
await this.persistManifest();
|
|
@@ -87,11 +87,24 @@ export declare class SideGitRunner {
|
|
|
87
87
|
/** Resolve `<commit>^{tree}` for an existing commit hash. */
|
|
88
88
|
treeOf(commit: string): Promise<string>;
|
|
89
89
|
/**
|
|
90
|
-
* Create a commit object from a tree
|
|
91
|
-
* moving any branch/HEAD.
|
|
92
|
-
*
|
|
90
|
+
* Create a commit object from a tree, deliberately WITHOUT a git parent
|
|
91
|
+
* (no `-p`) and WITHOUT moving any branch/HEAD.
|
|
92
|
+
*
|
|
93
|
+
* Checkpoint lineage is tracked exclusively via the manifest's `parentId`
|
|
94
|
+
* field (manager.ts) — never via git ancestry. This isn't just a style
|
|
95
|
+
* choice: if checkpoint commits chained via `-p` the way a normal git
|
|
96
|
+
* history does, deleting an OLD checkpoint's ref in `gc()` would free
|
|
97
|
+
* nothing, because that commit stays reachable through every NEWER
|
|
98
|
+
* checkpoint's parent pointer — the ref is gone but the commit (and any
|
|
99
|
+
* tree/blob objects unique to it) is still walkable from every surviving
|
|
100
|
+
* descendant. Parentless commits mean a checkpoint's own ref is the ONLY
|
|
101
|
+
* thing keeping its commit reachable, so once that ref is deleted,
|
|
102
|
+
* `git gc --prune=now` can genuinely reclaim it.
|
|
103
|
+
*
|
|
104
|
+
* The returned hash is only reachable once a ref is pointed at it via
|
|
105
|
+
* `updateRef`.
|
|
93
106
|
*/
|
|
94
|
-
commitTree(treeHash: string, message: string
|
|
107
|
+
commitTree(treeHash: string, message: string): Promise<string>;
|
|
95
108
|
updateRef(refName: string, commit: string): Promise<void>;
|
|
96
109
|
deleteRef(refName: string): Promise<void>;
|
|
97
110
|
/** List every ref under CHECKPOINT_REF_PREFIX as `{ id, commit }`. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"side-git.d.ts","sourceRoot":"","sources":["../../../../src/platform/workspace/checkpoint/side-git.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAQH,0EAA0E;AAC1E,eAAO,MAAM,eAAe,6CAA6C,CAAC;AAE1E,0DAA0D;AAC1D,eAAO,MAAM,qBAAqB,gCAAgC,CAAC;AA8CnE,MAAM,WAAW,oBAAoB;IACnC,2DAA2D;IAC3D,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,+DAA+D;IAC/D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,qBAAa,aAAa;IACxB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAY;gBAEpB,IAAI,EAAE,oBAAoB;IAUtC,gFAAgF;IAC1E,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAI1C;;;;;OAKG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB3B;;;;;;;;;OASG;IACH,OAAO,CAAC,sBAAsB;IAuB9B;;;;;;;;;;;;OAYG;IACG,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAO/C,wGAAwG;IAClG,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC;IAIlC,6DAA6D;IACvD,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAI7C
|
|
1
|
+
{"version":3,"file":"side-git.d.ts","sourceRoot":"","sources":["../../../../src/platform/workspace/checkpoint/side-git.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAQH,0EAA0E;AAC1E,eAAO,MAAM,eAAe,6CAA6C,CAAC;AAE1E,0DAA0D;AAC1D,eAAO,MAAM,qBAAqB,gCAAgC,CAAC;AA8CnE,MAAM,WAAW,oBAAoB;IACnC,2DAA2D;IAC3D,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,+DAA+D;IAC/D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,qBAAa,aAAa;IACxB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAY;gBAEpB,IAAI,EAAE,oBAAoB;IAUtC,gFAAgF;IAC1E,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAI1C;;;;;OAKG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAkB3B;;;;;;;;;OASG;IACH,OAAO,CAAC,sBAAsB;IAuB9B;;;;;;;;;;;;OAYG;IACG,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAO/C,wGAAwG;IAClG,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC;IAIlC,6DAA6D;IACvD,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAI7C;;;;;;;;;;;;;;;;;OAiBG;IACG,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAI9D,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIzD,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/C,sEAAsE;IAChE,kBAAkB,IAAI,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAmBrE,+DAA+D;IACzD,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAK/D,+FAA+F;IACzF,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlD,wGAAwG;IAClG,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAIvC,iGAAiG;IAC3F,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAItD,wGAAwG;IAClG,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAI1D,6GAA6G;IACvG,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAKhE,6CAA6C;IACvC,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC;CAG1B"}
|
|
@@ -185,15 +185,25 @@ export class SideGitRunner {
|
|
|
185
185
|
return (await this.git.raw(['rev-parse', `${commit}^{tree}`])).trim();
|
|
186
186
|
}
|
|
187
187
|
/**
|
|
188
|
-
* Create a commit object from a tree
|
|
189
|
-
* moving any branch/HEAD.
|
|
190
|
-
*
|
|
188
|
+
* Create a commit object from a tree, deliberately WITHOUT a git parent
|
|
189
|
+
* (no `-p`) and WITHOUT moving any branch/HEAD.
|
|
190
|
+
*
|
|
191
|
+
* Checkpoint lineage is tracked exclusively via the manifest's `parentId`
|
|
192
|
+
* field (manager.ts) — never via git ancestry. This isn't just a style
|
|
193
|
+
* choice: if checkpoint commits chained via `-p` the way a normal git
|
|
194
|
+
* history does, deleting an OLD checkpoint's ref in `gc()` would free
|
|
195
|
+
* nothing, because that commit stays reachable through every NEWER
|
|
196
|
+
* checkpoint's parent pointer — the ref is gone but the commit (and any
|
|
197
|
+
* tree/blob objects unique to it) is still walkable from every surviving
|
|
198
|
+
* descendant. Parentless commits mean a checkpoint's own ref is the ONLY
|
|
199
|
+
* thing keeping its commit reachable, so once that ref is deleted,
|
|
200
|
+
* `git gc --prune=now` can genuinely reclaim it.
|
|
201
|
+
*
|
|
202
|
+
* The returned hash is only reachable once a ref is pointed at it via
|
|
203
|
+
* `updateRef`.
|
|
191
204
|
*/
|
|
192
|
-
async commitTree(treeHash, message
|
|
193
|
-
|
|
194
|
-
if (parentCommit)
|
|
195
|
-
args.push('-p', parentCommit);
|
|
196
|
-
return (await this.git.raw(args)).trim();
|
|
205
|
+
async commitTree(treeHash, message) {
|
|
206
|
+
return (await this.git.raw(['commit-tree', treeHash, '-m', message])).trim();
|
|
197
207
|
}
|
|
198
208
|
async updateRef(refName, commit) {
|
|
199
209
|
await this.git.raw(['update-ref', refName, commit]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-sdk",
|
|
3
|
-
"version": "0.37.
|
|
3
|
+
"version": "0.37.1",
|
|
4
4
|
"description": "TypeScript SDK for building GoodVibes operator, peer, web, mobile, and daemon-connected apps with typed contracts, auth, realtime events, and transport layers.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"goodvibes",
|
|
@@ -453,14 +453,14 @@
|
|
|
453
453
|
"sideEffects": false,
|
|
454
454
|
"type": "module",
|
|
455
455
|
"dependencies": {
|
|
456
|
-
"@pellux/goodvibes-contracts": "0.37.
|
|
457
|
-
"@pellux/goodvibes-daemon-sdk": "0.37.
|
|
458
|
-
"@pellux/goodvibes-errors": "0.37.
|
|
459
|
-
"@pellux/goodvibes-operator-sdk": "0.37.
|
|
460
|
-
"@pellux/goodvibes-peer-sdk": "0.37.
|
|
461
|
-
"@pellux/goodvibes-transport-core": "0.37.
|
|
462
|
-
"@pellux/goodvibes-transport-http": "0.37.
|
|
463
|
-
"@pellux/goodvibes-transport-realtime": "0.37.
|
|
456
|
+
"@pellux/goodvibes-contracts": "0.37.1",
|
|
457
|
+
"@pellux/goodvibes-daemon-sdk": "0.37.1",
|
|
458
|
+
"@pellux/goodvibes-errors": "0.37.1",
|
|
459
|
+
"@pellux/goodvibes-operator-sdk": "0.37.1",
|
|
460
|
+
"@pellux/goodvibes-peer-sdk": "0.37.1",
|
|
461
|
+
"@pellux/goodvibes-transport-core": "0.37.1",
|
|
462
|
+
"@pellux/goodvibes-transport-http": "0.37.1",
|
|
463
|
+
"@pellux/goodvibes-transport-realtime": "0.37.1"
|
|
464
464
|
},
|
|
465
465
|
"optionalDependencies": {
|
|
466
466
|
"@agentclientprotocol/sdk": "^0.21.0",
|