skillwiki 0.10.98 → 0.10.102
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-MRYWP5HY.js → chunk-7UK67FAZ.js} +4 -2
- package/dist/{chunk-EEO2YDPX.js → chunk-PIXBNB74.js} +307 -185
- package/dist/cli.js +539 -240
- package/dist/{managed-write-preflight-THGIM2U7.js → managed-write-preflight-NS7OQ2Y7.js} +1 -1
- package/dist/skillwiki-mcp.js +2 -2
- package/package.json +1 -1
- package/skills/.claude-plugin/plugin.json +2 -2
- package/skills/.codex-plugin/plugin.json +3 -3
- package/skills/.cursor-plugin/plugin.json +7 -2
- package/skills/README.md +2 -2
- package/skills/package.json +2 -2
- package/skills/skills/skillwiki-connect/SKILL.md +75 -0
- package/skills/skills/skillwiki-mcp/SKILL.md +5 -5
- package/skills/skills/using-skillwiki/SKILL.md +5 -3
- package/skills/skills/using-skillwiki/activation.md +1 -0
- package/skills/skillwiki-connect/SKILL.md +75 -0
- package/skills/skillwiki-mcp/SKILL.md +5 -5
- package/skills/using-skillwiki/SKILL.md +5 -3
- package/skills/using-skillwiki/activation.md +1 -0
package/dist/cli.js
CHANGED
|
@@ -3,8 +3,12 @@ import {
|
|
|
3
3
|
CAPTURE_HYGIENE_CONTRACT,
|
|
4
4
|
DEFAULT_CAPTURE_BUDGET,
|
|
5
5
|
DEFAULT_DIRTY_VOLUME_THRESHOLD,
|
|
6
|
+
DEFAULT_MCP_URL,
|
|
6
7
|
DEFAULT_NO_DECISION_STREAK,
|
|
7
8
|
GateError,
|
|
9
|
+
MCP_HOST_ID_ENV,
|
|
10
|
+
MCP_TOKEN_ENV,
|
|
11
|
+
MCP_URL_ENV,
|
|
8
12
|
PUSH_NOT_IN_PROFILE,
|
|
9
13
|
SATELLITE_STALE_MS,
|
|
10
14
|
buildVectorIndex,
|
|
@@ -19,10 +23,13 @@ import {
|
|
|
19
23
|
isFailedRunStatus,
|
|
20
24
|
measureDirtyVolume,
|
|
21
25
|
memoryAuthorityTiersRank,
|
|
26
|
+
mergeMcpAuthIntoEnv,
|
|
27
|
+
nonEmptyEnvValue,
|
|
22
28
|
pruneVectorIndex,
|
|
23
29
|
readCliPackageJson,
|
|
24
30
|
readSatelliteLatestRunFromText,
|
|
25
31
|
readVaultSyncConfig,
|
|
32
|
+
redactMcpSecret,
|
|
26
33
|
reindexPageInVectorIndex,
|
|
27
34
|
renderIndexUpsert,
|
|
28
35
|
renderProjectIndex,
|
|
@@ -55,7 +62,7 @@ import {
|
|
|
55
62
|
snapshotterHealthChecks,
|
|
56
63
|
upsertIndexEntry,
|
|
57
64
|
vectorIndexStatus
|
|
58
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-PIXBNB74.js";
|
|
59
66
|
import {
|
|
60
67
|
normalizeDistTag,
|
|
61
68
|
readCache,
|
|
@@ -160,7 +167,7 @@ import {
|
|
|
160
167
|
supersedeStaleReviewRequiredJournals,
|
|
161
168
|
taxonomyCommentForPage,
|
|
162
169
|
writeDotenv
|
|
163
|
-
} from "./chunk-
|
|
170
|
+
} from "./chunk-7UK67FAZ.js";
|
|
164
171
|
import {
|
|
165
172
|
assertTargetInsideVault,
|
|
166
173
|
atomicWriteText,
|
|
@@ -214,7 +221,7 @@ import {
|
|
|
214
221
|
} from "./chunk-QM64LUEY.js";
|
|
215
222
|
|
|
216
223
|
// src/cli.ts
|
|
217
|
-
import { join as
|
|
224
|
+
import { join as join45 } from "path";
|
|
218
225
|
import { Command } from "commander";
|
|
219
226
|
|
|
220
227
|
// src/utils/output.ts
|
|
@@ -3576,13 +3583,334 @@ async function runHealth(input) {
|
|
|
3576
3583
|
return { exitCode, result: ok(report) };
|
|
3577
3584
|
}
|
|
3578
3585
|
|
|
3586
|
+
// src/commands/connect.ts
|
|
3587
|
+
import { chmod, mkdir as mkdir5, readFile as readFile6, stat as stat3 } from "fs/promises";
|
|
3588
|
+
import { join as join18 } from "path";
|
|
3589
|
+
|
|
3590
|
+
// src/utils/mcp-token-map.ts
|
|
3591
|
+
import { createHash as createHash2, randomBytes } from "crypto";
|
|
3592
|
+
import yaml from "js-yaml";
|
|
3593
|
+
var HOST_ID_RE = /^[a-z][a-z0-9-]{1,62}$/;
|
|
3594
|
+
function writerIdFromValue(value) {
|
|
3595
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
3596
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
3597
|
+
const rec = value;
|
|
3598
|
+
if (typeof rec.writer_id === "string" && rec.writer_id.length > 0) return rec.writer_id;
|
|
3599
|
+
if (typeof rec.host_id === "string" && rec.host_id.length > 0) return rec.host_id;
|
|
3600
|
+
}
|
|
3601
|
+
return void 0;
|
|
3602
|
+
}
|
|
3603
|
+
function parseAllowedVaultIds(raw) {
|
|
3604
|
+
if (!raw || raw.length === 0) return { vaults: [] };
|
|
3605
|
+
const vaults = [];
|
|
3606
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3607
|
+
for (const item of raw) {
|
|
3608
|
+
const id = item.trim();
|
|
3609
|
+
if (!id) continue;
|
|
3610
|
+
if (id.includes("*") || id.includes("/") || !HOST_ID_RE.test(id)) {
|
|
3611
|
+
return { error: "INVALID_ALLOWED_VAULTS" };
|
|
3612
|
+
}
|
|
3613
|
+
if (seen.has(id)) continue;
|
|
3614
|
+
seen.add(id);
|
|
3615
|
+
vaults.push(id);
|
|
3616
|
+
}
|
|
3617
|
+
return { vaults };
|
|
3618
|
+
}
|
|
3619
|
+
function parseMcpTokenMap(yamlText) {
|
|
3620
|
+
const parsed = yaml.load(yamlText);
|
|
3621
|
+
const map = /* @__PURE__ */ new Map();
|
|
3622
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
3623
|
+
return map;
|
|
3624
|
+
}
|
|
3625
|
+
const records = parsed;
|
|
3626
|
+
const nested = records.tokens;
|
|
3627
|
+
const source = nested && typeof nested === "object" && !Array.isArray(nested) ? nested : records;
|
|
3628
|
+
for (const [hash, value] of Object.entries(source)) {
|
|
3629
|
+
if (hash === "tokens") continue;
|
|
3630
|
+
const writerId = writerIdFromValue(value);
|
|
3631
|
+
if (!writerId) continue;
|
|
3632
|
+
const normalized = hash.trim().toLowerCase();
|
|
3633
|
+
if (!/^[0-9a-f]{64}$/.test(normalized)) continue;
|
|
3634
|
+
map.set(normalized, writerId);
|
|
3635
|
+
}
|
|
3636
|
+
return map;
|
|
3637
|
+
}
|
|
3638
|
+
function generateHostBearer(rng) {
|
|
3639
|
+
const bytes = (rng ?? (() => randomBytes(32)))();
|
|
3640
|
+
const raw = bytes.toString("base64url");
|
|
3641
|
+
const hashHex = createHash2("sha256").update(raw, "utf8").digest("hex");
|
|
3642
|
+
return { raw, hashHex };
|
|
3643
|
+
}
|
|
3644
|
+
function formatHostRow(hashHex, hostId, allowedVaults) {
|
|
3645
|
+
if (allowedVaults.length === 0) {
|
|
3646
|
+
return `${hashHex}: ${hostId}
|
|
3647
|
+
`;
|
|
3648
|
+
}
|
|
3649
|
+
return `${hashHex}:
|
|
3650
|
+
writer_id: ${hostId}
|
|
3651
|
+
allowed_vaults: [${allowedVaults.join(", ")}]
|
|
3652
|
+
`;
|
|
3653
|
+
}
|
|
3654
|
+
function appendHostHash(yamlText, hashHex, hostId, allowedVaults) {
|
|
3655
|
+
if (!HOST_ID_RE.test(hostId)) {
|
|
3656
|
+
return { error: "INVALID_HOST_ID" };
|
|
3657
|
+
}
|
|
3658
|
+
const granted = parseAllowedVaultIds(allowedVaults);
|
|
3659
|
+
if ("error" in granted) return granted;
|
|
3660
|
+
const map = parseMcpTokenMap(yamlText);
|
|
3661
|
+
const normalizedHash = hashHex.trim().toLowerCase();
|
|
3662
|
+
if ([...map.values()].includes(hostId)) {
|
|
3663
|
+
return { error: "DUPLICATE_HOST_ID" };
|
|
3664
|
+
}
|
|
3665
|
+
if (map.has(normalizedHash)) {
|
|
3666
|
+
return { error: "DUPLICATE_HASH" };
|
|
3667
|
+
}
|
|
3668
|
+
return { yaml: yamlText.replace(/\s*$/, "") + `
|
|
3669
|
+
${formatHostRow(normalizedHash, hostId, granted.vaults)}` };
|
|
3670
|
+
}
|
|
3671
|
+
|
|
3672
|
+
// src/commands/connect.ts
|
|
3673
|
+
var CONNECT_DESCRIPTION = "HTTP MCP leaf connect: ingest a chat-attached env file into ~/.skillwiki/.env (mode 0600). Never runs skillwiki init. Cloud Drive / \u4E91\u76D8 is unsupported.";
|
|
3674
|
+
var RESERVED_HOST_IDS = ["msi", "doubao-linux-agent", "macos-dev"];
|
|
3675
|
+
var DEST_MODE = 384;
|
|
3676
|
+
function isUnsupportedSecretPath(filePath) {
|
|
3677
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
3678
|
+
return /(?:^|\/)(?:Google Drive|OneDrive|iCloud Drive|CloudStorage|云盘|百度网盘|坚果云|Dropbox)(?:\/|$)/i.test(normalized) || /clouddrive|cloud-disk/i.test(normalized);
|
|
3679
|
+
}
|
|
3680
|
+
function fail(code, error, detail) {
|
|
3681
|
+
return { exitCode: code, result: err(error, detail) };
|
|
3682
|
+
}
|
|
3683
|
+
async function defaultReadStdin() {
|
|
3684
|
+
const chunks = [];
|
|
3685
|
+
for await (const chunk of process.stdin) {
|
|
3686
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
3687
|
+
}
|
|
3688
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
3689
|
+
}
|
|
3690
|
+
function looksLikeVaultInit(text) {
|
|
3691
|
+
return /^#\s*Vault Schema/im.test(text) || /^\s*---\s*$/m.test(text) && /taxonomy:/i.test(text);
|
|
3692
|
+
}
|
|
3693
|
+
function tokenPresence(token) {
|
|
3694
|
+
return token ? "TOKEN_SET" : "EMPTY";
|
|
3695
|
+
}
|
|
3696
|
+
function summaryHint(data) {
|
|
3697
|
+
return [
|
|
3698
|
+
data.token,
|
|
3699
|
+
`auth=${data.auth}`,
|
|
3700
|
+
`writer_id=${data.writer_id ?? "n/a"}`,
|
|
3701
|
+
`ok=${data.ok ?? "n/a"}`,
|
|
3702
|
+
`reconcile_ready=${data.reconcile_ready ?? "n/a"}`,
|
|
3703
|
+
data.dry_run ? "dry-run" : data.written ? "wrote ~/.skillwiki/.env 0600" : "not written"
|
|
3704
|
+
].join(" ");
|
|
3705
|
+
}
|
|
3706
|
+
function redactConnectPayload(data, secret) {
|
|
3707
|
+
return {
|
|
3708
|
+
...data,
|
|
3709
|
+
handshake: data.handshake ? redactMcpSecret(data.handshake, secret) : data.handshake,
|
|
3710
|
+
humanHint: redactMcpSecret(data.humanHint, secret)
|
|
3711
|
+
};
|
|
3712
|
+
}
|
|
3713
|
+
async function readWikiStatus(fetchFn, url, token) {
|
|
3714
|
+
const headers = {
|
|
3715
|
+
Authorization: `Bearer ${token}`,
|
|
3716
|
+
"Content-Type": "application/json",
|
|
3717
|
+
Accept: "application/json, text/event-stream"
|
|
3718
|
+
};
|
|
3719
|
+
try {
|
|
3720
|
+
const initRes = await fetchFn(url, {
|
|
3721
|
+
method: "POST",
|
|
3722
|
+
headers,
|
|
3723
|
+
body: JSON.stringify({
|
|
3724
|
+
jsonrpc: "2.0",
|
|
3725
|
+
id: 1,
|
|
3726
|
+
method: "initialize",
|
|
3727
|
+
params: {
|
|
3728
|
+
protocolVersion: "2025-11-25",
|
|
3729
|
+
capabilities: {},
|
|
3730
|
+
clientInfo: { name: "skillwiki-connect", version: "0" }
|
|
3731
|
+
}
|
|
3732
|
+
})
|
|
3733
|
+
});
|
|
3734
|
+
const session = initRes.headers.get("mcp-session-id");
|
|
3735
|
+
if (session) headers["mcp-session-id"] = session;
|
|
3736
|
+
const statusRes = await fetchFn(url, {
|
|
3737
|
+
method: "POST",
|
|
3738
|
+
headers,
|
|
3739
|
+
body: JSON.stringify({
|
|
3740
|
+
jsonrpc: "2.0",
|
|
3741
|
+
id: 2,
|
|
3742
|
+
method: "tools/call",
|
|
3743
|
+
params: { name: "wiki_status", arguments: {} }
|
|
3744
|
+
})
|
|
3745
|
+
});
|
|
3746
|
+
const raw = await statusRes.text();
|
|
3747
|
+
const parsed = JSON.parse(raw.startsWith("data:") ? raw.replace(/^data:\s*/m, "") : raw);
|
|
3748
|
+
const structured = parsed.result?.structuredContent;
|
|
3749
|
+
let body = structured;
|
|
3750
|
+
if (!body) {
|
|
3751
|
+
const text = parsed.result?.content?.find((c) => typeof c.text === "string")?.text;
|
|
3752
|
+
if (text) {
|
|
3753
|
+
try {
|
|
3754
|
+
body = JSON.parse(text);
|
|
3755
|
+
} catch {
|
|
3756
|
+
body = void 0;
|
|
3757
|
+
}
|
|
3758
|
+
}
|
|
3759
|
+
}
|
|
3760
|
+
if (!body) return { writer_id: null, ok: null, reconcile_ready: null };
|
|
3761
|
+
return {
|
|
3762
|
+
writer_id: typeof body.writer_id === "string" ? body.writer_id : null,
|
|
3763
|
+
ok: typeof body.ok === "boolean" ? body.ok : null,
|
|
3764
|
+
reconcile_ready: typeof body.reconcile_ready === "boolean" ? body.reconcile_ready : null
|
|
3765
|
+
};
|
|
3766
|
+
} catch {
|
|
3767
|
+
return { writer_id: null, ok: null, reconcile_ready: null };
|
|
3768
|
+
}
|
|
3769
|
+
}
|
|
3770
|
+
async function runConnect(input) {
|
|
3771
|
+
const dest = join18(input.home, ".skillwiki", ".env");
|
|
3772
|
+
const fromFile = input.fromFile?.trim();
|
|
3773
|
+
const fromStdin = Boolean(input.fromStdin);
|
|
3774
|
+
if (fromFile && fromStdin) {
|
|
3775
|
+
return fail(ExitCode.USAGE, "USAGE", { reason: "pass --from-file or --from-stdin, not both" });
|
|
3776
|
+
}
|
|
3777
|
+
if (!fromFile && !fromStdin) {
|
|
3778
|
+
return fail(ExitCode.USAGE, "USAGE", {
|
|
3779
|
+
reason: "pass --from-file <chat-attachment> or --from-stdin (Cloud Drive / \u4E91\u76D8 unsupported)"
|
|
3780
|
+
});
|
|
3781
|
+
}
|
|
3782
|
+
if (fromFile && isUnsupportedSecretPath(fromFile)) {
|
|
3783
|
+
return fail(ExitCode.PREFLIGHT_FAILED, "DRIVE_UNSUPPORTED", {
|
|
3784
|
+
reason: "Cloud Drive / \u4E91\u76D8 is not a secret path. Attach the env file in chat, then skillwiki connect --from-file <attachment>."
|
|
3785
|
+
});
|
|
3786
|
+
}
|
|
3787
|
+
let raw;
|
|
3788
|
+
try {
|
|
3789
|
+
raw = fromStdin ? await (input.readStdin ?? defaultReadStdin)() : await readFile6(fromFile, "utf8");
|
|
3790
|
+
} catch (e) {
|
|
3791
|
+
const code = e.code;
|
|
3792
|
+
if (code === "ENOENT" || code === "ENOTDIR") {
|
|
3793
|
+
return fail(ExitCode.FILE_NOT_FOUND, "FILE_NOT_FOUND", { path: fromFile });
|
|
3794
|
+
}
|
|
3795
|
+
if (code === "EISDIR") {
|
|
3796
|
+
return fail(ExitCode.PREFLIGHT_FAILED, "NOT_ENV_FILE", { reason: "source is a directory, not an env file" });
|
|
3797
|
+
}
|
|
3798
|
+
return fail(ExitCode.PREFLIGHT_FAILED, "READ_FAILED", { reason: "could not read env source" });
|
|
3799
|
+
}
|
|
3800
|
+
if (looksLikeVaultInit(raw)) {
|
|
3801
|
+
return fail(ExitCode.PREFLIGHT_FAILED, "REFUSE_INIT", {
|
|
3802
|
+
reason: "source looks like a vault init; skillwiki connect never runs skillwiki init"
|
|
3803
|
+
});
|
|
3804
|
+
}
|
|
3805
|
+
const parsed = parseDotenvText(raw);
|
|
3806
|
+
const incomingToken = nonEmptyEnvValue(parsed[MCP_TOKEN_ENV]);
|
|
3807
|
+
const incomingHost = nonEmptyEnvValue(parsed[MCP_HOST_ID_ENV]);
|
|
3808
|
+
const incomingUrl = nonEmptyEnvValue(parsed[MCP_URL_ENV]);
|
|
3809
|
+
if (!incomingToken) {
|
|
3810
|
+
return fail(ExitCode.INVALID_CONFIG_VALUE, "TOKEN_EMPTY", {
|
|
3811
|
+
reason: "SKILLWIKI_MCP_TOKEN missing or empty (value not shown)"
|
|
3812
|
+
});
|
|
3813
|
+
}
|
|
3814
|
+
if (!incomingHost) {
|
|
3815
|
+
return fail(ExitCode.INVALID_CONFIG_VALUE, "HOST_ID_REQUIRED", {
|
|
3816
|
+
reason: "SKILLWIKI_HOST_ID missing; each machine needs a unique host-id"
|
|
3817
|
+
});
|
|
3818
|
+
}
|
|
3819
|
+
if (!HOST_ID_RE.test(incomingHost)) {
|
|
3820
|
+
return fail(ExitCode.PREFLIGHT_FAILED, "INVALID_HOST_ID", { reason: "host-id must match [a-z][a-z0-9-]{1,62}" });
|
|
3821
|
+
}
|
|
3822
|
+
if (RESERVED_HOST_IDS.includes(incomingHost) && !input.force) {
|
|
3823
|
+
return fail(ExitCode.PREFLIGHT_FAILED, "RESERVED_HOST_ID", {
|
|
3824
|
+
reason: `refuse copying reserved host-id ${incomingHost}; issue a unique id on metal or pass --force`
|
|
3825
|
+
});
|
|
3826
|
+
}
|
|
3827
|
+
const existing = await parseDotenvFile(dest);
|
|
3828
|
+
const existingToken = nonEmptyEnvValue(existing[MCP_TOKEN_ENV]);
|
|
3829
|
+
if (existingToken && existingToken !== incomingToken && !input.force) {
|
|
3830
|
+
return fail(ExitCode.ENV_WRITE_CONFLICT, "ENV_WRITE_CONFLICT", {
|
|
3831
|
+
reason: "existing ~/.skillwiki/.env has a different token; pass --force to overwrite (value not shown)"
|
|
3832
|
+
});
|
|
3833
|
+
}
|
|
3834
|
+
const entries = {
|
|
3835
|
+
[MCP_TOKEN_ENV]: incomingToken,
|
|
3836
|
+
[MCP_HOST_ID_ENV]: incomingHost
|
|
3837
|
+
};
|
|
3838
|
+
if (incomingUrl) entries[MCP_URL_ENV] = incomingUrl;
|
|
3839
|
+
const dryRun = Boolean(input.dryRun);
|
|
3840
|
+
if (!dryRun) {
|
|
3841
|
+
await mkdir5(join18(input.home, ".skillwiki"), { recursive: true });
|
|
3842
|
+
let original;
|
|
3843
|
+
try {
|
|
3844
|
+
original = await readFile6(dest, "utf8");
|
|
3845
|
+
} catch {
|
|
3846
|
+
original = void 0;
|
|
3847
|
+
}
|
|
3848
|
+
await writeDotenv(dest, entries, original);
|
|
3849
|
+
await chmod(dest, DEST_MODE);
|
|
3850
|
+
try {
|
|
3851
|
+
const st = await stat3(dest);
|
|
3852
|
+
if ((st.mode & 511) !== DEST_MODE) await chmod(dest, DEST_MODE);
|
|
3853
|
+
} catch {
|
|
3854
|
+
}
|
|
3855
|
+
}
|
|
3856
|
+
let writerId = null;
|
|
3857
|
+
let statusOk = null;
|
|
3858
|
+
let reconcileReady = null;
|
|
3859
|
+
let handshake = null;
|
|
3860
|
+
let doctorExit = ExitCode.OK;
|
|
3861
|
+
const checkMcp = input.checkMcp ?? !dryRun;
|
|
3862
|
+
if (checkMcp) {
|
|
3863
|
+
const env = await mergeMcpAuthIntoEnv(input.home, input.env ?? {});
|
|
3864
|
+
if (!nonEmptyEnvValue(env[MCP_TOKEN_ENV])) env[MCP_TOKEN_ENV] = incomingToken;
|
|
3865
|
+
if (!nonEmptyEnvValue(env[MCP_HOST_ID_ENV])) env[MCP_HOST_ID_ENV] = incomingHost;
|
|
3866
|
+
if (incomingUrl && !nonEmptyEnvValue(env[MCP_URL_ENV])) env[MCP_URL_ENV] = incomingUrl;
|
|
3867
|
+
const doctor = await runDoctor({
|
|
3868
|
+
home: input.home,
|
|
3869
|
+
envValue: void 0,
|
|
3870
|
+
argv: ["node", "skillwiki", "connect"],
|
|
3871
|
+
currentVersion: input.currentVersion,
|
|
3872
|
+
checkMcp: true,
|
|
3873
|
+
env,
|
|
3874
|
+
mcpFetch: input.mcpFetch
|
|
3875
|
+
});
|
|
3876
|
+
doctorExit = doctor.exitCode;
|
|
3877
|
+
if (doctor.result.ok) {
|
|
3878
|
+
handshake = doctor.result.data.checks.find((c) => c.id === "mcp_handshake")?.detail ?? null;
|
|
3879
|
+
}
|
|
3880
|
+
const url = incomingUrl ?? DEFAULT_MCP_URL;
|
|
3881
|
+
const status = await readWikiStatus(input.mcpFetch ?? globalThis.fetch, url, incomingToken);
|
|
3882
|
+
writerId = status.writer_id;
|
|
3883
|
+
statusOk = status.ok;
|
|
3884
|
+
reconcileReady = status.reconcile_ready;
|
|
3885
|
+
}
|
|
3886
|
+
const payload = {
|
|
3887
|
+
dest,
|
|
3888
|
+
written: !dryRun,
|
|
3889
|
+
dry_run: dryRun,
|
|
3890
|
+
mode: "0600",
|
|
3891
|
+
token: tokenPresence(incomingToken),
|
|
3892
|
+
auth: incomingToken ? "present" : "not set",
|
|
3893
|
+
writer_id: writerId,
|
|
3894
|
+
ok: statusOk,
|
|
3895
|
+
reconcile_ready: reconcileReady,
|
|
3896
|
+
handshake,
|
|
3897
|
+
humanHint: ""
|
|
3898
|
+
};
|
|
3899
|
+
payload.humanHint = summaryHint(payload);
|
|
3900
|
+
const safe = redactConnectPayload(payload, incomingToken);
|
|
3901
|
+
if (!dryRun && checkMcp && doctorExit !== ExitCode.OK && doctorExit !== ExitCode.DOCTOR_HAS_WARNINGS) {
|
|
3902
|
+
return { exitCode: doctorExit, result: ok(safe) };
|
|
3903
|
+
}
|
|
3904
|
+
return { exitCode: ExitCode.OK, result: ok(safe) };
|
|
3905
|
+
}
|
|
3906
|
+
|
|
3579
3907
|
// src/commands/archive.ts
|
|
3580
|
-
import { rename as rename2, mkdir as
|
|
3581
|
-
import { join as
|
|
3908
|
+
import { rename as rename2, mkdir as mkdir7, readFile as readFile8 } from "fs/promises";
|
|
3909
|
+
import { join as join20, dirname as dirname5 } from "path";
|
|
3582
3910
|
|
|
3583
3911
|
// src/utils/delete-intent.ts
|
|
3584
|
-
import { mkdir as
|
|
3585
|
-
import { join as
|
|
3912
|
+
import { mkdir as mkdir6, writeFile as writeFile4, readdir as readdir4, readFile as readFile7 } from "fs/promises";
|
|
3913
|
+
import { join as join19 } from "path";
|
|
3586
3914
|
var DELETE_INTENT_SCHEMA = "vault-delete-intent/v1";
|
|
3587
3915
|
var DELETE_INTENT_DIR = "meta/delete-intents";
|
|
3588
3916
|
function normalizeVaultRelPath(path) {
|
|
@@ -3613,10 +3941,10 @@ function buildDeleteIntent(input) {
|
|
|
3613
3941
|
};
|
|
3614
3942
|
}
|
|
3615
3943
|
async function writeDeleteIntent(vault, intent) {
|
|
3616
|
-
const dir =
|
|
3617
|
-
await
|
|
3944
|
+
const dir = join19(vault, DELETE_INTENT_DIR);
|
|
3945
|
+
await mkdir6(dir, { recursive: true });
|
|
3618
3946
|
const rel = `${DELETE_INTENT_DIR}/${pathToIntentFilename(intent.path)}`;
|
|
3619
|
-
await writeFile4(
|
|
3947
|
+
await writeFile4(join19(vault, rel), JSON.stringify(intent, null, 2) + "\n", "utf8");
|
|
3620
3948
|
return rel;
|
|
3621
3949
|
}
|
|
3622
3950
|
|
|
@@ -3656,7 +3984,7 @@ async function runArchive(input) {
|
|
|
3656
3984
|
const slug = relPath.replace(/\.md$/, "").split("/").pop();
|
|
3657
3985
|
const rawArchive = isRaw ? lifecycleDestination(relPath, "archive") : void 0;
|
|
3658
3986
|
if (rawArchive && !rawArchive.ok) return { exitCode: ExitCode.USAGE, result: rawArchive };
|
|
3659
|
-
const archivePath = isRaw ? rawArchive.data :
|
|
3987
|
+
const archivePath = isRaw ? rawArchive.data : join20("_archive", relPath).replace(/\\/g, "/");
|
|
3660
3988
|
const remoteRoot = normalizeRemoteRoot(input.remote);
|
|
3661
3989
|
const remoteObjectPath = buildRemoteObjectPath(remoteRoot, relPath);
|
|
3662
3990
|
let cascade;
|
|
@@ -3686,7 +4014,7 @@ async function runArchive(input) {
|
|
|
3686
4014
|
const indexRefs = [];
|
|
3687
4015
|
if (!isRaw) {
|
|
3688
4016
|
try {
|
|
3689
|
-
const idx = await
|
|
4017
|
+
const idx = await readFile8(join20(input.vault, "index.md"), "utf8");
|
|
3690
4018
|
idx.split("\n").forEach((line, i) => {
|
|
3691
4019
|
if (line.includes(`[[${slug}]]`)) indexRefs.push({ line: i + 1, text: line });
|
|
3692
4020
|
});
|
|
@@ -3744,8 +4072,8 @@ async function runArchive(input) {
|
|
|
3744
4072
|
const rewrittenCascadePages = [];
|
|
3745
4073
|
if ((input.cascade || isRaw) && input.apply && cascade) {
|
|
3746
4074
|
for (const ref of cascade.source_array_refs) {
|
|
3747
|
-
const absPath =
|
|
3748
|
-
const text = await
|
|
4075
|
+
const absPath = join20(input.vault, ref.page);
|
|
4076
|
+
const text = await readFile8(absPath, "utf8");
|
|
3749
4077
|
if (!arraysEqual(ref.sources_after, ref.sources_before)) {
|
|
3750
4078
|
let updated;
|
|
3751
4079
|
if (isRaw) {
|
|
@@ -3771,8 +4099,8 @@ ${fmRewritten}
|
|
|
3771
4099
|
if (isRaw) {
|
|
3772
4100
|
for (const ref of cascade.citation_refs) {
|
|
3773
4101
|
if (cascade.source_array_refs.some((sourceRef) => sourceRef.page === ref.page)) continue;
|
|
3774
|
-
const absPath =
|
|
3775
|
-
const original = await
|
|
4102
|
+
const absPath = join20(input.vault, ref.page);
|
|
4103
|
+
const original = await readFile8(absPath, "utf8");
|
|
3776
4104
|
const updated = rewriteRawSourceReferences(original, relPath, archivePath).text;
|
|
3777
4105
|
if (updated !== original) {
|
|
3778
4106
|
const write = await safeWritePage(absPath, updated);
|
|
@@ -3783,13 +4111,13 @@ ${fmRewritten}
|
|
|
3783
4111
|
}
|
|
3784
4112
|
}
|
|
3785
4113
|
if (!isRaw) {
|
|
3786
|
-
await
|
|
3787
|
-
await rename2(
|
|
4114
|
+
await mkdir7(dirname5(join20(input.vault, archivePath)), { recursive: true });
|
|
4115
|
+
await rename2(join20(input.vault, relPath), join20(input.vault, archivePath));
|
|
3788
4116
|
}
|
|
3789
4117
|
let indexUpdated = false;
|
|
3790
4118
|
if (!isRaw) {
|
|
3791
4119
|
const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-6OHDINHT.js");
|
|
3792
|
-
const before = await
|
|
4120
|
+
const before = await readFile8(join20(input.vault, "index.md"), "utf8").catch(() => "");
|
|
3793
4121
|
const fullTarget = relPath.replace(/\.md$/, "");
|
|
3794
4122
|
const bare = fullTarget.split("/").pop() ?? fullTarget;
|
|
3795
4123
|
const hadIndexEntry = before.includes(`[[${fullTarget}]]`) || before.includes(`[[${bare}]]`);
|
|
@@ -3843,7 +4171,7 @@ ${fmRewritten}
|
|
|
3843
4171
|
|
|
3844
4172
|
// src/commands/remove.ts
|
|
3845
4173
|
import { unlink as unlink2, access } from "fs/promises";
|
|
3846
|
-
import { join as
|
|
4174
|
+
import { join as join21 } from "path";
|
|
3847
4175
|
async function pathExists(abs) {
|
|
3848
4176
|
try {
|
|
3849
4177
|
await access(abs);
|
|
@@ -3869,7 +4197,7 @@ async function runRemove(input) {
|
|
|
3869
4197
|
if (!relPath) {
|
|
3870
4198
|
try {
|
|
3871
4199
|
const candidate = normalizeVaultRelPath(input.page);
|
|
3872
|
-
if (await pathExists(
|
|
4200
|
+
if (await pathExists(join21(input.vault, candidate))) {
|
|
3873
4201
|
relPath = candidate;
|
|
3874
4202
|
}
|
|
3875
4203
|
} catch {
|
|
@@ -3907,12 +4235,12 @@ async function runRemove(input) {
|
|
|
3907
4235
|
reason: input.reason
|
|
3908
4236
|
});
|
|
3909
4237
|
const tombstonePath = await writeDeleteIntent(input.vault, intent);
|
|
3910
|
-
await unlink2(
|
|
4238
|
+
await unlink2(join21(input.vault, relPath));
|
|
3911
4239
|
if (relPath.endsWith(".md") && !relPath.startsWith("raw/")) {
|
|
3912
|
-
const { readFile:
|
|
4240
|
+
const { readFile: readFile24 } = await import("fs/promises");
|
|
3913
4241
|
const { join: pathJoin } = await import("path");
|
|
3914
4242
|
const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-6OHDINHT.js");
|
|
3915
|
-
const before = await
|
|
4243
|
+
const before = await readFile24(pathJoin(input.vault, "index.md"), "utf8").catch(() => "");
|
|
3916
4244
|
const fullTarget = relPath.replace(/\.md$/, "");
|
|
3917
4245
|
const bare = fullTarget.split("/").pop() ?? fullTarget;
|
|
3918
4246
|
const hadIndexEntry = before.includes(`[[${fullTarget}]]`) || before.includes(`[[${bare}]]`);
|
|
@@ -3950,7 +4278,7 @@ async function runRemove(input) {
|
|
|
3950
4278
|
}
|
|
3951
4279
|
|
|
3952
4280
|
// src/commands/drift.ts
|
|
3953
|
-
import { createHash as
|
|
4281
|
+
import { createHash as createHash3 } from "crypto";
|
|
3954
4282
|
|
|
3955
4283
|
// src/utils/fetch.ts
|
|
3956
4284
|
async function controlledFetch(url, opts) {
|
|
@@ -4029,7 +4357,7 @@ async function runDrift(input) {
|
|
|
4029
4357
|
});
|
|
4030
4358
|
continue;
|
|
4031
4359
|
}
|
|
4032
|
-
const currentHash =
|
|
4360
|
+
const currentHash = createHash3("sha256").update(Buffer.from(resp.data.body, "utf8")).digest("hex");
|
|
4033
4361
|
const identity = assessSourceIdentity({
|
|
4034
4362
|
rawPath: raw.relPath,
|
|
4035
4363
|
sourceUrl,
|
|
@@ -4261,7 +4589,7 @@ ${migratedBody}${newFooter}`;
|
|
|
4261
4589
|
|
|
4262
4590
|
// src/commands/update.ts
|
|
4263
4591
|
import { execSync } from "child_process";
|
|
4264
|
-
import { join as
|
|
4592
|
+
import { join as join22 } from "path";
|
|
4265
4593
|
function parseCoreSemver(version) {
|
|
4266
4594
|
const m = version.match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
4267
4595
|
if (!m) return null;
|
|
@@ -4313,7 +4641,7 @@ function resolveGlobalSkillsRoot() {
|
|
|
4313
4641
|
encoding: "utf8",
|
|
4314
4642
|
timeout: 5e3
|
|
4315
4643
|
}).trim();
|
|
4316
|
-
return
|
|
4644
|
+
return join22(globalRoot, "skillwiki", "skills");
|
|
4317
4645
|
} catch {
|
|
4318
4646
|
return null;
|
|
4319
4647
|
}
|
|
@@ -4341,7 +4669,7 @@ async function runUpdate(input) {
|
|
|
4341
4669
|
const pkg2 = readCliPackageJson();
|
|
4342
4670
|
const currentVersion = pkg2.version;
|
|
4343
4671
|
const tag = normalizeDistTag(input.distTag);
|
|
4344
|
-
const target =
|
|
4672
|
+
const target = join22(input.home, ".claude", "skills");
|
|
4345
4673
|
let latest;
|
|
4346
4674
|
try {
|
|
4347
4675
|
latest = execSync(`npm view skillwiki@${tag} version`, {
|
|
@@ -4422,13 +4750,13 @@ async function runUpdate(input) {
|
|
|
4422
4750
|
// src/commands/self-update.ts
|
|
4423
4751
|
import { execSync as execSync2 } from "child_process";
|
|
4424
4752
|
import { existsSync as existsSync8, readFileSync as readFileSync11 } from "fs";
|
|
4425
|
-
import { join as
|
|
4753
|
+
import { join as join23 } from "path";
|
|
4426
4754
|
var DEFAULT_SOURCE_ROOT_SUFFIX = "/Desktop/code/llm-wiki";
|
|
4427
4755
|
async function runSelfUpdate(input) {
|
|
4428
4756
|
const currentVersion = readCliPackageJson().version;
|
|
4429
4757
|
const sourceRoot = input.sourceRoot ?? `${input.home}${DEFAULT_SOURCE_ROOT_SUFFIX}`;
|
|
4430
4758
|
const distTag = normalizeDistTag(input.distTag);
|
|
4431
|
-
const localPkgPath =
|
|
4759
|
+
const localPkgPath = join23(sourceRoot, "packages", "cli", "package.json");
|
|
4432
4760
|
const hasLocalSource = existsSync8(localPkgPath);
|
|
4433
4761
|
if (input.check) {
|
|
4434
4762
|
let availableVersion = null;
|
|
@@ -4560,10 +4888,10 @@ async function runSelfUpdate(input) {
|
|
|
4560
4888
|
}
|
|
4561
4889
|
|
|
4562
4890
|
// src/commands/transcripts.ts
|
|
4563
|
-
import { readdir as readdir5, stat as
|
|
4564
|
-
import { join as
|
|
4891
|
+
import { readdir as readdir5, stat as stat4, readFile as readFile10 } from "fs/promises";
|
|
4892
|
+
import { join as join24 } from "path";
|
|
4565
4893
|
async function runTranscripts(input) {
|
|
4566
|
-
const dir =
|
|
4894
|
+
const dir = join24(input.vault, "raw", "transcripts");
|
|
4567
4895
|
let entries;
|
|
4568
4896
|
try {
|
|
4569
4897
|
entries = await readdir5(dir, { withFileTypes: true });
|
|
@@ -4573,13 +4901,13 @@ async function runTranscripts(input) {
|
|
|
4573
4901
|
const transcripts = [];
|
|
4574
4902
|
for (const entry of entries) {
|
|
4575
4903
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
4576
|
-
const filePath =
|
|
4577
|
-
const content = await
|
|
4904
|
+
const filePath = join24(dir, entry.name);
|
|
4905
|
+
const content = await readFile10(filePath, "utf8");
|
|
4578
4906
|
const fm = extractFrontmatter(content);
|
|
4579
4907
|
if (!fm.ok) continue;
|
|
4580
4908
|
const ingested = typeof fm.data.ingested === "string" ? fm.data.ingested : "";
|
|
4581
4909
|
if (input.since && ingested && ingested < input.since) continue;
|
|
4582
|
-
const s = await
|
|
4910
|
+
const s = await stat4(filePath);
|
|
4583
4911
|
transcripts.push({
|
|
4584
4912
|
file: `raw/transcripts/${entry.name}`,
|
|
4585
4913
|
ingested,
|
|
@@ -4591,8 +4919,8 @@ async function runTranscripts(input) {
|
|
|
4591
4919
|
}
|
|
4592
4920
|
|
|
4593
4921
|
// src/commands/claims-audit.ts
|
|
4594
|
-
import { readFile as
|
|
4595
|
-
import { join as
|
|
4922
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
4923
|
+
import { join as join25 } from "path";
|
|
4596
4924
|
function safeProjectPresentation(raw) {
|
|
4597
4925
|
if (safeDiagnosticValue(raw) === REDACTED_MALFORMED_REFERENCE) {
|
|
4598
4926
|
return REDACTED_MALFORMED_REFERENCE;
|
|
@@ -4632,9 +4960,9 @@ async function runClaimsAudit(input) {
|
|
|
4632
4960
|
return !input.project || slugByDir.get(relDir) === input.project;
|
|
4633
4961
|
});
|
|
4634
4962
|
const claimSourceResults = await mapWithConcurrency(scopedSpecDirs, vaultIoConcurrency(), async (relDir) => {
|
|
4635
|
-
const specPath =
|
|
4963
|
+
const specPath = join25(input.vault, relDir, "spec.md");
|
|
4636
4964
|
try {
|
|
4637
|
-
const specText = await
|
|
4965
|
+
const specText = await readFile11(specPath, "utf8");
|
|
4638
4966
|
const fm = extractFrontmatter(specText);
|
|
4639
4967
|
if (!fm.ok) return null;
|
|
4640
4968
|
return {
|
|
@@ -4728,10 +5056,10 @@ async function runClaimsAudit(input) {
|
|
|
4728
5056
|
}
|
|
4729
5057
|
|
|
4730
5058
|
// src/commands/compound.ts
|
|
4731
|
-
import { writeFile as writeFile6, mkdir as
|
|
4732
|
-
import { join as
|
|
5059
|
+
import { writeFile as writeFile6, mkdir as mkdir8, readdir as readdir6, unlink as unlink3 } from "fs/promises";
|
|
5060
|
+
import { join as join26 } from "path";
|
|
4733
5061
|
import { existsSync as existsSync9 } from "fs";
|
|
4734
|
-
import { readFile as
|
|
5062
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
4735
5063
|
var RETRO_HEADING_RE = /^## \[(\d{4}-\d{2}-\d{2})(?:\s+[^\]]+)?\] retro \| loop cycle(?: (\d+))?: (.+)$/;
|
|
4736
5064
|
var FIELD_RE = {
|
|
4737
5065
|
improve: /^-\s+\*?\*?Improve:?\*?\*?\s*(.+)$/m,
|
|
@@ -4829,17 +5157,17 @@ function extractRetroFields(date, cycleName, block) {
|
|
|
4829
5157
|
};
|
|
4830
5158
|
}
|
|
4831
5159
|
async function runCompound(input) {
|
|
4832
|
-
const logPath =
|
|
5160
|
+
const logPath = join26(input.vault, "log.md");
|
|
4833
5161
|
let logText;
|
|
4834
5162
|
try {
|
|
4835
|
-
logText = await
|
|
5163
|
+
logText = await readFile12(logPath, "utf8");
|
|
4836
5164
|
} catch {
|
|
4837
5165
|
return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: logPath }) };
|
|
4838
5166
|
}
|
|
4839
5167
|
const entries = parseRetroEntries(logText);
|
|
4840
5168
|
const promoted = [];
|
|
4841
5169
|
const skipped = [];
|
|
4842
|
-
const compoundDir =
|
|
5170
|
+
const compoundDir = join26(input.vault, "projects", input.project, "compound");
|
|
4843
5171
|
for (const entry of entries) {
|
|
4844
5172
|
const generalizeValue = entry.generalize.trim();
|
|
4845
5173
|
if (!/^yes/i.test(generalizeValue)) {
|
|
@@ -4847,7 +5175,7 @@ async function runCompound(input) {
|
|
|
4847
5175
|
continue;
|
|
4848
5176
|
}
|
|
4849
5177
|
const slug = slugify(entry.cycleName);
|
|
4850
|
-
const compoundPath =
|
|
5178
|
+
const compoundPath = join26(compoundDir, `${slug}.md`);
|
|
4851
5179
|
if (existsSync9(compoundPath)) {
|
|
4852
5180
|
skipped.push(entry.date);
|
|
4853
5181
|
continue;
|
|
@@ -4887,7 +5215,7 @@ async function runCompound(input) {
|
|
|
4887
5215
|
const content = frontmatter + "\n" + body;
|
|
4888
5216
|
if (!input.dryRun) {
|
|
4889
5217
|
if (!existsSync9(compoundDir)) {
|
|
4890
|
-
await
|
|
5218
|
+
await mkdir8(compoundDir, { recursive: true });
|
|
4891
5219
|
}
|
|
4892
5220
|
await writeFile6(compoundPath, content, "utf8");
|
|
4893
5221
|
}
|
|
@@ -4908,7 +5236,7 @@ async function runCompound(input) {
|
|
|
4908
5236
|
};
|
|
4909
5237
|
}
|
|
4910
5238
|
async function runCompoundDelete(input) {
|
|
4911
|
-
const projectDir =
|
|
5239
|
+
const projectDir = join26(input.vault, "projects", input.project);
|
|
4912
5240
|
if (!existsSync9(projectDir)) {
|
|
4913
5241
|
return {
|
|
4914
5242
|
exitCode: ExitCode.PROJECT_NOT_FOUND,
|
|
@@ -4916,7 +5244,7 @@ async function runCompoundDelete(input) {
|
|
|
4916
5244
|
};
|
|
4917
5245
|
}
|
|
4918
5246
|
const entryName = input.entry.replace(/\.md$/, "");
|
|
4919
|
-
const compoundPath =
|
|
5247
|
+
const compoundPath = join26(projectDir, "compound", `${entryName}.md`);
|
|
4920
5248
|
if (!existsSync9(compoundPath)) {
|
|
4921
5249
|
return {
|
|
4922
5250
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
@@ -4950,7 +5278,7 @@ knowledge.md regenerated`
|
|
|
4950
5278
|
};
|
|
4951
5279
|
}
|
|
4952
5280
|
async function runCompoundList(input) {
|
|
4953
|
-
const compoundDir =
|
|
5281
|
+
const compoundDir = join26(input.vault, "projects", input.project, "compound");
|
|
4954
5282
|
if (!existsSync9(compoundDir)) {
|
|
4955
5283
|
return {
|
|
4956
5284
|
exitCode: ExitCode.OK,
|
|
@@ -4981,10 +5309,10 @@ could not read compound directory`
|
|
|
4981
5309
|
const entries = [];
|
|
4982
5310
|
for (const dirent of dirents) {
|
|
4983
5311
|
if (!dirent.isFile() || !dirent.name.endsWith(".md")) continue;
|
|
4984
|
-
const filePath =
|
|
5312
|
+
const filePath = join26(compoundDir, dirent.name);
|
|
4985
5313
|
let text;
|
|
4986
5314
|
try {
|
|
4987
|
-
text = await
|
|
5315
|
+
text = await readFile12(filePath, "utf8");
|
|
4988
5316
|
} catch {
|
|
4989
5317
|
continue;
|
|
4990
5318
|
}
|
|
@@ -5013,8 +5341,8 @@ no compound entries found`;
|
|
|
5013
5341
|
}
|
|
5014
5342
|
|
|
5015
5343
|
// src/commands/session-brief.ts
|
|
5016
|
-
import { mkdir as
|
|
5017
|
-
import { join as
|
|
5344
|
+
import { mkdir as mkdir9, readFile as readFile13, writeFile as writeFile7 } from "fs/promises";
|
|
5345
|
+
import { join as join27, relative, sep } from "path";
|
|
5018
5346
|
var MAX_WORDS = 900;
|
|
5019
5347
|
async function runSessionBrief(input) {
|
|
5020
5348
|
const { root: scanRoot } = resolveReadOnlyVaultRoot(input.vault);
|
|
@@ -5123,7 +5451,7 @@ async function resolveProject(input) {
|
|
|
5123
5451
|
const envProject = input.env?.SKILLWIKI_PROJECT;
|
|
5124
5452
|
if (envProject) return envProject;
|
|
5125
5453
|
const cwd = input.cwd ?? process.cwd();
|
|
5126
|
-
const projectDotenv = await readProjectSlug(
|
|
5454
|
+
const projectDotenv = await readProjectSlug(join27(cwd, ".skillwiki", ".env"));
|
|
5127
5455
|
if (projectDotenv) return projectDotenv;
|
|
5128
5456
|
const inferred = inferProjectFromPath(input.vault, cwd);
|
|
5129
5457
|
if (inferred) return inferred;
|
|
@@ -5132,7 +5460,7 @@ async function resolveProject(input) {
|
|
|
5132
5460
|
async function readProjectSlug(file) {
|
|
5133
5461
|
let text;
|
|
5134
5462
|
try {
|
|
5135
|
-
text = await
|
|
5463
|
+
text = await readFile13(file, "utf8");
|
|
5136
5464
|
} catch {
|
|
5137
5465
|
return void 0;
|
|
5138
5466
|
}
|
|
@@ -5223,7 +5551,7 @@ async function loadTrendDigests(typedPages) {
|
|
|
5223
5551
|
return out;
|
|
5224
5552
|
}
|
|
5225
5553
|
async function loadSessionPins(vault, project) {
|
|
5226
|
-
const text = await readIfExists(
|
|
5554
|
+
const text = await readIfExists(join27(vault, "meta", "session-pins.md"));
|
|
5227
5555
|
if (!text) return [];
|
|
5228
5556
|
const fm = extractFrontmatter(text);
|
|
5229
5557
|
if (!fm.ok) return [];
|
|
@@ -5342,7 +5670,7 @@ function satelliteHealthWarnings(warning) {
|
|
|
5342
5670
|
return warning ? [warning] : [];
|
|
5343
5671
|
}
|
|
5344
5672
|
async function loadHealthWarnings(vault) {
|
|
5345
|
-
const text = await readIfExists(
|
|
5673
|
+
const text = await readIfExists(join27(vault, ".skillwiki", "health.json"));
|
|
5346
5674
|
if (!text) return [];
|
|
5347
5675
|
try {
|
|
5348
5676
|
const parsed = JSON.parse(text);
|
|
@@ -5355,7 +5683,7 @@ async function loadHealthWarnings(vault) {
|
|
|
5355
5683
|
}
|
|
5356
5684
|
}
|
|
5357
5685
|
async function loadMemoryTopics(vault, project) {
|
|
5358
|
-
const text = await readIfExists(
|
|
5686
|
+
const text = await readIfExists(join27(vault, ".skillwiki", "memory", project, "topics.json"));
|
|
5359
5687
|
if (!text) return [];
|
|
5360
5688
|
try {
|
|
5361
5689
|
const parsed = JSON.parse(text);
|
|
@@ -5380,11 +5708,11 @@ async function loadMemoryTopics(vault, project) {
|
|
|
5380
5708
|
}
|
|
5381
5709
|
}
|
|
5382
5710
|
async function writeBriefArtifacts(vault, input) {
|
|
5383
|
-
const metaPath =
|
|
5384
|
-
const cacheMdPath =
|
|
5385
|
-
const cacheJsonPath =
|
|
5386
|
-
await
|
|
5387
|
-
await
|
|
5711
|
+
const metaPath = join27(vault, "meta", "latest-session-brief.md");
|
|
5712
|
+
const cacheMdPath = join27(vault, ".skillwiki", "session-brief.md");
|
|
5713
|
+
const cacheJsonPath = join27(vault, ".skillwiki", "session-brief.json");
|
|
5714
|
+
await mkdir9(join27(vault, "meta"), { recursive: true });
|
|
5715
|
+
await mkdir9(join27(vault, ".skillwiki"), { recursive: true });
|
|
5388
5716
|
const committed = renderCommittedBrief(input);
|
|
5389
5717
|
const previousComparable = comparableBrief(await readIfExists(metaPath));
|
|
5390
5718
|
const nextComparable = comparableBrief(committed);
|
|
@@ -5457,7 +5785,7 @@ function renderCommittedBrief(input) {
|
|
|
5457
5785
|
].filter((line) => line !== "").join("\n");
|
|
5458
5786
|
}
|
|
5459
5787
|
async function rebuildRootIndexProjection(vault) {
|
|
5460
|
-
const before = await readIfExists(
|
|
5788
|
+
const before = await readIfExists(join27(vault, "index.md"));
|
|
5461
5789
|
if (!before) return false;
|
|
5462
5790
|
const projection = await renderRootIndex({ vault, currentText: before });
|
|
5463
5791
|
if (!projection.ok) return false;
|
|
@@ -5467,7 +5795,7 @@ async function rebuildRootIndexProjection(vault) {
|
|
|
5467
5795
|
}
|
|
5468
5796
|
async function readIfExists(path) {
|
|
5469
5797
|
try {
|
|
5470
|
-
return await
|
|
5798
|
+
return await readFile13(path, "utf8");
|
|
5471
5799
|
} catch {
|
|
5472
5800
|
return "";
|
|
5473
5801
|
}
|
|
@@ -5509,23 +5837,23 @@ function dateFromPath(path) {
|
|
|
5509
5837
|
}
|
|
5510
5838
|
|
|
5511
5839
|
// src/commands/ingest.ts
|
|
5512
|
-
import { readFile as
|
|
5513
|
-
import { join as
|
|
5514
|
-
import { createHash as
|
|
5840
|
+
import { readFile as readFile16, open, unlink as unlink4, mkdir as mkdir11 } from "fs/promises";
|
|
5841
|
+
import { join as join32 } from "path";
|
|
5842
|
+
import { createHash as createHash5 } from "crypto";
|
|
5515
5843
|
|
|
5516
5844
|
// src/commands/page-publish.ts
|
|
5517
5845
|
import { readFileSync as readFileSync13, realpathSync as realpathSync2 } from "fs";
|
|
5518
|
-
import { mkdir as
|
|
5519
|
-
import { dirname as dirname8, join as
|
|
5846
|
+
import { mkdir as mkdir10, readFile as readFile15 } from "fs/promises";
|
|
5847
|
+
import { dirname as dirname8, join as join31, resolve as resolve4 } from "path";
|
|
5520
5848
|
|
|
5521
5849
|
// src/utils/publication-approval.ts
|
|
5522
|
-
import { createHash as
|
|
5850
|
+
import { createHash as createHash4 } from "crypto";
|
|
5523
5851
|
var APPROVAL_CONTRACT = "skillwiki-publication-approval-v1";
|
|
5524
5852
|
var APPROVAL_TOKEN_VERSION = "swpub1";
|
|
5525
5853
|
var SHA256_HEX = /^[0-9a-f]{64}$/;
|
|
5526
5854
|
var PUBLISHERS = /* @__PURE__ */ new Set(["page", "project-page"]);
|
|
5527
5855
|
function sha256Hex(bytes) {
|
|
5528
|
-
return
|
|
5856
|
+
return createHash4("sha256").update(bytes).digest("hex");
|
|
5529
5857
|
}
|
|
5530
5858
|
function normalizeLogNote(note) {
|
|
5531
5859
|
if (note === void 0) return "";
|
|
@@ -5744,8 +6072,8 @@ function redactApprovalTokens(text) {
|
|
|
5744
6072
|
}
|
|
5745
6073
|
|
|
5746
6074
|
// src/publish/pipeline.ts
|
|
5747
|
-
import { readFile as
|
|
5748
|
-
import { dirname as dirname7, join as
|
|
6075
|
+
import { readFile as readFile14 } from "fs/promises";
|
|
6076
|
+
import { dirname as dirname7, join as join30, resolve as resolve3 } from "path";
|
|
5749
6077
|
|
|
5750
6078
|
// src/utils/publication-operation-journal.ts
|
|
5751
6079
|
import {
|
|
@@ -5759,7 +6087,7 @@ import {
|
|
|
5759
6087
|
writeFileSync as writeFileSync5
|
|
5760
6088
|
} from "fs";
|
|
5761
6089
|
import { homedir } from "os";
|
|
5762
|
-
import { dirname as dirname6, join as
|
|
6090
|
+
import { dirname as dirname6, join as join28 } from "path";
|
|
5763
6091
|
var PUBLICATION_JOURNAL_SCHEMA = "skillwiki-publication-operation-v1";
|
|
5764
6092
|
var PHASE_ORDER2 = [
|
|
5765
6093
|
"locked",
|
|
@@ -5788,16 +6116,16 @@ function vaultIdentity(vaultPath) {
|
|
|
5788
6116
|
return sha256Hex(real).slice(0, 16);
|
|
5789
6117
|
}
|
|
5790
6118
|
function publicationJournalRoot(home = process.env.HOME || homedir()) {
|
|
5791
|
-
return
|
|
6119
|
+
return join28(home, ".skillwiki", "publication-operations");
|
|
5792
6120
|
}
|
|
5793
6121
|
function resolveJournalPaths(vaultPath, operationId2, home = process.env.HOME || homedir()) {
|
|
5794
6122
|
const identity = vaultIdentity(vaultPath);
|
|
5795
6123
|
const rootDir = publicationJournalRoot(home);
|
|
5796
|
-
const vaultDir =
|
|
6124
|
+
const vaultDir = join28(rootDir, identity);
|
|
5797
6125
|
return {
|
|
5798
6126
|
rootDir,
|
|
5799
6127
|
vaultDir,
|
|
5800
|
-
journalPath:
|
|
6128
|
+
journalPath: join28(vaultDir, `${operationId2}.json`)
|
|
5801
6129
|
};
|
|
5802
6130
|
}
|
|
5803
6131
|
function ensurePrivateDir(dir) {
|
|
@@ -6018,7 +6346,7 @@ function completePublicationJournal(input) {
|
|
|
6018
6346
|
}
|
|
6019
6347
|
|
|
6020
6348
|
// src/publish/hold-gates.ts
|
|
6021
|
-
import { join as
|
|
6349
|
+
import { join as join29 } from "path";
|
|
6022
6350
|
function evaluateSchemaGate(content, target, publisherKind) {
|
|
6023
6351
|
const frontmatter = extractFrontmatter(content);
|
|
6024
6352
|
if (!frontmatter.ok) return false;
|
|
@@ -6054,7 +6382,7 @@ function evaluateSchemaGate(content, target, publisherKind) {
|
|
|
6054
6382
|
return false;
|
|
6055
6383
|
}
|
|
6056
6384
|
function evaluateBrokenWikilinkGate(body, scan, ownTarget) {
|
|
6057
|
-
const draftPage = { absPath:
|
|
6385
|
+
const draftPage = { absPath: join29(scan.root, ownTarget), relPath: ownTarget };
|
|
6058
6386
|
const resolver = buildWikilinkResolver([...scan.allMarkdown, draftPage]);
|
|
6059
6387
|
const wikilinks = extractBodyWikilinks(body);
|
|
6060
6388
|
for (const link of wikilinks) {
|
|
@@ -6241,7 +6569,7 @@ function phaseAtLeast(current, needed) {
|
|
|
6241
6569
|
}
|
|
6242
6570
|
async function readPageChanged(targetPath, content) {
|
|
6243
6571
|
try {
|
|
6244
|
-
return ok(await
|
|
6572
|
+
return ok(await readFile14(targetPath, "utf8") !== content);
|
|
6245
6573
|
} catch (error) {
|
|
6246
6574
|
if (error.code === "ENOENT") return ok(true);
|
|
6247
6575
|
return err("WRITE_FAILED", { path: targetPath, message: String(error) });
|
|
@@ -6249,7 +6577,7 @@ async function readPageChanged(targetPath, content) {
|
|
|
6249
6577
|
}
|
|
6250
6578
|
async function readPriorTargetSha(targetPath) {
|
|
6251
6579
|
try {
|
|
6252
|
-
const text = await
|
|
6580
|
+
const text = await readFile14(targetPath, "utf8");
|
|
6253
6581
|
return ok(sha256Hex(text));
|
|
6254
6582
|
} catch (error) {
|
|
6255
6583
|
if (error.code === "ENOENT") return ok("absent");
|
|
@@ -6282,10 +6610,10 @@ function successReceipt(strategy, prepared, details) {
|
|
|
6282
6610
|
};
|
|
6283
6611
|
}
|
|
6284
6612
|
async function runPipelinePreview(strategy, prepared, vault) {
|
|
6285
|
-
const schemaPath =
|
|
6613
|
+
const schemaPath = join30(vault, "SCHEMA.md");
|
|
6286
6614
|
let schemaText;
|
|
6287
6615
|
try {
|
|
6288
|
-
schemaText = await
|
|
6616
|
+
schemaText = await readFile14(schemaPath, "utf8");
|
|
6289
6617
|
} catch (error) {
|
|
6290
6618
|
const result = err("FILE_NOT_FOUND", { path: schemaPath, message: String(error) });
|
|
6291
6619
|
return { exitCode: ExitCode.FILE_NOT_FOUND, result };
|
|
@@ -6309,10 +6637,10 @@ async function runPipelinePreview(strategy, prepared, vault) {
|
|
|
6309
6637
|
if (!indices.ok) {
|
|
6310
6638
|
return { exitCode: indices.exitCode, result: indices.result };
|
|
6311
6639
|
}
|
|
6312
|
-
const logPath =
|
|
6640
|
+
const logPath = join30(vault, "log.md");
|
|
6313
6641
|
let logAppended;
|
|
6314
6642
|
try {
|
|
6315
|
-
const logText = await
|
|
6643
|
+
const logText = await readFile14(logPath, "utf8");
|
|
6316
6644
|
const marker = strategy.publisherKind === "page" ? `<!-- skillwiki-page-publish:${prepared.operationId} -->` : `<!-- skillwiki-project-page-publish:${prepared.operationId} -->`;
|
|
6317
6645
|
logAppended = !logText.includes(marker);
|
|
6318
6646
|
} catch (error) {
|
|
@@ -6403,10 +6731,10 @@ async function runPipelineLockedPrimaryStages(strategy, prepared, vault, deps, j
|
|
|
6403
6731
|
return { ok: false, exitCode: ExitCode.WRITE_FAILED, stage: "journal", state, cause: journalHook };
|
|
6404
6732
|
}
|
|
6405
6733
|
}
|
|
6406
|
-
const schemaPath =
|
|
6734
|
+
const schemaPath = join30(vault, "SCHEMA.md");
|
|
6407
6735
|
let schemaText;
|
|
6408
6736
|
try {
|
|
6409
|
-
schemaText = await
|
|
6737
|
+
schemaText = await readFile14(schemaPath, "utf8");
|
|
6410
6738
|
} catch (error) {
|
|
6411
6739
|
return {
|
|
6412
6740
|
ok: false,
|
|
@@ -6455,8 +6783,8 @@ async function runPipelineLockedPrimaryStages(strategy, prepared, vault, deps, j
|
|
|
6455
6783
|
if (!strategy.usesJournal || !phaseAtLeast(currentPhase, "page")) {
|
|
6456
6784
|
if (strategy.publisherKind === "project-page") {
|
|
6457
6785
|
try {
|
|
6458
|
-
const { mkdir:
|
|
6459
|
-
await
|
|
6786
|
+
const { mkdir: mkdir14 } = await import("fs/promises");
|
|
6787
|
+
await mkdir14(dirname7(prepared.targetPath), { recursive: true });
|
|
6460
6788
|
} catch (error) {
|
|
6461
6789
|
return {
|
|
6462
6790
|
ok: false,
|
|
@@ -6504,8 +6832,8 @@ async function runPipelineLockedPrimaryStages(strategy, prepared, vault, deps, j
|
|
|
6504
6832
|
let visibleSchema;
|
|
6505
6833
|
try {
|
|
6506
6834
|
[visible, visibleSchema] = await Promise.all([
|
|
6507
|
-
|
|
6508
|
-
|
|
6835
|
+
readFile14(prepared.targetPath, "utf8"),
|
|
6836
|
+
readFile14(schemaPath, "utf8")
|
|
6509
6837
|
]);
|
|
6510
6838
|
} catch (error) {
|
|
6511
6839
|
return {
|
|
@@ -6897,16 +7225,16 @@ async function refreshProjectIndexForTarget(vault, projectSlugs, today) {
|
|
|
6897
7225
|
for (const slug of projectSlugs) {
|
|
6898
7226
|
const rendered = await renderProjectIndex(vault, slug, { today });
|
|
6899
7227
|
if (!rendered.ok) return rendered;
|
|
6900
|
-
const indexPath =
|
|
7228
|
+
const indexPath = join31(vault, rendered.data.index_path);
|
|
6901
7229
|
try {
|
|
6902
|
-
await
|
|
7230
|
+
await mkdir10(dirname8(indexPath), { recursive: true });
|
|
6903
7231
|
} catch (error) {
|
|
6904
7232
|
return err("WRITE_FAILED", { path: indexPath, message: String(error) });
|
|
6905
7233
|
}
|
|
6906
7234
|
const written = await atomicWriteText(indexPath, rendered.data.text);
|
|
6907
7235
|
if (!written.ok) return written;
|
|
6908
7236
|
try {
|
|
6909
|
-
const visible = await
|
|
7237
|
+
const visible = await readFile15(indexPath, "utf8");
|
|
6910
7238
|
if (visible !== rendered.data.text) {
|
|
6911
7239
|
return err("WRITE_FAILED", { path: indexPath, message: "project index verification failed" });
|
|
6912
7240
|
}
|
|
@@ -7007,7 +7335,7 @@ function preparePagePublicationFromContent(input) {
|
|
|
7007
7335
|
async function preparePagePublication(input) {
|
|
7008
7336
|
let content;
|
|
7009
7337
|
try {
|
|
7010
|
-
content = await
|
|
7338
|
+
content = await readFile15(input.draftPath, "utf8");
|
|
7011
7339
|
} catch (error) {
|
|
7012
7340
|
return err("FILE_NOT_FOUND", { path: input.draftPath, message: String(error) });
|
|
7013
7341
|
}
|
|
@@ -7095,10 +7423,10 @@ var PAGE_PUBLISH_STRATEGY = {
|
|
|
7095
7423
|
return void 0;
|
|
7096
7424
|
},
|
|
7097
7425
|
async previewIndices(prepared, vault, pageChanged) {
|
|
7098
|
-
const indexPath =
|
|
7426
|
+
const indexPath = join31(vault, "index.md");
|
|
7099
7427
|
let indexText;
|
|
7100
7428
|
try {
|
|
7101
|
-
indexText = await
|
|
7429
|
+
indexText = await readFile15(indexPath, "utf8");
|
|
7102
7430
|
} catch (error) {
|
|
7103
7431
|
const result = err("FILE_NOT_FOUND", { path: indexPath, message: String(error) });
|
|
7104
7432
|
return { ok: false, exitCode: ExitCode.FILE_NOT_FOUND, result };
|
|
@@ -7116,7 +7444,7 @@ var PAGE_PUBLISH_STRATEGY = {
|
|
|
7116
7444
|
if (!renderedProject.ok) {
|
|
7117
7445
|
return { ok: false, exitCode: errorExitCode(renderedProject.error), result: renderedProject };
|
|
7118
7446
|
}
|
|
7119
|
-
const projectIndexPath =
|
|
7447
|
+
const projectIndexPath = join31(vault, renderedProject.data.index_path);
|
|
7120
7448
|
const projectIndexChanged = await readPageChanged(projectIndexPath, renderedProject.data.text);
|
|
7121
7449
|
if (!projectIndexChanged.ok) {
|
|
7122
7450
|
return { ok: false, exitCode: errorExitCode(projectIndexChanged.error), result: projectIndexChanged };
|
|
@@ -7304,7 +7632,7 @@ function buildTypedContent(title, ingested, type, tags, rawRelPath, provenance)
|
|
|
7304
7632
|
}
|
|
7305
7633
|
async function resolveRawCapture(input) {
|
|
7306
7634
|
try {
|
|
7307
|
-
const existing = await
|
|
7635
|
+
const existing = await readFile16(input.path, "utf8");
|
|
7308
7636
|
const frontmatter = extractFrontmatter(existing);
|
|
7309
7637
|
if (!frontmatter.ok) {
|
|
7310
7638
|
return err("INGEST_VALIDATION_FAILED", {
|
|
@@ -7443,7 +7771,7 @@ async function runIngest(input, pagePublishDeps = defaultPagePublishDeps()) {
|
|
|
7443
7771
|
sourceContent = fetchResult.data.body;
|
|
7444
7772
|
} else {
|
|
7445
7773
|
try {
|
|
7446
|
-
sourceContent = await
|
|
7774
|
+
sourceContent = await readFile16(input.source, "utf8");
|
|
7447
7775
|
} catch {
|
|
7448
7776
|
return {
|
|
7449
7777
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
@@ -7461,14 +7789,14 @@ async function runIngest(input, pagePublishDeps = defaultPagePublishDeps()) {
|
|
|
7461
7789
|
})
|
|
7462
7790
|
};
|
|
7463
7791
|
}
|
|
7464
|
-
const sha2562 =
|
|
7792
|
+
const sha2562 = createHash5("sha256").update(Buffer.from(sourceContent, "utf8")).digest("hex");
|
|
7465
7793
|
const today = todayIso();
|
|
7466
7794
|
const slug = slugify2(input.title);
|
|
7467
7795
|
const tags = input.tags && input.tags.length > 0 ? input.tags : [];
|
|
7468
7796
|
const rawRelPath = `raw/articles/${slug}.md`;
|
|
7469
7797
|
const typedDir = TYPE_DIR[input.type] ?? `${input.type}s`;
|
|
7470
7798
|
const typedRelPath = `${typedDir}/${slug}.md`;
|
|
7471
|
-
const rawAbsPath =
|
|
7799
|
+
const rawAbsPath = join32(input.vault, rawRelPath);
|
|
7472
7800
|
const identity = assessSourceIdentity({
|
|
7473
7801
|
rawPath: rawRelPath,
|
|
7474
7802
|
sourceUrl: sourceUrl ?? void 0,
|
|
@@ -7512,11 +7840,11 @@ async function runIngest(input, pagePublishDeps = defaultPagePublishDeps()) {
|
|
|
7512
7840
|
);
|
|
7513
7841
|
if (!input.dryRun) {
|
|
7514
7842
|
try {
|
|
7515
|
-
await
|
|
7843
|
+
await mkdir11(join32(input.vault, typedDir), { recursive: true });
|
|
7516
7844
|
} catch (error) {
|
|
7517
7845
|
return {
|
|
7518
7846
|
exitCode: ExitCode.WRITE_FAILED,
|
|
7519
|
-
result: err("WRITE_FAILED", { path:
|
|
7847
|
+
result: err("WRITE_FAILED", { path: join32(input.vault, typedDir), message: String(error) })
|
|
7520
7848
|
};
|
|
7521
7849
|
}
|
|
7522
7850
|
}
|
|
@@ -7570,11 +7898,11 @@ async function runIngest(input, pagePublishDeps = defaultPagePublishDeps()) {
|
|
|
7570
7898
|
preflight: pagePublishDeps.preflight,
|
|
7571
7899
|
mutate: async (receipt) => {
|
|
7572
7900
|
try {
|
|
7573
|
-
await
|
|
7901
|
+
await mkdir11(join32(input.vault, "raw", "articles"), { recursive: true });
|
|
7574
7902
|
} catch (error) {
|
|
7575
7903
|
return {
|
|
7576
7904
|
exitCode: ExitCode.WRITE_FAILED,
|
|
7577
|
-
result: err("WRITE_FAILED", { path:
|
|
7905
|
+
result: err("WRITE_FAILED", { path: join32(input.vault, "raw", "articles"), message: String(error) })
|
|
7578
7906
|
};
|
|
7579
7907
|
}
|
|
7580
7908
|
const rawWrite = await writeResolvedRaw({
|
|
@@ -7810,8 +8138,8 @@ ${body}`;
|
|
|
7810
8138
|
}
|
|
7811
8139
|
|
|
7812
8140
|
// src/commands/tag-reconcile.ts
|
|
7813
|
-
import { readFile as
|
|
7814
|
-
import { join as
|
|
8141
|
+
import { readFile as readFile17 } from "fs/promises";
|
|
8142
|
+
import { join as join33, posix } from "path";
|
|
7815
8143
|
var TYPED_TARGET_RE = /^(entities|concepts|comparisons|queries|meta)\/[a-z0-9][a-z0-9./_-]*\.md$/;
|
|
7816
8144
|
function errorExitCode2(error) {
|
|
7817
8145
|
switch (error) {
|
|
@@ -7854,7 +8182,7 @@ function asTagArray(frontmatter, path) {
|
|
|
7854
8182
|
async function readTagsFromFile(path) {
|
|
7855
8183
|
let text;
|
|
7856
8184
|
try {
|
|
7857
|
-
text = await
|
|
8185
|
+
text = await readFile17(path, "utf8");
|
|
7858
8186
|
} catch (error) {
|
|
7859
8187
|
if (error.code === "ENOENT") {
|
|
7860
8188
|
return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path }) };
|
|
@@ -7875,7 +8203,7 @@ async function resolveRequestedTags(input, page) {
|
|
|
7875
8203
|
result: err("INVALID_FRONTMATTER", { message: "explicit tags must be an array of strings" })
|
|
7876
8204
|
};
|
|
7877
8205
|
}
|
|
7878
|
-
const source = input.from ?? (explicit.length === 0 ?
|
|
8206
|
+
const source = input.from ?? (explicit.length === 0 ? join33(input.vault, page) : void 0);
|
|
7879
8207
|
if (!source) return { exitCode: ExitCode.OK, result: ok({ tags: [...new Set(explicit)].sort() }) };
|
|
7880
8208
|
const sourced = await readTagsFromFile(source);
|
|
7881
8209
|
if (!sourced.result.ok) return { exitCode: sourced.exitCode, result: sourced.result };
|
|
@@ -7886,7 +8214,7 @@ async function resolveRequestedTags(input, page) {
|
|
|
7886
8214
|
}
|
|
7887
8215
|
async function readSchema(schemaPath) {
|
|
7888
8216
|
try {
|
|
7889
|
-
return { exitCode: ExitCode.OK, result: ok(await
|
|
8217
|
+
return { exitCode: ExitCode.OK, result: ok(await readFile17(schemaPath, "utf8")) };
|
|
7890
8218
|
} catch (error) {
|
|
7891
8219
|
if (error.code === "ENOENT") {
|
|
7892
8220
|
return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: schemaPath }) };
|
|
@@ -7914,7 +8242,7 @@ function previewResult(page, tags, reconciled, dryRun, filesChanged) {
|
|
|
7914
8242
|
};
|
|
7915
8243
|
}
|
|
7916
8244
|
async function reconcileTagsWhileLocked(input, page, tags, comment) {
|
|
7917
|
-
const schemaPath =
|
|
8245
|
+
const schemaPath = join33(input.vault, "SCHEMA.md");
|
|
7918
8246
|
const current = await readSchema(schemaPath);
|
|
7919
8247
|
if (!current.result.ok) return { exitCode: current.exitCode, result: current.result };
|
|
7920
8248
|
const next = reconcileTaxonomyDocument(current.result.data, { tags, comment });
|
|
@@ -7925,7 +8253,7 @@ async function reconcileTagsWhileLocked(input, page, tags, comment) {
|
|
|
7925
8253
|
}
|
|
7926
8254
|
let verifiedText;
|
|
7927
8255
|
try {
|
|
7928
|
-
verifiedText = await
|
|
8256
|
+
verifiedText = await readFile17(schemaPath, "utf8");
|
|
7929
8257
|
} catch (error) {
|
|
7930
8258
|
return {
|
|
7931
8259
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -7951,7 +8279,7 @@ async function runTagReconcile(input) {
|
|
|
7951
8279
|
const comment = taxonomyCommentForPage(page.data, date, input.reason);
|
|
7952
8280
|
if (!comment.ok) return { exitCode: ExitCode.SCHEME_REJECTED, result: comment };
|
|
7953
8281
|
if (!input.write) {
|
|
7954
|
-
const schema = await readSchema(
|
|
8282
|
+
const schema = await readSchema(join33(input.vault, "SCHEMA.md"));
|
|
7955
8283
|
if (!schema.result.ok) return { exitCode: schema.exitCode, result: schema.result };
|
|
7956
8284
|
const preview = reconcileTaxonomyDocument(schema.result.data, { tags, comment: comment.data });
|
|
7957
8285
|
return previewResult(page.data, tags, preview, true, []);
|
|
@@ -8004,8 +8332,8 @@ async function runTagReconcile(input) {
|
|
|
8004
8332
|
|
|
8005
8333
|
// src/commands/project-page-publish.ts
|
|
8006
8334
|
import { realpathSync as realpathSync4 } from "fs";
|
|
8007
|
-
import { mkdir as
|
|
8008
|
-
import { dirname as dirname10, join as
|
|
8335
|
+
import { mkdir as mkdir12, readFile as readFile18 } from "fs/promises";
|
|
8336
|
+
import { dirname as dirname10, join as join34, resolve as resolve6 } from "path";
|
|
8009
8337
|
|
|
8010
8338
|
// src/utils/architecture-page.ts
|
|
8011
8339
|
import { lstatSync, realpathSync as realpathSync3 } from "fs";
|
|
@@ -8177,7 +8505,7 @@ function validateLogNote(logNote) {
|
|
|
8177
8505
|
async function prepareProjectPagePublication(input) {
|
|
8178
8506
|
let content;
|
|
8179
8507
|
try {
|
|
8180
|
-
content = await
|
|
8508
|
+
content = await readFile18(input.draftPath, "utf8");
|
|
8181
8509
|
} catch (error) {
|
|
8182
8510
|
return err("FILE_NOT_FOUND", { path: input.draftPath, message: String(error) });
|
|
8183
8511
|
}
|
|
@@ -8310,9 +8638,9 @@ var PROJECT_PAGE_PUBLISH_STRATEGY = {
|
|
|
8310
8638
|
async updateIndicesLocked(prepared, vault, state, observe) {
|
|
8311
8639
|
const rendered = await renderProjectIndex(vault, prepared.page.project, { today: prepared.date });
|
|
8312
8640
|
if (!rendered.ok) return rendered;
|
|
8313
|
-
const knowledgePath =
|
|
8641
|
+
const knowledgePath = join34(vault, rendered.data.index_path);
|
|
8314
8642
|
try {
|
|
8315
|
-
await
|
|
8643
|
+
await mkdir12(dirname10(knowledgePath), { recursive: true });
|
|
8316
8644
|
} catch (error) {
|
|
8317
8645
|
return err("WRITE_FAILED", { message: String(error) });
|
|
8318
8646
|
}
|
|
@@ -8321,7 +8649,7 @@ var PROJECT_PAGE_PUBLISH_STRATEGY = {
|
|
|
8321
8649
|
state.projectIndexUpdated = knowledgeWrite.data.changed;
|
|
8322
8650
|
if (state.projectIndexUpdated) state.changed.add(rendered.data.index_path);
|
|
8323
8651
|
try {
|
|
8324
|
-
const visibleIndex = await
|
|
8652
|
+
const visibleIndex = await readFile18(knowledgePath, "utf8");
|
|
8325
8653
|
if (visibleIndex !== rendered.data.text) {
|
|
8326
8654
|
return err("WRITE_FAILED", { message: "project knowledge.md verification failed" });
|
|
8327
8655
|
}
|
|
@@ -8341,9 +8669,9 @@ var PROJECT_PAGE_PUBLISH_STRATEGY = {
|
|
|
8341
8669
|
}
|
|
8342
8670
|
let projectIndexChanged = true;
|
|
8343
8671
|
if (projectIndex.ok) {
|
|
8344
|
-
const indexPath =
|
|
8672
|
+
const indexPath = join34(vault, projectIndex.data.index_path);
|
|
8345
8673
|
try {
|
|
8346
|
-
const existing = await
|
|
8674
|
+
const existing = await readFile18(indexPath, "utf8");
|
|
8347
8675
|
projectIndexChanged = existing !== projectIndex.data.text || pageChanged;
|
|
8348
8676
|
} catch {
|
|
8349
8677
|
projectIndexChanged = true;
|
|
@@ -8500,10 +8828,10 @@ function runSyncJournalClearStale(input) {
|
|
|
8500
8828
|
|
|
8501
8829
|
// src/commands/snapshot-maintenance.ts
|
|
8502
8830
|
import { existsSync as existsSync11, writeFileSync as writeFileSync6, mkdirSync as mkdirSync4, appendFileSync } from "fs";
|
|
8503
|
-
import { createHash as
|
|
8831
|
+
import { createHash as createHash6 } from "crypto";
|
|
8504
8832
|
import { execSync as execSync3, spawn } from "child_process";
|
|
8505
8833
|
import { platform as platform2 } from "os";
|
|
8506
|
-
import { join as
|
|
8834
|
+
import { join as join35, resolve as resolvePath } from "path";
|
|
8507
8835
|
var MAINTENANCE_SCHEMA_VERSION = 1;
|
|
8508
8836
|
var MAINTENANCE_COMMAND = "snapshot-maintenance journal clear-stale";
|
|
8509
8837
|
var REPAIR_COMMAND = "snapshot-maintenance projection-conflict repair";
|
|
@@ -8527,7 +8855,7 @@ function computeApprovalId(plan) {
|
|
|
8527
8855
|
targets,
|
|
8528
8856
|
normalizeReason(plan.operator_reason)
|
|
8529
8857
|
].join("|");
|
|
8530
|
-
return "smap1-" +
|
|
8858
|
+
return "smap1-" + createHash6("sha256").update(payload).digest("hex").slice(0, 32);
|
|
8531
8859
|
}
|
|
8532
8860
|
function normalizeReason(reason) {
|
|
8533
8861
|
return reason.trim().replace(/\s+/g, " ");
|
|
@@ -8568,7 +8896,7 @@ async function runSnapshotMaintenanceDryRun(input) {
|
|
|
8568
8896
|
audit(makeAuditEvent(input, fleetLoad.hostId, now, "refusal", "MAINTENANCE_WRONG_WORKTREE"));
|
|
8569
8897
|
return { exitCode: ExitCode.PROTECTED_SNAPSHOTTER_WRITE_BLOCKED, result: refusalErr("MAINTENANCE_WRONG_WORKTREE", `requested path '${requested}' is not the configured snapshot worktree '${canonicalize(configuredWorktree)}'`) };
|
|
8570
8898
|
}
|
|
8571
|
-
if (!existsSync11(requested) || !existsSync11(
|
|
8899
|
+
if (!existsSync11(requested) || !existsSync11(join35(requested, ".git"))) {
|
|
8572
8900
|
audit(makeAuditEvent(input, fleetLoad.hostId, now, "refusal", "MAINTENANCE_MISSING_GIT_REPO"));
|
|
8573
8901
|
return { exitCode: ExitCode.PROTECTED_SNAPSHOTTER_WRITE_BLOCKED, result: refusalErr("MAINTENANCE_MISSING_GIT_REPO", `snapshot worktree is not a git repository: ${requested}`) };
|
|
8574
8902
|
}
|
|
@@ -8648,14 +8976,14 @@ async function resolveLiveVault(input) {
|
|
|
8648
8976
|
return resolved.ok ? canonicalize(resolved.data.path) : void 0;
|
|
8649
8977
|
}
|
|
8650
8978
|
function snapshotMaintenanceAuditLogPath(home) {
|
|
8651
|
-
const stateDir = platform2() === "darwin" ?
|
|
8652
|
-
return
|
|
8979
|
+
const stateDir = platform2() === "darwin" ? join35(home, "Library", "Application Support", "vault-sync") : join35(home, ".local", "state", "vault-sync");
|
|
8980
|
+
return join35(stateDir, "snapshot-maintenance-audit.jsonl");
|
|
8653
8981
|
}
|
|
8654
8982
|
function defaultAuditSink(home) {
|
|
8655
8983
|
return (event) => {
|
|
8656
8984
|
try {
|
|
8657
8985
|
const logPath = snapshotMaintenanceAuditLogPath(home);
|
|
8658
|
-
mkdirSync4(
|
|
8986
|
+
mkdirSync4(join35(logPath, ".."), { recursive: true });
|
|
8659
8987
|
appendFileSync(logPath, JSON.stringify(event) + "\n", { encoding: "utf8" });
|
|
8660
8988
|
} catch {
|
|
8661
8989
|
}
|
|
@@ -8797,15 +9125,15 @@ function computeRepairApprovalId(plan) {
|
|
|
8797
9125
|
paths,
|
|
8798
9126
|
normalizeReason(plan.operator_reason)
|
|
8799
9127
|
].join("|");
|
|
8800
|
-
return "smap1-" +
|
|
9128
|
+
return "smap1-" + createHash6("sha256").update(payload).digest("hex").slice(0, 32);
|
|
8801
9129
|
}
|
|
8802
9130
|
function nonMergeSequencerActive(repo) {
|
|
8803
9131
|
const gitDir = git(repo, ["rev-parse", "--absolute-git-dir"]);
|
|
8804
9132
|
if (!gitDir) return false;
|
|
8805
9133
|
for (const m of ["CHERRY_PICK_HEAD", "REVERT_HEAD"]) {
|
|
8806
|
-
if (existsSync11(
|
|
9134
|
+
if (existsSync11(join35(gitDir, m))) return true;
|
|
8807
9135
|
}
|
|
8808
|
-
return existsSync11(
|
|
9136
|
+
return existsSync11(join35(gitDir, "rebase-merge")) || existsSync11(join35(gitDir, "rebase-apply"));
|
|
8809
9137
|
}
|
|
8810
9138
|
function extraUnmergedPaths(unmerged) {
|
|
8811
9139
|
const allow = new Set(PROJECTION_CONFLICT_ALLOWLIST);
|
|
@@ -8844,7 +9172,7 @@ async function authorizeRepairContext(input) {
|
|
|
8844
9172
|
audit(makeAuditEvent(input, fleetLoad.hostId, now, "refusal", "MAINTENANCE_WRONG_WORKTREE", void 0, REPAIR_COMMAND));
|
|
8845
9173
|
return { exitCode: ExitCode.PROTECTED_SNAPSHOTTER_WRITE_BLOCKED, result: refusalErr("MAINTENANCE_WRONG_WORKTREE", `requested path '${requested}' is not the configured snapshot worktree '${canonicalize(configuredWorktree)}'`) };
|
|
8846
9174
|
}
|
|
8847
|
-
if (!existsSync11(requested) || !existsSync11(
|
|
9175
|
+
if (!existsSync11(requested) || !existsSync11(join35(requested, ".git"))) {
|
|
8848
9176
|
audit(makeAuditEvent(input, fleetLoad.hostId, now, "refusal", "MAINTENANCE_MISSING_GIT_REPO", void 0, REPAIR_COMMAND));
|
|
8849
9177
|
return { exitCode: ExitCode.PROTECTED_SNAPSHOTTER_WRITE_BLOCKED, result: refusalErr("MAINTENANCE_MISSING_GIT_REPO", `snapshot worktree is not a git repository: ${requested}`) };
|
|
8850
9178
|
}
|
|
@@ -8945,8 +9273,8 @@ async function performProjectionRepair(input, plan, audit, now) {
|
|
|
8945
9273
|
return { exitCode: ExitCode.WRITE_FAILED, result: rendered };
|
|
8946
9274
|
}
|
|
8947
9275
|
try {
|
|
8948
|
-
writeFileSync6(
|
|
8949
|
-
writeFileSync6(
|
|
9276
|
+
writeFileSync6(join35(plan.snapshot_worktree, "index.md"), rendered.data.index);
|
|
9277
|
+
writeFileSync6(join35(plan.snapshot_worktree, "log.md"), rendered.data.log);
|
|
8950
9278
|
gitStrict(plan.snapshot_worktree, ["add", "--", "index.md", "log.md"]);
|
|
8951
9279
|
gitStrict(plan.snapshot_worktree, [
|
|
8952
9280
|
"commit",
|
|
@@ -9054,7 +9382,7 @@ function releaseSnapshotFlock(handle) {
|
|
|
9054
9382
|
|
|
9055
9383
|
// src/commands/backup.ts
|
|
9056
9384
|
import { statSync as statSync3, readdirSync as readdirSync2, readFileSync as readFileSync15, mkdirSync as mkdirSync5, writeFileSync as writeFileSync7 } from "fs";
|
|
9057
|
-
import { join as
|
|
9385
|
+
import { join as join36, relative as relative3, dirname as dirname11 } from "path";
|
|
9058
9386
|
import { PutObjectCommand, HeadObjectCommand, ListObjectsV2Command, GetObjectCommand, DeleteObjectsCommand } from "@aws-sdk/client-s3";
|
|
9059
9387
|
|
|
9060
9388
|
// src/utils/s3-client.ts
|
|
@@ -9078,7 +9406,7 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([".git", ".obsidian", "_archive", "node_
|
|
|
9078
9406
|
function* walkMarkdown(dir, base) {
|
|
9079
9407
|
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
9080
9408
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
9081
|
-
const full =
|
|
9409
|
+
const full = join36(dir, entry.name);
|
|
9082
9410
|
if (entry.isDirectory()) {
|
|
9083
9411
|
yield* walkMarkdown(full, base);
|
|
9084
9412
|
} else if (entry.name.endsWith(".md")) {
|
|
@@ -9101,7 +9429,7 @@ async function runBackupSync(input) {
|
|
|
9101
9429
|
let failed = 0;
|
|
9102
9430
|
const files = [...walkMarkdown(input.vault, input.vault)];
|
|
9103
9431
|
for (const relPath of files) {
|
|
9104
|
-
const absPath =
|
|
9432
|
+
const absPath = join36(input.vault, relPath);
|
|
9105
9433
|
const localStat = statSync3(absPath);
|
|
9106
9434
|
let needsUpload = true;
|
|
9107
9435
|
try {
|
|
@@ -9177,7 +9505,7 @@ async function runBackupRestore(input) {
|
|
|
9177
9505
|
const objects = list.Contents ?? [];
|
|
9178
9506
|
for (const obj of objects) {
|
|
9179
9507
|
if (!obj.Key) continue;
|
|
9180
|
-
const localPath =
|
|
9508
|
+
const localPath = join36(target, obj.Key);
|
|
9181
9509
|
try {
|
|
9182
9510
|
const localStat = statSync3(localPath);
|
|
9183
9511
|
if (obj.LastModified && localStat.mtime > obj.LastModified) {
|
|
@@ -9224,8 +9552,8 @@ async function runBackupRestore(input) {
|
|
|
9224
9552
|
|
|
9225
9553
|
// src/commands/status.ts
|
|
9226
9554
|
import { existsSync as existsSync12, statSync as statSync4 } from "fs";
|
|
9227
|
-
import { readFile as
|
|
9228
|
-
import { join as
|
|
9555
|
+
import { readFile as readFile19 } from "fs/promises";
|
|
9556
|
+
import { join as join37 } from "path";
|
|
9229
9557
|
var FUSE_NO_MIRROR_NOTE = "FUSE vault with no read mirror - status scan may be slow";
|
|
9230
9558
|
async function runStatus(input) {
|
|
9231
9559
|
if (!existsSync12(input.vault)) {
|
|
@@ -9264,7 +9592,7 @@ async function runStatus(input) {
|
|
|
9264
9592
|
const compound = scan.data.compound.length;
|
|
9265
9593
|
let schemaVersion = "v1";
|
|
9266
9594
|
try {
|
|
9267
|
-
const schemaContent = await
|
|
9595
|
+
const schemaContent = await readFile19(join37(scanRoot, "SCHEMA.md"), "utf8");
|
|
9268
9596
|
const versionMatch = schemaContent.match(/version:\s*["']?([^"'\s\n]+)/i);
|
|
9269
9597
|
if (versionMatch) schemaVersion = versionMatch[1];
|
|
9270
9598
|
} catch {
|
|
@@ -9332,13 +9660,13 @@ async function runStatus(input) {
|
|
|
9332
9660
|
|
|
9333
9661
|
// src/commands/copy-status.ts
|
|
9334
9662
|
import { existsSync as existsSync13, realpathSync as realpathSync5 } from "fs";
|
|
9335
|
-
import { isAbsolute as isAbsolute2, join as
|
|
9663
|
+
import { isAbsolute as isAbsolute2, join as join39, relative as relative5, resolve as resolve8 } from "path";
|
|
9336
9664
|
|
|
9337
9665
|
// src/utils/fetch-projection.ts
|
|
9338
|
-
import { isAbsolute, join as
|
|
9666
|
+
import { isAbsolute, join as join38, resolve as resolve7 } from "path";
|
|
9339
9667
|
function inspectConfiguredFetchProjection(home) {
|
|
9340
9668
|
if (!home) return { configured: false };
|
|
9341
|
-
const config = readSkillWikiConfig(
|
|
9669
|
+
const config = readSkillWikiConfig(join38(home, ".skillwiki", ".env"));
|
|
9342
9670
|
const configured = config["vault_sync.fetch_projection"];
|
|
9343
9671
|
if (!configured || configured === "none") return { configured: false };
|
|
9344
9672
|
if (!isAbsolute(configured)) {
|
|
@@ -9348,8 +9676,8 @@ function inspectConfiguredFetchProjection(home) {
|
|
|
9348
9676
|
}
|
|
9349
9677
|
|
|
9350
9678
|
// src/utils/live-drift.ts
|
|
9351
|
-
import { createHash as
|
|
9352
|
-
import { readFile as
|
|
9679
|
+
import { createHash as createHash7 } from "crypto";
|
|
9680
|
+
import { readFile as readFile20 } from "fs/promises";
|
|
9353
9681
|
import { relative as relative4, sep as sep3 } from "path";
|
|
9354
9682
|
|
|
9355
9683
|
// src/utils/git-promotion.ts
|
|
@@ -9370,7 +9698,7 @@ function isGitPromotablePath(path) {
|
|
|
9370
9698
|
// src/utils/live-drift.ts
|
|
9371
9699
|
function gitBlobOid(bytes) {
|
|
9372
9700
|
const header = Buffer.from(`blob ${bytes.length}\0`);
|
|
9373
|
-
return
|
|
9701
|
+
return createHash7("sha1").update(header).update(bytes).digest("hex");
|
|
9374
9702
|
}
|
|
9375
9703
|
function headBlobOids(projection) {
|
|
9376
9704
|
const output = git(projection, ["ls-tree", "-rz", "-r", "HEAD"]);
|
|
@@ -9394,7 +9722,7 @@ async function measureAuthoritativeLiveDrift(liveVault, projection) {
|
|
|
9394
9722
|
const rel = relative4(liveVault, page.absPath).split(sep3).join("/");
|
|
9395
9723
|
if (!isGitPromotablePath(rel)) continue;
|
|
9396
9724
|
livePromotable.add(rel);
|
|
9397
|
-
const bytes = await
|
|
9725
|
+
const bytes = await readFile20(page.absPath);
|
|
9398
9726
|
if (projectionBlobs.get(rel) !== gitBlobOid(bytes)) content += 1;
|
|
9399
9727
|
}
|
|
9400
9728
|
for (const rel of projectionBlobs.keys()) {
|
|
@@ -9572,7 +9900,7 @@ function defaultCopyStatusDeps(input) {
|
|
|
9572
9900
|
const projectionWarning = selection.configured ? `configured fetch projection ignored for Git status; using live vault${projectionProblem ? ` (${projectionProblem})` : ""}` : void 0;
|
|
9573
9901
|
const invalidProjectionDetail = projectionProblem ?? "configured fetch projection is not a git repository";
|
|
9574
9902
|
const gitRootProblem = () => {
|
|
9575
|
-
if (!existsSync13(
|
|
9903
|
+
if (!existsSync13(join39(gitVault, ".git"))) {
|
|
9576
9904
|
return "vault is not a git repository";
|
|
9577
9905
|
}
|
|
9578
9906
|
return void 0;
|
|
@@ -9640,7 +9968,7 @@ function defaultCopyStatusDeps(input) {
|
|
|
9640
9968
|
if (!configuredProjection) {
|
|
9641
9969
|
return { unknown: true, detail: projectionProblem ?? "live drift requires a configured fetch projection" };
|
|
9642
9970
|
}
|
|
9643
|
-
if (!existsSync13(
|
|
9971
|
+
if (!existsSync13(join39(configuredProjection, ".git"))) {
|
|
9644
9972
|
return { unknown: true, detail: invalidProjectionDetail };
|
|
9645
9973
|
}
|
|
9646
9974
|
return measureAuthoritativeLiveDrift(input.vault, configuredProjection);
|
|
@@ -9656,8 +9984,8 @@ async function runCopyStatusCommand(input) {
|
|
|
9656
9984
|
}
|
|
9657
9985
|
|
|
9658
9986
|
// src/commands/seed.ts
|
|
9659
|
-
import { mkdir as
|
|
9660
|
-
import { join as
|
|
9987
|
+
import { mkdir as mkdir13, writeFile as writeFile8, stat as stat5 } from "fs/promises";
|
|
9988
|
+
import { join as join40 } from "path";
|
|
9661
9989
|
var TODAY = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
9662
9990
|
var EXAMPLE_PAGES = {
|
|
9663
9991
|
"entities/example-project.md": `---
|
|
@@ -9726,29 +10054,29 @@ Real sources are immutable after ingestion \u2014 never edit them.
|
|
|
9726
10054
|
`;
|
|
9727
10055
|
async function runSeed(input) {
|
|
9728
10056
|
try {
|
|
9729
|
-
await
|
|
10057
|
+
await stat5(join40(input.vault, "SCHEMA.md"));
|
|
9730
10058
|
} catch {
|
|
9731
10059
|
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { root: input.vault, reason: "SCHEMA.md missing \u2014 run `skillwiki init` first" }) };
|
|
9732
10060
|
}
|
|
9733
10061
|
const created = [];
|
|
9734
10062
|
const skipped = [];
|
|
9735
10063
|
for (const [relPath, content] of Object.entries(EXAMPLE_PAGES)) {
|
|
9736
|
-
const absPath =
|
|
10064
|
+
const absPath = join40(input.vault, relPath);
|
|
9737
10065
|
try {
|
|
9738
|
-
await
|
|
10066
|
+
await stat5(absPath);
|
|
9739
10067
|
skipped.push(relPath);
|
|
9740
10068
|
} catch {
|
|
9741
|
-
await
|
|
10069
|
+
await mkdir13(join40(absPath, ".."), { recursive: true });
|
|
9742
10070
|
await writeFile8(absPath, content, "utf8");
|
|
9743
10071
|
created.push(relPath);
|
|
9744
10072
|
}
|
|
9745
10073
|
}
|
|
9746
|
-
const rawPath =
|
|
10074
|
+
const rawPath = join40(input.vault, "raw", "articles", "example-source.md");
|
|
9747
10075
|
try {
|
|
9748
|
-
await
|
|
10076
|
+
await stat5(rawPath);
|
|
9749
10077
|
skipped.push("raw/articles/example-source.md");
|
|
9750
10078
|
} catch {
|
|
9751
|
-
await
|
|
10079
|
+
await mkdir13(join40(rawPath, ".."), { recursive: true });
|
|
9752
10080
|
await writeFile8(rawPath, EXAMPLE_RAW, "utf8");
|
|
9753
10081
|
created.push("raw/articles/example-source.md");
|
|
9754
10082
|
}
|
|
@@ -9771,9 +10099,9 @@ async function runSeed(input) {
|
|
|
9771
10099
|
}
|
|
9772
10100
|
|
|
9773
10101
|
// src/commands/canvas.ts
|
|
9774
|
-
import { readFile as
|
|
10102
|
+
import { readFile as readFile21, writeFile as writeFile9 } from "fs/promises";
|
|
9775
10103
|
import { existsSync as existsSync14 } from "fs";
|
|
9776
|
-
import { join as
|
|
10104
|
+
import { join as join41 } from "path";
|
|
9777
10105
|
var NODE_WIDTH = 240;
|
|
9778
10106
|
var NODE_HEIGHT = 60;
|
|
9779
10107
|
var COLUMN_SPACING = 400;
|
|
@@ -9851,7 +10179,7 @@ function buildCanvasEdges(adjacency) {
|
|
|
9851
10179
|
return edges;
|
|
9852
10180
|
}
|
|
9853
10181
|
async function runCanvasGenerate(input) {
|
|
9854
|
-
const graphPath = input.graphPath ??
|
|
10182
|
+
const graphPath = input.graphPath ?? join41(input.vault, ".skillwiki", "graph.json");
|
|
9855
10183
|
if (!existsSync14(graphPath)) {
|
|
9856
10184
|
return {
|
|
9857
10185
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
@@ -9863,7 +10191,7 @@ async function runCanvasGenerate(input) {
|
|
|
9863
10191
|
}
|
|
9864
10192
|
let raw;
|
|
9865
10193
|
try {
|
|
9866
|
-
raw = await
|
|
10194
|
+
raw = await readFile21(graphPath, "utf8");
|
|
9867
10195
|
} catch (e) {
|
|
9868
10196
|
return {
|
|
9869
10197
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
@@ -9889,7 +10217,7 @@ async function runCanvasGenerate(input) {
|
|
|
9889
10217
|
const nodes = buildCanvasNodes(paths);
|
|
9890
10218
|
const edges = buildCanvasEdges(graph.adjacency);
|
|
9891
10219
|
const canvas = { nodes, edges };
|
|
9892
|
-
const outPath =
|
|
10220
|
+
const outPath = join41(input.vault, "vault-graph.canvas");
|
|
9893
10221
|
try {
|
|
9894
10222
|
await writeFile9(outPath, JSON.stringify(canvas, null, 2));
|
|
9895
10223
|
} catch (e) {
|
|
@@ -10033,12 +10361,12 @@ async function runSourceDisposition(input) {
|
|
|
10033
10361
|
}
|
|
10034
10362
|
|
|
10035
10363
|
// src/utils/source-disposal.ts
|
|
10036
|
-
import { createHash as
|
|
10037
|
-
import { readFile as
|
|
10364
|
+
import { createHash as createHash8 } from "crypto";
|
|
10365
|
+
import { readFile as readFile23, unlink as unlink5 } from "fs/promises";
|
|
10038
10366
|
|
|
10039
10367
|
// src/utils/raw-asset-reference-index.ts
|
|
10040
|
-
import { readdir as readdir7, readFile as
|
|
10041
|
-
import { join as
|
|
10368
|
+
import { readdir as readdir7, readFile as readFile22 } from "fs/promises";
|
|
10369
|
+
import { join as join42, relative as relative6, sep as sep4 } from "path";
|
|
10042
10370
|
var PREVIEW_EXTENSIONS = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "gif", "webp", "svg", "bmp", "avif", "pdf", "mp3", "wav", "m4a", "mp4", "webm"]);
|
|
10043
10371
|
async function walkFiles(root, dir = root) {
|
|
10044
10372
|
let entries;
|
|
@@ -10049,7 +10377,7 @@ async function walkFiles(root, dir = root) {
|
|
|
10049
10377
|
}
|
|
10050
10378
|
const out = [];
|
|
10051
10379
|
for (const entry of entries) {
|
|
10052
|
-
const path =
|
|
10380
|
+
const path = join42(dir, entry.name);
|
|
10053
10381
|
if (entry.isDirectory()) out.push(...await walkFiles(root, path));
|
|
10054
10382
|
else if (entry.isFile()) out.push(relative6(root, path).split(sep4).join("/"));
|
|
10055
10383
|
}
|
|
@@ -10065,7 +10393,7 @@ function addInbound(map, target, source) {
|
|
|
10065
10393
|
map.set(target, refs);
|
|
10066
10394
|
}
|
|
10067
10395
|
async function buildRawAssetReferenceIndex(vault) {
|
|
10068
|
-
const assetRoot =
|
|
10396
|
+
const assetRoot = join42(vault, "raw", "assets");
|
|
10069
10397
|
const relativeAssets = await walkFiles(assetRoot);
|
|
10070
10398
|
const assets = relativeAssets.map((path) => `raw/assets/${path}`).sort((a, b) => a.localeCompare(b));
|
|
10071
10399
|
const assetSet = new Set(assets);
|
|
@@ -10081,7 +10409,7 @@ async function buildRawAssetReferenceIndex(vault) {
|
|
|
10081
10409
|
for (const page of scan.data.allMarkdown) {
|
|
10082
10410
|
let text;
|
|
10083
10411
|
try {
|
|
10084
|
-
text = await
|
|
10412
|
+
text = await readFile22(page.absPath, "utf8");
|
|
10085
10413
|
} catch {
|
|
10086
10414
|
continue;
|
|
10087
10415
|
}
|
|
@@ -10117,7 +10445,7 @@ async function buildRawAssetReferenceIndex(vault) {
|
|
|
10117
10445
|
|
|
10118
10446
|
// src/utils/source-disposal.ts
|
|
10119
10447
|
function sha256(bytes) {
|
|
10120
|
-
return
|
|
10448
|
+
return createHash8("sha256").update(bytes).digest("hex");
|
|
10121
10449
|
}
|
|
10122
10450
|
function exactTarget(path) {
|
|
10123
10451
|
if (/[?*\[\]{}]/.test(path) || !path.includes("/") || path.endsWith("/")) return err("RAW_DISPOSAL_TARGET_INVALID", { path, message: "exact file path required; globs, basenames, and directories are refused" });
|
|
@@ -10141,7 +10469,7 @@ async function planSourceDisposal(input) {
|
|
|
10141
10469
|
if (sensitive.length > 0) return err("SENSITIVE_CONTENT_DETECTED", { findings: sensitive });
|
|
10142
10470
|
const target = await resolveExistingRegularFileInsideVault(input.vault, input.rawPath);
|
|
10143
10471
|
if (!target.ok) return err("RAW_DISPOSAL_TARGET_INVALID", { path: input.rawPath, cause: target });
|
|
10144
|
-
const bytes = await
|
|
10472
|
+
const bytes = await readFile23(target.data);
|
|
10145
10473
|
const size = bytes.byteLength;
|
|
10146
10474
|
let typedReferences = [];
|
|
10147
10475
|
let otherReferences = [];
|
|
@@ -10265,7 +10593,7 @@ async function runSourceDisposal(input) {
|
|
|
10265
10593
|
import { existsSync as existsSync15, readFileSync as readFileSync16 } from "fs";
|
|
10266
10594
|
import { execSync as nodeExecSync } from "child_process";
|
|
10267
10595
|
import { hostname as nodeHostname, platform as nodePlatform } from "os";
|
|
10268
|
-
import { join as
|
|
10596
|
+
import { join as join43 } from "path";
|
|
10269
10597
|
var SSH_TIMEOUT_MS = 15e3;
|
|
10270
10598
|
var TIMER_UNIT = "agent-memory-trends.timer";
|
|
10271
10599
|
var SERVICE_UNIT = "agent-memory-trends.service";
|
|
@@ -10495,7 +10823,7 @@ async function runFleetHealth(input) {
|
|
|
10495
10823
|
const home = input.home ?? env.HOME ?? "";
|
|
10496
10824
|
const osHostname = input.osHostname ?? env.HOSTNAME ?? nodeHostname();
|
|
10497
10825
|
const vault = input.vault ?? env.WIKI_PATH;
|
|
10498
|
-
const file = input.file ?? (vault ?
|
|
10826
|
+
const file = input.file ?? (vault ? join43(vault, FLEET_REL_PATH) : void 0);
|
|
10499
10827
|
if (!file) {
|
|
10500
10828
|
return {
|
|
10501
10829
|
exitCode: ExitCode.NO_VAULT_CONFIGURED,
|
|
@@ -10580,7 +10908,7 @@ async function runFleetHealth(input) {
|
|
|
10580
10908
|
|
|
10581
10909
|
// src/utils/auto-commit.ts
|
|
10582
10910
|
import { existsSync as existsSync16 } from "fs";
|
|
10583
|
-
import { isAbsolute as isAbsolute3, join as
|
|
10911
|
+
import { isAbsolute as isAbsolute3, join as join44, normalize } from "path";
|
|
10584
10912
|
function isValidLastOpPath(file) {
|
|
10585
10913
|
if (!file || typeof file !== "string") return false;
|
|
10586
10914
|
const trimmed = file.trim();
|
|
@@ -10601,7 +10929,7 @@ async function postCommit(vault, exitCode) {
|
|
|
10601
10929
|
const dotenv = await parseDotenvFile(configPath(home));
|
|
10602
10930
|
const autoCommit = process.env.AUTO_COMMIT ?? dotenv["AUTO_COMMIT"];
|
|
10603
10931
|
if (autoCommit === "false") return;
|
|
10604
|
-
if (!existsSync16(
|
|
10932
|
+
if (!existsSync16(join44(vault, ".git"))) return;
|
|
10605
10933
|
const lastOps = readLastOp(vault);
|
|
10606
10934
|
if (lastOps.length === 0) return;
|
|
10607
10935
|
const porcelain = git(vault, ["status", "--porcelain", "--", ...VAULT_COMMIT_PATHSPEC]);
|
|
@@ -10641,53 +10969,6 @@ async function postCommit(vault, exitCode) {
|
|
|
10641
10969
|
|
|
10642
10970
|
// src/commands/mcp-auth.ts
|
|
10643
10971
|
import { readFileSync as readFileSync17, writeFileSync as writeFileSync8 } from "fs";
|
|
10644
|
-
|
|
10645
|
-
// src/utils/mcp-token-map.ts
|
|
10646
|
-
import { createHash as createHash8, randomBytes } from "crypto";
|
|
10647
|
-
import yaml from "js-yaml";
|
|
10648
|
-
var HOST_ID_RE = /^[a-z][a-z0-9-]{1,62}$/;
|
|
10649
|
-
function parseMcpTokenMap(yamlText) {
|
|
10650
|
-
const parsed = yaml.load(yamlText);
|
|
10651
|
-
const map = /* @__PURE__ */ new Map();
|
|
10652
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
10653
|
-
return map;
|
|
10654
|
-
}
|
|
10655
|
-
const records = parsed;
|
|
10656
|
-
const nested = records.tokens;
|
|
10657
|
-
const source = nested && typeof nested === "object" && !Array.isArray(nested) ? nested : records;
|
|
10658
|
-
for (const [hash, hostId] of Object.entries(source)) {
|
|
10659
|
-
if (hash === "tokens") continue;
|
|
10660
|
-
if (typeof hostId !== "string" || hostId.length === 0) continue;
|
|
10661
|
-
const normalized = hash.trim().toLowerCase();
|
|
10662
|
-
if (!/^[0-9a-f]{64}$/.test(normalized)) continue;
|
|
10663
|
-
map.set(normalized, hostId);
|
|
10664
|
-
}
|
|
10665
|
-
return map;
|
|
10666
|
-
}
|
|
10667
|
-
function generateHostBearer(rng) {
|
|
10668
|
-
const bytes = (rng ?? (() => randomBytes(32)))();
|
|
10669
|
-
const raw = bytes.toString("base64url");
|
|
10670
|
-
const hashHex = createHash8("sha256").update(raw, "utf8").digest("hex");
|
|
10671
|
-
return { raw, hashHex };
|
|
10672
|
-
}
|
|
10673
|
-
function appendHostHash(yamlText, hashHex, hostId) {
|
|
10674
|
-
if (!HOST_ID_RE.test(hostId)) {
|
|
10675
|
-
return { error: "INVALID_HOST_ID" };
|
|
10676
|
-
}
|
|
10677
|
-
const map = parseMcpTokenMap(yamlText);
|
|
10678
|
-
const normalizedHash = hashHex.trim().toLowerCase();
|
|
10679
|
-
if ([...map.values()].includes(hostId)) {
|
|
10680
|
-
return { error: "DUPLICATE_HOST_ID" };
|
|
10681
|
-
}
|
|
10682
|
-
if (map.has(normalizedHash)) {
|
|
10683
|
-
return { error: "DUPLICATE_HASH" };
|
|
10684
|
-
}
|
|
10685
|
-
return { yaml: yamlText.replace(/\s*$/, "") + `
|
|
10686
|
-
${normalizedHash}: ${hostId}
|
|
10687
|
-
` };
|
|
10688
|
-
}
|
|
10689
|
-
|
|
10690
|
-
// src/commands/mcp-auth.ts
|
|
10691
10972
|
function isEnoent(error) {
|
|
10692
10973
|
return error.code === "ENOENT";
|
|
10693
10974
|
}
|
|
@@ -10696,7 +10977,7 @@ function preflight(error) {
|
|
|
10696
10977
|
}
|
|
10697
10978
|
async function runMcpAuthIssueHost(input) {
|
|
10698
10979
|
const isTty = input.isTty ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
10699
|
-
const
|
|
10980
|
+
const readFile24 = input.readFile ?? readFileSync17;
|
|
10700
10981
|
const writeFile10 = input.writeFile ?? writeFileSync8;
|
|
10701
10982
|
const stderrWrite = input.stderrWrite ?? ((s) => {
|
|
10702
10983
|
process.stderr.write(s);
|
|
@@ -10706,10 +10987,12 @@ async function runMcpAuthIssueHost(input) {
|
|
|
10706
10987
|
const hostId = input.hostId;
|
|
10707
10988
|
if (!mapPath) return preflight("MAP_PATH_REQUIRED");
|
|
10708
10989
|
if (!HOST_ID_RE.test(hostId)) return preflight("INVALID_HOST_ID");
|
|
10990
|
+
const granted = parseAllowedVaultIds(input.allowedVaults);
|
|
10991
|
+
if ("error" in granted) return preflight(granted.error);
|
|
10709
10992
|
if (write && !isTty) return preflight("NO_TTY");
|
|
10710
10993
|
let yamlText = "";
|
|
10711
10994
|
try {
|
|
10712
|
-
yamlText =
|
|
10995
|
+
yamlText = readFile24(mapPath, "utf8");
|
|
10713
10996
|
} catch (error) {
|
|
10714
10997
|
if (!isEnoent(error)) throw error;
|
|
10715
10998
|
}
|
|
@@ -10722,12 +11005,13 @@ async function runMcpAuthIssueHost(input) {
|
|
|
10722
11005
|
host_id: hostId,
|
|
10723
11006
|
hash_prefix: "",
|
|
10724
11007
|
map_path: mapPath,
|
|
10725
|
-
wrote: false
|
|
11008
|
+
wrote: false,
|
|
11009
|
+
allowed_vaults: granted.vaults
|
|
10726
11010
|
})
|
|
10727
11011
|
};
|
|
10728
11012
|
}
|
|
10729
11013
|
const { raw, hashHex } = generateHostBearer(input.rng);
|
|
10730
|
-
const appended = appendHostHash(yamlText, hashHex, hostId);
|
|
11014
|
+
const appended = appendHostHash(yamlText, hashHex, hostId, granted.vaults);
|
|
10731
11015
|
if ("error" in appended) return preflight(appended.error);
|
|
10732
11016
|
try {
|
|
10733
11017
|
writeFile10(mapPath, appended.yaml, { encoding: "utf8", mode: 416 });
|
|
@@ -10737,7 +11021,8 @@ async function runMcpAuthIssueHost(input) {
|
|
|
10737
11021
|
}
|
|
10738
11022
|
throw error;
|
|
10739
11023
|
}
|
|
10740
|
-
|
|
11024
|
+
const grantNote = granted.vaults.length > 0 ? ` allowed_vaults=${granted.vaults.join(",")}` : "";
|
|
11025
|
+
stderrWrite(`issued host_id=${hostId}${grantNote} copy once: ${raw}
|
|
10741
11026
|
`);
|
|
10742
11027
|
return {
|
|
10743
11028
|
exitCode: ExitCode.OK,
|
|
@@ -10745,7 +11030,8 @@ async function runMcpAuthIssueHost(input) {
|
|
|
10745
11030
|
host_id: hostId,
|
|
10746
11031
|
hash_prefix: hashHex.slice(0, 8),
|
|
10747
11032
|
map_path: mapPath,
|
|
10748
|
-
wrote: true
|
|
11033
|
+
wrote: true,
|
|
11034
|
+
allowed_vaults: granted.vaults
|
|
10749
11035
|
})
|
|
10750
11036
|
};
|
|
10751
11037
|
}
|
|
@@ -10888,7 +11174,7 @@ async function emitManagedVaultWrite(vault, command, mutate, opts) {
|
|
|
10888
11174
|
if (dirty) {
|
|
10889
11175
|
return emit(dirty, void 0, { postCommit: false });
|
|
10890
11176
|
}
|
|
10891
|
-
const { runManagedWriteTransaction: runManagedWriteTransaction2 } = await import("./managed-write-preflight-
|
|
11177
|
+
const { runManagedWriteTransaction: runManagedWriteTransaction2 } = await import("./managed-write-preflight-NS7OQ2Y7.js");
|
|
10892
11178
|
const run = await runManagedWriteTransaction2({
|
|
10893
11179
|
vault,
|
|
10894
11180
|
command,
|
|
@@ -10918,7 +11204,7 @@ program.command("validate <file>").description("validate vault page frontmatter
|
|
|
10918
11204
|
emit(await runValidate({ file, apply: !!opts.apply, vault }), vault);
|
|
10919
11205
|
});
|
|
10920
11206
|
program.command("graph").description("graph subcommands").command("build <vault>").option("--out <path>", "graph output path (default: <vault>/.skillwiki/graph.json)").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
10921
|
-
const out = opts.out ??
|
|
11207
|
+
const out = opts.out ?? join45(vault, ".skillwiki", "graph.json");
|
|
10922
11208
|
return emitGuardedVaultWrite(vault, "graph build", () => runGraphBuild({ vault, out }));
|
|
10923
11209
|
});
|
|
10924
11210
|
var canvasCmd = program.command("canvas").description("manage Obsidian canvas files");
|
|
@@ -11529,7 +11815,18 @@ program.command("doctor").description("diagnose skillwiki setup issues").option(
|
|
|
11529
11815
|
currentVersion: pkg.version,
|
|
11530
11816
|
cwd: process.cwd(),
|
|
11531
11817
|
checkSnapshotter: !!opts.checkSnapshotter,
|
|
11532
|
-
checkMcp: !!opts.checkMcp
|
|
11818
|
+
checkMcp: !!opts.checkMcp,
|
|
11819
|
+
env: process.env
|
|
11820
|
+
})));
|
|
11821
|
+
program.command("connect").description(CONNECT_DESCRIPTION).option("--from-file <path>", "chat-attached env file (not Drive / \u4E91\u76D8)").option("--from-stdin", "read env file from stdin (no echo)").option("--dry-run", "validate without writing ~/.skillwiki/.env").option("--force", "overwrite a different existing token").action(async (opts) => emit(await runConnect({
|
|
11822
|
+
home: process.env.HOME ?? "",
|
|
11823
|
+
fromFile: opts.fromFile,
|
|
11824
|
+
fromStdin: !!opts.fromStdin,
|
|
11825
|
+
dryRun: !!opts.dryRun,
|
|
11826
|
+
force: !!opts.force,
|
|
11827
|
+
checkMcp: !opts.dryRun,
|
|
11828
|
+
currentVersion: pkg.version,
|
|
11829
|
+
env: process.env
|
|
11533
11830
|
})));
|
|
11534
11831
|
program.command("status [vault]").description("output vault diagnostics").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
11535
11832
|
const v = await resolveVaultArg(vault, opts.wiki);
|
|
@@ -12123,12 +12420,14 @@ program.command("mcp").description("start stdio Model Context Protocol server (r
|
|
|
12123
12420
|
await runSkillwikiMcpStdio();
|
|
12124
12421
|
});
|
|
12125
12422
|
var mcpAuthCmd = program.command("mcp-auth").description("attended HTTP MCP host-id bearer issuance (metal)");
|
|
12126
|
-
mcpAuthCmd.command("issue-host").description("append a host-id hash to the metal map; print raw once on a TTY").requiredOption("--host-id <id>", "host identity to bind").option("--map <path>", "hash map file (defaults to SKILLWIKI_MCP_TOKEN_MAP)").option("--write", "append the hash (default is dry-run)", false).action(async (opts) => {
|
|
12423
|
+
mcpAuthCmd.command("issue-host").description("append a host-id hash to the metal map; print raw once on a TTY").requiredOption("--host-id <id>", "host identity to bind").option("--map <path>", "hash map file (defaults to SKILLWIKI_MCP_TOKEN_MAP)").option("--allowed-vaults <ids>", "comma-separated exact vault ids; omit for default vault only").option("--write", "append the hash (default is dry-run)", false).action(async (opts) => {
|
|
12127
12424
|
const mapPath = opts.map || process.env.SKILLWIKI_MCP_TOKEN_MAP || "";
|
|
12425
|
+
const allowedVaults = String(opts.allowedVaults ?? "").split(/[,\s]+/).map((id) => id.trim()).filter(Boolean);
|
|
12128
12426
|
emit(await runMcpAuthIssueHost({
|
|
12129
12427
|
hostId: String(opts.hostId),
|
|
12130
12428
|
mapPath,
|
|
12131
|
-
write: !!opts.write
|
|
12429
|
+
write: !!opts.write,
|
|
12430
|
+
allowedVaults
|
|
12132
12431
|
}));
|
|
12133
12432
|
});
|
|
12134
12433
|
for (const w of getDeprecatedWarnings(process.env.HOME ?? "")) {
|