skillwiki 0.10.0 → 0.10.2
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/{chunk-S5ABQCXQ.js → chunk-R6BKJWVC.js} +311 -1
- package/dist/{chunk-5TBIMLTZ.js → chunk-SCGC7YNM.js} +14 -144
- package/dist/{chunk-IZABIE44.js → chunk-U34B2XQJ.js} +126 -88
- package/dist/{chunk-DR7KFHNH.js → chunk-Z3WS45PL.js} +60 -15
- package/dist/cli.js +104 -11
- package/dist/{index-projection-ERFX76U5.js → index-projection-VNJ3TLEK.js} +1 -1
- package/dist/{managed-write-preflight-SILEUQEV.js → managed-write-preflight-PW4OOOMV.js} +2 -2
- package/dist/skillwiki-mcp.js +3 -3
- 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/skills/skills/using-skillwiki/SKILL.md +11 -0
- package/skills/using-skillwiki/SKILL.md +11 -0
|
@@ -562,6 +562,305 @@ function safeUserName() {
|
|
|
562
562
|
}
|
|
563
563
|
}
|
|
564
564
|
|
|
565
|
+
// src/utils/git.ts
|
|
566
|
+
import { execFileSync } from "child_process";
|
|
567
|
+
function git(cwd, args) {
|
|
568
|
+
try {
|
|
569
|
+
return execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
570
|
+
} catch {
|
|
571
|
+
return "";
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
function gitStrict(cwd, args) {
|
|
575
|
+
return execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// src/utils/operation-journal.ts
|
|
579
|
+
import {
|
|
580
|
+
existsSync,
|
|
581
|
+
mkdirSync,
|
|
582
|
+
readdirSync,
|
|
583
|
+
readFileSync,
|
|
584
|
+
renameSync,
|
|
585
|
+
writeFileSync
|
|
586
|
+
} from "fs";
|
|
587
|
+
import { join as join2 } from "path";
|
|
588
|
+
function journalDir(vault) {
|
|
589
|
+
const gitPath = git(vault, ["rev-parse", "--git-path", "vault-sync/operations"]);
|
|
590
|
+
if (!gitPath) return null;
|
|
591
|
+
return gitPath.startsWith("/") ? gitPath : join2(vault, gitPath);
|
|
592
|
+
}
|
|
593
|
+
function parseJournalEnv(text) {
|
|
594
|
+
return Object.fromEntries(
|
|
595
|
+
text.split("\n").filter((line) => line.includes("=")).map((line) => {
|
|
596
|
+
const i = line.indexOf("=");
|
|
597
|
+
return [line.slice(0, i), line.slice(i + 1)];
|
|
598
|
+
})
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
function serializeJournalEnv(fields, preferredOrder = []) {
|
|
602
|
+
const keys = [...preferredOrder.filter((k) => k in fields), ...Object.keys(fields).filter((k) => !preferredOrder.includes(k))];
|
|
603
|
+
const seen = /* @__PURE__ */ new Set();
|
|
604
|
+
const lines = [];
|
|
605
|
+
for (const k of keys) {
|
|
606
|
+
if (seen.has(k)) continue;
|
|
607
|
+
seen.add(k);
|
|
608
|
+
lines.push(`${k}=${fields[k]}`);
|
|
609
|
+
}
|
|
610
|
+
return lines.join("\n") + "\n";
|
|
611
|
+
}
|
|
612
|
+
function readJournal(vault, opId) {
|
|
613
|
+
const dir = journalDir(vault);
|
|
614
|
+
if (!dir) return null;
|
|
615
|
+
const path = join2(dir, `${opId}.env`);
|
|
616
|
+
if (!existsSync(path)) return null;
|
|
617
|
+
try {
|
|
618
|
+
return parseJournalEnv(readFileSync(path, "utf8"));
|
|
619
|
+
} catch {
|
|
620
|
+
return null;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
function writeJournal(vault, opId, fields) {
|
|
624
|
+
const dir = journalDir(vault);
|
|
625
|
+
if (!dir) return false;
|
|
626
|
+
try {
|
|
627
|
+
mkdirSync(dir, { recursive: true });
|
|
628
|
+
const path = join2(dir, `${opId}.env`);
|
|
629
|
+
const tmp = `${path}.tmp.${process.pid}`;
|
|
630
|
+
const order = [
|
|
631
|
+
"operation_id",
|
|
632
|
+
"phase",
|
|
633
|
+
"retry_count",
|
|
634
|
+
"original_branch",
|
|
635
|
+
"original_head",
|
|
636
|
+
"target_oid",
|
|
637
|
+
"owned_stash_oid",
|
|
638
|
+
"preservation_scope",
|
|
639
|
+
"lock_identity",
|
|
640
|
+
"helper_version",
|
|
641
|
+
"deployed_runtime_hash",
|
|
642
|
+
"conflict_identity",
|
|
643
|
+
"handoff",
|
|
644
|
+
"reason",
|
|
645
|
+
"prior_reason",
|
|
646
|
+
"superseded_at",
|
|
647
|
+
"cleared_reason",
|
|
648
|
+
"cleared_by",
|
|
649
|
+
"worktree_path",
|
|
650
|
+
"worktree_git_dir",
|
|
651
|
+
"inventory_path"
|
|
652
|
+
];
|
|
653
|
+
writeFileSync(tmp, serializeJournalEnv(fields, order), "utf8");
|
|
654
|
+
renameSync(tmp, path);
|
|
655
|
+
return true;
|
|
656
|
+
} catch {
|
|
657
|
+
return false;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
function listJournalOpIds(vault) {
|
|
661
|
+
const dir = journalDir(vault);
|
|
662
|
+
if (!dir || !existsSync(dir)) return [];
|
|
663
|
+
try {
|
|
664
|
+
return readdirSync(dir).filter((f) => f.endsWith(".env")).map((f) => f.replace(/\.env$/, "")).sort();
|
|
665
|
+
} catch {
|
|
666
|
+
return [];
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
function listReviewRequiredOps(vault) {
|
|
670
|
+
const currentGitDir = git(vault, ["rev-parse", "--absolute-git-dir"]);
|
|
671
|
+
const out = [];
|
|
672
|
+
for (const opId of listJournalOpIds(vault)) {
|
|
673
|
+
const fields = readJournal(vault, opId);
|
|
674
|
+
if (!fields) continue;
|
|
675
|
+
if (fields.phase !== "review-required" || fields.handoff !== "1") continue;
|
|
676
|
+
const journalGitDir = fields.worktree_git_dir ?? "";
|
|
677
|
+
if (!journalGitDir || !currentGitDir || journalGitDir === currentGitDir) {
|
|
678
|
+
out.push({ opId, fields });
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
return out;
|
|
682
|
+
}
|
|
683
|
+
function findReviewRequiredOp(vault) {
|
|
684
|
+
return listReviewRequiredOps(vault)[0]?.opId;
|
|
685
|
+
}
|
|
686
|
+
function hasUnmergedPaths(vault) {
|
|
687
|
+
const unmergedRaw = git(vault, ["diff", "--name-only", "--diff-filter=U"]);
|
|
688
|
+
return unmergedRaw ? unmergedRaw.split("\n").map((s) => s.trim()).filter(Boolean) : [];
|
|
689
|
+
}
|
|
690
|
+
function hasActiveGitSequencer(vault) {
|
|
691
|
+
const gitDir = git(vault, ["rev-parse", "--absolute-git-dir"]);
|
|
692
|
+
if (!gitDir) return false;
|
|
693
|
+
for (const m of ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD"]) {
|
|
694
|
+
if (existsSync(join2(gitDir, m))) return true;
|
|
695
|
+
}
|
|
696
|
+
if (existsSync(join2(gitDir, "rebase-merge")) || existsSync(join2(gitDir, "rebase-apply"))) {
|
|
697
|
+
return true;
|
|
698
|
+
}
|
|
699
|
+
return false;
|
|
700
|
+
}
|
|
701
|
+
function isWorktreeClean(vault) {
|
|
702
|
+
const porcelain = git(vault, ["status", "--porcelain"]);
|
|
703
|
+
return !porcelain || porcelain.trim() === "";
|
|
704
|
+
}
|
|
705
|
+
function canSupersedeJournal(vault, fields) {
|
|
706
|
+
if (hasUnmergedPaths(vault).length > 0) return false;
|
|
707
|
+
if (hasActiveGitSequencer(vault)) return false;
|
|
708
|
+
if (!isWorktreeClean(vault)) return false;
|
|
709
|
+
const target = fields.target_oid?.trim();
|
|
710
|
+
if (!target) return false;
|
|
711
|
+
const head = git(vault, ["rev-parse", "HEAD"]);
|
|
712
|
+
if (!head) return false;
|
|
713
|
+
return gitMergeBaseIsAncestor(vault, target, head);
|
|
714
|
+
}
|
|
715
|
+
function gitMergeBaseIsAncestor(vault, ancestor, tip) {
|
|
716
|
+
if (ancestor === tip) return true;
|
|
717
|
+
const mb = git(vault, ["merge-base", ancestor, tip]);
|
|
718
|
+
return mb !== "" && mb === ancestor;
|
|
719
|
+
}
|
|
720
|
+
function markJournalSuperseded(vault, opId, fields, by) {
|
|
721
|
+
const next = { ...fields };
|
|
722
|
+
if (next.reason && next.reason !== "superseded-stale-review-required") {
|
|
723
|
+
next.prior_reason = next.prior_reason || next.reason;
|
|
724
|
+
}
|
|
725
|
+
next.phase = "complete";
|
|
726
|
+
next.reason = "superseded-stale-review-required";
|
|
727
|
+
next.superseded_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
728
|
+
next.cleared_by = by;
|
|
729
|
+
next.cleared_reason = `operator-or-preflight ${next.superseded_at}`;
|
|
730
|
+
if (!next.handoff) next.handoff = "1";
|
|
731
|
+
if (!next.operation_id) next.operation_id = opId;
|
|
732
|
+
return writeJournal(vault, opId, next);
|
|
733
|
+
}
|
|
734
|
+
function supersedeStaleReviewRequiredJournals(vault, opts = {}) {
|
|
735
|
+
const by = opts.by ?? "skillwiki-preflight";
|
|
736
|
+
const superseded = [];
|
|
737
|
+
const skipped = [];
|
|
738
|
+
if (hasUnmergedPaths(vault).length > 0 || hasActiveGitSequencer(vault) || !isWorktreeClean(vault)) {
|
|
739
|
+
for (const { opId } of listReviewRequiredOps(vault)) skipped.push(opId);
|
|
740
|
+
return { superseded, skipped };
|
|
741
|
+
}
|
|
742
|
+
for (const { opId, fields } of listReviewRequiredOps(vault)) {
|
|
743
|
+
if (!canSupersedeJournal(vault, fields)) {
|
|
744
|
+
skipped.push(opId);
|
|
745
|
+
continue;
|
|
746
|
+
}
|
|
747
|
+
if (opts.dryRun) {
|
|
748
|
+
superseded.push(opId);
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
if (markJournalSuperseded(vault, opId, fields, by)) {
|
|
752
|
+
superseded.push(opId);
|
|
753
|
+
} else {
|
|
754
|
+
skipped.push(opId);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
return { superseded, skipped };
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// src/utils/vault-sync-helper.ts
|
|
761
|
+
import { spawnSync } from "child_process";
|
|
762
|
+
import { existsSync as existsSync2 } from "fs";
|
|
763
|
+
import { homedir, platform } from "os";
|
|
764
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
765
|
+
import { fileURLToPath } from "url";
|
|
766
|
+
var HELPER_NAME = "wiki-pull-with-auto-resolve.sh";
|
|
767
|
+
function candidateHelperPaths(input = { vault: "" }) {
|
|
768
|
+
const env = input.env ?? process.env;
|
|
769
|
+
const paths = [];
|
|
770
|
+
if (input.helperPath) paths.push(input.helperPath);
|
|
771
|
+
if (env.SKILLWIKI_VAULT_SYNC_PULL_HELPER) paths.push(env.SKILLWIKI_VAULT_SYNC_PULL_HELPER);
|
|
772
|
+
let here = input.moduleDir;
|
|
773
|
+
if (!here) {
|
|
774
|
+
try {
|
|
775
|
+
here = dirname2(fileURLToPath(import.meta.url));
|
|
776
|
+
} catch {
|
|
777
|
+
here = void 0;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
if (here) {
|
|
781
|
+
paths.push(join3(here, "vault-sync", "scripts", HELPER_NAME));
|
|
782
|
+
paths.push(join3(here, "..", "vault-sync", "scripts", HELPER_NAME));
|
|
783
|
+
paths.push(join3(here, "..", "..", "vault-sync", "scripts", HELPER_NAME));
|
|
784
|
+
paths.push(join3(here, "..", "..", "..", "vault-sync", "scripts", HELPER_NAME));
|
|
785
|
+
}
|
|
786
|
+
const home = input.home ?? env.HOME ?? env.USERPROFILE ?? (() => {
|
|
787
|
+
try {
|
|
788
|
+
return homedir();
|
|
789
|
+
} catch {
|
|
790
|
+
return void 0;
|
|
791
|
+
}
|
|
792
|
+
})();
|
|
793
|
+
if (home) {
|
|
794
|
+
const xdg = env.XDG_DATA_HOME;
|
|
795
|
+
const isDarwin = platform() === "darwin";
|
|
796
|
+
if (isDarwin) {
|
|
797
|
+
paths.push(join3(home, "Library", "Application Support", "vault-sync", "bin", HELPER_NAME));
|
|
798
|
+
}
|
|
799
|
+
paths.push(join3(xdg || join3(home, ".local", "share"), "vault-sync", "bin", HELPER_NAME));
|
|
800
|
+
if (!isDarwin) {
|
|
801
|
+
paths.push(join3(home, "Library", "Application Support", "vault-sync", "bin", HELPER_NAME));
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
return paths;
|
|
805
|
+
}
|
|
806
|
+
function resolveVaultSyncPullHelper(input) {
|
|
807
|
+
for (const p of candidateHelperPaths(input)) {
|
|
808
|
+
if (p && existsSync2(p)) return p;
|
|
809
|
+
}
|
|
810
|
+
return null;
|
|
811
|
+
}
|
|
812
|
+
async function runVaultSyncPullHelper(input) {
|
|
813
|
+
const helperPath = resolveVaultSyncPullHelper(input);
|
|
814
|
+
if (!helperPath) {
|
|
815
|
+
const tried = candidateHelperPaths(input).filter(Boolean);
|
|
816
|
+
return err("GIT_PULL_FAILED", {
|
|
817
|
+
message: "canonical vault-sync pull helper not found; run skillwiki doctor; install skillwiki@0.10.1+ or set SKILLWIKI_VAULT_SYNC_PULL_HELPER; host install: ~/Library/Application Support/vault-sync/bin or ~/.local/share/vault-sync/bin",
|
|
818
|
+
tried_paths: tried.slice(0, 12)
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
const remote = input.remote ?? "origin";
|
|
822
|
+
const branch = input.branch ?? "main";
|
|
823
|
+
const beforeOid = git(input.vault, ["rev-parse", "HEAD"]);
|
|
824
|
+
if (!beforeOid) {
|
|
825
|
+
return err("GIT_PULL_FAILED", { message: "could not read HEAD before pull" });
|
|
826
|
+
}
|
|
827
|
+
const env = {
|
|
828
|
+
...process.env,
|
|
829
|
+
...input.env ?? {},
|
|
830
|
+
WIKI_DIR: input.vault
|
|
831
|
+
};
|
|
832
|
+
if (input.lockToken) {
|
|
833
|
+
env.VAULT_SYNC_MANAGED_LOCK_TOKEN = input.lockToken;
|
|
834
|
+
}
|
|
835
|
+
const result = spawnSync("bash", [helperPath, remote, branch], {
|
|
836
|
+
env,
|
|
837
|
+
encoding: "utf8",
|
|
838
|
+
cwd: input.vault
|
|
839
|
+
});
|
|
840
|
+
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`;
|
|
841
|
+
const status = result.status ?? 1;
|
|
842
|
+
if (status === 2) {
|
|
843
|
+
return err("PREFLIGHT_FAILED", { reason: "existing-handoff", output, helper_path: helperPath });
|
|
844
|
+
}
|
|
845
|
+
if (status !== 0) {
|
|
846
|
+
return err("GIT_PULL_FAILED", {
|
|
847
|
+
message: result.error ? String(result.error) : `helper exited ${status}`,
|
|
848
|
+
output,
|
|
849
|
+
helper_path: helperPath
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
const afterOid = git(input.vault, ["rev-parse", "HEAD"]);
|
|
853
|
+
if (!afterOid) {
|
|
854
|
+
return err("GIT_PULL_FAILED", { message: "could not read HEAD after pull", helper_path: helperPath });
|
|
855
|
+
}
|
|
856
|
+
return ok({
|
|
857
|
+
before_oid: beforeOid,
|
|
858
|
+
after_oid: afterOid,
|
|
859
|
+
changed: beforeOid !== afterOid,
|
|
860
|
+
helper_path: helperPath
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
|
|
565
864
|
export {
|
|
566
865
|
CONFIG_KEYS,
|
|
567
866
|
isValidWikiProfileKey,
|
|
@@ -576,5 +875,16 @@ export {
|
|
|
576
875
|
snapshotterAliasForLocalHost,
|
|
577
876
|
satelliteGateFromFleetLoad,
|
|
578
877
|
loadFleetManifest,
|
|
579
|
-
resolveFleetHostId
|
|
878
|
+
resolveFleetHostId,
|
|
879
|
+
git,
|
|
880
|
+
gitStrict,
|
|
881
|
+
readJournal,
|
|
882
|
+
listJournalOpIds,
|
|
883
|
+
listReviewRequiredOps,
|
|
884
|
+
findReviewRequiredOp,
|
|
885
|
+
hasUnmergedPaths,
|
|
886
|
+
hasActiveGitSequencer,
|
|
887
|
+
supersedeStaleReviewRequiredJournals,
|
|
888
|
+
resolveVaultSyncPullHelper,
|
|
889
|
+
runVaultSyncPullHelper
|
|
580
890
|
};
|
|
@@ -1,30 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
findReviewRequiredOp,
|
|
4
|
+
git,
|
|
5
|
+
hasActiveGitSequencer,
|
|
6
|
+
hasUnmergedPaths,
|
|
7
|
+
loadFleetManifestAndHost,
|
|
8
|
+
runVaultSyncPullHelper,
|
|
9
|
+
supersedeStaleReviewRequiredJournals
|
|
10
|
+
} from "./chunk-R6BKJWVC.js";
|
|
5
11
|
import {
|
|
6
12
|
ExitCode,
|
|
7
13
|
err,
|
|
8
14
|
ok
|
|
9
15
|
} from "./chunk-C5OLZRRM.js";
|
|
10
16
|
|
|
11
|
-
// src/utils/managed-write-preflight.ts
|
|
12
|
-
import { readdirSync, readFileSync as readFileSync2 } from "fs";
|
|
13
|
-
import { join as join3 } from "path";
|
|
14
|
-
|
|
15
|
-
// src/utils/git.ts
|
|
16
|
-
import { execFileSync } from "child_process";
|
|
17
|
-
function git(cwd, args) {
|
|
18
|
-
try {
|
|
19
|
-
return execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
20
|
-
} catch {
|
|
21
|
-
return "";
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
function gitStrict(cwd, args) {
|
|
25
|
-
return execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
17
|
// src/utils/managed-write-lock.ts
|
|
29
18
|
import { randomBytes } from "crypto";
|
|
30
19
|
import { mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
@@ -68,122 +57,12 @@ function releaseManagedWriteLock(handle) {
|
|
|
68
57
|
}
|
|
69
58
|
}
|
|
70
59
|
|
|
71
|
-
// src/utils/vault-sync-helper.ts
|
|
72
|
-
import { spawnSync } from "child_process";
|
|
73
|
-
import { existsSync } from "fs";
|
|
74
|
-
import { dirname as dirname2, join as join2 } from "path";
|
|
75
|
-
import { fileURLToPath } from "url";
|
|
76
|
-
function candidateHelperPaths(input) {
|
|
77
|
-
const env = input.env ?? process.env;
|
|
78
|
-
const paths = [];
|
|
79
|
-
if (input.helperPath) paths.push(input.helperPath);
|
|
80
|
-
if (env.SKILLWIKI_VAULT_SYNC_PULL_HELPER) paths.push(env.SKILLWIKI_VAULT_SYNC_PULL_HELPER);
|
|
81
|
-
try {
|
|
82
|
-
const here = dirname2(fileURLToPath(import.meta.url));
|
|
83
|
-
paths.push(join2(here, "..", "vault-sync", "scripts", "wiki-pull-with-auto-resolve.sh"));
|
|
84
|
-
paths.push(join2(here, "..", "..", "vault-sync", "scripts", "wiki-pull-with-auto-resolve.sh"));
|
|
85
|
-
paths.push(join2(here, "..", "..", "..", "vault-sync", "scripts", "wiki-pull-with-auto-resolve.sh"));
|
|
86
|
-
} catch {
|
|
87
|
-
}
|
|
88
|
-
return paths;
|
|
89
|
-
}
|
|
90
|
-
function resolveVaultSyncPullHelper(input) {
|
|
91
|
-
for (const p of candidateHelperPaths(input)) {
|
|
92
|
-
if (p && existsSync(p)) return p;
|
|
93
|
-
}
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
96
|
-
async function runVaultSyncPullHelper(input) {
|
|
97
|
-
const helperPath = resolveVaultSyncPullHelper(input);
|
|
98
|
-
if (!helperPath) {
|
|
99
|
-
return err("GIT_PULL_FAILED", { message: "canonical vault-sync pull helper not found" });
|
|
100
|
-
}
|
|
101
|
-
const remote = input.remote ?? "origin";
|
|
102
|
-
const branch = input.branch ?? "main";
|
|
103
|
-
const beforeOid = git(input.vault, ["rev-parse", "HEAD"]);
|
|
104
|
-
if (!beforeOid) {
|
|
105
|
-
return err("GIT_PULL_FAILED", { message: "could not read HEAD before pull" });
|
|
106
|
-
}
|
|
107
|
-
const env = {
|
|
108
|
-
...process.env,
|
|
109
|
-
...input.env ?? {},
|
|
110
|
-
WIKI_DIR: input.vault
|
|
111
|
-
};
|
|
112
|
-
if (input.lockToken) {
|
|
113
|
-
env.VAULT_SYNC_MANAGED_LOCK_TOKEN = input.lockToken;
|
|
114
|
-
}
|
|
115
|
-
const result = spawnSync("bash", [helperPath, remote, branch], {
|
|
116
|
-
env,
|
|
117
|
-
encoding: "utf8",
|
|
118
|
-
cwd: input.vault
|
|
119
|
-
});
|
|
120
|
-
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`;
|
|
121
|
-
const status = result.status ?? 1;
|
|
122
|
-
if (status === 2) {
|
|
123
|
-
return err("PREFLIGHT_FAILED", { reason: "existing-handoff", output, helper_path: helperPath });
|
|
124
|
-
}
|
|
125
|
-
if (status !== 0) {
|
|
126
|
-
return err("GIT_PULL_FAILED", {
|
|
127
|
-
message: result.error ? String(result.error) : `helper exited ${status}`,
|
|
128
|
-
output,
|
|
129
|
-
helper_path: helperPath
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
const afterOid = git(input.vault, ["rev-parse", "HEAD"]);
|
|
133
|
-
if (!afterOid) {
|
|
134
|
-
return err("GIT_PULL_FAILED", { message: "could not read HEAD after pull", helper_path: helperPath });
|
|
135
|
-
}
|
|
136
|
-
return ok({
|
|
137
|
-
before_oid: beforeOid,
|
|
138
|
-
after_oid: afterOid,
|
|
139
|
-
changed: beforeOid !== afterOid,
|
|
140
|
-
helper_path: helperPath
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
|
|
144
60
|
// src/utils/managed-write-preflight.ts
|
|
145
61
|
var DEFAULT_DEPS = {
|
|
146
62
|
converge: (input) => runVaultSyncPullHelper(input)
|
|
147
63
|
};
|
|
148
|
-
function journalDir(vault) {
|
|
149
|
-
const gitPath = git(vault, ["rev-parse", "--git-path", "vault-sync/operations"]);
|
|
150
|
-
if (!gitPath) return null;
|
|
151
|
-
return gitPath.startsWith("/") ? gitPath : join3(vault, gitPath);
|
|
152
|
-
}
|
|
153
|
-
function findReviewRequiredOp(vault) {
|
|
154
|
-
const dir = journalDir(vault);
|
|
155
|
-
if (!dir) return void 0;
|
|
156
|
-
let files;
|
|
157
|
-
try {
|
|
158
|
-
files = readdirSync(dir).filter((f) => f.endsWith(".env"));
|
|
159
|
-
} catch {
|
|
160
|
-
return void 0;
|
|
161
|
-
}
|
|
162
|
-
const currentGitDir = git(vault, ["rev-parse", "--absolute-git-dir"]);
|
|
163
|
-
for (const file of files) {
|
|
164
|
-
let text;
|
|
165
|
-
try {
|
|
166
|
-
text = readFileSync2(join3(dir, file), "utf8");
|
|
167
|
-
} catch {
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
const fields = Object.fromEntries(
|
|
171
|
-
text.split("\n").filter((line) => line.includes("=")).map((line) => {
|
|
172
|
-
const i = line.indexOf("=");
|
|
173
|
-
return [line.slice(0, i), line.slice(i + 1)];
|
|
174
|
-
})
|
|
175
|
-
);
|
|
176
|
-
if (fields.phase !== "review-required" || fields.handoff !== "1") continue;
|
|
177
|
-
const journalGitDir = fields.worktree_git_dir ?? "";
|
|
178
|
-
if (!journalGitDir || !currentGitDir || journalGitDir === currentGitDir) {
|
|
179
|
-
return file.replace(/\.env$/, "");
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
return void 0;
|
|
183
|
-
}
|
|
184
64
|
function preflightBlocker(vault) {
|
|
185
|
-
const
|
|
186
|
-
const unmerged = unmergedRaw ? unmergedRaw.split("\n").map((s) => s.trim()).filter(Boolean) : [];
|
|
65
|
+
const unmerged = hasUnmergedPaths(vault);
|
|
187
66
|
if (unmerged.length > 0) {
|
|
188
67
|
return {
|
|
189
68
|
reason: "unmerged-paths",
|
|
@@ -191,17 +70,10 @@ function preflightBlocker(vault) {
|
|
|
191
70
|
unmerged_paths: unmerged
|
|
192
71
|
};
|
|
193
72
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const markers = ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD"];
|
|
197
|
-
for (const m of markers) {
|
|
198
|
-
try {
|
|
199
|
-
readFileSync2(join3(gitDir, m));
|
|
200
|
-
return { reason: "git-operation-in-progress" };
|
|
201
|
-
} catch {
|
|
202
|
-
}
|
|
203
|
-
}
|
|
73
|
+
if (hasActiveGitSequencer(vault)) {
|
|
74
|
+
return { reason: "git-operation-in-progress" };
|
|
204
75
|
}
|
|
76
|
+
supersedeStaleReviewRequiredJournals(vault, { by: "skillwiki-managed-write-preflight" });
|
|
205
77
|
const op = findReviewRequiredOp(vault);
|
|
206
78
|
if (op) return { reason: "review-required", operation_id: op };
|
|
207
79
|
return null;
|
|
@@ -269,7 +141,8 @@ async function runManagedWritePreflight(input, deps = DEFAULT_DEPS) {
|
|
|
269
141
|
const converge = await deps.converge({
|
|
270
142
|
vault,
|
|
271
143
|
lockToken: input.lockToken,
|
|
272
|
-
env: input.env
|
|
144
|
+
env: input.env,
|
|
145
|
+
home: input.home
|
|
273
146
|
});
|
|
274
147
|
if (!converge.ok) {
|
|
275
148
|
const exitCode = converge.error === "PREFLIGHT_FAILED" ? ExitCode.PREFLIGHT_FAILED : ExitCode.SYNC_PULL_FAILED;
|
|
@@ -333,11 +206,8 @@ async function runManagedWriteTransaction(input, deps = DEFAULT_DEPS) {
|
|
|
333
206
|
}
|
|
334
207
|
|
|
335
208
|
export {
|
|
336
|
-
git,
|
|
337
|
-
gitStrict,
|
|
338
209
|
acquireManagedWriteLock,
|
|
339
210
|
releaseManagedWriteLock,
|
|
340
|
-
runVaultSyncPullHelper,
|
|
341
211
|
runManagedWritePreflight,
|
|
342
212
|
runManagedWriteTransaction
|
|
343
213
|
};
|
|
@@ -9,8 +9,7 @@ import {
|
|
|
9
9
|
} from "./chunk-C5OLZRRM.js";
|
|
10
10
|
|
|
11
11
|
// src/utils/index-projection.ts
|
|
12
|
-
import {
|
|
13
|
-
import { readFile as readFile3 } from "fs/promises";
|
|
12
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
14
13
|
import { join as join3 } from "path";
|
|
15
14
|
|
|
16
15
|
// src/utils/atomic-write.ts
|
|
@@ -59,6 +58,9 @@ async function atomicWriteText(path, text) {
|
|
|
59
58
|
}
|
|
60
59
|
}
|
|
61
60
|
|
|
61
|
+
// src/utils/index-universe.ts
|
|
62
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
63
|
+
|
|
62
64
|
// src/utils/typed-page.ts
|
|
63
65
|
import { lstatSync, realpathSync } from "fs";
|
|
64
66
|
import { dirname as dirname2, posix, relative, resolve, sep } from "path";
|
|
@@ -440,8 +442,15 @@ async function readPageCached(p, cache) {
|
|
|
440
442
|
return pending;
|
|
441
443
|
}
|
|
442
444
|
|
|
443
|
-
// src/utils/index-
|
|
444
|
-
var
|
|
445
|
+
// src/utils/index-universe.ts
|
|
446
|
+
var ROOT_INDEX_SECTION_ORDER = [
|
|
447
|
+
"Entities",
|
|
448
|
+
"Concepts",
|
|
449
|
+
"Comparisons",
|
|
450
|
+
"Queries",
|
|
451
|
+
"Meta",
|
|
452
|
+
"Projects"
|
|
453
|
+
];
|
|
445
454
|
var TYPE_SECTION = {
|
|
446
455
|
entity: "Entities",
|
|
447
456
|
concept: "Concepts",
|
|
@@ -450,6 +459,106 @@ var TYPE_SECTION = {
|
|
|
450
459
|
meta: "Meta"
|
|
451
460
|
};
|
|
452
461
|
var compareText = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
462
|
+
function projectTitleFromReadme(text, slug) {
|
|
463
|
+
const match = text.match(/^#\s+Project:\s+(.+)$/m);
|
|
464
|
+
return match?.[1]?.trim() || slug;
|
|
465
|
+
}
|
|
466
|
+
async function buildRootIndexUniverse(input) {
|
|
467
|
+
const scanned = input.scan ? ok(input.scan) : await scanVault(input.vault);
|
|
468
|
+
if (!scanned.ok) return scanned;
|
|
469
|
+
const required = [];
|
|
470
|
+
const rejectedTyped = [];
|
|
471
|
+
const seen = /* @__PURE__ */ new Map();
|
|
472
|
+
let duplicatesRemoved = 0;
|
|
473
|
+
const typedPages = [...scanned.data.typedKnowledge].sort((a, b) => compareText(a.relPath, b.relPath));
|
|
474
|
+
for (const page of typedPages) {
|
|
475
|
+
let text;
|
|
476
|
+
try {
|
|
477
|
+
text = await readFile3(page.absPath, "utf8");
|
|
478
|
+
} catch {
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
const prepared = prepareTypedPage(text, page.relPath);
|
|
482
|
+
if (!prepared.ok) {
|
|
483
|
+
rejectedTyped.push({
|
|
484
|
+
relPath: page.relPath,
|
|
485
|
+
error: prepared.error,
|
|
486
|
+
detail: prepared.detail
|
|
487
|
+
});
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
const section = TYPE_SECTION[prepared.data.type];
|
|
491
|
+
if (!section) {
|
|
492
|
+
rejectedTyped.push({
|
|
493
|
+
relPath: page.relPath,
|
|
494
|
+
error: "SCHEME_REJECTED",
|
|
495
|
+
detail: { type: prepared.data.type }
|
|
496
|
+
});
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
const target = prepared.data.target.replace(/\.md$/, "");
|
|
500
|
+
const previousTitle = seen.get(target);
|
|
501
|
+
if (previousTitle !== void 0) {
|
|
502
|
+
if (previousTitle !== prepared.data.title) {
|
|
503
|
+
return err("SCHEME_REJECTED", {
|
|
504
|
+
message: `duplicate index target ${target} with differing titles`,
|
|
505
|
+
titles: [previousTitle, prepared.data.title]
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
duplicatesRemoved += 1;
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
seen.set(target, prepared.data.title);
|
|
512
|
+
required.push({
|
|
513
|
+
section,
|
|
514
|
+
target,
|
|
515
|
+
title: prepared.data.title,
|
|
516
|
+
source: "typed"
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
const projectReadmes = scanned.data.allMarkdown.filter((page) => /^projects\/[^/]+\/README\.md$/.test(page.relPath)).sort((a, b) => compareText(a.relPath, b.relPath));
|
|
520
|
+
for (const readme of projectReadmes) {
|
|
521
|
+
let text;
|
|
522
|
+
try {
|
|
523
|
+
text = await readFile3(readme.absPath, "utf8");
|
|
524
|
+
} catch {
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
const projectSlug = readme.relPath.split("/")[1];
|
|
528
|
+
const target = `projects/${projectSlug}/README`;
|
|
529
|
+
const title = projectTitleFromReadme(text, projectSlug);
|
|
530
|
+
const previousTitle = seen.get(target);
|
|
531
|
+
if (previousTitle !== void 0) {
|
|
532
|
+
if (previousTitle !== title) {
|
|
533
|
+
return err("SCHEME_REJECTED", {
|
|
534
|
+
message: `duplicate index target ${target} with differing titles`
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
duplicatesRemoved += 1;
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
seen.set(target, title);
|
|
541
|
+
required.push({ section: "Projects", target, title, source: "project" });
|
|
542
|
+
}
|
|
543
|
+
required.sort((a, b) => {
|
|
544
|
+
const sectionA = ROOT_INDEX_SECTION_ORDER.indexOf(a.section);
|
|
545
|
+
const sectionB = ROOT_INDEX_SECTION_ORDER.indexOf(b.section);
|
|
546
|
+
if (sectionA !== sectionB) return sectionA - sectionB;
|
|
547
|
+
return compareText(a.target, b.target);
|
|
548
|
+
});
|
|
549
|
+
const knownTargets = new Set(required.map((entry) => entry.target));
|
|
550
|
+
for (const page of [...scanned.data.compound].sort((a, b) => compareText(a.relPath, b.relPath))) {
|
|
551
|
+
knownTargets.add(page.relPath.replace(/\.md$/, ""));
|
|
552
|
+
}
|
|
553
|
+
return ok({
|
|
554
|
+
required,
|
|
555
|
+
knownTargets,
|
|
556
|
+
rejectedTyped,
|
|
557
|
+
duplicatesRemoved
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// src/utils/index-projection.ts
|
|
453
562
|
var UNMANAGED_START = "<!-- skillwiki:index-unmanaged:start -->";
|
|
454
563
|
var UNMANAGED_END = "<!-- skillwiki:index-unmanaged:end -->";
|
|
455
564
|
function extractUnmanaged(currentText) {
|
|
@@ -480,94 +589,27 @@ function priorWikilinkTargets(text) {
|
|
|
480
589
|
}
|
|
481
590
|
return out;
|
|
482
591
|
}
|
|
483
|
-
function projectTitleFromReadme(text, slug) {
|
|
484
|
-
const m = text.match(/^#\s+Project:\s+(.+)$/m);
|
|
485
|
-
return m?.[1]?.trim() || slug;
|
|
486
|
-
}
|
|
487
592
|
async function renderRootIndex(input) {
|
|
488
593
|
const vault = input.vault;
|
|
489
594
|
let currentText = input.currentText;
|
|
490
595
|
if (currentText === void 0) {
|
|
491
596
|
try {
|
|
492
|
-
currentText = await
|
|
597
|
+
currentText = await readFile4(join3(vault, "index.md"), "utf8");
|
|
493
598
|
} catch {
|
|
494
599
|
currentText = "";
|
|
495
600
|
}
|
|
496
601
|
}
|
|
497
602
|
const unmanaged = extractUnmanaged(currentText);
|
|
498
603
|
if (!unmanaged.ok) return unmanaged;
|
|
499
|
-
const
|
|
500
|
-
if (!
|
|
501
|
-
const entries =
|
|
502
|
-
|
|
503
|
-
let duplicatesRemoved = 0;
|
|
504
|
-
for (const page of scan.data.typedKnowledge) {
|
|
505
|
-
let text;
|
|
506
|
-
try {
|
|
507
|
-
text = await readFile3(join3(vault, page.relPath), "utf8");
|
|
508
|
-
} catch {
|
|
509
|
-
continue;
|
|
510
|
-
}
|
|
511
|
-
const prepared = prepareTypedPage(text, page.relPath);
|
|
512
|
-
if (!prepared.ok) continue;
|
|
513
|
-
const section = TYPE_SECTION[prepared.data.type];
|
|
514
|
-
if (!section) continue;
|
|
515
|
-
const target = prepared.data.target.replace(/\.md$/, "");
|
|
516
|
-
const prev = seen.get(target);
|
|
517
|
-
if (prev !== void 0) {
|
|
518
|
-
if (prev !== prepared.data.title) {
|
|
519
|
-
return err("SCHEME_REJECTED", {
|
|
520
|
-
message: `duplicate index target ${target} with differing titles`,
|
|
521
|
-
titles: [prev, prepared.data.title]
|
|
522
|
-
});
|
|
523
|
-
}
|
|
524
|
-
duplicatesRemoved += 1;
|
|
525
|
-
continue;
|
|
526
|
-
}
|
|
527
|
-
seen.set(target, prepared.data.title);
|
|
528
|
-
entries.push({
|
|
529
|
-
section,
|
|
530
|
-
target,
|
|
531
|
-
title: prepared.data.title,
|
|
532
|
-
source: "typed"
|
|
533
|
-
});
|
|
534
|
-
}
|
|
535
|
-
try {
|
|
536
|
-
const projectsRoot = join3(vault, "projects");
|
|
537
|
-
for (const slug of readdirSync(projectsRoot, { withFileTypes: true })) {
|
|
538
|
-
if (!slug.isDirectory()) continue;
|
|
539
|
-
const readmePath = join3(projectsRoot, slug.name, "README.md");
|
|
540
|
-
let text;
|
|
541
|
-
try {
|
|
542
|
-
text = readFileSync2(readmePath, "utf8");
|
|
543
|
-
} catch {
|
|
544
|
-
continue;
|
|
545
|
-
}
|
|
546
|
-
const target = `projects/${slug.name}/README`;
|
|
547
|
-
const title = projectTitleFromReadme(text, slug.name);
|
|
548
|
-
const prev = seen.get(target);
|
|
549
|
-
if (prev !== void 0) {
|
|
550
|
-
if (prev !== title) {
|
|
551
|
-
return err("SCHEME_REJECTED", {
|
|
552
|
-
message: `duplicate index target ${target} with differing titles`
|
|
553
|
-
});
|
|
554
|
-
}
|
|
555
|
-
duplicatesRemoved += 1;
|
|
556
|
-
continue;
|
|
557
|
-
}
|
|
558
|
-
seen.set(target, title);
|
|
559
|
-
entries.push({ section: "Projects", target, title, source: "project" });
|
|
560
|
-
}
|
|
561
|
-
} catch {
|
|
562
|
-
}
|
|
563
|
-
entries.sort((a, b) => {
|
|
564
|
-
const sa = SECTION_ORDER.indexOf(a.section);
|
|
565
|
-
const sb = SECTION_ORDER.indexOf(b.section);
|
|
566
|
-
if (sa !== sb) return sa - sb;
|
|
567
|
-
return compareText(a.target, b.target);
|
|
568
|
-
});
|
|
604
|
+
const universe = await buildRootIndexUniverse({ vault });
|
|
605
|
+
if (!universe.ok) return universe;
|
|
606
|
+
const entries = universe.data.required;
|
|
607
|
+
let duplicatesRemoved = universe.data.duplicatesRemoved;
|
|
569
608
|
const prior = priorWikilinkTargets(currentText);
|
|
570
|
-
const
|
|
609
|
+
const knownTargets = universe.data.knownTargets;
|
|
610
|
+
const knownBasenames = new Set(
|
|
611
|
+
[...knownTargets].map((target) => target.split("/").pop().toLowerCase())
|
|
612
|
+
);
|
|
571
613
|
const priorLower = /* @__PURE__ */ new Map();
|
|
572
614
|
for (const t of prior) {
|
|
573
615
|
const k = t.toLowerCase();
|
|
@@ -585,15 +627,10 @@ async function renderRootIndex(input) {
|
|
|
585
627
|
const ghostsRemoved = [
|
|
586
628
|
...new Set(
|
|
587
629
|
prior.filter((t) => {
|
|
588
|
-
const bare = t.includes("/") ? t : null;
|
|
589
|
-
if (bare && !generatedTargets.has(bare) && !generatedTargets.has(t)) {
|
|
590
|
-
if (!seen.has(t)) return true;
|
|
591
|
-
}
|
|
592
630
|
if (!t.includes("/")) {
|
|
593
|
-
|
|
594
|
-
return matches.length === 0;
|
|
631
|
+
return !knownBasenames.has(t.toLowerCase());
|
|
595
632
|
}
|
|
596
|
-
return !
|
|
633
|
+
return !knownTargets.has(t);
|
|
597
634
|
})
|
|
598
635
|
)
|
|
599
636
|
];
|
|
@@ -603,7 +640,7 @@ async function renderRootIndex(input) {
|
|
|
603
640
|
"Generated by `skillwiki index rebuild`. Generated sections are derived from typed-page frontmatter and project manifests.",
|
|
604
641
|
""
|
|
605
642
|
];
|
|
606
|
-
for (const section of
|
|
643
|
+
for (const section of ROOT_INDEX_SECTION_ORDER) {
|
|
607
644
|
lines.push(`## ${section}`, "");
|
|
608
645
|
const sectionEntries = entries.filter((e) => e.section === section);
|
|
609
646
|
for (const e of sectionEntries) {
|
|
@@ -642,6 +679,7 @@ export {
|
|
|
642
679
|
scanVault,
|
|
643
680
|
readPage,
|
|
644
681
|
readPageCached,
|
|
682
|
+
buildRootIndexUniverse,
|
|
645
683
|
renderRootIndex,
|
|
646
684
|
writeRootIndexProjection
|
|
647
685
|
};
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
} from "./chunk-7I2TPIV5.js";
|
|
6
6
|
import {
|
|
7
7
|
atomicWriteText,
|
|
8
|
+
buildRootIndexUniverse,
|
|
8
9
|
extractFrontmatter,
|
|
9
10
|
mapWithConcurrency,
|
|
10
11
|
prepareTypedPage,
|
|
@@ -18,18 +19,20 @@ import {
|
|
|
18
19
|
splitFrontmatter,
|
|
19
20
|
vaultIoConcurrency,
|
|
20
21
|
writeRootIndexProjection
|
|
21
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-U34B2XQJ.js";
|
|
22
23
|
import {
|
|
23
24
|
CONFIG_KEYS,
|
|
24
25
|
isValidWikiProfileKey,
|
|
26
|
+
listReviewRequiredOps,
|
|
25
27
|
loadFleetManifestAndHost,
|
|
26
28
|
parseDotenvFile,
|
|
27
29
|
parseDotenvText,
|
|
28
30
|
profileKey,
|
|
31
|
+
resolveVaultSyncPullHelper,
|
|
29
32
|
satelliteGateFromFleetLoad,
|
|
30
33
|
snapshotterAliasForLocalHost,
|
|
31
34
|
writeDotenv
|
|
32
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-R6BKJWVC.js";
|
|
33
36
|
import {
|
|
34
37
|
CompoundSchema,
|
|
35
38
|
ExitCode,
|
|
@@ -1559,8 +1562,11 @@ function normalizeIndexTarget(raw) {
|
|
|
1559
1562
|
return raw.replace(/\.md$/, "").replace(/^\.?\//, "");
|
|
1560
1563
|
}
|
|
1561
1564
|
async function runIndexCheck(input) {
|
|
1562
|
-
const
|
|
1563
|
-
if (!
|
|
1565
|
+
const universe = await buildRootIndexUniverse({ vault: input.vault, scan: input.scan });
|
|
1566
|
+
if (!universe.ok) {
|
|
1567
|
+
const exitCode = universe.error === "VAULT_PATH_INVALID" ? ExitCode.VAULT_PATH_INVALID : ExitCode.SCHEME_REJECTED;
|
|
1568
|
+
return { exitCode, result: universe };
|
|
1569
|
+
}
|
|
1564
1570
|
let indexText = "";
|
|
1565
1571
|
try {
|
|
1566
1572
|
indexText = await readFile6(join10(input.vault, "index.md"), "utf8");
|
|
@@ -1577,25 +1583,22 @@ async function runIndexCheck(input) {
|
|
|
1577
1583
|
indexBare.set(bare, list);
|
|
1578
1584
|
}
|
|
1579
1585
|
const required = /* @__PURE__ */ new Map();
|
|
1580
|
-
const
|
|
1581
|
-
|
|
1582
|
-
const target = p.relPath.replace(/\.md$/, "");
|
|
1583
|
-
required.set(target, p.relPath);
|
|
1584
|
-
known.add(target);
|
|
1586
|
+
for (const entry of universe.data.required) {
|
|
1587
|
+
required.set(entry.target, `${entry.target}.md`);
|
|
1585
1588
|
}
|
|
1586
|
-
|
|
1587
|
-
|
|
1589
|
+
const requiredBasenameCounts = /* @__PURE__ */ new Map();
|
|
1590
|
+
for (const target of required.keys()) {
|
|
1591
|
+
const basename3 = target.split("/").pop().toLowerCase();
|
|
1592
|
+
requiredBasenameCounts.set(basename3, (requiredBasenameCounts.get(basename3) ?? 0) + 1);
|
|
1588
1593
|
}
|
|
1594
|
+
const known = universe.data.knownTargets;
|
|
1589
1595
|
const missing_from_index = [];
|
|
1590
1596
|
for (const [target, relPath] of required.entries()) {
|
|
1591
1597
|
if (indexTargets.has(target)) continue;
|
|
1592
1598
|
const bare = target.split("/").pop().toLowerCase();
|
|
1593
1599
|
const bareHits = indexBare.get(bare) ?? [];
|
|
1594
1600
|
const basenameOnly = bareHits.filter((t) => !t.includes("/"));
|
|
1595
|
-
|
|
1596
|
-
(t) => t.split("/").pop().toLowerCase() === bare
|
|
1597
|
-
);
|
|
1598
|
-
if (basenameOnly.length === 1 && sameNameRequired.length === 1) continue;
|
|
1601
|
+
if (basenameOnly.length === 1 && requiredBasenameCounts.get(bare) === 1) continue;
|
|
1599
1602
|
missing_from_index.push(relPath);
|
|
1600
1603
|
}
|
|
1601
1604
|
const ghost_entries = [];
|
|
@@ -3032,6 +3035,10 @@ function buildCliSurface() {
|
|
|
3032
3035
|
syncCmd.command("status").option("--wiki <name>").option("--include-stashes").option("--include-remote-health").option("--check-snapshotter");
|
|
3033
3036
|
syncCmd.command("push").option("--wiki <name>");
|
|
3034
3037
|
syncCmd.command("pull").option("--wiki <name>");
|
|
3038
|
+
syncCmd.command("resolve-derived").option("--operation-id <id>").option("--wiki <name>");
|
|
3039
|
+
const syncJournalCmd = syncCmd.command("journal");
|
|
3040
|
+
syncJournalCmd.command("list").option("--wiki <name>");
|
|
3041
|
+
syncJournalCmd.command("clear-stale").option("--dry-run").option("--wiki <name>");
|
|
3035
3042
|
syncCmd.command("lock").option("--summary <text>").option("--ttl-minutes <n>").option("--force").option("--wiki <name>");
|
|
3036
3043
|
syncCmd.command("unlock").option("--force").option("--wiki <name>");
|
|
3037
3044
|
syncCmd.command("peers").option("--wiki <name>");
|
|
@@ -6210,6 +6217,42 @@ function checkVfsCacheHealth(resolvedPath) {
|
|
|
6210
6217
|
`${stats.files} files, ${(stats.bytesUsed / 1024 / 1024).toFixed(1)}MB \u2014 clean (0 errored, 0 pending)`
|
|
6211
6218
|
);
|
|
6212
6219
|
}
|
|
6220
|
+
function checkVaultSyncPullHelper(home, env) {
|
|
6221
|
+
const path = resolveVaultSyncPullHelper({
|
|
6222
|
+
vault: "",
|
|
6223
|
+
home,
|
|
6224
|
+
env
|
|
6225
|
+
});
|
|
6226
|
+
if (path) {
|
|
6227
|
+
return check("pass", "vault_sync_pull_helper", "Vault-sync pull helper", `Resolved: ${path}`);
|
|
6228
|
+
}
|
|
6229
|
+
return check(
|
|
6230
|
+
"error",
|
|
6231
|
+
"vault_sync_pull_helper",
|
|
6232
|
+
"Vault-sync pull helper",
|
|
6233
|
+
"Not found \u2014 install skillwiki@0.10.1+, redeploy vault-sync, or set SKILLWIKI_VAULT_SYNC_PULL_HELPER"
|
|
6234
|
+
);
|
|
6235
|
+
}
|
|
6236
|
+
function checkVaultSyncReviewRequiredJournals(vaultPath) {
|
|
6237
|
+
if (!vaultPath || !existsSync13(join24(vaultPath, ".git"))) {
|
|
6238
|
+
return check("pass", "vault_sync_review_required_journals", "Review-required journals", "No git vault \u2014 check skipped");
|
|
6239
|
+
}
|
|
6240
|
+
try {
|
|
6241
|
+
const ops = listReviewRequiredOps(vaultPath);
|
|
6242
|
+
if (ops.length === 0) {
|
|
6243
|
+
return check("pass", "vault_sync_review_required_journals", "Review-required journals", "None");
|
|
6244
|
+
}
|
|
6245
|
+
const sample = ops[0]?.opId ?? "?";
|
|
6246
|
+
return check(
|
|
6247
|
+
"warn",
|
|
6248
|
+
"vault_sync_review_required_journals",
|
|
6249
|
+
"Review-required journals",
|
|
6250
|
+
`${ops.length} handoff(s); oldest/sample: ${sample} \u2014 if worktree clean: skillwiki sync journal clear-stale --dry-run`
|
|
6251
|
+
);
|
|
6252
|
+
} catch {
|
|
6253
|
+
return check("pass", "vault_sync_review_required_journals", "Review-required journals", "Could not read journals \u2014 check skipped");
|
|
6254
|
+
}
|
|
6255
|
+
}
|
|
6213
6256
|
function readVaultSyncConfig(home) {
|
|
6214
6257
|
try {
|
|
6215
6258
|
const content = readFileSync10(join24(home, ".skillwiki", ".env"), "utf8");
|
|
@@ -6820,6 +6863,8 @@ async function runDoctor(input) {
|
|
|
6820
6863
|
vaultSyncServiceScope: vsConfig.serviceScope,
|
|
6821
6864
|
snapshotScriptPath: vsConfig.snapshotScript
|
|
6822
6865
|
}));
|
|
6866
|
+
checks.push(checkVaultSyncPullHelper(input.home, input.env ?? process.env));
|
|
6867
|
+
checks.push(checkVaultSyncReviewRequiredJournals(resolvedPath));
|
|
6823
6868
|
const satelliteGate = satelliteGateFromFleetLoad(fleetLoad);
|
|
6824
6869
|
checks.push(checkSatelliteLastRun(resolvedPath, satelliteGate.satelliteExpected));
|
|
6825
6870
|
checks.push(checkSatelliteTimer(satelliteGate.satelliteExpected));
|
package/dist/cli.js
CHANGED
|
@@ -68,7 +68,7 @@ import {
|
|
|
68
68
|
satelliteLatestRunPath,
|
|
69
69
|
taxonomyCommentForPage,
|
|
70
70
|
upsertIndexEntry
|
|
71
|
-
} from "./chunk-
|
|
71
|
+
} from "./chunk-Z3WS45PL.js";
|
|
72
72
|
import {
|
|
73
73
|
normalizeDistTag,
|
|
74
74
|
readCache,
|
|
@@ -88,29 +88,33 @@ import {
|
|
|
88
88
|
scanVault,
|
|
89
89
|
splitFrontmatter,
|
|
90
90
|
writeRootIndexProjection
|
|
91
|
-
} from "./chunk-
|
|
91
|
+
} from "./chunk-U34B2XQJ.js";
|
|
92
92
|
import {
|
|
93
93
|
acquireManagedWriteLock,
|
|
94
|
-
git,
|
|
95
|
-
gitStrict,
|
|
96
94
|
releaseManagedWriteLock,
|
|
97
95
|
runManagedWritePreflight,
|
|
98
|
-
runManagedWriteTransaction
|
|
99
|
-
|
|
100
|
-
} from "./chunk-5TBIMLTZ.js";
|
|
96
|
+
runManagedWriteTransaction
|
|
97
|
+
} from "./chunk-SCGC7YNM.js";
|
|
101
98
|
import {
|
|
102
99
|
FLEET_REL_PATH,
|
|
100
|
+
git,
|
|
101
|
+
gitStrict,
|
|
102
|
+
listJournalOpIds,
|
|
103
|
+
listReviewRequiredOps,
|
|
103
104
|
loadFleetManifest,
|
|
104
105
|
loadFleetManifestAndHost,
|
|
105
106
|
parseDotenvFile,
|
|
106
107
|
parseDotenvText,
|
|
107
108
|
profileKey,
|
|
109
|
+
readJournal,
|
|
108
110
|
resolveFleetHostId,
|
|
109
111
|
runFleetContext,
|
|
110
112
|
runFleetValidate,
|
|
113
|
+
runVaultSyncPullHelper,
|
|
111
114
|
snapshotterAliasForLocalHost,
|
|
115
|
+
supersedeStaleReviewRequiredJournals,
|
|
112
116
|
writeDotenv
|
|
113
|
-
} from "./chunk-
|
|
117
|
+
} from "./chunk-R6BKJWVC.js";
|
|
114
118
|
import {
|
|
115
119
|
ExitCode,
|
|
116
120
|
MetaSchema,
|
|
@@ -2529,7 +2533,7 @@ ${fmRewritten}
|
|
|
2529
2533
|
await rename2(join17(input.vault, relPath), join17(input.vault, archivePath));
|
|
2530
2534
|
let indexUpdated = false;
|
|
2531
2535
|
if (!isRaw) {
|
|
2532
|
-
const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-
|
|
2536
|
+
const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-VNJ3TLEK.js");
|
|
2533
2537
|
const before = await readFile7(join17(input.vault, "index.md"), "utf8").catch(() => "");
|
|
2534
2538
|
const fullTarget = relPath.replace(/\.md$/, "");
|
|
2535
2539
|
const bare = fullTarget.split("/").pop() ?? fullTarget;
|
|
@@ -2640,7 +2644,7 @@ async function runRemove(input) {
|
|
|
2640
2644
|
if (relPath.endsWith(".md") && !relPath.startsWith("raw/")) {
|
|
2641
2645
|
const { readFile: readFile17 } = await import("fs/promises");
|
|
2642
2646
|
const { join: pathJoin } = await import("path");
|
|
2643
|
-
const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-
|
|
2647
|
+
const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-VNJ3TLEK.js");
|
|
2644
2648
|
const before = await readFile17(pathJoin(input.vault, "index.md"), "utf8").catch(() => "");
|
|
2645
2649
|
const fullTarget = relPath.replace(/\.md$/, "");
|
|
2646
2650
|
const bare = fullTarget.split("/").pop() ?? fullTarget;
|
|
@@ -2990,6 +2994,30 @@ ${migratedBody}${newFooter}`;
|
|
|
2990
2994
|
// src/commands/update.ts
|
|
2991
2995
|
import { execSync } from "child_process";
|
|
2992
2996
|
import { join as join19 } from "path";
|
|
2997
|
+
function parseCoreSemver(version) {
|
|
2998
|
+
const m = version.match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
2999
|
+
if (!m) return null;
|
|
3000
|
+
return { major: parseInt(m[1], 10), minor: parseInt(m[2], 10), patch: parseInt(m[3], 10) };
|
|
3001
|
+
}
|
|
3002
|
+
function needs0101Migration(previousVersion, newVersion) {
|
|
3003
|
+
const p = parseCoreSemver(previousVersion);
|
|
3004
|
+
const n = parseCoreSemver(newVersion);
|
|
3005
|
+
if (!p || !n) return false;
|
|
3006
|
+
const prevLt = p.major < 0 || p.major === 0 && p.minor < 10 || p.major === 0 && p.minor === 10 && p.patch < 1;
|
|
3007
|
+
const newGe = n.major > 0 || n.major === 0 && n.minor > 10 || n.major === 0 && n.minor === 10 && n.patch >= 1;
|
|
3008
|
+
return prevLt && newGe;
|
|
3009
|
+
}
|
|
3010
|
+
function migrationNotesForUpgrade(previousVersion, newVersion) {
|
|
3011
|
+
if (!needs0101Migration(previousVersion, newVersion)) return [];
|
|
3012
|
+
return [
|
|
3013
|
+
"Migration 0.10.1:",
|
|
3014
|
+
"- pull helper resolves from dist/ + host vault-sync install",
|
|
3015
|
+
"- run: skillwiki doctor",
|
|
3016
|
+
"- if managed writes blocked: skillwiki sync journal list",
|
|
3017
|
+
"- then: skillwiki sync journal clear-stale --dry-run",
|
|
3018
|
+
"- legacy override: SKILLWIKI_VAULT_SYNC_PULL_HELPER=<path-to-wiki-pull-with-auto-resolve.sh>"
|
|
3019
|
+
];
|
|
3020
|
+
}
|
|
2993
3021
|
function resolveGlobalSkillsRoot() {
|
|
2994
3022
|
try {
|
|
2995
3023
|
const globalRoot = execSync("npm root -g", {
|
|
@@ -3085,6 +3113,9 @@ async function runUpdate(input) {
|
|
|
3085
3113
|
hintLines.push(`version warnings: ${version_warnings.length}`);
|
|
3086
3114
|
for (const w of version_warnings) hintLines.push(` ${w}`);
|
|
3087
3115
|
}
|
|
3116
|
+
for (const line of migrationNotesForUpgrade(currentVersion, latest)) {
|
|
3117
|
+
hintLines.push(line);
|
|
3118
|
+
}
|
|
3088
3119
|
return {
|
|
3089
3120
|
exitCode: ExitCode.OK,
|
|
3090
3121
|
result: ok({
|
|
@@ -5772,6 +5803,50 @@ function runSyncUnlock(input) {
|
|
|
5772
5803
|
};
|
|
5773
5804
|
}
|
|
5774
5805
|
|
|
5806
|
+
// src/commands/sync-journal.ts
|
|
5807
|
+
function runSyncJournalList(input) {
|
|
5808
|
+
const byPhase = {};
|
|
5809
|
+
for (const opId of listJournalOpIds(input.vault)) {
|
|
5810
|
+
const fields = readJournal(input.vault, opId);
|
|
5811
|
+
const phase = fields?.phase ?? "unknown";
|
|
5812
|
+
byPhase[phase] = (byPhase[phase] ?? 0) + 1;
|
|
5813
|
+
}
|
|
5814
|
+
const review = listReviewRequiredOps(input.vault).map(({ opId, fields }) => ({
|
|
5815
|
+
operation_id: opId,
|
|
5816
|
+
reason: fields.reason,
|
|
5817
|
+
target_oid: fields.target_oid,
|
|
5818
|
+
original_head: fields.original_head
|
|
5819
|
+
}));
|
|
5820
|
+
const total = Object.values(byPhase).reduce((a, b) => a + b, 0);
|
|
5821
|
+
const hint = review.length === 0 ? `journals: ${total} total; no review-required handoffs` : `journals: ${total} total; ${review.length} review-required \u2014 if worktree clean: skillwiki sync journal clear-stale --dry-run`;
|
|
5822
|
+
return {
|
|
5823
|
+
exitCode: ExitCode.OK,
|
|
5824
|
+
result: ok({
|
|
5825
|
+
total,
|
|
5826
|
+
by_phase: byPhase,
|
|
5827
|
+
review_required: review,
|
|
5828
|
+
humanHint: hint
|
|
5829
|
+
})
|
|
5830
|
+
};
|
|
5831
|
+
}
|
|
5832
|
+
function runSyncJournalClearStale(input) {
|
|
5833
|
+
const { superseded, skipped } = supersedeStaleReviewRequiredJournals(input.vault, {
|
|
5834
|
+
dryRun: !!input.dryRun,
|
|
5835
|
+
by: input.dryRun ? "skillwiki-sync-journal-clear-stale-dry-run" : "skillwiki-sync-journal-clear-stale"
|
|
5836
|
+
});
|
|
5837
|
+
const mode = input.dryRun ? "dry-run" : "write";
|
|
5838
|
+
const hint = superseded.length === 0 ? `clear-stale (${mode}): nothing to supersede; skipped=${skipped.length}` : `clear-stale (${mode}): ${superseded.length} journal(s); skipped=${skipped.length}`;
|
|
5839
|
+
return {
|
|
5840
|
+
exitCode: ExitCode.OK,
|
|
5841
|
+
result: ok({
|
|
5842
|
+
dry_run: !!input.dryRun,
|
|
5843
|
+
superseded,
|
|
5844
|
+
skipped,
|
|
5845
|
+
humanHint: hint
|
|
5846
|
+
})
|
|
5847
|
+
};
|
|
5848
|
+
}
|
|
5849
|
+
|
|
5775
5850
|
// src/commands/backup.ts
|
|
5776
5851
|
import { statSync as statSync2, readdirSync, readFileSync as readFileSync11, mkdirSync as mkdirSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
5777
5852
|
import { join as join28, relative as relative2, dirname as dirname6 } from "path";
|
|
@@ -6674,7 +6749,7 @@ async function emitManagedVaultWrite(vault, command, mutate, opts) {
|
|
|
6674
6749
|
if (guard.blocked) {
|
|
6675
6750
|
return emit({ exitCode: guard.exitCode, result: guard.result }, void 0, { postCommit: false });
|
|
6676
6751
|
}
|
|
6677
|
-
const { runManagedWriteTransaction: runManagedWriteTransaction2 } = await import("./managed-write-preflight-
|
|
6752
|
+
const { runManagedWriteTransaction: runManagedWriteTransaction2 } = await import("./managed-write-preflight-PW4OOOMV.js");
|
|
6678
6753
|
const run = await runManagedWriteTransaction2({
|
|
6679
6754
|
vault,
|
|
6680
6755
|
command,
|
|
@@ -7225,6 +7300,24 @@ syncCmd.command("resolve-derived [vault]").description("resolve mixed derived co
|
|
|
7225
7300
|
);
|
|
7226
7301
|
}
|
|
7227
7302
|
});
|
|
7303
|
+
var syncJournalCmd = syncCmd.command("journal").description("inspect or clear vault-sync operation journals");
|
|
7304
|
+
syncJournalCmd.command("list [vault]").description("list vault-sync operation journals and review-required handoffs").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
7305
|
+
const v = await resolveVaultArg(vault, opts.wiki);
|
|
7306
|
+
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
7307
|
+
else emit(runSyncJournalList({ vault: v.vault }));
|
|
7308
|
+
});
|
|
7309
|
+
syncJournalCmd.command("clear-stale [vault]").description("supersede stale review-required journals when worktree is clean").option("--dry-run", "report what would be cleared without writing", false).option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
7310
|
+
const v = await resolveVaultArg(vault, opts.wiki);
|
|
7311
|
+
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
7312
|
+
else if (opts.dryRun) emit(runSyncJournalClearStale({ vault: v.vault, dryRun: true }));
|
|
7313
|
+
else {
|
|
7314
|
+
return emitGuardedVaultWrite(
|
|
7315
|
+
v.vault,
|
|
7316
|
+
"sync journal clear-stale",
|
|
7317
|
+
async () => runSyncJournalClearStale({ vault: v.vault, dryRun: false })
|
|
7318
|
+
);
|
|
7319
|
+
}
|
|
7320
|
+
});
|
|
7228
7321
|
syncCmd.command("lock [vault]").description("acquire advisory lock on vault").option("--summary <text>", "lock description", "skillwiki sync").option("--ttl-minutes <n>", "lock time-to-live in minutes", "30").option("--force", "overwrite existing lock", false).option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
7229
7322
|
const v = await resolveVaultArg(vault, opts.wiki);
|
|
7230
7323
|
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import {
|
|
3
3
|
runManagedWritePreflight,
|
|
4
4
|
runManagedWriteTransaction
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-SCGC7YNM.js";
|
|
6
|
+
import "./chunk-R6BKJWVC.js";
|
|
7
7
|
import "./chunk-C5OLZRRM.js";
|
|
8
8
|
export {
|
|
9
9
|
runManagedWritePreflight,
|
package/dist/skillwiki-mcp.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
runSkillwikiMcpStdio
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-Z3WS45PL.js";
|
|
5
5
|
import "./chunk-7I2TPIV5.js";
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-U34B2XQJ.js";
|
|
7
|
+
import "./chunk-R6BKJWVC.js";
|
|
8
8
|
import "./chunk-C5OLZRRM.js";
|
|
9
9
|
|
|
10
10
|
// src/mcp-entry.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillwiki",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
4
4
|
"skills": "./",
|
|
5
5
|
"description": "Project-aware Karpathy-style knowledge base for Claude Code: 19 prompt-only skills (wiki-*, proj-*, using-skillwiki) backed by the deterministic `skillwiki` CLI.",
|
|
6
6
|
"author": {
|
package/skills/package.json
CHANGED
|
@@ -80,6 +80,17 @@ path, run publisher dry-run, then run the same command with `--write`.
|
|
|
80
80
|
|
|
81
81
|
Before a managed vault mutation, invoke the managed SkillWiki command while the draft remains outside the authoritative target path. The command resolves fleet authority, refuses existing unmerged/review-required state, converges an authorized Git writer, freezes the base OID, and only then applies the write. Do not run `git pull --rebase --autostash` after placing the authoritative change in the live worktree. Do not edit root `index.md` or root `log.md` directly; projection and log commands own those compatibility files.
|
|
82
82
|
|
|
83
|
+
### Managed write / 0.10.1 migration troubleshooting
|
|
84
|
+
|
|
85
|
+
After upgrading to skillwiki **≥0.10.1** (or when managed write fails):
|
|
86
|
+
|
|
87
|
+
1. Run `skillwiki doctor` — checks `vault_sync_pull_helper` and `vault_sync_review_required_journals`.
|
|
88
|
+
2. If pull helper is missing: install `skillwiki@0.10.1+` (helper must resolve from `dist/vault-sync/scripts/`) and/or redeploy vault-sync host install. Last-resort override: `SKILLWIKI_VAULT_SYNC_PULL_HELPER` pointing at `wiki-pull-with-auto-resolve.sh` under host vault-sync `bin/` (macOS Application Support or Linux `~/.local/share/vault-sync/bin`).
|
|
89
|
+
3. If preflight reports `review-required` on a **clean** worktree: `skillwiki sync journal list`, then `skillwiki sync journal clear-stale --dry-run`, then `clear-stale` without dry-run. Preflight also auto-supersedes stale handoffs when criteria match; do not force-clear during active rebase/unmerged state.
|
|
90
|
+
4. After `skillwiki update` across 0.10.1, read the printed Migration 0.10.1 notes.
|
|
91
|
+
|
|
92
|
+
Also mirror these pointers in vault-presync / vault-sync-status skills when operating pull/push.
|
|
93
|
+
|
|
83
94
|
- typed pages: `skillwiki page publish <draft> <vault> --target <path>` then the same command with `--write`
|
|
84
95
|
- archive: `skillwiki archive <path> <vault>`
|
|
85
96
|
- ad-hoc structural log: `skillwiki log-append <vault> --content '<entry>'` (Release A dual-write) or event materialization (Release B)
|
|
@@ -80,6 +80,17 @@ path, run publisher dry-run, then run the same command with `--write`.
|
|
|
80
80
|
|
|
81
81
|
Before a managed vault mutation, invoke the managed SkillWiki command while the draft remains outside the authoritative target path. The command resolves fleet authority, refuses existing unmerged/review-required state, converges an authorized Git writer, freezes the base OID, and only then applies the write. Do not run `git pull --rebase --autostash` after placing the authoritative change in the live worktree. Do not edit root `index.md` or root `log.md` directly; projection and log commands own those compatibility files.
|
|
82
82
|
|
|
83
|
+
### Managed write / 0.10.1 migration troubleshooting
|
|
84
|
+
|
|
85
|
+
After upgrading to skillwiki **≥0.10.1** (or when managed write fails):
|
|
86
|
+
|
|
87
|
+
1. Run `skillwiki doctor` — checks `vault_sync_pull_helper` and `vault_sync_review_required_journals`.
|
|
88
|
+
2. If pull helper is missing: install `skillwiki@0.10.1+` (helper must resolve from `dist/vault-sync/scripts/`) and/or redeploy vault-sync host install. Last-resort override: `SKILLWIKI_VAULT_SYNC_PULL_HELPER` pointing at `wiki-pull-with-auto-resolve.sh` under host vault-sync `bin/` (macOS Application Support or Linux `~/.local/share/vault-sync/bin`).
|
|
89
|
+
3. If preflight reports `review-required` on a **clean** worktree: `skillwiki sync journal list`, then `skillwiki sync journal clear-stale --dry-run`, then `clear-stale` without dry-run. Preflight also auto-supersedes stale handoffs when criteria match; do not force-clear during active rebase/unmerged state.
|
|
90
|
+
4. After `skillwiki update` across 0.10.1, read the printed Migration 0.10.1 notes.
|
|
91
|
+
|
|
92
|
+
Also mirror these pointers in vault-presync / vault-sync-status skills when operating pull/push.
|
|
93
|
+
|
|
83
94
|
- typed pages: `skillwiki page publish <draft> <vault> --target <path>` then the same command with `--write`
|
|
84
95
|
- archive: `skillwiki archive <path> <vault>`
|
|
85
96
|
- ad-hoc structural log: `skillwiki log-append <vault> --content '<entry>'` (Release A dual-write) or event materialization (Release B)
|