skillwiki 0.10.26 → 0.10.28

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.
@@ -1,455 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- FLEET_REL_PATH,
4
- findReviewRequiredOp,
5
- git,
6
- hasActiveGitSequencer,
7
- hasUnmergedPaths,
8
- loadFleetManifestAndHost,
9
- resolveConfiguredSnapshotWorktree,
10
- runVaultSyncPullHelper,
11
- supersedeStaleReviewRequiredJournals
12
- } from "./chunk-PQG26AGJ.js";
13
- import {
14
- ExitCode,
15
- err,
16
- ok
17
- } from "./chunk-C2DKFJFA.js";
18
-
19
- // src/utils/managed-write-preflight.ts
20
- import { existsSync as existsSync2 } from "fs";
21
- import { join as join2, resolve as resolve2 } from "path";
22
-
23
- // src/utils/managed-write-lock.ts
24
- import { randomBytes } from "crypto";
25
- import {
26
- existsSync,
27
- mkdirSync,
28
- readFileSync,
29
- unlinkSync,
30
- writeFileSync
31
- } from "fs";
32
- import { hostname } from "os";
33
- import { dirname, join, resolve } from "path";
34
- function managedWriteLockPath(vault) {
35
- const gitPath = git(vault, ["rev-parse", "--git-path", "vault-sync/managed-write.lock"]);
36
- if (gitPath) return gitPath.startsWith("/") ? gitPath : join(vault, gitPath);
37
- return join(vault, ".skillwiki", "managed-write.lock");
38
- }
39
- function readLockRecord(path) {
40
- try {
41
- return JSON.parse(readFileSync(path, "utf8"));
42
- } catch {
43
- return null;
44
- }
45
- }
46
- function isManagedWriteLockOwnerAlive(pid) {
47
- if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0) return false;
48
- try {
49
- process.kill(pid, 0);
50
- return true;
51
- } catch (error) {
52
- if (error.code === "EPERM") return true;
53
- return false;
54
- }
55
- }
56
- function hasUnsafeGitState(vault) {
57
- const gitDirRaw = git(vault, ["rev-parse", "--git-dir"]);
58
- if (!gitDirRaw) return true;
59
- const gitDir = gitDirRaw.startsWith("/") ? gitDirRaw : join(vault, gitDirRaw);
60
- for (const rel of ["rebase-merge", "rebase-apply"]) {
61
- if (existsSync(join(gitDir, rel))) return true;
62
- }
63
- for (const rel of ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD"]) {
64
- if (existsSync(join(gitDir, rel))) return true;
65
- }
66
- const unmerged = git(vault, ["ls-files", "-u"]);
67
- return Boolean(unmerged && unmerged.trim().length > 0);
68
- }
69
- function isGitBackedVault(vault) {
70
- return Boolean(git(vault, ["rev-parse", "--absolute-git-dir"]));
71
- }
72
- function hasLocalOwnerProof(vault, record) {
73
- return isGitBackedVault(vault) || typeof record.owner_hostname === "string" && record.owner_hostname === hostname();
74
- }
75
- function reclaimDeadManagedWriteLockOwner(vault, options = {}) {
76
- const path = managedWriteLockPath(vault);
77
- const gitStateVault = resolve(options.gitStateVault ?? vault);
78
- if (!existsSync(path)) return ok({ reclaimed: false });
79
- const record = readLockRecord(path);
80
- if (!record) {
81
- return err("SYNC_LOCK_HELD", { path, message: "managed-write lock unreadable" });
82
- }
83
- if (!hasLocalOwnerProof(vault, record)) {
84
- return err("SYNC_LOCK_HELD", {
85
- path,
86
- owner_hostname: record.owner_hostname,
87
- current_hostname: hostname(),
88
- message: "managed-write lock origin is foreign or unknown"
89
- });
90
- }
91
- if (isManagedWriteLockOwnerAlive(record.pid)) {
92
- return err("SYNC_LOCK_HELD", { path, message: "managed-write lock owner is alive" });
93
- }
94
- if (hasUnsafeGitState(gitStateVault)) {
95
- return err("SYNC_LOCK_HELD", {
96
- path,
97
- git_state_vault: gitStateVault,
98
- message: "managed-write lock not reclaimed: unsafe git state"
99
- });
100
- }
101
- try {
102
- const recoveryDir = join(dirname(path), "recovery");
103
- mkdirSync(recoveryDir, { recursive: true });
104
- const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
105
- const recoveryPath = join(recoveryDir, `stale-managed-write-lock-${stamp}-${process.pid}.json`);
106
- const meta = {
107
- recovered_at: (/* @__PURE__ */ new Date()).toISOString(),
108
- recovery_reason: "owner_pid_dead",
109
- owner_pid_alive: false,
110
- git_state_vault: gitStateVault,
111
- lock: record
112
- };
113
- writeFileSync(recoveryPath, `${JSON.stringify(meta, null, 2)}
114
- `, { flag: "wx" });
115
- unlinkSync(path);
116
- return ok({ reclaimed: true, recoveryPath });
117
- } catch (error) {
118
- return err("WRITE_FAILED", { path, message: String(error) });
119
- }
120
- }
121
- function tryCreateLock(path, command) {
122
- const ownerToken = randomBytes(16).toString("hex");
123
- const acquired = (/* @__PURE__ */ new Date()).toISOString();
124
- try {
125
- mkdirSync(dirname(path), { recursive: true });
126
- writeFileSync(
127
- path,
128
- `${JSON.stringify({
129
- pid: process.pid,
130
- owner_hostname: hostname(),
131
- owner_token: ownerToken,
132
- acquired,
133
- command
134
- })}
135
- `,
136
- { flag: "wx" }
137
- );
138
- return ok({ vault: "", path, ownerToken, acquired });
139
- } catch (error) {
140
- if (error.code === "EEXIST") return err("SYNC_LOCK_HELD", { path });
141
- return err("WRITE_FAILED", { path, message: String(error) });
142
- }
143
- }
144
- function acquireManagedWriteLock(vault, command, options = {}) {
145
- const path = managedWriteLockPath(vault);
146
- const first = tryCreateLock(path, command);
147
- if (first.ok) {
148
- return ok({ ...first.data, vault });
149
- }
150
- if (first.error !== "SYNC_LOCK_HELD") return first;
151
- const reclaimed = reclaimDeadManagedWriteLockOwner(vault, options);
152
- if (!reclaimed.ok || !reclaimed.data.reclaimed) {
153
- return err("SYNC_LOCK_HELD", { path });
154
- }
155
- const second = tryCreateLock(path, command);
156
- if (second.ok) return ok({ ...second.data, vault });
157
- return second.ok === false ? second : err("SYNC_LOCK_HELD", { path });
158
- }
159
- function releaseManagedWriteLock(handle) {
160
- try {
161
- const parsed = JSON.parse(readFileSync(handle.path, "utf8"));
162
- if (parsed.owner_token !== handle.ownerToken || parsed.acquired !== handle.acquired) {
163
- return err("SYNC_LOCK_HELD", {
164
- path: handle.path,
165
- message: "managed-write lock ownership changed"
166
- });
167
- }
168
- unlinkSync(handle.path);
169
- return ok({ released: true });
170
- } catch (error) {
171
- return err("WRITE_FAILED", { path: handle.path, message: String(error) });
172
- }
173
- }
174
-
175
- // src/utils/managed-write-preflight.ts
176
- var DEFAULT_DEPS = {
177
- converge: (input) => runVaultSyncPullHelper(input),
178
- resolveConfiguredSnapshotWorktree
179
- };
180
- function preflightBlocker(vault) {
181
- const unmerged = hasUnmergedPaths(vault);
182
- if (unmerged.length > 0) {
183
- return {
184
- reason: "unmerged-paths",
185
- operation_id: findReviewRequiredOp(vault),
186
- unmerged_paths: unmerged
187
- };
188
- }
189
- if (hasActiveGitSequencer(vault)) {
190
- return { reason: "git-operation-in-progress" };
191
- }
192
- supersedeStaleReviewRequiredJournals(vault, {
193
- by: "skillwiki-managed-write-preflight",
194
- requireClean: false
195
- });
196
- const op = findReviewRequiredOp(vault);
197
- if (op) return { reason: "review-required", operation_id: op };
198
- return null;
199
- }
200
- function hasFleetManifest(vault) {
201
- return existsSync2(join2(vault, FLEET_REL_PATH));
202
- }
203
- function isGitVault(vault) {
204
- return Boolean(git(vault, ["rev-parse", "--absolute-git-dir"]));
205
- }
206
- async function runManagedWritePreflight(input, deps = DEFAULT_DEPS) {
207
- const mutationVault = resolve2(input.vault);
208
- let convergenceVault = input.convergenceVault && resolve2(input.convergenceVault) !== mutationVault ? resolve2(input.convergenceVault) : void 0;
209
- let convergenceSource = convergenceVault ? "explicit" : "single-path";
210
- const mutationBlocker = preflightBlocker(mutationVault);
211
- if (mutationBlocker) {
212
- return {
213
- exitCode: ExitCode.PREFLIGHT_FAILED,
214
- result: err("PREFLIGHT_FAILED", {
215
- reason: mutationBlocker.reason,
216
- operation_id: mutationBlocker.operation_id,
217
- unmerged_paths: mutationBlocker.unmerged_paths
218
- })
219
- };
220
- }
221
- const fleet = await loadFleetManifestAndHost({
222
- vault: mutationVault,
223
- hostId: input.hostId,
224
- env: input.env,
225
- home: input.home,
226
- cwd: input.cwd,
227
- osHostname: input.osHostname,
228
- user: input.user
229
- });
230
- if (!fleet) {
231
- const gitVault2 = isGitVault(mutationVault) ? mutationVault : null;
232
- const head = gitVault2 ? git(gitVault2, ["rev-parse", "HEAD"]) || null : null;
233
- return {
234
- exitCode: ExitCode.OK,
235
- result: ok({
236
- mode: "standalone",
237
- mutation_vault: mutationVault,
238
- git_vault: gitVault2,
239
- base_oid: head,
240
- converged: false,
241
- convergence_source: "single-path"
242
- })
243
- };
244
- }
245
- if (fleet.identityStatus === "unknown" || fleet.identityStatus === "invalid" || !fleet.hostId) {
246
- return {
247
- exitCode: ExitCode.PREFLIGHT_FAILED,
248
- result: err("PREFLIGHT_FAILED", {
249
- reason: "fleet-identity-unresolved",
250
- identity_status: fleet.identityStatus,
251
- host_id: fleet.hostId
252
- })
253
- };
254
- }
255
- const host = fleet.manifest.hosts[fleet.hostId];
256
- if (!host) {
257
- return {
258
- exitCode: ExitCode.PREFLIGHT_FAILED,
259
- result: err("PREFLIGHT_FAILED", { reason: "fleet-host-missing", host_id: fleet.hostId })
260
- };
261
- }
262
- const writesGithub = host.writes_to.includes("github");
263
- if (!writesGithub) {
264
- return {
265
- exitCode: ExitCode.OK,
266
- result: ok({
267
- mode: "immutable-record",
268
- host_id: fleet.hostId,
269
- mutation_vault: mutationVault,
270
- git_vault: null,
271
- base_oid: null,
272
- converged: false,
273
- ...convergenceVault ? { convergence_vault: convergenceVault } : {},
274
- convergence_source: convergenceSource
275
- })
276
- };
277
- }
278
- if (!convergenceVault && host.role === "snapshotter" && host.protected === true) {
279
- const home = input.home ?? input.env?.HOME ?? process.env.HOME ?? "";
280
- const configured = (deps.resolveConfiguredSnapshotWorktree ?? resolveConfiguredSnapshotWorktree)(home);
281
- if (!configured) {
282
- return {
283
- exitCode: ExitCode.PREFLIGHT_FAILED,
284
- result: err("PREFLIGHT_FAILED", {
285
- reason: "convergence-vault-not-configured",
286
- host_id: fleet.hostId,
287
- mutation_vault: mutationVault
288
- })
289
- };
290
- }
291
- convergenceVault = resolve2(configured);
292
- convergenceSource = "configured";
293
- if (convergenceVault === mutationVault) {
294
- return {
295
- exitCode: ExitCode.PREFLIGHT_FAILED,
296
- result: err("PREFLIGHT_FAILED", {
297
- reason: "convergence-vault-not-distinct",
298
- host_id: fleet.hostId,
299
- mutation_vault: mutationVault,
300
- convergence_vault: convergenceVault
301
- })
302
- };
303
- }
304
- }
305
- const gitVault = convergenceVault ?? mutationVault;
306
- if (convergenceVault) {
307
- if (!isGitVault(convergenceVault)) {
308
- return {
309
- exitCode: ExitCode.PREFLIGHT_FAILED,
310
- result: err("PREFLIGHT_FAILED", {
311
- reason: "convergence-vault-not-git",
312
- convergence_vault: convergenceVault
313
- })
314
- };
315
- }
316
- const convergenceHasFleet = hasFleetManifest(convergenceVault);
317
- if (convergenceSource === "configured" && !convergenceHasFleet) {
318
- return {
319
- exitCode: ExitCode.PREFLIGHT_FAILED,
320
- result: err("PREFLIGHT_FAILED", {
321
- reason: "convergence-vault-fleet-missing",
322
- host_id: fleet.hostId,
323
- convergence_vault: convergenceVault
324
- })
325
- };
326
- }
327
- const gitBlocker = preflightBlocker(convergenceVault);
328
- if (gitBlocker) {
329
- return {
330
- exitCode: ExitCode.PREFLIGHT_FAILED,
331
- result: err("PREFLIGHT_FAILED", {
332
- reason: gitBlocker.reason,
333
- operation_id: gitBlocker.operation_id,
334
- unmerged_paths: gitBlocker.unmerged_paths,
335
- convergence_vault: convergenceVault
336
- })
337
- };
338
- }
339
- }
340
- if (convergenceVault && hasFleetManifest(convergenceVault)) {
341
- const convergeFleetCtx = await loadFleetManifestAndHost({
342
- vault: convergenceVault,
343
- hostId: input.hostId ?? fleet.hostId,
344
- env: input.env,
345
- home: input.home,
346
- cwd: input.cwd,
347
- osHostname: input.osHostname,
348
- user: input.user
349
- });
350
- if (!convergeFleetCtx || convergeFleetCtx.identityStatus !== "known" || convergeFleetCtx.hostId !== fleet.hostId) {
351
- return {
352
- exitCode: ExitCode.PREFLIGHT_FAILED,
353
- result: err("PREFLIGHT_FAILED", {
354
- reason: "convergence-vault-identity-mismatch",
355
- host_id: fleet.hostId,
356
- convergence_host_id: convergeFleetCtx?.hostId,
357
- convergence_identity_status: convergeFleetCtx?.identityStatus,
358
- convergence_vault: convergenceVault
359
- })
360
- };
361
- }
362
- }
363
- const dualPathMeta = convergenceVault ? { convergence_vault: convergenceVault } : {};
364
- const converge = await deps.converge({
365
- vault: gitVault,
366
- lockToken: convergenceVault ? void 0 : input.lockToken,
367
- env: input.env,
368
- home: input.home
369
- });
370
- if (!converge.ok) {
371
- const exitCode = converge.error === "PREFLIGHT_FAILED" ? ExitCode.PREFLIGHT_FAILED : ExitCode.SYNC_PULL_FAILED;
372
- return { exitCode, result: converge };
373
- }
374
- const baseOid = git(gitVault, ["rev-parse", "HEAD"]);
375
- if (!baseOid) {
376
- return {
377
- exitCode: ExitCode.PREFLIGHT_FAILED,
378
- result: err("PREFLIGHT_FAILED", {
379
- reason: "missing-head-after-converge",
380
- ...dualPathMeta
381
- })
382
- };
383
- }
384
- return {
385
- exitCode: ExitCode.OK,
386
- result: ok({
387
- mode: "git-writer",
388
- host_id: fleet.hostId,
389
- mutation_vault: mutationVault,
390
- git_vault: gitVault,
391
- base_oid: baseOid,
392
- converged: true,
393
- helper_path: converge.data.helper_path,
394
- ...dualPathMeta,
395
- convergence_source: convergenceSource
396
- })
397
- };
398
- }
399
- async function runManagedWriteTransaction(input, deps = DEFAULT_DEPS) {
400
- const mutationVault = resolve2(input.vault);
401
- const lock = acquireManagedWriteLock(mutationVault, input.command, {
402
- gitStateVault: input.convergenceVault ? resolve2(input.convergenceVault) : mutationVault
403
- });
404
- if (!lock.ok) {
405
- return { exitCode: ExitCode.SYNC_LOCK_HELD, result: lock };
406
- }
407
- const handle = lock.data;
408
- try {
409
- const preflightInput = {
410
- vault: mutationVault,
411
- command: input.command,
412
- convergenceVault: input.convergenceVault,
413
- hostId: input.hostId,
414
- lockToken: handle.ownerToken,
415
- env: input.env,
416
- home: input.home,
417
- cwd: input.cwd,
418
- osHostname: input.osHostname,
419
- user: input.user
420
- };
421
- const preflight = input.preflight ? await input.preflight(preflightInput) : await runManagedWritePreflight(preflightInput, deps);
422
- if (!preflight.result.ok) {
423
- return { exitCode: preflight.exitCode, result: preflight.result };
424
- }
425
- const receipt = preflight.result.data;
426
- if (receipt.mutation_vault !== mutationVault) {
427
- return {
428
- exitCode: ExitCode.PREFLIGHT_FAILED,
429
- result: err("PREFLIGHT_FAILED", {
430
- reason: "mutation-vault-receipt-mismatch",
431
- expected: mutationVault,
432
- actual: receipt.mutation_vault
433
- })
434
- };
435
- }
436
- if (receipt.mode === "immutable-record" && !input.allowImmutableRecord) {
437
- return {
438
- exitCode: ExitCode.PREFLIGHT_FAILED,
439
- result: err("PREFLIGHT_FAILED", {
440
- reason: "immutable-record-not-enabled",
441
- message: "Release A rejects immutable-record mode; event mode arrives in Release B",
442
- host_id: receipt.host_id
443
- })
444
- };
445
- }
446
- return await input.mutate(receipt);
447
- } finally {
448
- releaseManagedWriteLock(handle);
449
- }
450
- }
451
-
452
- export {
453
- runManagedWritePreflight,
454
- runManagedWriteTransaction
455
- };