skillwiki 0.10.17 → 0.10.18

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.
@@ -867,6 +867,45 @@ async function runVaultSyncPullHelper(input) {
867
867
  });
868
868
  }
869
869
 
870
+ // src/utils/snapshot-worktree.ts
871
+ import { readFileSync as readFileSync2 } from "fs";
872
+ import { join as join4, resolve } from "path";
873
+ function readSkillWikiConfig(path) {
874
+ try {
875
+ return parseDotenvText(readFileSync2(path, "utf8"));
876
+ } catch {
877
+ return {};
878
+ }
879
+ }
880
+ function readSnapshotProfile(path) {
881
+ const allowed = /* @__PURE__ */ new Set(["WIKI_GIT_WORKTREE", "SNAPSHOT_WORKTREE", "GIT_DIR"]);
882
+ const profile = {};
883
+ try {
884
+ for (const rawLine of readFileSync2(path, "utf8").split(/\r?\n/)) {
885
+ const line = rawLine.trim();
886
+ if (line.length === 0 || line.startsWith("#")) continue;
887
+ const eq = line.indexOf("=");
888
+ if (eq <= 0) continue;
889
+ const key = line.slice(0, eq).trim();
890
+ const value = line.slice(eq + 1).trim();
891
+ if (allowed.has(key) && value.length > 0) profile[key] = value;
892
+ }
893
+ } catch {
894
+ }
895
+ return profile;
896
+ }
897
+ function resolveConfiguredSnapshotWorktree(home) {
898
+ if (!home) return void 0;
899
+ const config = readSkillWikiConfig(join4(home, ".skillwiki", ".env"));
900
+ const explicit = config["vault_sync.snapshot_worktree"];
901
+ if (explicit) return resolve(explicit);
902
+ const snapshotProfile = config["vault_sync.snapshot_profile"];
903
+ if (!snapshotProfile) return void 0;
904
+ const profile = readSnapshotProfile(resolve(snapshotProfile));
905
+ const fromProfile = profile.WIKI_GIT_WORKTREE ?? profile.SNAPSHOT_WORKTREE ?? profile.GIT_DIR;
906
+ return fromProfile ? resolve(fromProfile) : void 0;
907
+ }
908
+
870
909
  export {
871
910
  CONFIG_KEYS,
872
911
  isValidWikiProfileKey,
@@ -897,5 +936,6 @@ export {
897
936
  markJournalSuperseded,
898
937
  supersedeStaleReviewRequiredJournals,
899
938
  resolveVaultSyncPullHelper,
900
- runVaultSyncPullHelper
939
+ runVaultSyncPullHelper,
940
+ resolveConfiguredSnapshotWorktree
901
941
  };
@@ -29,11 +29,12 @@ import {
29
29
  parseDotenvFile,
30
30
  parseDotenvText,
31
31
  profileKey,
32
+ resolveConfiguredSnapshotWorktree,
32
33
  resolveVaultSyncPullHelper,
33
34
  satelliteGateFromFleetLoad,
34
35
  snapshotterAliasForLocalHost,
35
36
  writeDotenv
36
- } from "./chunk-VOVXDN56.js";
37
+ } from "./chunk-GNS2ZV5P.js";
37
38
  import {
38
39
  CompoundSchema,
39
40
  ExitCode,
@@ -6482,8 +6483,6 @@ function readVaultSyncConfig(home) {
6482
6483
  let role;
6483
6484
  let serviceScope;
6484
6485
  let snapshotScript;
6485
- let snapshotProfile;
6486
- let snapshotWorktree;
6487
6486
  for (const line of content.split(/\r?\n/)) {
6488
6487
  const trimmed = line.trim();
6489
6488
  if (trimmed.length === 0 || trimmed.startsWith("#")) continue;
@@ -6496,37 +6495,15 @@ function readVaultSyncConfig(home) {
6496
6495
  if (k === "vault_sync.role") role = v;
6497
6496
  if (k === "vault_sync.service_scope") serviceScope = v;
6498
6497
  if (k === "vault_sync.snapshot_script") snapshotScript = v;
6499
- if (k === "vault_sync.snapshot_profile") snapshotProfile = v;
6500
- if (k === "vault_sync.snapshot_worktree") snapshotWorktree = v;
6501
6498
  }
6502
- return { installed, role, serviceScope, snapshotScript, snapshotProfile, snapshotWorktree };
6499
+ return { installed, role, serviceScope, snapshotScript };
6503
6500
  } catch {
6504
6501
  return { installed: false };
6505
6502
  }
6506
6503
  }
6507
- function readKeyFromEnvFile(path, keys) {
6508
- try {
6509
- const content = readFileSync10(path, "utf8");
6510
- for (const line of content.split(/\r?\n/)) {
6511
- const trimmed = line.trim();
6512
- if (trimmed.length === 0 || trimmed.startsWith("#")) continue;
6513
- const eq = trimmed.indexOf("=");
6514
- if (eq <= 0) continue;
6515
- const key = trimmed.slice(0, eq).trim();
6516
- if (!keys.includes(key)) continue;
6517
- const value = trimmed.slice(eq + 1).trim();
6518
- if (value.length > 0) return value;
6519
- }
6520
- } catch {
6521
- }
6522
- return void 0;
6523
- }
6524
- function resolveSnapshotGitWorktree(config) {
6525
- if (config.snapshotWorktree) return config.snapshotWorktree;
6526
- if (config.snapshotProfile) {
6527
- const fromProfile = readKeyFromEnvFile(config.snapshotProfile, ["WIKI_GIT_WORKTREE", "SNAPSHOT_WORKTREE", "GIT_DIR"]);
6528
- if (fromProfile) return fromProfile;
6529
- }
6504
+ function resolveSnapshotGitWorktree(home) {
6505
+ const configured = resolveConfiguredSnapshotWorktree(home);
6506
+ if (configured) return configured;
6530
6507
  const defaultPath = "/root/wiki-git";
6531
6508
  return existsSync13(defaultPath) ? defaultPath : void 0;
6532
6509
  }
@@ -7126,7 +7103,7 @@ async function runDoctor(input) {
7126
7103
  checks.push(check("error", "wiki_path_set", "WIKI_PATH configured", "No vault configured. Run `skillwiki init` or pass --vault."));
7127
7104
  }
7128
7105
  const resolvedPath = resolved.ok ? resolved.data.path : void 0;
7129
- const gitCheckPath = vsConfig.role === "snapshotter" ? resolveSnapshotGitWorktree(vsConfig) ?? resolvedPath : resolvedPath;
7106
+ const gitCheckPath = vsConfig.role === "snapshotter" ? resolveSnapshotGitWorktree(input.home) ?? resolvedPath : resolvedPath;
7130
7107
  checks.push(checkWikiPathExists(resolvedPath));
7131
7108
  checks.push(checkVaultStructure(resolvedPath));
7132
7109
  checks.push(checkObsidianTemplates(resolvedPath));
@@ -6,9 +6,10 @@ import {
6
6
  hasActiveGitSequencer,
7
7
  hasUnmergedPaths,
8
8
  loadFleetManifestAndHost,
9
+ resolveConfiguredSnapshotWorktree,
9
10
  runVaultSyncPullHelper,
10
11
  supersedeStaleReviewRequiredJournals
11
- } from "./chunk-VOVXDN56.js";
12
+ } from "./chunk-GNS2ZV5P.js";
12
13
  import {
13
14
  ExitCode,
14
15
  err,
@@ -16,7 +17,7 @@ import {
16
17
  } from "./chunk-Y6KRDGI2.js";
17
18
 
18
19
  // src/utils/managed-write-preflight.ts
19
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
20
+ import { existsSync as existsSync2 } from "fs";
20
21
  import { join as join2, resolve } from "path";
21
22
 
22
23
  // src/utils/managed-write-lock.ts
@@ -149,7 +150,8 @@ function releaseManagedWriteLock(handle) {
149
150
 
150
151
  // src/utils/managed-write-preflight.ts
151
152
  var DEFAULT_DEPS = {
152
- converge: (input) => runVaultSyncPullHelper(input)
153
+ converge: (input) => runVaultSyncPullHelper(input),
154
+ resolveConfiguredSnapshotWorktree
153
155
  };
154
156
  function preflightBlocker(vault) {
155
157
  const unmerged = hasUnmergedPaths(vault);
@@ -171,19 +173,17 @@ function preflightBlocker(vault) {
171
173
  if (op) return { reason: "review-required", operation_id: op };
172
174
  return null;
173
175
  }
174
- function fleetManifestBytes(vault) {
175
- const path = join2(vault, FLEET_REL_PATH);
176
- if (!existsSync2(path)) return null;
177
- return readFileSync2(path);
176
+ function hasFleetManifest(vault) {
177
+ return existsSync2(join2(vault, FLEET_REL_PATH));
178
178
  }
179
179
  function isGitVault(vault) {
180
180
  return Boolean(git(vault, ["rev-parse", "--absolute-git-dir"]));
181
181
  }
182
182
  async function runManagedWritePreflight(input, deps = DEFAULT_DEPS) {
183
- const vault = input.vault;
184
- const convergenceVault = input.convergenceVault && resolve(input.convergenceVault) !== resolve(vault) ? resolve(input.convergenceVault) : void 0;
185
- const gitVault = convergenceVault ?? vault;
186
- const mutationBlocker = preflightBlocker(vault);
183
+ const mutationVault = resolve(input.vault);
184
+ let convergenceVault = input.convergenceVault && resolve(input.convergenceVault) !== mutationVault ? resolve(input.convergenceVault) : void 0;
185
+ let convergenceSource = convergenceVault ? "explicit" : "single-path";
186
+ const mutationBlocker = preflightBlocker(mutationVault);
187
187
  if (mutationBlocker) {
188
188
  return {
189
189
  exitCode: ExitCode.PREFLIGHT_FAILED,
@@ -194,46 +194,27 @@ async function runManagedWritePreflight(input, deps = DEFAULT_DEPS) {
194
194
  })
195
195
  };
196
196
  }
197
- if (convergenceVault) {
198
- if (!isGitVault(convergenceVault)) {
199
- return {
200
- exitCode: ExitCode.PREFLIGHT_FAILED,
201
- result: err("PREFLIGHT_FAILED", {
202
- reason: "convergence-vault-not-git",
203
- convergence_vault: convergenceVault
204
- })
205
- };
206
- }
207
- const gitBlocker = preflightBlocker(convergenceVault);
208
- if (gitBlocker) {
209
- return {
210
- exitCode: ExitCode.PREFLIGHT_FAILED,
211
- result: err("PREFLIGHT_FAILED", {
212
- reason: gitBlocker.reason,
213
- operation_id: gitBlocker.operation_id,
214
- unmerged_paths: gitBlocker.unmerged_paths,
215
- convergence_vault: convergenceVault
216
- })
217
- };
218
- }
219
- }
220
197
  const fleet = await loadFleetManifestAndHost({
221
- vault,
198
+ vault: mutationVault,
222
199
  hostId: input.hostId,
223
200
  env: input.env,
224
201
  home: input.home,
225
- osHostname: input.osHostname
202
+ cwd: input.cwd,
203
+ osHostname: input.osHostname,
204
+ user: input.user
226
205
  });
227
- const dualPathMeta = convergenceVault ? { convergence_vault: convergenceVault } : {};
228
206
  if (!fleet) {
229
- const head = git(gitVault, ["rev-parse", "HEAD"]) || null;
207
+ const gitVault2 = isGitVault(mutationVault) ? mutationVault : null;
208
+ const head = gitVault2 ? git(gitVault2, ["rev-parse", "HEAD"]) || null : null;
230
209
  return {
231
210
  exitCode: ExitCode.OK,
232
211
  result: ok({
233
212
  mode: "standalone",
213
+ mutation_vault: mutationVault,
214
+ git_vault: gitVault2,
234
215
  base_oid: head,
235
216
  converged: false,
236
- ...dualPathMeta
217
+ convergence_source: "single-path"
237
218
  })
238
219
  };
239
220
  }
@@ -247,27 +228,6 @@ async function runManagedWritePreflight(input, deps = DEFAULT_DEPS) {
247
228
  })
248
229
  };
249
230
  }
250
- if (convergenceVault && fleetManifestBytes(convergenceVault)) {
251
- const convergeFleetCtx = await loadFleetManifestAndHost({
252
- vault: convergenceVault,
253
- hostId: input.hostId ?? fleet.hostId,
254
- env: input.env,
255
- home: input.home,
256
- osHostname: input.osHostname
257
- });
258
- if (!convergeFleetCtx || convergeFleetCtx.identityStatus !== "known" || convergeFleetCtx.hostId !== fleet.hostId) {
259
- return {
260
- exitCode: ExitCode.PREFLIGHT_FAILED,
261
- result: err("PREFLIGHT_FAILED", {
262
- reason: "convergence-vault-identity-mismatch",
263
- host_id: fleet.hostId,
264
- convergence_host_id: convergeFleetCtx?.hostId,
265
- convergence_identity_status: convergeFleetCtx?.identityStatus,
266
- convergence_vault: convergenceVault
267
- })
268
- };
269
- }
270
- }
271
231
  const host = fleet.manifest.hosts[fleet.hostId];
272
232
  if (!host) {
273
233
  return {
@@ -282,12 +242,101 @@ async function runManagedWritePreflight(input, deps = DEFAULT_DEPS) {
282
242
  result: ok({
283
243
  mode: "immutable-record",
284
244
  host_id: fleet.hostId,
245
+ mutation_vault: mutationVault,
246
+ git_vault: null,
285
247
  base_oid: null,
286
248
  converged: false,
287
- ...dualPathMeta
249
+ ...convergenceVault ? { convergence_vault: convergenceVault } : {},
250
+ convergence_source: convergenceSource
288
251
  })
289
252
  };
290
253
  }
254
+ if (!convergenceVault && host.role === "snapshotter" && host.protected === true) {
255
+ const home = input.home ?? input.env?.HOME ?? process.env.HOME ?? "";
256
+ const configured = (deps.resolveConfiguredSnapshotWorktree ?? resolveConfiguredSnapshotWorktree)(home);
257
+ if (!configured) {
258
+ return {
259
+ exitCode: ExitCode.PREFLIGHT_FAILED,
260
+ result: err("PREFLIGHT_FAILED", {
261
+ reason: "convergence-vault-not-configured",
262
+ host_id: fleet.hostId,
263
+ mutation_vault: mutationVault
264
+ })
265
+ };
266
+ }
267
+ convergenceVault = resolve(configured);
268
+ convergenceSource = "configured";
269
+ if (convergenceVault === mutationVault) {
270
+ return {
271
+ exitCode: ExitCode.PREFLIGHT_FAILED,
272
+ result: err("PREFLIGHT_FAILED", {
273
+ reason: "convergence-vault-not-distinct",
274
+ host_id: fleet.hostId,
275
+ mutation_vault: mutationVault,
276
+ convergence_vault: convergenceVault
277
+ })
278
+ };
279
+ }
280
+ }
281
+ const gitVault = convergenceVault ?? mutationVault;
282
+ if (convergenceVault) {
283
+ if (!isGitVault(convergenceVault)) {
284
+ return {
285
+ exitCode: ExitCode.PREFLIGHT_FAILED,
286
+ result: err("PREFLIGHT_FAILED", {
287
+ reason: "convergence-vault-not-git",
288
+ convergence_vault: convergenceVault
289
+ })
290
+ };
291
+ }
292
+ const convergenceHasFleet = hasFleetManifest(convergenceVault);
293
+ if (convergenceSource === "configured" && !convergenceHasFleet) {
294
+ return {
295
+ exitCode: ExitCode.PREFLIGHT_FAILED,
296
+ result: err("PREFLIGHT_FAILED", {
297
+ reason: "convergence-vault-fleet-missing",
298
+ host_id: fleet.hostId,
299
+ convergence_vault: convergenceVault
300
+ })
301
+ };
302
+ }
303
+ const gitBlocker = preflightBlocker(convergenceVault);
304
+ if (gitBlocker) {
305
+ return {
306
+ exitCode: ExitCode.PREFLIGHT_FAILED,
307
+ result: err("PREFLIGHT_FAILED", {
308
+ reason: gitBlocker.reason,
309
+ operation_id: gitBlocker.operation_id,
310
+ unmerged_paths: gitBlocker.unmerged_paths,
311
+ convergence_vault: convergenceVault
312
+ })
313
+ };
314
+ }
315
+ }
316
+ if (convergenceVault && hasFleetManifest(convergenceVault)) {
317
+ const convergeFleetCtx = await loadFleetManifestAndHost({
318
+ vault: convergenceVault,
319
+ hostId: input.hostId ?? fleet.hostId,
320
+ env: input.env,
321
+ home: input.home,
322
+ cwd: input.cwd,
323
+ osHostname: input.osHostname,
324
+ user: input.user
325
+ });
326
+ if (!convergeFleetCtx || convergeFleetCtx.identityStatus !== "known" || convergeFleetCtx.hostId !== fleet.hostId) {
327
+ return {
328
+ exitCode: ExitCode.PREFLIGHT_FAILED,
329
+ result: err("PREFLIGHT_FAILED", {
330
+ reason: "convergence-vault-identity-mismatch",
331
+ host_id: fleet.hostId,
332
+ convergence_host_id: convergeFleetCtx?.hostId,
333
+ convergence_identity_status: convergeFleetCtx?.identityStatus,
334
+ convergence_vault: convergenceVault
335
+ })
336
+ };
337
+ }
338
+ }
339
+ const dualPathMeta = convergenceVault ? { convergence_vault: convergenceVault } : {};
291
340
  const converge = await deps.converge({
292
341
  vault: gitVault,
293
342
  lockToken: convergenceVault ? void 0 : input.lockToken,
@@ -313,37 +362,51 @@ async function runManagedWritePreflight(input, deps = DEFAULT_DEPS) {
313
362
  result: ok({
314
363
  mode: "git-writer",
315
364
  host_id: fleet.hostId,
365
+ mutation_vault: mutationVault,
366
+ git_vault: gitVault,
316
367
  base_oid: baseOid,
317
368
  converged: true,
318
369
  helper_path: converge.data.helper_path,
319
- ...dualPathMeta
370
+ ...dualPathMeta,
371
+ convergence_source: convergenceSource
320
372
  })
321
373
  };
322
374
  }
323
375
  async function runManagedWriteTransaction(input, deps = DEFAULT_DEPS) {
324
- const lock = acquireManagedWriteLock(input.vault, input.command);
376
+ const mutationVault = resolve(input.vault);
377
+ const lock = acquireManagedWriteLock(mutationVault, input.command);
325
378
  if (!lock.ok) {
326
379
  return { exitCode: ExitCode.SYNC_LOCK_HELD, result: lock };
327
380
  }
328
- let handle = lock.data;
381
+ const handle = lock.data;
329
382
  try {
330
- const preflight = await runManagedWritePreflight(
331
- {
332
- vault: input.vault,
333
- command: input.command,
334
- convergenceVault: input.convergenceVault,
335
- hostId: input.hostId,
336
- lockToken: handle.ownerToken,
337
- env: input.env,
338
- home: input.home,
339
- osHostname: input.osHostname
340
- },
341
- deps
342
- );
383
+ const preflightInput = {
384
+ vault: mutationVault,
385
+ command: input.command,
386
+ convergenceVault: input.convergenceVault,
387
+ hostId: input.hostId,
388
+ lockToken: handle.ownerToken,
389
+ env: input.env,
390
+ home: input.home,
391
+ cwd: input.cwd,
392
+ osHostname: input.osHostname,
393
+ user: input.user
394
+ };
395
+ const preflight = input.preflight ? await input.preflight(preflightInput) : await runManagedWritePreflight(preflightInput, deps);
343
396
  if (!preflight.result.ok) {
344
397
  return { exitCode: preflight.exitCode, result: preflight.result };
345
398
  }
346
399
  const receipt = preflight.result.data;
400
+ if (receipt.mutation_vault !== mutationVault) {
401
+ return {
402
+ exitCode: ExitCode.PREFLIGHT_FAILED,
403
+ result: err("PREFLIGHT_FAILED", {
404
+ reason: "mutation-vault-receipt-mismatch",
405
+ expected: mutationVault,
406
+ actual: receipt.mutation_vault
407
+ })
408
+ };
409
+ }
347
410
  if (receipt.mode === "immutable-record" && !input.allowImmutableRecord) {
348
411
  return {
349
412
  exitCode: ExitCode.PREFLIGHT_FAILED,
@@ -361,8 +424,6 @@ async function runManagedWriteTransaction(input, deps = DEFAULT_DEPS) {
361
424
  }
362
425
 
363
426
  export {
364
- acquireManagedWriteLock,
365
- releaseManagedWriteLock,
366
427
  runManagedWritePreflight,
367
428
  runManagedWriteTransaction
368
429
  };