@goodandready/dsh-time-machine 0.1.5 → 0.1.6

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/lib/index.js CHANGED
@@ -29,6 +29,7 @@ function sessionIdOf(execution, ctx) {
29
29
  export function apply(ctx, config) {
30
30
  let getConfig = () => config;
31
31
  let engine = new ShadowSnapshotEngine({ maxSnapshots: config?.maxSnapshots ?? 20 });
32
+ engine.loadFromRefs().catch(() => {});
32
33
 
33
34
  ctx.inject(['settings'], (sctx) => {
34
35
  const scope = sctx.settings.register(NS, Config, { base: config });
package/lib/snapshot.js CHANGED
@@ -48,6 +48,52 @@ export class ShadowSnapshotEngine {
48
48
  } catch { return false; }
49
49
  }
50
50
 
51
+ // Restore checkpoint metadata from git shadow refs after a restart.
52
+ // git is the durable store; memory is rebuilt from refs/dsh-time-machine/*.
53
+ async loadFromRefs() {
54
+ if (!(await this._isGitRepo())) return 0;
55
+ let refs = [];
56
+ try {
57
+ const { stdout } = await this.exec(
58
+ 'git',
59
+ ['for-each-ref', '--format=%(refname)%00%(objectname)', 'refs/dsh-time-machine/'],
60
+ this.cwd ? { cwd: this.cwd } : {},
61
+ );
62
+ refs = String(stdout ?? '').trim().split('\n').filter(Boolean);
63
+ } catch { return 0; }
64
+ const restored = [];
65
+ for (const line of refs) {
66
+ const [ref, commit] = line.split('\0');
67
+ if (!ref || !commit) continue;
68
+ if (this.snapshots.some(s => s.ref === ref)) continue;
69
+ let createdAt = 0;
70
+ let label = ref.split('/').pop();
71
+ try {
72
+ const { stdout } = await this.exec(
73
+ 'git',
74
+ ['log', '-1', '--format=%ct%00%s', ref],
75
+ this.cwd ? { cwd: this.cwd } : {},
76
+ );
77
+ const [ct, msg] = String(stdout ?? '').split('\0');
78
+ createdAt = Number(ct) * 1000 || 0;
79
+ if (msg && String(msg).trim()) label = String(msg).trim();
80
+ } catch {}
81
+ const short = ref.replace(/^refs\/dsh-time-machine\//, '');
82
+ const slash = short.indexOf('/');
83
+ const sessionId = slash === -1 ? '' : short.slice(0, slash);
84
+ const id = slash === -1 ? short : short.slice(slash + 1);
85
+ restored.push({ id: id || short, label, sessionId, createdAt, commit, ref, seq: 0 });
86
+ }
87
+ // stable order: oldest first, tie-break by ref name
88
+ restored.sort((a, b) => a.createdAt - b.createdAt || (a.ref < b.ref ? -1 : a.ref > b.ref ? 1 : 0));
89
+ for (const snap of restored) snap.seq = ++this.seq;
90
+ this.snapshots.push(...restored);
91
+ const sessions = [...new Set(this.snapshots.map(s => s.sessionId))];
92
+ for (const sid of sessions) this._trimFor(sid);
93
+ if (sessions.length === 0) while (this.snapshots.length > this.maxSnapshots) this.snapshots.shift();
94
+ return restored.length;
95
+ }
96
+
51
97
  async createSnapshot(label = '', { sessionId = '' } = {}) {
52
98
  const id = crypto.randomUUID().slice(0, 8);
53
99
  const createdAt = this._now();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-time-machine",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "DSH plugin for smart checkpoints, workspace safety guards, and instant rollback",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",