skillwiki 0.6.1 → 0.7.0
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/cli.js +416 -40
- package/package.json +1 -1
- package/skills/.claude-plugin/plugin.json +1 -1
- package/skills/.codex-plugin/plugin.json +1 -1
- package/skills/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from "./chunk-TPS5XD2J.js";
|
|
9
9
|
|
|
10
10
|
// src/cli.ts
|
|
11
|
-
import { readFileSync as
|
|
11
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
12
12
|
import { join as join41 } from "path";
|
|
13
13
|
import { Command as Command2 } from "commander";
|
|
14
14
|
|
|
@@ -3489,9 +3489,10 @@ async function runConfigPath(input) {
|
|
|
3489
3489
|
}
|
|
3490
3490
|
|
|
3491
3491
|
// src/commands/doctor.ts
|
|
3492
|
-
import { existsSync as existsSync7, lstatSync, readlinkSync, readdirSync, statSync as statSync2 } from "fs";
|
|
3492
|
+
import { existsSync as existsSync7, lstatSync, readlinkSync, readdirSync, statSync as statSync2, readFileSync as readFileSync7 } from "fs";
|
|
3493
3493
|
import { join as join24, resolve as resolve4 } from "path";
|
|
3494
3494
|
import { execSync as execSync2 } from "child_process";
|
|
3495
|
+
import { platform as platform2 } from "os";
|
|
3495
3496
|
|
|
3496
3497
|
// src/utils/auto-update.ts
|
|
3497
3498
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
|
|
@@ -4209,12 +4210,12 @@ function checkRcloneFlagAudit(resolvedPath) {
|
|
|
4209
4210
|
}
|
|
4210
4211
|
return check("pass", "rclone_flags", "rclone VFS flags", `PID ${pid}: all critical flags at safe values`);
|
|
4211
4212
|
}
|
|
4212
|
-
function checkRcloneVersion(resolvedPath) {
|
|
4213
|
-
if (!resolvedPath) {
|
|
4213
|
+
function checkRcloneVersion(resolvedPath, vaultSyncInstalled) {
|
|
4214
|
+
if (!resolvedPath && !vaultSyncInstalled) {
|
|
4214
4215
|
return check("pass", "rclone_version", "rclone version", "No vault path \u2014 check skipped");
|
|
4215
4216
|
}
|
|
4216
|
-
const fuse = detectFuseMount(resolvedPath);
|
|
4217
|
-
if (!fuse) {
|
|
4217
|
+
const fuse = resolvedPath ? detectFuseMount(resolvedPath) : null;
|
|
4218
|
+
if (!fuse && !vaultSyncInstalled) {
|
|
4218
4219
|
return check("pass", "rclone_version", "rclone version", "local disk \u2014 check skipped");
|
|
4219
4220
|
}
|
|
4220
4221
|
const ver = getRcloneVersion();
|
|
@@ -4326,6 +4327,301 @@ function checkVfsCacheHealth(resolvedPath) {
|
|
|
4326
4327
|
`${stats.files} files, ${(stats.bytesUsed / 1024 / 1024).toFixed(1)}MB \u2014 clean (0 errored, 0 pending)`
|
|
4327
4328
|
);
|
|
4328
4329
|
}
|
|
4330
|
+
function readVaultSyncConfig(home) {
|
|
4331
|
+
try {
|
|
4332
|
+
const content = readFileSync7(join24(home, ".skillwiki", ".env"), "utf8");
|
|
4333
|
+
let installed = false;
|
|
4334
|
+
let role;
|
|
4335
|
+
for (const line of content.split(/\r?\n/)) {
|
|
4336
|
+
const trimmed = line.trim();
|
|
4337
|
+
if (trimmed.length === 0 || trimmed.startsWith("#")) continue;
|
|
4338
|
+
const eq = trimmed.indexOf("=");
|
|
4339
|
+
if (eq <= 0) continue;
|
|
4340
|
+
const k = trimmed.slice(0, eq).trim();
|
|
4341
|
+
const v = trimmed.slice(eq + 1).trim();
|
|
4342
|
+
if (v.length === 0) continue;
|
|
4343
|
+
if (k === "vault_sync.installed" && v === "true") installed = true;
|
|
4344
|
+
if (k === "vault_sync.role") role = v;
|
|
4345
|
+
}
|
|
4346
|
+
return { installed, role };
|
|
4347
|
+
} catch {
|
|
4348
|
+
return { installed: false };
|
|
4349
|
+
}
|
|
4350
|
+
}
|
|
4351
|
+
function vaultSyncChecks(input) {
|
|
4352
|
+
const os = input.os ?? platform2();
|
|
4353
|
+
const home = input.home;
|
|
4354
|
+
if (!input.vaultSyncInstalled) {
|
|
4355
|
+
const skip = (id, label) => check("pass", id, label, "vault-sync not installed \u2014 check skipped");
|
|
4356
|
+
return [
|
|
4357
|
+
skip("vault_sync_installed", "Vault sync installed"),
|
|
4358
|
+
skip("vault_sync_jobs_enabled", "Vault sync jobs enabled"),
|
|
4359
|
+
skip("vault_sync_last_push_age", "Vault sync last push recency"),
|
|
4360
|
+
skip("vault_sync_last_fetch_status", "Vault sync last fetch status"),
|
|
4361
|
+
skip("vault_sync_filter_present", "Vault sync filter file present"),
|
|
4362
|
+
skip("vault_sync_snapshot_guard", "Snapshot script guard")
|
|
4363
|
+
];
|
|
4364
|
+
}
|
|
4365
|
+
const isMac = os === "darwin";
|
|
4366
|
+
const logDir = input.logDir ?? (isMac ? join24(home, "Library", "Logs") : join24(home, ".local", "state", "vault-sync", "log"));
|
|
4367
|
+
const shareDir = input.shareDir ?? (isMac ? join24(home, "Library", "Application Support", "vault-sync", "bin") : join24(home, ".local", "share", "vault-sync", "bin"));
|
|
4368
|
+
const filterPath = input.filterPath ?? join24(home, ".config", "rclone", "wiki-push-filters.txt");
|
|
4369
|
+
const snapshotPath = input.snapshotScriptPath ?? "/root/.hermes/scripts/wiki-snapshot-v3.sh";
|
|
4370
|
+
const pushScriptPath = join24(shareDir, "wiki-push.sh");
|
|
4371
|
+
const c1 = existsSync7(pushScriptPath) ? check("pass", "vault_sync_installed", "Vault sync installed", `Found: ${pushScriptPath}`) : check("error", "vault_sync_installed", "Vault sync installed", `Script not found at ${pushScriptPath} \u2014 run vault-sync-install`);
|
|
4372
|
+
let c2;
|
|
4373
|
+
try {
|
|
4374
|
+
if (isMac) {
|
|
4375
|
+
const uidStr = execSync2("id -u", {
|
|
4376
|
+
encoding: "utf8",
|
|
4377
|
+
timeout: 2e3,
|
|
4378
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4379
|
+
}).trim();
|
|
4380
|
+
const uid = parseInt(uidStr, 10);
|
|
4381
|
+
execSync2(`launchctl print gui/${uid}/com.karlchow.wiki-push`, {
|
|
4382
|
+
encoding: "utf8",
|
|
4383
|
+
timeout: 2e3,
|
|
4384
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4385
|
+
});
|
|
4386
|
+
c2 = check(
|
|
4387
|
+
"pass",
|
|
4388
|
+
"vault_sync_jobs_enabled",
|
|
4389
|
+
"Vault sync jobs enabled",
|
|
4390
|
+
"launchd: com.karlchow.wiki-push loaded"
|
|
4391
|
+
);
|
|
4392
|
+
} else {
|
|
4393
|
+
const out = execSync2("systemctl --user is-enabled wiki-push.timer", {
|
|
4394
|
+
encoding: "utf8",
|
|
4395
|
+
timeout: 2e3,
|
|
4396
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
4397
|
+
}).trim();
|
|
4398
|
+
if (out === "enabled") {
|
|
4399
|
+
c2 = check(
|
|
4400
|
+
"pass",
|
|
4401
|
+
"vault_sync_jobs_enabled",
|
|
4402
|
+
"Vault sync jobs enabled",
|
|
4403
|
+
"systemd: wiki-push.timer enabled"
|
|
4404
|
+
);
|
|
4405
|
+
} else {
|
|
4406
|
+
c2 = check(
|
|
4407
|
+
"error",
|
|
4408
|
+
"vault_sync_jobs_enabled",
|
|
4409
|
+
"Vault sync jobs enabled",
|
|
4410
|
+
`systemd: wiki-push.timer is ${out} \u2014 run vault-sync-install`
|
|
4411
|
+
);
|
|
4412
|
+
}
|
|
4413
|
+
}
|
|
4414
|
+
} catch {
|
|
4415
|
+
c2 = check(
|
|
4416
|
+
"error",
|
|
4417
|
+
"vault_sync_jobs_enabled",
|
|
4418
|
+
"Vault sync jobs enabled",
|
|
4419
|
+
"Scheduler check failed \u2014 run vault-sync-install"
|
|
4420
|
+
);
|
|
4421
|
+
}
|
|
4422
|
+
const logFile = join24(logDir, "wiki-push.log");
|
|
4423
|
+
let c3;
|
|
4424
|
+
try {
|
|
4425
|
+
const logContent = readFileSync7(logFile, "utf8");
|
|
4426
|
+
const lines = logContent.trim().split("\n").filter(Boolean);
|
|
4427
|
+
if (lines.length === 0) {
|
|
4428
|
+
c3 = check(
|
|
4429
|
+
"warn",
|
|
4430
|
+
"vault_sync_last_push_age",
|
|
4431
|
+
"Vault sync last push recency",
|
|
4432
|
+
"Log file is empty"
|
|
4433
|
+
);
|
|
4434
|
+
} else {
|
|
4435
|
+
const lastLine = lines[lines.length - 1];
|
|
4436
|
+
if (/FAIL/.test(lastLine)) {
|
|
4437
|
+
c3 = check(
|
|
4438
|
+
"error",
|
|
4439
|
+
"vault_sync_last_push_age",
|
|
4440
|
+
"Vault sync last push recency",
|
|
4441
|
+
`Last push failed: ${lastLine}`
|
|
4442
|
+
);
|
|
4443
|
+
} else if (/OK push/.test(lastLine)) {
|
|
4444
|
+
const tsMatch = lastLine.match(/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z)/);
|
|
4445
|
+
if (tsMatch) {
|
|
4446
|
+
const lastPush = new Date(tsMatch[1]).getTime();
|
|
4447
|
+
const ageSec = (Date.now() - lastPush) / 1e3;
|
|
4448
|
+
if (ageSec <= 180) {
|
|
4449
|
+
c3 = check(
|
|
4450
|
+
"pass",
|
|
4451
|
+
"vault_sync_last_push_age",
|
|
4452
|
+
"Vault sync last push recency",
|
|
4453
|
+
`Last push ${ageSec.toFixed(0)}s ago`
|
|
4454
|
+
);
|
|
4455
|
+
} else {
|
|
4456
|
+
c3 = check(
|
|
4457
|
+
"warn",
|
|
4458
|
+
"vault_sync_last_push_age",
|
|
4459
|
+
"Vault sync last push recency",
|
|
4460
|
+
`Last push ${Math.round(ageSec)}s ago (>3 min)`
|
|
4461
|
+
);
|
|
4462
|
+
}
|
|
4463
|
+
} else {
|
|
4464
|
+
c3 = check(
|
|
4465
|
+
"warn",
|
|
4466
|
+
"vault_sync_last_push_age",
|
|
4467
|
+
"Vault sync last push recency",
|
|
4468
|
+
`Unparseable push line: ${lastLine.slice(0, 80)}`
|
|
4469
|
+
);
|
|
4470
|
+
}
|
|
4471
|
+
} else {
|
|
4472
|
+
c3 = check(
|
|
4473
|
+
"warn",
|
|
4474
|
+
"vault_sync_last_push_age",
|
|
4475
|
+
"Vault sync last push recency",
|
|
4476
|
+
`Last log entry: ${lastLine.slice(0, 80)}`
|
|
4477
|
+
);
|
|
4478
|
+
}
|
|
4479
|
+
}
|
|
4480
|
+
} catch {
|
|
4481
|
+
c3 = existsSync7(logDir) ? check(
|
|
4482
|
+
"warn",
|
|
4483
|
+
"vault_sync_last_push_age",
|
|
4484
|
+
"Vault sync last push recency",
|
|
4485
|
+
`Log file not found at ${logFile}`
|
|
4486
|
+
) : check(
|
|
4487
|
+
"error",
|
|
4488
|
+
"vault_sync_last_push_age",
|
|
4489
|
+
"Vault sync last push recency",
|
|
4490
|
+
`Log directory not found at ${logDir}`
|
|
4491
|
+
);
|
|
4492
|
+
}
|
|
4493
|
+
const fetchLogFile = join24(logDir, "wiki-fetch.log");
|
|
4494
|
+
let cFetch;
|
|
4495
|
+
try {
|
|
4496
|
+
const logContent = readFileSync7(fetchLogFile, "utf8");
|
|
4497
|
+
const lines = logContent.trim().split("\n").filter(Boolean);
|
|
4498
|
+
if (lines.length === 0) {
|
|
4499
|
+
cFetch = check(
|
|
4500
|
+
"warn",
|
|
4501
|
+
"vault_sync_last_fetch_status",
|
|
4502
|
+
"Vault sync last fetch status",
|
|
4503
|
+
"Fetch log file is empty"
|
|
4504
|
+
);
|
|
4505
|
+
} else {
|
|
4506
|
+
const lastLine = lines[lines.length - 1];
|
|
4507
|
+
if (/fetch failed/i.test(lastLine)) {
|
|
4508
|
+
cFetch = check(
|
|
4509
|
+
"error",
|
|
4510
|
+
"vault_sync_last_fetch_status",
|
|
4511
|
+
"Vault sync last fetch status",
|
|
4512
|
+
`Last fetch failed: ${lastLine.slice(0, 100)}`
|
|
4513
|
+
);
|
|
4514
|
+
} else if (/OK/.test(lastLine)) {
|
|
4515
|
+
cFetch = check(
|
|
4516
|
+
"pass",
|
|
4517
|
+
"vault_sync_last_fetch_status",
|
|
4518
|
+
"Vault sync last fetch status",
|
|
4519
|
+
lastLine.slice(0, 100)
|
|
4520
|
+
);
|
|
4521
|
+
} else {
|
|
4522
|
+
cFetch = check(
|
|
4523
|
+
"warn",
|
|
4524
|
+
"vault_sync_last_fetch_status",
|
|
4525
|
+
"Vault sync last fetch status",
|
|
4526
|
+
`Last fetch log entry: ${lastLine.slice(0, 80)}`
|
|
4527
|
+
);
|
|
4528
|
+
}
|
|
4529
|
+
}
|
|
4530
|
+
} catch {
|
|
4531
|
+
cFetch = check(
|
|
4532
|
+
"warn",
|
|
4533
|
+
"vault_sync_last_fetch_status",
|
|
4534
|
+
"Vault sync last fetch status",
|
|
4535
|
+
`Fetch log not found at ${fetchLogFile}`
|
|
4536
|
+
);
|
|
4537
|
+
}
|
|
4538
|
+
let c4;
|
|
4539
|
+
try {
|
|
4540
|
+
if (!existsSync7(filterPath)) {
|
|
4541
|
+
c4 = check(
|
|
4542
|
+
"error",
|
|
4543
|
+
"vault_sync_filter_present",
|
|
4544
|
+
"Vault sync filter file present",
|
|
4545
|
+
`Filter file not found at ${filterPath}`
|
|
4546
|
+
);
|
|
4547
|
+
} else {
|
|
4548
|
+
const content = readFileSync7(filterPath, "utf8");
|
|
4549
|
+
const requiredExcludes = [
|
|
4550
|
+
"remotely-save/data.json",
|
|
4551
|
+
".skillwiki/sync.lock",
|
|
4552
|
+
".claude/settings.local.json"
|
|
4553
|
+
];
|
|
4554
|
+
const missing = requiredExcludes.filter((ex) => !content.includes(ex));
|
|
4555
|
+
if (missing.length > 0) {
|
|
4556
|
+
c4 = check(
|
|
4557
|
+
"warn",
|
|
4558
|
+
"vault_sync_filter_present",
|
|
4559
|
+
"Vault sync filter file present",
|
|
4560
|
+
`Missing required excludes: ${missing.join(", ")}`
|
|
4561
|
+
);
|
|
4562
|
+
} else {
|
|
4563
|
+
c4 = check(
|
|
4564
|
+
"pass",
|
|
4565
|
+
"vault_sync_filter_present",
|
|
4566
|
+
"Vault sync filter file present",
|
|
4567
|
+
`Found with required excludes at ${filterPath}`
|
|
4568
|
+
);
|
|
4569
|
+
}
|
|
4570
|
+
}
|
|
4571
|
+
} catch {
|
|
4572
|
+
c4 = check(
|
|
4573
|
+
"error",
|
|
4574
|
+
"vault_sync_filter_present",
|
|
4575
|
+
"Vault sync filter file present",
|
|
4576
|
+
`Cannot read filter file at ${filterPath}`
|
|
4577
|
+
);
|
|
4578
|
+
}
|
|
4579
|
+
let c5;
|
|
4580
|
+
if (input.vaultSyncRole !== "snapshotter") {
|
|
4581
|
+
c5 = check(
|
|
4582
|
+
"pass",
|
|
4583
|
+
"vault_sync_snapshot_guard",
|
|
4584
|
+
"Snapshot script guard",
|
|
4585
|
+
"Not a snapshotter host \u2014 check skipped"
|
|
4586
|
+
);
|
|
4587
|
+
} else {
|
|
4588
|
+
try {
|
|
4589
|
+
if (!existsSync7(snapshotPath)) {
|
|
4590
|
+
c5 = check(
|
|
4591
|
+
"error",
|
|
4592
|
+
"vault_sync_snapshot_guard",
|
|
4593
|
+
"Snapshot script guard",
|
|
4594
|
+
`Snapshot script not found at ${snapshotPath}`
|
|
4595
|
+
);
|
|
4596
|
+
} else {
|
|
4597
|
+
const content = readFileSync7(snapshotPath, "utf8");
|
|
4598
|
+
if (!content.includes("--max-delete")) {
|
|
4599
|
+
c5 = check(
|
|
4600
|
+
"error",
|
|
4601
|
+
"vault_sync_snapshot_guard",
|
|
4602
|
+
"Snapshot script guard",
|
|
4603
|
+
`${snapshotPath} is missing --max-delete guard \u2014 dangerous without it`
|
|
4604
|
+
);
|
|
4605
|
+
} else {
|
|
4606
|
+
c5 = check(
|
|
4607
|
+
"pass",
|
|
4608
|
+
"vault_sync_snapshot_guard",
|
|
4609
|
+
"Snapshot script guard",
|
|
4610
|
+
`--max-delete present in ${snapshotPath}`
|
|
4611
|
+
);
|
|
4612
|
+
}
|
|
4613
|
+
}
|
|
4614
|
+
} catch {
|
|
4615
|
+
c5 = check(
|
|
4616
|
+
"error",
|
|
4617
|
+
"vault_sync_snapshot_guard",
|
|
4618
|
+
"Snapshot script guard",
|
|
4619
|
+
`Cannot read ${snapshotPath}`
|
|
4620
|
+
);
|
|
4621
|
+
}
|
|
4622
|
+
}
|
|
4623
|
+
return [c1, c2, c3, cFetch, c4, c5];
|
|
4624
|
+
}
|
|
4329
4625
|
function findSkillMd(dir) {
|
|
4330
4626
|
const results = [];
|
|
4331
4627
|
let entries;
|
|
@@ -4360,6 +4656,7 @@ function findSkillNames(dir) {
|
|
|
4360
4656
|
}
|
|
4361
4657
|
async function runDoctor(input) {
|
|
4362
4658
|
const checks = [];
|
|
4659
|
+
const vsConfig = readVaultSyncConfig(input.home);
|
|
4363
4660
|
checks.push(checkNodeVersion());
|
|
4364
4661
|
checks.push(checkCliChannels(input.argv, input.home));
|
|
4365
4662
|
checks.push(await checkConfigFile(input.home));
|
|
@@ -4380,13 +4677,18 @@ async function runDoctor(input) {
|
|
|
4380
4677
|
checks.push(checkDotStoreClean(resolvedPath));
|
|
4381
4678
|
checks.push(checkS3MountPerf(resolvedPath));
|
|
4382
4679
|
checks.push(checkRcloneFlagAudit(resolvedPath));
|
|
4383
|
-
checks.push(checkRcloneVersion(resolvedPath));
|
|
4680
|
+
checks.push(checkRcloneVersion(resolvedPath, vsConfig.installed));
|
|
4384
4681
|
checks.push(checkWriteTest(resolvedPath));
|
|
4385
4682
|
checks.push(checkVfsCacheHealth(resolvedPath));
|
|
4386
4683
|
checks.push(checkSkillsInstalled(input.home, input.cwd));
|
|
4387
4684
|
checks.push(checkDuplicateSkills(input.home));
|
|
4388
4685
|
checks.push(checkNpmUpdate(input.home, input.currentVersion));
|
|
4389
4686
|
checks.push(checkPluginVersionDrift(input.home, input.currentVersion));
|
|
4687
|
+
checks.push(...vaultSyncChecks({
|
|
4688
|
+
home: input.home,
|
|
4689
|
+
vaultSyncInstalled: vsConfig.installed,
|
|
4690
|
+
vaultSyncRole: vsConfig.role
|
|
4691
|
+
}));
|
|
4390
4692
|
const summary = {
|
|
4391
4693
|
pass: checks.filter((c) => c.status === "pass").length,
|
|
4392
4694
|
info: checks.filter((c) => c.status === "info").length,
|
|
@@ -4909,7 +5211,7 @@ ${newBody}`;
|
|
|
4909
5211
|
|
|
4910
5212
|
// src/commands/update.ts
|
|
4911
5213
|
import { execSync as execSync3 } from "child_process";
|
|
4912
|
-
import { readFileSync as
|
|
5214
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
4913
5215
|
import { join as join26 } from "path";
|
|
4914
5216
|
function resolveGlobalSkillsRoot() {
|
|
4915
5217
|
try {
|
|
@@ -4939,7 +5241,7 @@ async function refreshInstalledSkills(target) {
|
|
|
4939
5241
|
}
|
|
4940
5242
|
async function runUpdate(input) {
|
|
4941
5243
|
const pkg2 = JSON.parse(
|
|
4942
|
-
|
|
5244
|
+
readFileSync8(new URL("../../package.json", import.meta.url), "utf8")
|
|
4943
5245
|
);
|
|
4944
5246
|
const currentVersion = pkg2.version;
|
|
4945
5247
|
const tag = input.distTag ?? "latest";
|
|
@@ -5013,12 +5315,12 @@ async function runUpdate(input) {
|
|
|
5013
5315
|
|
|
5014
5316
|
// src/commands/self-update.ts
|
|
5015
5317
|
import { execSync as execSync4 } from "child_process";
|
|
5016
|
-
import { existsSync as existsSync8, readFileSync as
|
|
5318
|
+
import { existsSync as existsSync8, readFileSync as readFileSync9 } from "fs";
|
|
5017
5319
|
import { join as join27 } from "path";
|
|
5018
5320
|
var DEFAULT_SOURCE_ROOT_SUFFIX = "/Desktop/code/llm-wiki";
|
|
5019
5321
|
async function runSelfUpdate(input) {
|
|
5020
5322
|
const currentVersion = JSON.parse(
|
|
5021
|
-
|
|
5323
|
+
readFileSync9(new URL("../../package.json", import.meta.url), "utf8")
|
|
5022
5324
|
).version;
|
|
5023
5325
|
const sourceRoot = input.sourceRoot ?? `${input.home}${DEFAULT_SOURCE_ROOT_SUFFIX}`;
|
|
5024
5326
|
const localPkgPath = join27(sourceRoot, "packages", "cli", "package.json");
|
|
@@ -5029,7 +5331,7 @@ async function runSelfUpdate(input) {
|
|
|
5029
5331
|
if (hasLocalSource) {
|
|
5030
5332
|
source = "local";
|
|
5031
5333
|
try {
|
|
5032
|
-
availableVersion = JSON.parse(
|
|
5334
|
+
availableVersion = JSON.parse(readFileSync9(localPkgPath, "utf8")).version ?? null;
|
|
5033
5335
|
} catch {
|
|
5034
5336
|
availableVersion = null;
|
|
5035
5337
|
}
|
|
@@ -5087,7 +5389,7 @@ async function runSelfUpdate(input) {
|
|
|
5087
5389
|
}
|
|
5088
5390
|
const newVersion = (() => {
|
|
5089
5391
|
try {
|
|
5090
|
-
return JSON.parse(
|
|
5392
|
+
return JSON.parse(readFileSync9(localPkgPath, "utf8")).version ?? "unknown";
|
|
5091
5393
|
} catch {
|
|
5092
5394
|
return "unknown";
|
|
5093
5395
|
}
|
|
@@ -6127,7 +6429,7 @@ import { existsSync as existsSync12 } from "fs";
|
|
|
6127
6429
|
import { join as join34 } from "path";
|
|
6128
6430
|
|
|
6129
6431
|
// src/utils/sync-lock.ts
|
|
6130
|
-
import { existsSync as existsSync11, mkdirSync as mkdirSync3, readFileSync as
|
|
6432
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync3, readFileSync as readFileSync10, renameSync, unlinkSync as unlinkSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
6131
6433
|
import { join as join33 } from "path";
|
|
6132
6434
|
import { createHash as createHash6 } from "crypto";
|
|
6133
6435
|
function getSessionId() {
|
|
@@ -6142,7 +6444,7 @@ function readLock(vault) {
|
|
|
6142
6444
|
const path = lockPath(vault);
|
|
6143
6445
|
if (!existsSync11(path)) return null;
|
|
6144
6446
|
try {
|
|
6145
|
-
const raw =
|
|
6447
|
+
const raw = readFileSync10(path, "utf8");
|
|
6146
6448
|
return JSON.parse(raw);
|
|
6147
6449
|
} catch {
|
|
6148
6450
|
return null;
|
|
@@ -6206,6 +6508,15 @@ function releaseLock(vault, opts = {}) {
|
|
|
6206
6508
|
}
|
|
6207
6509
|
const sessionId = opts.sessionId ?? getSessionId();
|
|
6208
6510
|
const existing = readLock(vault);
|
|
6511
|
+
if (opts.force) {
|
|
6512
|
+
try {
|
|
6513
|
+
unlinkSync4(path);
|
|
6514
|
+
const prior = existing && existing.session_id !== sessionId ? existing : void 0;
|
|
6515
|
+
return { released: true, prior };
|
|
6516
|
+
} catch {
|
|
6517
|
+
return { released: false };
|
|
6518
|
+
}
|
|
6519
|
+
}
|
|
6209
6520
|
if (!existing || existing.session_id !== sessionId) {
|
|
6210
6521
|
return { released: false };
|
|
6211
6522
|
}
|
|
@@ -6421,6 +6732,7 @@ async function runSyncPull(input) {
|
|
|
6421
6732
|
let pulled = false;
|
|
6422
6733
|
let conflicts = 0;
|
|
6423
6734
|
let filesUpdated = 0;
|
|
6735
|
+
let autoResolved = 0;
|
|
6424
6736
|
try {
|
|
6425
6737
|
const pullOutput = gitStrict(vault, ["pull", "--rebase", "origin", "HEAD"]);
|
|
6426
6738
|
pulled = true;
|
|
@@ -6429,25 +6741,71 @@ async function runSyncPull(input) {
|
|
|
6429
6741
|
} catch (e) {
|
|
6430
6742
|
const errString = String(e);
|
|
6431
6743
|
if (errString.includes("conflict")) {
|
|
6432
|
-
|
|
6433
|
-
|
|
6744
|
+
let inConflict = true;
|
|
6745
|
+
while (inConflict) {
|
|
6746
|
+
const stoppedSha = git(vault, ["rev-parse", "--verify", "REBASE_HEAD"]);
|
|
6747
|
+
let commitMsg = "";
|
|
6748
|
+
if (stoppedSha) {
|
|
6749
|
+
commitMsg = git(vault, ["log", "--format=%s", "-1", stoppedSha]);
|
|
6750
|
+
}
|
|
6751
|
+
const isArchiveOrSnapshot = commitMsg.startsWith("archive: moved") || commitMsg.startsWith("Snapshot ");
|
|
6752
|
+
const conflictedFiles = git(vault, ["diff", "--name-only", "--diff-filter=U"]);
|
|
6753
|
+
const conflictedList = conflictedFiles ? conflictedFiles.split("\n").filter((l) => l.trim().length > 0) : [];
|
|
6754
|
+
if (conflictedList.length === 0) {
|
|
6755
|
+
try {
|
|
6756
|
+
gitStrict(vault, ["rebase", "--continue"]);
|
|
6757
|
+
inConflict = true;
|
|
6758
|
+
} catch {
|
|
6759
|
+
inConflict = false;
|
|
6760
|
+
}
|
|
6761
|
+
continue;
|
|
6762
|
+
}
|
|
6763
|
+
if (isArchiveOrSnapshot) {
|
|
6764
|
+
for (const f of conflictedList) {
|
|
6765
|
+
try {
|
|
6766
|
+
gitStrict(vault, ["checkout", "--ours", f]);
|
|
6767
|
+
gitStrict(vault, ["add", f]);
|
|
6768
|
+
} catch {
|
|
6769
|
+
}
|
|
6770
|
+
}
|
|
6771
|
+
autoResolved += conflictedList.length;
|
|
6772
|
+
try {
|
|
6773
|
+
gitStrict(vault, ["rebase", "--continue"]);
|
|
6774
|
+
} catch (continueErr) {
|
|
6775
|
+
continue;
|
|
6776
|
+
}
|
|
6777
|
+
} else {
|
|
6778
|
+
conflicts = conflictedList.length;
|
|
6779
|
+
return {
|
|
6780
|
+
exitCode: ExitCode.SYNC_PULL_FAILED,
|
|
6781
|
+
result: ok({
|
|
6782
|
+
fetched,
|
|
6783
|
+
pulled: false,
|
|
6784
|
+
files_updated: 0,
|
|
6785
|
+
conflicts,
|
|
6786
|
+
auto_resolved: 0,
|
|
6787
|
+
lint_errors: 0,
|
|
6788
|
+
lint_warnings: 0,
|
|
6789
|
+
humanHint: `pull failed with ${conflicts} conflict(s) on non-archive commit "${commitMsg}" \u2014 resolve manually`
|
|
6790
|
+
})
|
|
6791
|
+
};
|
|
6792
|
+
}
|
|
6793
|
+
}
|
|
6794
|
+
if (autoResolved > 0) {
|
|
6795
|
+
const diffOutput = git(vault, ["diff", "--stat", "HEAD@{1}..HEAD"]);
|
|
6796
|
+
if (diffOutput) {
|
|
6797
|
+
const fileMatch = diffOutput.match(/(\d+) file[s]? changed/);
|
|
6798
|
+
if (fileMatch) filesUpdated = parseInt(fileMatch[1], 10);
|
|
6799
|
+
}
|
|
6800
|
+
pulled = true;
|
|
6801
|
+
conflicts = 0;
|
|
6802
|
+
}
|
|
6803
|
+
} else {
|
|
6434
6804
|
return {
|
|
6435
6805
|
exitCode: ExitCode.SYNC_PULL_FAILED,
|
|
6436
|
-
result:
|
|
6437
|
-
fetched,
|
|
6438
|
-
pulled: false,
|
|
6439
|
-
files_updated: 0,
|
|
6440
|
-
conflicts,
|
|
6441
|
-
lint_errors: 0,
|
|
6442
|
-
lint_warnings: 0,
|
|
6443
|
-
humanHint: `pull failed with ${conflicts} conflict(s) \u2014 resolve manually`
|
|
6444
|
-
})
|
|
6806
|
+
result: err("GIT_PULL_FAILED", { message: errString })
|
|
6445
6807
|
};
|
|
6446
6808
|
}
|
|
6447
|
-
return {
|
|
6448
|
-
exitCode: ExitCode.SYNC_PULL_FAILED,
|
|
6449
|
-
result: err("GIT_PULL_FAILED", { message: errString })
|
|
6450
|
-
};
|
|
6451
6809
|
}
|
|
6452
6810
|
let lintErrors = 0;
|
|
6453
6811
|
let lintWarnings = 0;
|
|
@@ -6459,6 +6817,7 @@ async function runSyncPull(input) {
|
|
|
6459
6817
|
const hintParts = [];
|
|
6460
6818
|
if (filesUpdated > 0) hintParts.push(`updated ${filesUpdated} file(s)`);
|
|
6461
6819
|
else hintParts.push("already up to date");
|
|
6820
|
+
if (autoResolved > 0) hintParts.push(`${autoResolved} conflict(s) auto-resolved`);
|
|
6462
6821
|
if (lintErrors > 0) hintParts.push(`${lintErrors} lint error(s)`);
|
|
6463
6822
|
if (lintWarnings > 0) hintParts.push(`${lintWarnings} lint warning(s)`);
|
|
6464
6823
|
const exitCode = lintErrors > 0 ? ExitCode.LINT_HAS_ERRORS : lintWarnings > 0 ? ExitCode.LINT_HAS_WARNINGS : ExitCode.OK;
|
|
@@ -6469,6 +6828,7 @@ async function runSyncPull(input) {
|
|
|
6469
6828
|
pulled,
|
|
6470
6829
|
files_updated: filesUpdated,
|
|
6471
6830
|
conflicts,
|
|
6831
|
+
auto_resolved: autoResolved,
|
|
6472
6832
|
lint_errors: lintErrors,
|
|
6473
6833
|
lint_warnings: lintWarnings,
|
|
6474
6834
|
humanHint: hintParts.join(", ")
|
|
@@ -6562,18 +6922,34 @@ function runSyncUnlock(input) {
|
|
|
6562
6922
|
result: err("VAULT_PATH_INVALID", { path: vault })
|
|
6563
6923
|
};
|
|
6564
6924
|
}
|
|
6565
|
-
const result = releaseLock(vault, { sessionId: input.sessionId });
|
|
6925
|
+
const result = releaseLock(vault, { sessionId: input.sessionId, force: input.force });
|
|
6926
|
+
let humanHint;
|
|
6927
|
+
if (result.released && result.prior) {
|
|
6928
|
+
humanHint = `lock force-released (was held by session ${result.prior.session_id}, PID ${result.prior.pid})`;
|
|
6929
|
+
} else if (result.released) {
|
|
6930
|
+
humanHint = "lock released";
|
|
6931
|
+
} else {
|
|
6932
|
+
humanHint = "lock not held by this session (no-op)";
|
|
6933
|
+
}
|
|
6934
|
+
const output = {
|
|
6935
|
+
released: result.released,
|
|
6936
|
+
humanHint
|
|
6937
|
+
};
|
|
6938
|
+
if (result.prior) {
|
|
6939
|
+
output.prior = {
|
|
6940
|
+
session_id: result.prior.session_id,
|
|
6941
|
+
pid: result.prior.pid,
|
|
6942
|
+
summary: result.prior.summary
|
|
6943
|
+
};
|
|
6944
|
+
}
|
|
6566
6945
|
return {
|
|
6567
6946
|
exitCode: ExitCode.OK,
|
|
6568
|
-
result: ok(
|
|
6569
|
-
released: result.released,
|
|
6570
|
-
humanHint: result.released ? "lock released" : "lock not held by this session (no-op)"
|
|
6571
|
-
})
|
|
6947
|
+
result: ok(output)
|
|
6572
6948
|
};
|
|
6573
6949
|
}
|
|
6574
6950
|
|
|
6575
6951
|
// src/commands/backup.ts
|
|
6576
|
-
import { statSync as statSync4, readdirSync as readdirSync2, readFileSync as
|
|
6952
|
+
import { statSync as statSync4, readdirSync as readdirSync2, readFileSync as readFileSync11, mkdirSync as mkdirSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
6577
6953
|
import { join as join35, relative as relative3, dirname as dirname11 } from "path";
|
|
6578
6954
|
import { PutObjectCommand, HeadObjectCommand, ListObjectsV2Command, GetObjectCommand, DeleteObjectsCommand } from "@aws-sdk/client-s3";
|
|
6579
6955
|
|
|
@@ -6640,7 +7016,7 @@ async function runBackupSync(input) {
|
|
|
6640
7016
|
continue;
|
|
6641
7017
|
}
|
|
6642
7018
|
try {
|
|
6643
|
-
const body =
|
|
7019
|
+
const body = readFileSync11(absPath);
|
|
6644
7020
|
await client.send(new PutObjectCommand({ Bucket: input.bucket, Key: relPath, Body: body }));
|
|
6645
7021
|
uploaded++;
|
|
6646
7022
|
} catch {
|
|
@@ -7267,7 +7643,7 @@ async function postCommit(vault, exitCode) {
|
|
|
7267
7643
|
}
|
|
7268
7644
|
|
|
7269
7645
|
// src/cli.ts
|
|
7270
|
-
var pkg = JSON.parse(
|
|
7646
|
+
var pkg = JSON.parse(readFileSync12(new URL("../package.json", import.meta.url), "utf8"));
|
|
7271
7647
|
var program = new Command2();
|
|
7272
7648
|
program.name("skillwiki").description("Deterministic helpers for CodeWiki skills").version(pkg.version);
|
|
7273
7649
|
program.option("--human", "render terminal-readable output instead of JSON");
|
|
@@ -7537,10 +7913,10 @@ syncCmd.command("lock [vault]").description("acquire advisory lock on vault").op
|
|
|
7537
7913
|
emit(runSyncLock({ vault: v.vault, summary: opts.summary, ttlMinutes: ttl, force: !!opts.force }));
|
|
7538
7914
|
}
|
|
7539
7915
|
});
|
|
7540
|
-
syncCmd.command("unlock [vault]").description("release advisory lock on vault").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
7916
|
+
syncCmd.command("unlock [vault]").description("release advisory lock on vault").option("--force", "release lock regardless of holder", false).option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
7541
7917
|
const v = await resolveVaultArg(vault, opts.wiki);
|
|
7542
7918
|
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
7543
|
-
else emit(runSyncUnlock({ vault: v.vault }));
|
|
7919
|
+
else emit(runSyncUnlock({ vault: v.vault, force: !!opts.force }));
|
|
7544
7920
|
});
|
|
7545
7921
|
syncCmd.command("peers [vault]").description("list active locks and recent wiki-sync stashes").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
7546
7922
|
const v = await resolveVaultArg(vault, opts.wiki);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillwiki",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"skills": "./",
|
|
5
5
|
"description": "Project-aware Karpathy-style knowledge base for Claude Code: 18 prompt-only skills (wiki-*, proj-*, using-skillwiki) backed by the deterministic `skillwiki` CLI.",
|
|
6
6
|
"author": {
|