skillwiki 0.8.5-beta.4 → 0.8.5-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +595 -129
- package/package.json +1 -1
- package/skills/.claude-plugin/plugin.json +1 -1
- package/skills/.codex-plugin/plugin.json +1 -1
- package/skills/agents/wiki-lint.md +17 -13
- package/skills/package.json +1 -1
- package/skills/skills/using-skillwiki/SKILL.md +4 -4
- package/skills/skills/wiki-lint/SKILL.md +8 -6
- package/skills/using-skillwiki/SKILL.md +4 -4
- package/skills/wiki-lint/SKILL.md +8 -6
package/dist/cli.js
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from "./chunk-TPS5XD2J.js";
|
|
15
15
|
|
|
16
16
|
// src/cli.ts
|
|
17
|
-
import { join as
|
|
17
|
+
import { join as join45 } from "path";
|
|
18
18
|
import { Command as Command2 } from "commander";
|
|
19
19
|
|
|
20
20
|
// ../shared/src/exit-codes.ts
|
|
@@ -3047,8 +3047,9 @@ function buildCliSurface() {
|
|
|
3047
3047
|
program2.command("pagesize").option("--lines <n>").option("--wiki <name>");
|
|
3048
3048
|
program2.command("log-rotate").option("--threshold <n>").option("--apply").option("--wiki <name>");
|
|
3049
3049
|
program2.command("log-append").requiredOption("--content <text>").option("--wiki <name>");
|
|
3050
|
-
program2.command("lint").option("--days <n>").option("--lines <n>").option("--log-threshold <n>").option("--fix").option("--only <bucket>").option("--wiki <name>");
|
|
3050
|
+
program2.command("lint").option("--days <n>").option("--lines <n>").option("--log-threshold <n>").option("--fix").option("--only <bucket>").option("--summary").option("--examples <n>").option("--wiki <name>");
|
|
3051
3051
|
program2.command("config");
|
|
3052
|
+
program2.command("health").option("--wiki <name>").option("--sync <mode>").option("--no-fail").option("--out <path>").option("--examples <n>");
|
|
3052
3053
|
program2.command("doctor");
|
|
3053
3054
|
program2.command("status").option("--wiki <name>");
|
|
3054
3055
|
program2.command("archive").option("--wiki <name>").option("--cascade").option("--apply");
|
|
@@ -3271,6 +3272,58 @@ var ERROR_ORDER = ["broken_wikilinks", "invalid_frontmatter", "raw_source_identi
|
|
|
3271
3272
|
var WARNING_ORDER = ["raw_body_duplicate", "raw_subdirectory_duplicate", "file_source_url", "index_incomplete", "index_link_format", "stale_page", "page_too_large", "log_rotate_needed", "orphans", "compound_refs", "legacy_citation_style", "orphaned_citations", "duplicate_frontmatter", "work_item_health", "orphaned_project_pages", "missing_overview", "missing_diagram"];
|
|
3272
3273
|
var INFO_ORDER = ["bridges", "sparse_community", "page_structure", "topic_map_recommended", "frontmatter_wikilink", "wikilink_citation", "missing_tldr", "stale_sections", "cli_refs"];
|
|
3273
3274
|
var KNOWN_BUCKETS = [...ERROR_ORDER, ...WARNING_ORDER, ...INFO_ORDER];
|
|
3275
|
+
function shellQuote(value) {
|
|
3276
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
3277
|
+
}
|
|
3278
|
+
function formatExample(item) {
|
|
3279
|
+
if (typeof item === "string") return item;
|
|
3280
|
+
if (item === null || item === void 0) return String(item);
|
|
3281
|
+
try {
|
|
3282
|
+
return JSON.stringify(item);
|
|
3283
|
+
} catch {
|
|
3284
|
+
return String(item);
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3287
|
+
function summarizeBucket(bucket, severity, vaultPath, examplesLimit) {
|
|
3288
|
+
const safeLimit = Math.max(0, Math.min(examplesLimit, 10));
|
|
3289
|
+
const examples = bucket.items.slice(0, safeLimit).map(formatExample);
|
|
3290
|
+
return {
|
|
3291
|
+
kind: bucket.kind,
|
|
3292
|
+
severity,
|
|
3293
|
+
count: bucket.items.length,
|
|
3294
|
+
examples,
|
|
3295
|
+
examples_limit: safeLimit,
|
|
3296
|
+
sample_truncated: bucket.items.length > examples.length,
|
|
3297
|
+
details_command: `skillwiki lint ${shellQuote(vaultPath)} --only ${bucket.kind}`
|
|
3298
|
+
};
|
|
3299
|
+
}
|
|
3300
|
+
function summarizeLintOutput(output, examplesLimit = 3) {
|
|
3301
|
+
const buckets = [
|
|
3302
|
+
...output.by_severity.error.map((bucket) => summarizeBucket(bucket, "error", output.vault.path, examplesLimit)),
|
|
3303
|
+
...output.by_severity.warning.map((bucket) => summarizeBucket(bucket, "warning", output.vault.path, examplesLimit)),
|
|
3304
|
+
...output.by_severity.info.map((bucket) => summarizeBucket(bucket, "info", output.vault.path, examplesLimit))
|
|
3305
|
+
];
|
|
3306
|
+
const lines = [];
|
|
3307
|
+
lines.push(`errors: ${output.summary.errors}`);
|
|
3308
|
+
lines.push(`warnings: ${output.summary.warnings}`);
|
|
3309
|
+
lines.push(`info: ${output.summary.info}`);
|
|
3310
|
+
for (const bucket of buckets) {
|
|
3311
|
+
lines.push(` ${bucket.kind}: ${bucket.count}`);
|
|
3312
|
+
if (bucket.examples.length > 0) {
|
|
3313
|
+
lines.push(` e.g. ${bucket.examples[0]}`);
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3316
|
+
return {
|
|
3317
|
+
vault: output.vault,
|
|
3318
|
+
summary: output.summary,
|
|
3319
|
+
buckets,
|
|
3320
|
+
details_included: false,
|
|
3321
|
+
truncated: false,
|
|
3322
|
+
fixed: output.fixed,
|
|
3323
|
+
unresolved: output.unresolved,
|
|
3324
|
+
humanHint: lines.join("\n")
|
|
3325
|
+
};
|
|
3326
|
+
}
|
|
3274
3327
|
async function runLint(input) {
|
|
3275
3328
|
if (input.only && !KNOWN_BUCKETS.includes(input.only)) {
|
|
3276
3329
|
return {
|
|
@@ -3903,17 +3956,18 @@ ${newBody}`;
|
|
|
3903
3956
|
let fExit = ExitCode.OK;
|
|
3904
3957
|
if (fSummary.errors > 0) fExit = ExitCode.LINT_HAS_ERRORS;
|
|
3905
3958
|
else if (fSummary.warnings > 0 || fSummary.info > 0) fExit = ExitCode.LINT_HAS_WARNINGS;
|
|
3959
|
+
const output2 = {
|
|
3960
|
+
vault: { path: input.vault, source: input.source ?? "resolved" },
|
|
3961
|
+
summary: fSummary,
|
|
3962
|
+
by_severity: filtered,
|
|
3963
|
+
fixed,
|
|
3964
|
+
unresolved,
|
|
3965
|
+
humanHint: `--only ${input.only}
|
|
3966
|
+
${match.length === 0 ? "0 violations" : match.map((b) => ` ${b.kind}: ${b.items.length}`).join("\n")}`
|
|
3967
|
+
};
|
|
3906
3968
|
return {
|
|
3907
3969
|
exitCode: fExit,
|
|
3908
|
-
result: ok(
|
|
3909
|
-
vault: { path: input.vault, source: input.source ?? "resolved" },
|
|
3910
|
-
summary: fSummary,
|
|
3911
|
-
by_severity: filtered,
|
|
3912
|
-
fixed,
|
|
3913
|
-
unresolved,
|
|
3914
|
-
humanHint: `--only ${input.only}
|
|
3915
|
-
${match.length === 0 ? "0 violations" : match.map((b) => ` ${b.kind}: ${b.items.length}`).join("\n")}`
|
|
3916
|
-
})
|
|
3970
|
+
result: ok(input.summary ? summarizeLintOutput(output2, input.examplesLimit) : output2)
|
|
3917
3971
|
};
|
|
3918
3972
|
}
|
|
3919
3973
|
const summary = {
|
|
@@ -3941,19 +3995,31 @@ ${match.length === 0 ? "0 violations" : match.map((b) => ` ${b.kind}: ${b.items
|
|
|
3941
3995
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3942
3996
|
});
|
|
3943
3997
|
}
|
|
3998
|
+
const output = {
|
|
3999
|
+
vault: { path: input.vault, source: input.source ?? "resolved" },
|
|
4000
|
+
summary,
|
|
4001
|
+
by_severity: { error: errorOut, warning: warningOut, info: infoOut },
|
|
4002
|
+
fixed,
|
|
4003
|
+
unresolved,
|
|
4004
|
+
humanHint: hintLines.join("\n")
|
|
4005
|
+
};
|
|
3944
4006
|
return {
|
|
3945
4007
|
exitCode,
|
|
3946
|
-
result: ok(
|
|
3947
|
-
vault: { path: input.vault, source: input.source ?? "resolved" },
|
|
3948
|
-
summary,
|
|
3949
|
-
by_severity: { error: errorOut, warning: warningOut, info: infoOut },
|
|
3950
|
-
fixed,
|
|
3951
|
-
unresolved,
|
|
3952
|
-
humanHint: hintLines.join("\n")
|
|
3953
|
-
})
|
|
4008
|
+
result: ok(input.summary ? summarizeLintOutput(output, input.examplesLimit) : output)
|
|
3954
4009
|
};
|
|
3955
4010
|
}
|
|
3956
4011
|
|
|
4012
|
+
// src/commands/health.ts
|
|
4013
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync4, readFileSync as readFileSync8, renameSync, writeFileSync as writeFileSync6 } from "fs";
|
|
4014
|
+
import { dirname as dirname10, join as join28, resolve as resolve6 } from "path";
|
|
4015
|
+
import { platform as platform3 } from "os";
|
|
4016
|
+
|
|
4017
|
+
// src/commands/doctor.ts
|
|
4018
|
+
import { existsSync as existsSync10, lstatSync, readlinkSync, readdirSync as readdirSync2, statSync as statSync3, readFileSync as readFileSync7 } from "fs";
|
|
4019
|
+
import { join as join27, resolve as resolve5 } from "path";
|
|
4020
|
+
import { execSync as execSync2 } from "child_process";
|
|
4021
|
+
import { platform as platform2 } from "os";
|
|
4022
|
+
|
|
3957
4023
|
// src/commands/config.ts
|
|
3958
4024
|
import { readFile as readFile18 } from "fs/promises";
|
|
3959
4025
|
import { existsSync as existsSync6 } from "fs";
|
|
@@ -4015,12 +4081,6 @@ async function runConfigPath(input) {
|
|
|
4015
4081
|
return { exitCode: ExitCode.OK, result: ok({ path: filePath, exists: existsSync6(filePath), humanHint: filePath }) };
|
|
4016
4082
|
}
|
|
4017
4083
|
|
|
4018
|
-
// src/commands/doctor.ts
|
|
4019
|
-
import { existsSync as existsSync10, lstatSync, readlinkSync, readdirSync as readdirSync2, statSync as statSync3, readFileSync as readFileSync7 } from "fs";
|
|
4020
|
-
import { join as join27, resolve as resolve5 } from "path";
|
|
4021
|
-
import { execSync as execSync2 } from "child_process";
|
|
4022
|
-
import { platform as platform2 } from "os";
|
|
4023
|
-
|
|
4024
4084
|
// src/utils/auto-update.ts
|
|
4025
4085
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
|
|
4026
4086
|
import { join as join24, dirname as dirname9 } from "path";
|
|
@@ -5623,9 +5683,383 @@ async function runDoctor(input) {
|
|
|
5623
5683
|
return { exitCode, result: ok({ checks, summary, humanHint }) };
|
|
5624
5684
|
}
|
|
5625
5685
|
|
|
5686
|
+
// src/commands/health.ts
|
|
5687
|
+
function statusFromCounts(counts) {
|
|
5688
|
+
if ((counts.error ?? 0) > 0) return "error";
|
|
5689
|
+
if ((counts.warn ?? counts.warnings ?? 0) > 0) return "warn";
|
|
5690
|
+
if ((counts.info ?? 0) > 0) return "info";
|
|
5691
|
+
return "pass";
|
|
5692
|
+
}
|
|
5693
|
+
function maxStatus(statuses) {
|
|
5694
|
+
const order = { pass: 0, info: 1, warn: 2, error: 3, unknown: 4 };
|
|
5695
|
+
return statuses.reduce((max, status) => order[status] > order[max] ? status : max, "pass");
|
|
5696
|
+
}
|
|
5697
|
+
function bucketCount(buckets, kind) {
|
|
5698
|
+
return buckets.find((bucket) => bucket.kind === kind)?.count ?? 0;
|
|
5699
|
+
}
|
|
5700
|
+
function bucketEvidence(prefix, buckets, kinds) {
|
|
5701
|
+
return kinds.map((kind) => ({ kind, count: bucketCount(buckets, kind) })).filter((item) => item.count > 0).map((item) => `${prefix}.${item.kind}: ${item.count}`);
|
|
5702
|
+
}
|
|
5703
|
+
function shellQuote2(value) {
|
|
5704
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
5705
|
+
}
|
|
5706
|
+
function lintCommand(vaultPath, args) {
|
|
5707
|
+
return `skillwiki lint ${shellQuote2(vaultPath)} ${args}`;
|
|
5708
|
+
}
|
|
5709
|
+
function deriveRiskFlags(lint, vaultSync, syncMode) {
|
|
5710
|
+
const flags = [];
|
|
5711
|
+
const integrityEvidence = bucketEvidence("lint", lint.buckets, [
|
|
5712
|
+
"broken_sources",
|
|
5713
|
+
"invalid_frontmatter",
|
|
5714
|
+
"broken_wikilinks",
|
|
5715
|
+
"raw_source_identity_conflict"
|
|
5716
|
+
]);
|
|
5717
|
+
if (integrityEvidence.length > 0) {
|
|
5718
|
+
flags.push({
|
|
5719
|
+
id: "content_integrity_risk",
|
|
5720
|
+
status: "error",
|
|
5721
|
+
blocking: true,
|
|
5722
|
+
evidence: integrityEvidence,
|
|
5723
|
+
suggested_commands: [
|
|
5724
|
+
lintCommand(lint.vault.path, "--summary"),
|
|
5725
|
+
lintCommand(lint.vault.path, "--only broken_sources")
|
|
5726
|
+
]
|
|
5727
|
+
});
|
|
5728
|
+
}
|
|
5729
|
+
const retrievalEvidence = bucketEvidence("lint", lint.buckets, [
|
|
5730
|
+
"tag_not_in_taxonomy",
|
|
5731
|
+
"orphans",
|
|
5732
|
+
"orphaned_project_pages",
|
|
5733
|
+
"missing_overview"
|
|
5734
|
+
]);
|
|
5735
|
+
if (retrievalEvidence.length > 0) {
|
|
5736
|
+
const hasError = bucketCount(lint.buckets, "tag_not_in_taxonomy") > 0;
|
|
5737
|
+
flags.push({
|
|
5738
|
+
id: "retrieval_quality_risk",
|
|
5739
|
+
status: hasError ? "error" : "warn",
|
|
5740
|
+
blocking: hasError,
|
|
5741
|
+
evidence: retrievalEvidence,
|
|
5742
|
+
suggested_commands: [lintCommand(lint.vault.path, "--summary")]
|
|
5743
|
+
});
|
|
5744
|
+
}
|
|
5745
|
+
const duplicationEvidence = bucketEvidence("lint", lint.buckets, [
|
|
5746
|
+
"raw_dedup",
|
|
5747
|
+
"raw_body_duplicate",
|
|
5748
|
+
"raw_subdirectory_duplicate"
|
|
5749
|
+
]);
|
|
5750
|
+
if (duplicationEvidence.length > 0) {
|
|
5751
|
+
flags.push({
|
|
5752
|
+
id: "content_duplication_risk",
|
|
5753
|
+
status: "warn",
|
|
5754
|
+
blocking: false,
|
|
5755
|
+
evidence: duplicationEvidence,
|
|
5756
|
+
suggested_commands: [lintCommand(lint.vault.path, "--only raw_dedup")]
|
|
5757
|
+
});
|
|
5758
|
+
}
|
|
5759
|
+
const syncEvidence = vaultSync.checks.filter((check2) => check2.status === "error" || check2.status === "warn").map((check2) => `vault_sync.${check2.id}: ${check2.status}`);
|
|
5760
|
+
if (syncEvidence.length > 0) {
|
|
5761
|
+
flags.push({
|
|
5762
|
+
id: "sync_visibility_risk",
|
|
5763
|
+
status: vaultSync.status === "error" ? "error" : "warn",
|
|
5764
|
+
blocking: syncMode === "required",
|
|
5765
|
+
evidence: syncEvidence,
|
|
5766
|
+
suggested_commands: ["vault-sync-install"]
|
|
5767
|
+
});
|
|
5768
|
+
}
|
|
5769
|
+
const backlogEvidence = bucketEvidence("lint", lint.buckets, [
|
|
5770
|
+
"stale_page",
|
|
5771
|
+
"page_too_large",
|
|
5772
|
+
"work_item_health",
|
|
5773
|
+
"log_rotate_needed"
|
|
5774
|
+
]);
|
|
5775
|
+
if (backlogEvidence.length > 0) {
|
|
5776
|
+
flags.push({
|
|
5777
|
+
id: "maintenance_backlog",
|
|
5778
|
+
status: "warn",
|
|
5779
|
+
blocking: false,
|
|
5780
|
+
evidence: backlogEvidence,
|
|
5781
|
+
suggested_commands: [lintCommand(lint.vault.path, "--summary")]
|
|
5782
|
+
});
|
|
5783
|
+
}
|
|
5784
|
+
return flags;
|
|
5785
|
+
}
|
|
5786
|
+
function summarizeChecks(checks) {
|
|
5787
|
+
return {
|
|
5788
|
+
pass: checks.filter((check2) => check2.status === "pass").length,
|
|
5789
|
+
info: checks.filter((check2) => check2.status === "info").length,
|
|
5790
|
+
warn: checks.filter((check2) => check2.status === "warn").length,
|
|
5791
|
+
error: checks.filter((check2) => check2.status === "error").length,
|
|
5792
|
+
skipped: checks.filter((check2) => /skipped/i.test(check2.detail)).length
|
|
5793
|
+
};
|
|
5794
|
+
}
|
|
5795
|
+
function classifyLog(path, id, label, okPattern) {
|
|
5796
|
+
if (!existsSync11(path)) return { id, label, status: "warn", detail: `log file missing: ${path}` };
|
|
5797
|
+
const lines = readFileSync8(path, "utf8").split(/\r?\n/).filter(Boolean);
|
|
5798
|
+
const last = lines[lines.length - 1] ?? "";
|
|
5799
|
+
if (!last) return { id, label, status: "warn", detail: `log file empty: ${path}` };
|
|
5800
|
+
if (/fail|error/i.test(last)) return { id, label, status: "error", detail: last.slice(0, 120) };
|
|
5801
|
+
if (okPattern.test(last)) return { id, label, status: "pass", detail: last.slice(0, 120) };
|
|
5802
|
+
return { id, label, status: "warn", detail: last.slice(0, 120) };
|
|
5803
|
+
}
|
|
5804
|
+
function runVaultSyncHealth(home, syncMode) {
|
|
5805
|
+
if (syncMode === "off") {
|
|
5806
|
+
return {
|
|
5807
|
+
status: "pass",
|
|
5808
|
+
blocking: false,
|
|
5809
|
+
installed: false,
|
|
5810
|
+
summary: { pass: 0, info: 0, warn: 0, error: 0, skipped: 1 },
|
|
5811
|
+
checks: [{ id: "vault_sync_skipped", status: "pass", detail: "vault-sync checks skipped by --sync off" }]
|
|
5812
|
+
};
|
|
5813
|
+
}
|
|
5814
|
+
const isMac = platform3() === "darwin";
|
|
5815
|
+
const shareDir = isMac ? join28(home, "Library", "Application Support", "vault-sync", "bin") : join28(home, ".local", "share", "vault-sync", "bin");
|
|
5816
|
+
const logDir = isMac ? join28(home, "Library", "Logs") : join28(home, ".local", "state", "vault-sync", "log");
|
|
5817
|
+
const filterPath = join28(home, ".config", "rclone", "wiki-push-filters.txt");
|
|
5818
|
+
const checks = [];
|
|
5819
|
+
const pushScript = join28(shareDir, "wiki-push.sh");
|
|
5820
|
+
checks.push(existsSync11(pushScript) ? { id: "vault_sync_installed", label: "Vault sync installed", status: "pass", detail: `Found: ${pushScript}` } : { id: "vault_sync_installed", label: "Vault sync installed", status: "error", detail: `Script missing: ${pushScript}` });
|
|
5821
|
+
if (isMac) {
|
|
5822
|
+
const pushPlist = join28(home, "Library", "LaunchAgents", "com.karlchow.wiki-push.plist");
|
|
5823
|
+
const fetchPlist = join28(home, "Library", "LaunchAgents", "com.karlchow.wiki-fetch.plist");
|
|
5824
|
+
checks.push(existsSync11(pushPlist) && existsSync11(fetchPlist) ? { id: "vault_sync_jobs_enabled", label: "Vault sync jobs enabled", status: "pass", detail: "launchd unit files present (read-only mode)" } : { id: "vault_sync_jobs_enabled", label: "Vault sync jobs enabled", status: "warn", detail: "launchd unit files missing (read-only mode)" });
|
|
5825
|
+
checks.push({ id: "vault_sync_fuse_refresh_job", label: "Vault sync fuse refresh job", status: "pass", detail: "macOS host \u2014 check skipped" });
|
|
5826
|
+
} else {
|
|
5827
|
+
const pushTimer = join28(home, ".config", "systemd", "user", "wiki-push.timer");
|
|
5828
|
+
const fetchTimer = join28(home, ".config", "systemd", "user", "wiki-fetch.timer");
|
|
5829
|
+
const fuseTimer = join28(home, ".config", "systemd", "user", "wiki-fuse-refresh.timer");
|
|
5830
|
+
const fuseService = join28(home, ".config", "systemd", "user", "wiki-fuse-refresh.service");
|
|
5831
|
+
checks.push(existsSync11(pushTimer) && existsSync11(fetchTimer) ? { id: "vault_sync_jobs_enabled", label: "Vault sync jobs enabled", status: "pass", detail: "systemd timer unit files present (read-only mode)" } : { id: "vault_sync_jobs_enabled", label: "Vault sync jobs enabled", status: "warn", detail: "systemd timer unit files missing (read-only mode)" });
|
|
5832
|
+
checks.push(existsSync11(fuseTimer) && existsSync11(fuseService) ? { id: "vault_sync_fuse_refresh_job", label: "Vault sync fuse refresh job", status: "pass", detail: "wiki-fuse-refresh unit files present (read-only mode)" } : { id: "vault_sync_fuse_refresh_job", label: "Vault sync fuse refresh job", status: "warn", detail: "wiki-fuse-refresh unit files missing (read-only mode)" });
|
|
5833
|
+
}
|
|
5834
|
+
checks.push(classifyLog(join28(logDir, "wiki-push.log"), "vault_sync_last_push_age", "Vault sync last push recency", /OK push/));
|
|
5835
|
+
checks.push(classifyLog(join28(logDir, "wiki-fetch.log"), "vault_sync_last_fetch_status", "Vault sync last fetch status", /NOTIFY|OK behind|OK/));
|
|
5836
|
+
if (!existsSync11(filterPath)) {
|
|
5837
|
+
checks.push({ id: "vault_sync_filter_present", label: "Vault sync filter file present", status: "error", detail: `Filter missing: ${filterPath}` });
|
|
5838
|
+
} else {
|
|
5839
|
+
const content = readFileSync8(filterPath, "utf8");
|
|
5840
|
+
const missing = ["remotely-save/data.json", ".skillwiki/sync.lock", ".claude/settings.local.json"].filter((item) => !content.includes(item));
|
|
5841
|
+
checks.push(missing.length > 0 ? { id: "vault_sync_filter_present", label: "Vault sync filter file present", status: "warn", detail: `Missing excludes: ${missing.join(", ")}` } : { id: "vault_sync_filter_present", label: "Vault sync filter file present", status: "pass", detail: "Required excludes present" });
|
|
5842
|
+
}
|
|
5843
|
+
checks.push({ id: "vault_sync_snapshot_guard", label: "Snapshot script guard", status: "pass", detail: "Not a snapshotter host \u2014 check skipped" });
|
|
5844
|
+
const summary = summarizeChecks(checks);
|
|
5845
|
+
return {
|
|
5846
|
+
status: statusFromCounts(summary),
|
|
5847
|
+
blocking: syncMode === "required",
|
|
5848
|
+
installed: checks.find((check2) => check2.id === "vault_sync_installed")?.status === "pass",
|
|
5849
|
+
summary,
|
|
5850
|
+
checks: checks.map((check2) => ({ id: check2.id, status: check2.status, detail: check2.detail }))
|
|
5851
|
+
};
|
|
5852
|
+
}
|
|
5853
|
+
function deriveQueryReadiness(lint) {
|
|
5854
|
+
const taxonomyErrors = bucketCount(lint.buckets, "tag_not_in_taxonomy");
|
|
5855
|
+
const brokenLinks = bucketCount(lint.buckets, "broken_wikilinks") + bucketCount(lint.buckets, "broken_sources");
|
|
5856
|
+
const orphanCount = bucketCount(lint.buckets, "orphans") + bucketCount(lint.buckets, "orphaned_project_pages");
|
|
5857
|
+
const missingOverview = bucketCount(lint.buckets, "missing_overview");
|
|
5858
|
+
const missingTldr = bucketCount(lint.buckets, "missing_tldr");
|
|
5859
|
+
let score = 100;
|
|
5860
|
+
score -= Math.min(40, taxonomyErrors * 2);
|
|
5861
|
+
score -= Math.min(30, brokenLinks * 10);
|
|
5862
|
+
score -= Math.min(20, orphanCount * 3);
|
|
5863
|
+
score -= Math.min(15, missingOverview);
|
|
5864
|
+
score -= Math.min(5, missingTldr);
|
|
5865
|
+
score = Math.max(0, score);
|
|
5866
|
+
const status = taxonomyErrors > 0 || brokenLinks > 0 ? "error" : score < 80 ? "warn" : "pass";
|
|
5867
|
+
return {
|
|
5868
|
+
status,
|
|
5869
|
+
blocking: false,
|
|
5870
|
+
summary: { score },
|
|
5871
|
+
signals: [
|
|
5872
|
+
{ id: "taxonomy_errors", status: taxonomyErrors > 0 ? "error" : "pass", value: taxonomyErrors },
|
|
5873
|
+
{ id: "broken_links", status: brokenLinks > 0 ? "error" : "pass", value: brokenLinks },
|
|
5874
|
+
{ id: "orphan_count", status: orphanCount > 0 ? "warn" : "pass", value: orphanCount },
|
|
5875
|
+
{ id: "missing_overview", status: missingOverview > 0 ? "warn" : "pass", value: missingOverview },
|
|
5876
|
+
{ id: "missing_tldr", status: missingTldr > 0 ? "info" : "pass", value: missingTldr }
|
|
5877
|
+
]
|
|
5878
|
+
};
|
|
5879
|
+
}
|
|
5880
|
+
function selfCheckReport(report) {
|
|
5881
|
+
const errors = [];
|
|
5882
|
+
const lint = report.components.lint;
|
|
5883
|
+
const sumBySeverity = (severity) => lint.buckets.filter((bucket) => bucket.severity === severity).reduce((sum, bucket) => sum + bucket.count, 0);
|
|
5884
|
+
if (lint.summary.errors !== sumBySeverity("error")) {
|
|
5885
|
+
errors.push(`lint.summary.errors=${lint.summary.errors} but error bucket counts sum to ${sumBySeverity("error")}`);
|
|
5886
|
+
}
|
|
5887
|
+
if (lint.summary.warnings !== sumBySeverity("warning")) {
|
|
5888
|
+
errors.push(`lint.summary.warnings=${lint.summary.warnings} but warning bucket counts sum to ${sumBySeverity("warning")}`);
|
|
5889
|
+
}
|
|
5890
|
+
if (lint.summary.info !== sumBySeverity("info")) {
|
|
5891
|
+
errors.push(`lint.summary.info=${lint.summary.info} but info bucket counts sum to ${sumBySeverity("info")}`);
|
|
5892
|
+
}
|
|
5893
|
+
for (const component of ["doctor", "lint", "vault_sync", "query_readiness", "source_freshness"]) {
|
|
5894
|
+
if (!report.coverage[component]) errors.push(`missing coverage entry for ${component}`);
|
|
5895
|
+
}
|
|
5896
|
+
for (const flag of report.risk_flags) {
|
|
5897
|
+
if (flag.evidence.length === 0) errors.push(`risk flag ${flag.id} has no evidence`);
|
|
5898
|
+
}
|
|
5899
|
+
return { status: errors.length > 0 ? "error" : "pass", errors };
|
|
5900
|
+
}
|
|
5901
|
+
function buildHumanHint(report) {
|
|
5902
|
+
const lines = [
|
|
5903
|
+
`vault: ${report.vault.path} (via ${report.vault.source})`,
|
|
5904
|
+
`overall: ${report.overall_status}`,
|
|
5905
|
+
`doctor: ${report.components.doctor.summary.pass} pass, ${report.components.doctor.summary.info} info, ${report.components.doctor.summary.warn} warn, ${report.components.doctor.summary.error} error`,
|
|
5906
|
+
`lint: ${report.components.lint.summary.errors} errors, ${report.components.lint.summary.warnings} warnings, ${report.components.lint.summary.info} info`
|
|
5907
|
+
];
|
|
5908
|
+
for (const bucket of report.components.lint.buckets) {
|
|
5909
|
+
lines.push(` ${bucket.severity} ${bucket.kind}: ${bucket.count}`);
|
|
5910
|
+
}
|
|
5911
|
+
lines.push(`vault_sync: ${report.components.vault_sync.summary.pass} pass, ${report.components.vault_sync.summary.warn} warn, ${report.components.vault_sync.summary.error} error`);
|
|
5912
|
+
lines.push(`query_readiness: ${report.components.query_readiness.status} (${report.components.query_readiness.summary.score}/100)`);
|
|
5913
|
+
if (report.risk_flags.length > 0) {
|
|
5914
|
+
lines.push("risk flags:");
|
|
5915
|
+
for (const flag of report.risk_flags) {
|
|
5916
|
+
lines.push(` ${flag.id}: ${flag.status}${flag.blocking ? " (blocking)" : ""}`);
|
|
5917
|
+
}
|
|
5918
|
+
}
|
|
5919
|
+
if (report.self_check.status === "error") {
|
|
5920
|
+
lines.push("self-check: error");
|
|
5921
|
+
for (const error of report.self_check.errors) lines.push(` ${error}`);
|
|
5922
|
+
}
|
|
5923
|
+
return lines.join("\n");
|
|
5924
|
+
}
|
|
5925
|
+
function writeReport(out, report) {
|
|
5926
|
+
mkdirSync4(dirname10(out), { recursive: true });
|
|
5927
|
+
const tmp = `${out}.tmp-${process.pid}-${Date.now()}`;
|
|
5928
|
+
writeFileSync6(tmp, JSON.stringify(ok(report), null, 2) + "\n", "utf8");
|
|
5929
|
+
renameSync(tmp, out);
|
|
5930
|
+
}
|
|
5931
|
+
async function runHealth(input) {
|
|
5932
|
+
const syncMode = input.sync ?? "optional";
|
|
5933
|
+
const doctor = await runDoctor({
|
|
5934
|
+
home: input.home,
|
|
5935
|
+
envValue: input.envValue,
|
|
5936
|
+
argv: input.argv,
|
|
5937
|
+
currentVersion: input.currentVersion,
|
|
5938
|
+
cwd: input.cwd
|
|
5939
|
+
});
|
|
5940
|
+
const lint = await runLint({
|
|
5941
|
+
vault: input.vault,
|
|
5942
|
+
source: input.vaultSource ?? "resolved",
|
|
5943
|
+
days: 90,
|
|
5944
|
+
lines: 200,
|
|
5945
|
+
logThreshold: 500,
|
|
5946
|
+
summary: true,
|
|
5947
|
+
examplesLimit: input.examplesLimit ?? 3
|
|
5948
|
+
});
|
|
5949
|
+
if (!doctor.result.ok) return { exitCode: doctor.exitCode, result: doctor.result };
|
|
5950
|
+
if (!lint.result.ok) return { exitCode: lint.exitCode, result: lint.result };
|
|
5951
|
+
const doctorStatus = statusFromCounts(doctor.result.data.summary);
|
|
5952
|
+
const vaultSync = runVaultSyncHealth(input.home, syncMode);
|
|
5953
|
+
const queryReadiness = deriveQueryReadiness(lint.result.data);
|
|
5954
|
+
const sourceFreshness = {
|
|
5955
|
+
status: bucketCount(lint.result.data.buckets, "stale_page") > 0 || bucketCount(lint.result.data.buckets, "file_source_url") > 0 ? "warn" : "pass",
|
|
5956
|
+
blocking: false,
|
|
5957
|
+
summary: {
|
|
5958
|
+
stale_pages: bucketCount(lint.result.data.buckets, "stale_page"),
|
|
5959
|
+
file_source_url: bucketCount(lint.result.data.buckets, "file_source_url"),
|
|
5960
|
+
stale_sections: bucketCount(lint.result.data.buckets, "stale_sections")
|
|
5961
|
+
}
|
|
5962
|
+
};
|
|
5963
|
+
const doctorComponent = {
|
|
5964
|
+
status: doctorStatus,
|
|
5965
|
+
blocking: false,
|
|
5966
|
+
summary: doctor.result.data.summary,
|
|
5967
|
+
checks: doctor.result.data.checks.map((check2) => ({ id: check2.id, status: check2.status, detail: check2.detail }))
|
|
5968
|
+
};
|
|
5969
|
+
const lintComponent = {
|
|
5970
|
+
status: statusFromCounts({ error: lint.result.data.summary.errors, warnings: lint.result.data.summary.warnings, info: lint.result.data.summary.info }),
|
|
5971
|
+
blocking: true,
|
|
5972
|
+
summary: lint.result.data.summary,
|
|
5973
|
+
buckets: lint.result.data.buckets,
|
|
5974
|
+
details_included: false,
|
|
5975
|
+
truncated: false
|
|
5976
|
+
};
|
|
5977
|
+
const riskFlags = deriveRiskFlags(lint.result.data, vaultSync, syncMode);
|
|
5978
|
+
const blockingStatuses = [
|
|
5979
|
+
lintComponent.blocking ? lintComponent.status : "pass",
|
|
5980
|
+
vaultSync.blocking ? vaultSync.status : "pass",
|
|
5981
|
+
...riskFlags.filter((flag) => flag.blocking).map((flag) => flag.status)
|
|
5982
|
+
];
|
|
5983
|
+
const advisoryStatuses = [
|
|
5984
|
+
doctorComponent.status,
|
|
5985
|
+
vaultSync.status,
|
|
5986
|
+
queryReadiness.status,
|
|
5987
|
+
sourceFreshness.status,
|
|
5988
|
+
...riskFlags.filter((flag) => !flag.blocking).map((flag) => flag.status)
|
|
5989
|
+
];
|
|
5990
|
+
const blockingStatus = maxStatus(blockingStatuses);
|
|
5991
|
+
const advisoryStatus = maxStatus(advisoryStatuses);
|
|
5992
|
+
const baseReport = {
|
|
5993
|
+
schema_version: 1,
|
|
5994
|
+
policy_version: 1,
|
|
5995
|
+
tool: { name: "skillwiki", version: input.currentVersion },
|
|
5996
|
+
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5997
|
+
command_kind: "diagnostic",
|
|
5998
|
+
vault: { path: input.vault, source: input.vaultSource ?? "resolved" },
|
|
5999
|
+
overall_status: maxStatus([blockingStatus, advisoryStatus]),
|
|
6000
|
+
blocking_status: blockingStatus,
|
|
6001
|
+
advisory_status: advisoryStatus,
|
|
6002
|
+
coverage: {
|
|
6003
|
+
doctor: { state: "checked", status: doctorStatus },
|
|
6004
|
+
lint: { state: "checked", status: lintComponent.status },
|
|
6005
|
+
vault_sync: syncMode === "off" ? { state: "skipped", status: "pass", reason: "--sync off" } : { state: "checked", status: vaultSync.status },
|
|
6006
|
+
query_readiness: { state: "checked", status: queryReadiness.status },
|
|
6007
|
+
source_freshness: { state: "checked", status: sourceFreshness.status }
|
|
6008
|
+
},
|
|
6009
|
+
components: {
|
|
6010
|
+
doctor: doctorComponent,
|
|
6011
|
+
lint: lintComponent,
|
|
6012
|
+
vault_sync: vaultSync,
|
|
6013
|
+
query_readiness: queryReadiness,
|
|
6014
|
+
source_freshness: sourceFreshness
|
|
6015
|
+
},
|
|
6016
|
+
risk_flags: riskFlags,
|
|
6017
|
+
details_included: false,
|
|
6018
|
+
truncated: false,
|
|
6019
|
+
mutated: false,
|
|
6020
|
+
post_commit_ran: false,
|
|
6021
|
+
report_complete: true,
|
|
6022
|
+
report_written: false,
|
|
6023
|
+
warnings: []
|
|
6024
|
+
};
|
|
6025
|
+
if (input.out) {
|
|
6026
|
+
baseReport.report_path = input.out;
|
|
6027
|
+
}
|
|
6028
|
+
if (input.out && resolve6(input.out).startsWith(resolve6(input.vault) + "/")) {
|
|
6029
|
+
baseReport.warnings.push({
|
|
6030
|
+
id: "report_inside_vault",
|
|
6031
|
+
message: "health report was written inside the vault; this may create sync churn"
|
|
6032
|
+
});
|
|
6033
|
+
}
|
|
6034
|
+
const self_check = selfCheckReport(baseReport);
|
|
6035
|
+
const reportWithoutHint = {
|
|
6036
|
+
...baseReport,
|
|
6037
|
+
self_check,
|
|
6038
|
+
overall_status: self_check.status === "error" ? "unknown" : baseReport.overall_status,
|
|
6039
|
+
report_complete: self_check.status === "error" ? false : baseReport.report_complete
|
|
6040
|
+
};
|
|
6041
|
+
const report = {
|
|
6042
|
+
...reportWithoutHint,
|
|
6043
|
+
humanHint: buildHumanHint(reportWithoutHint)
|
|
6044
|
+
};
|
|
6045
|
+
if (input.out) {
|
|
6046
|
+
report.report_written = true;
|
|
6047
|
+
writeReport(input.out, report);
|
|
6048
|
+
}
|
|
6049
|
+
let exitCode = ExitCode.OK;
|
|
6050
|
+
if (!input.noFail) {
|
|
6051
|
+
if (report.self_check.status === "error") exitCode = ExitCode.INTERNAL_ERROR;
|
|
6052
|
+
else if (report.blocking_status === "error") exitCode = ExitCode.LINT_HAS_ERRORS;
|
|
6053
|
+
else if (report.blocking_status === "warn" || report.advisory_status === "warn" || report.advisory_status === "error") {
|
|
6054
|
+
exitCode = ExitCode.LINT_HAS_WARNINGS;
|
|
6055
|
+
}
|
|
6056
|
+
}
|
|
6057
|
+
return { exitCode, result: ok(report) };
|
|
6058
|
+
}
|
|
6059
|
+
|
|
5626
6060
|
// src/commands/archive.ts
|
|
5627
6061
|
import { rename as rename7, mkdir as mkdir9, readFile as readFile20, writeFile as writeFile10 } from "fs/promises";
|
|
5628
|
-
import { join as
|
|
6062
|
+
import { join as join29, dirname as dirname11 } from "path";
|
|
5629
6063
|
function countWikilinks(body, slug) {
|
|
5630
6064
|
const escaped = slug.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5631
6065
|
const re = new RegExp(`\\[\\[${escaped}(?:[|#][^\\]]*)?\\]\\]`, "g");
|
|
@@ -5653,7 +6087,7 @@ async function runArchive(input) {
|
|
|
5653
6087
|
if (!relPath) return { exitCode: ExitCode.ARCHIVE_TARGET_NOT_FOUND, result: err("ARCHIVE_TARGET_NOT_FOUND", { page: input.page }) };
|
|
5654
6088
|
if (relPath.startsWith("_archive/")) return { exitCode: ExitCode.ARCHIVE_ALREADY_ARCHIVED, result: err("ARCHIVE_ALREADY_ARCHIVED", { page: relPath }) };
|
|
5655
6089
|
const slug = relPath.replace(/\.md$/, "").split("/").pop();
|
|
5656
|
-
const archivePath =
|
|
6090
|
+
const archivePath = join29("_archive", relPath).replace(/\\/g, "/");
|
|
5657
6091
|
let cascade;
|
|
5658
6092
|
if (input.cascade) {
|
|
5659
6093
|
const wikilinkRefs = [];
|
|
@@ -5677,7 +6111,7 @@ async function runArchive(input) {
|
|
|
5677
6111
|
const indexRefs = [];
|
|
5678
6112
|
if (!isRaw) {
|
|
5679
6113
|
try {
|
|
5680
|
-
const idx = await readFile20(
|
|
6114
|
+
const idx = await readFile20(join29(input.vault, "index.md"), "utf8");
|
|
5681
6115
|
idx.split("\n").forEach((line, i) => {
|
|
5682
6116
|
if (line.includes(`[[${slug}]]`)) indexRefs.push({ line: i + 1, text: line });
|
|
5683
6117
|
});
|
|
@@ -5703,7 +6137,7 @@ async function runArchive(input) {
|
|
|
5703
6137
|
}
|
|
5704
6138
|
if (input.cascade && input.apply && cascade) {
|
|
5705
6139
|
for (const ref of cascade.source_array_refs) {
|
|
5706
|
-
const absPath =
|
|
6140
|
+
const absPath = join29(input.vault, ref.page);
|
|
5707
6141
|
const text = await readFile20(absPath, "utf8");
|
|
5708
6142
|
const split = splitFrontmatter(text);
|
|
5709
6143
|
if (!split.ok) continue;
|
|
@@ -5721,10 +6155,10 @@ ${fmRewritten}
|
|
|
5721
6155
|
}
|
|
5722
6156
|
}
|
|
5723
6157
|
}
|
|
5724
|
-
await mkdir9(
|
|
6158
|
+
await mkdir9(dirname11(join29(input.vault, archivePath)), { recursive: true });
|
|
5725
6159
|
let indexUpdated = false;
|
|
5726
6160
|
if (!isRaw) {
|
|
5727
|
-
const indexPath =
|
|
6161
|
+
const indexPath = join29(input.vault, "index.md");
|
|
5728
6162
|
try {
|
|
5729
6163
|
const idx = await readFile20(indexPath, "utf8");
|
|
5730
6164
|
const originalLines = idx.split("\n");
|
|
@@ -5737,7 +6171,7 @@ ${fmRewritten}
|
|
|
5737
6171
|
if (e instanceof Error && "code" in e && e.code !== "ENOENT") throw e;
|
|
5738
6172
|
}
|
|
5739
6173
|
}
|
|
5740
|
-
await rename7(
|
|
6174
|
+
await rename7(join29(input.vault, relPath), join29(input.vault, archivePath));
|
|
5741
6175
|
appendLastOp(input.vault, {
|
|
5742
6176
|
operation: input.cascade ? "archive-cascade" : "archive",
|
|
5743
6177
|
summary: `moved ${relPath} to ${archivePath}${input.cascade ? ` (cascade: ${cascade?.source_array_refs.length ?? 0} source arrays updated)` : ""}`,
|
|
@@ -6146,10 +6580,10 @@ ${newBody}`;
|
|
|
6146
6580
|
|
|
6147
6581
|
// src/commands/update.ts
|
|
6148
6582
|
import { execSync as execSync3 } from "child_process";
|
|
6149
|
-
import { join as
|
|
6583
|
+
import { join as join30 } from "path";
|
|
6150
6584
|
|
|
6151
6585
|
// src/utils/package-info.ts
|
|
6152
|
-
import { readFileSync as
|
|
6586
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
6153
6587
|
function packageJsonCandidateUrls(baseUrl = import.meta.url) {
|
|
6154
6588
|
return [
|
|
6155
6589
|
new URL("../package.json", baseUrl),
|
|
@@ -6159,7 +6593,7 @@ function packageJsonCandidateUrls(baseUrl = import.meta.url) {
|
|
|
6159
6593
|
function readCliPackageJson(baseUrl = import.meta.url) {
|
|
6160
6594
|
for (const url of packageJsonCandidateUrls(baseUrl)) {
|
|
6161
6595
|
try {
|
|
6162
|
-
const pkg2 = JSON.parse(
|
|
6596
|
+
const pkg2 = JSON.parse(readFileSync9(url, "utf8"));
|
|
6163
6597
|
if (typeof pkg2.version === "string") {
|
|
6164
6598
|
return { ...pkg2, version: pkg2.version };
|
|
6165
6599
|
}
|
|
@@ -6176,7 +6610,7 @@ function resolveGlobalSkillsRoot() {
|
|
|
6176
6610
|
encoding: "utf8",
|
|
6177
6611
|
timeout: 5e3
|
|
6178
6612
|
}).trim();
|
|
6179
|
-
return
|
|
6613
|
+
return join30(globalRoot, "skillwiki", "skills");
|
|
6180
6614
|
} catch {
|
|
6181
6615
|
return null;
|
|
6182
6616
|
}
|
|
@@ -6200,7 +6634,7 @@ async function runUpdate(input) {
|
|
|
6200
6634
|
const pkg2 = readCliPackageJson();
|
|
6201
6635
|
const currentVersion = pkg2.version;
|
|
6202
6636
|
const tag = normalizeDistTag(input.distTag);
|
|
6203
|
-
const target =
|
|
6637
|
+
const target = join30(input.home, ".claude", "skills");
|
|
6204
6638
|
let latest;
|
|
6205
6639
|
try {
|
|
6206
6640
|
latest = execSync3(`npm view skillwiki@${tag} version`, {
|
|
@@ -6271,22 +6705,22 @@ async function runUpdate(input) {
|
|
|
6271
6705
|
|
|
6272
6706
|
// src/commands/self-update.ts
|
|
6273
6707
|
import { execSync as execSync4 } from "child_process";
|
|
6274
|
-
import { existsSync as
|
|
6275
|
-
import { join as
|
|
6708
|
+
import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
|
|
6709
|
+
import { join as join31 } from "path";
|
|
6276
6710
|
var DEFAULT_SOURCE_ROOT_SUFFIX = "/Desktop/code/llm-wiki";
|
|
6277
6711
|
async function runSelfUpdate(input) {
|
|
6278
6712
|
const currentVersion = readCliPackageJson().version;
|
|
6279
6713
|
const sourceRoot = input.sourceRoot ?? `${input.home}${DEFAULT_SOURCE_ROOT_SUFFIX}`;
|
|
6280
6714
|
const distTag = normalizeDistTag(input.distTag);
|
|
6281
|
-
const localPkgPath =
|
|
6282
|
-
const hasLocalSource =
|
|
6715
|
+
const localPkgPath = join31(sourceRoot, "packages", "cli", "package.json");
|
|
6716
|
+
const hasLocalSource = existsSync12(localPkgPath);
|
|
6283
6717
|
if (input.check) {
|
|
6284
6718
|
let availableVersion = null;
|
|
6285
6719
|
let source;
|
|
6286
6720
|
if (hasLocalSource) {
|
|
6287
6721
|
source = "local";
|
|
6288
6722
|
try {
|
|
6289
|
-
availableVersion = JSON.parse(
|
|
6723
|
+
availableVersion = JSON.parse(readFileSync10(localPkgPath, "utf8")).version ?? null;
|
|
6290
6724
|
} catch {
|
|
6291
6725
|
availableVersion = null;
|
|
6292
6726
|
}
|
|
@@ -6344,7 +6778,7 @@ async function runSelfUpdate(input) {
|
|
|
6344
6778
|
}
|
|
6345
6779
|
const newVersion = (() => {
|
|
6346
6780
|
try {
|
|
6347
|
-
return JSON.parse(
|
|
6781
|
+
return JSON.parse(readFileSync10(localPkgPath, "utf8")).version ?? "unknown";
|
|
6348
6782
|
} catch {
|
|
6349
6783
|
return "unknown";
|
|
6350
6784
|
}
|
|
@@ -6411,9 +6845,9 @@ async function runSelfUpdate(input) {
|
|
|
6411
6845
|
|
|
6412
6846
|
// src/commands/transcripts.ts
|
|
6413
6847
|
import { readdir as readdir5, stat as stat7, readFile as readFile21 } from "fs/promises";
|
|
6414
|
-
import { join as
|
|
6848
|
+
import { join as join32 } from "path";
|
|
6415
6849
|
async function runTranscripts(input) {
|
|
6416
|
-
const dir =
|
|
6850
|
+
const dir = join32(input.vault, "raw", "transcripts");
|
|
6417
6851
|
let entries;
|
|
6418
6852
|
try {
|
|
6419
6853
|
entries = await readdir5(dir, { withFileTypes: true });
|
|
@@ -6423,7 +6857,7 @@ async function runTranscripts(input) {
|
|
|
6423
6857
|
const transcripts = [];
|
|
6424
6858
|
for (const entry of entries) {
|
|
6425
6859
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
6426
|
-
const filePath =
|
|
6860
|
+
const filePath = join32(dir, entry.name);
|
|
6427
6861
|
const content = await readFile21(filePath, "utf8");
|
|
6428
6862
|
const fm = extractFrontmatter(content);
|
|
6429
6863
|
if (!fm.ok) continue;
|
|
@@ -6442,11 +6876,11 @@ async function runTranscripts(input) {
|
|
|
6442
6876
|
|
|
6443
6877
|
// src/commands/project-index.ts
|
|
6444
6878
|
import { readdir as readdir6, readFile as readFile22, writeFile as writeFile11, mkdir as mkdir10 } from "fs/promises";
|
|
6445
|
-
import { join as
|
|
6879
|
+
import { join as join33, dirname as dirname12 } from "path";
|
|
6446
6880
|
var LAYER2_DIRS = ["entities", "concepts", "comparisons", "queries", "meta"];
|
|
6447
6881
|
async function runProjectIndex(input) {
|
|
6448
6882
|
const slug = input.slug;
|
|
6449
|
-
const projectDir =
|
|
6883
|
+
const projectDir = join33(input.vault, "projects", slug);
|
|
6450
6884
|
try {
|
|
6451
6885
|
await readdir6(projectDir);
|
|
6452
6886
|
} catch {
|
|
@@ -6457,12 +6891,12 @@ async function runProjectIndex(input) {
|
|
|
6457
6891
|
}
|
|
6458
6892
|
const wikilinkPattern = `[[${slug}]]`;
|
|
6459
6893
|
const entries = [];
|
|
6460
|
-
const compoundDir =
|
|
6894
|
+
const compoundDir = join33(input.vault, "projects", slug, "compound");
|
|
6461
6895
|
try {
|
|
6462
6896
|
const compoundFiles = await readdir6(compoundDir, { withFileTypes: true });
|
|
6463
6897
|
for (const entry of compoundFiles) {
|
|
6464
6898
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
6465
|
-
const filePath =
|
|
6899
|
+
const filePath = join33(compoundDir, entry.name);
|
|
6466
6900
|
let text;
|
|
6467
6901
|
try {
|
|
6468
6902
|
text = await readFile22(filePath, "utf8");
|
|
@@ -6482,13 +6916,13 @@ async function runProjectIndex(input) {
|
|
|
6482
6916
|
for (const dir of LAYER2_DIRS) {
|
|
6483
6917
|
let files;
|
|
6484
6918
|
try {
|
|
6485
|
-
files = await readdir6(
|
|
6919
|
+
files = await readdir6(join33(input.vault, dir), { withFileTypes: true });
|
|
6486
6920
|
} catch {
|
|
6487
6921
|
continue;
|
|
6488
6922
|
}
|
|
6489
6923
|
for (const entry of files) {
|
|
6490
6924
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
6491
|
-
const filePath =
|
|
6925
|
+
const filePath = join33(input.vault, dir, entry.name);
|
|
6492
6926
|
let text;
|
|
6493
6927
|
try {
|
|
6494
6928
|
text = await readFile22(filePath, "utf8");
|
|
@@ -6512,7 +6946,7 @@ async function runProjectIndex(input) {
|
|
|
6512
6946
|
const tb = typeOrder[b.type] ?? 99;
|
|
6513
6947
|
return ta !== tb ? ta - tb : a.title.localeCompare(b.title);
|
|
6514
6948
|
});
|
|
6515
|
-
const indexPath =
|
|
6949
|
+
const indexPath = join33(projectDir, "knowledge.md");
|
|
6516
6950
|
let existing = false;
|
|
6517
6951
|
let stale = false;
|
|
6518
6952
|
try {
|
|
@@ -6556,7 +6990,7 @@ Autogenerated by \`skillwiki project-index\` on ${today}.
|
|
|
6556
6990
|
}
|
|
6557
6991
|
if (input.apply) {
|
|
6558
6992
|
try {
|
|
6559
|
-
await mkdir10(
|
|
6993
|
+
await mkdir10(dirname12(indexPath), { recursive: true });
|
|
6560
6994
|
await writeFile11(indexPath, body, "utf8");
|
|
6561
6995
|
} catch (e) {
|
|
6562
6996
|
return {
|
|
@@ -6586,8 +7020,8 @@ ${entries.map((e) => ` ${e.type}: [[${e.page.replace(/\.md$/, "")}]] \u2014 ${e
|
|
|
6586
7020
|
|
|
6587
7021
|
// src/commands/compound.ts
|
|
6588
7022
|
import { writeFile as writeFile12, mkdir as mkdir11, readdir as readdir7, unlink as unlink4 } from "fs/promises";
|
|
6589
|
-
import { join as
|
|
6590
|
-
import { existsSync as
|
|
7023
|
+
import { join as join34 } from "path";
|
|
7024
|
+
import { existsSync as existsSync13 } from "fs";
|
|
6591
7025
|
import { readFile as readFile23 } from "fs/promises";
|
|
6592
7026
|
var RETRO_HEADING_RE = /^## \[(\d{4}-\d{2}-\d{2})(?:\s+[^\]]+)?\] retro \| loop cycle(?: (\d+))?: (.+)$/;
|
|
6593
7027
|
var FIELD_RE = {
|
|
@@ -6686,7 +7120,7 @@ function extractRetroFields(date, cycleName, block) {
|
|
|
6686
7120
|
};
|
|
6687
7121
|
}
|
|
6688
7122
|
async function runCompound(input) {
|
|
6689
|
-
const logPath =
|
|
7123
|
+
const logPath = join34(input.vault, "log.md");
|
|
6690
7124
|
let logText;
|
|
6691
7125
|
try {
|
|
6692
7126
|
logText = await readFile23(logPath, "utf8");
|
|
@@ -6696,7 +7130,7 @@ async function runCompound(input) {
|
|
|
6696
7130
|
const entries = parseRetroEntries(logText);
|
|
6697
7131
|
const promoted = [];
|
|
6698
7132
|
const skipped = [];
|
|
6699
|
-
const compoundDir =
|
|
7133
|
+
const compoundDir = join34(input.vault, "projects", input.project, "compound");
|
|
6700
7134
|
for (const entry of entries) {
|
|
6701
7135
|
const generalizeValue = entry.generalize.trim();
|
|
6702
7136
|
if (!/^yes/i.test(generalizeValue)) {
|
|
@@ -6704,8 +7138,8 @@ async function runCompound(input) {
|
|
|
6704
7138
|
continue;
|
|
6705
7139
|
}
|
|
6706
7140
|
const slug = slugify(entry.cycleName);
|
|
6707
|
-
const compoundPath =
|
|
6708
|
-
if (
|
|
7141
|
+
const compoundPath = join34(compoundDir, `${slug}.md`);
|
|
7142
|
+
if (existsSync13(compoundPath)) {
|
|
6709
7143
|
skipped.push(entry.date);
|
|
6710
7144
|
continue;
|
|
6711
7145
|
}
|
|
@@ -6743,7 +7177,7 @@ async function runCompound(input) {
|
|
|
6743
7177
|
].join("\n");
|
|
6744
7178
|
const content = frontmatter + "\n" + body;
|
|
6745
7179
|
if (!input.dryRun) {
|
|
6746
|
-
if (!
|
|
7180
|
+
if (!existsSync13(compoundDir)) {
|
|
6747
7181
|
await mkdir11(compoundDir, { recursive: true });
|
|
6748
7182
|
}
|
|
6749
7183
|
await writeFile12(compoundPath, content, "utf8");
|
|
@@ -6765,16 +7199,16 @@ async function runCompound(input) {
|
|
|
6765
7199
|
};
|
|
6766
7200
|
}
|
|
6767
7201
|
async function runCompoundDelete(input) {
|
|
6768
|
-
const projectDir =
|
|
6769
|
-
if (!
|
|
7202
|
+
const projectDir = join34(input.vault, "projects", input.project);
|
|
7203
|
+
if (!existsSync13(projectDir)) {
|
|
6770
7204
|
return {
|
|
6771
7205
|
exitCode: ExitCode.PROJECT_NOT_FOUND,
|
|
6772
7206
|
result: err("PROJECT_NOT_FOUND", { slug: input.project, path: projectDir })
|
|
6773
7207
|
};
|
|
6774
7208
|
}
|
|
6775
7209
|
const entryName = input.entry.replace(/\.md$/, "");
|
|
6776
|
-
const compoundPath =
|
|
6777
|
-
if (!
|
|
7210
|
+
const compoundPath = join34(projectDir, "compound", `${entryName}.md`);
|
|
7211
|
+
if (!existsSync13(compoundPath)) {
|
|
6778
7212
|
return {
|
|
6779
7213
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
6780
7214
|
result: err("FILE_NOT_FOUND", { path: compoundPath })
|
|
@@ -6807,8 +7241,8 @@ knowledge.md regenerated`
|
|
|
6807
7241
|
};
|
|
6808
7242
|
}
|
|
6809
7243
|
async function runCompoundList(input) {
|
|
6810
|
-
const compoundDir =
|
|
6811
|
-
if (!
|
|
7244
|
+
const compoundDir = join34(input.vault, "projects", input.project, "compound");
|
|
7245
|
+
if (!existsSync13(compoundDir)) {
|
|
6812
7246
|
return {
|
|
6813
7247
|
exitCode: ExitCode.OK,
|
|
6814
7248
|
result: ok({
|
|
@@ -6838,7 +7272,7 @@ could not read compound directory`
|
|
|
6838
7272
|
const entries = [];
|
|
6839
7273
|
for (const dirent of dirents) {
|
|
6840
7274
|
if (!dirent.isFile() || !dirent.name.endsWith(".md")) continue;
|
|
6841
|
-
const filePath =
|
|
7275
|
+
const filePath = join34(compoundDir, dirent.name);
|
|
6842
7276
|
let text;
|
|
6843
7277
|
try {
|
|
6844
7278
|
text = await readFile23(filePath, "utf8");
|
|
@@ -6871,8 +7305,8 @@ no compound entries found`;
|
|
|
6871
7305
|
|
|
6872
7306
|
// src/commands/observe.ts
|
|
6873
7307
|
import { mkdir as mkdir12, writeFile as writeFile13 } from "fs/promises";
|
|
6874
|
-
import { existsSync as
|
|
6875
|
-
import { join as
|
|
7308
|
+
import { existsSync as existsSync14, statSync as statSync4 } from "fs";
|
|
7309
|
+
import { join as join35 } from "path";
|
|
6876
7310
|
import { createHash as createHash4 } from "crypto";
|
|
6877
7311
|
var ALLOWED_KINDS = /* @__PURE__ */ new Set(["note", "bug", "task", "idea", "session-log"]);
|
|
6878
7312
|
function slugify2(text) {
|
|
@@ -6895,13 +7329,13 @@ async function runObserve(input) {
|
|
|
6895
7329
|
result: err("SCHEME_REJECTED", { message: "Text must not be empty" })
|
|
6896
7330
|
};
|
|
6897
7331
|
}
|
|
6898
|
-
if (!
|
|
7332
|
+
if (!existsSync14(input.vault) || !statSync4(input.vault).isDirectory()) {
|
|
6899
7333
|
return {
|
|
6900
7334
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
6901
7335
|
result: err("VAULT_PATH_INVALID", { path: input.vault })
|
|
6902
7336
|
};
|
|
6903
7337
|
}
|
|
6904
|
-
const transcriptsDir =
|
|
7338
|
+
const transcriptsDir = join35(input.vault, "raw", "transcripts");
|
|
6905
7339
|
try {
|
|
6906
7340
|
await mkdir12(transcriptsDir, { recursive: true });
|
|
6907
7341
|
} catch {
|
|
@@ -6913,7 +7347,7 @@ async function runObserve(input) {
|
|
|
6913
7347
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
6914
7348
|
const slug = slugify2(input.text);
|
|
6915
7349
|
const fileName = `${today}-observation-${slug}.md`;
|
|
6916
|
-
const filePath =
|
|
7350
|
+
const filePath = join35(transcriptsDir, fileName);
|
|
6917
7351
|
const body = `
|
|
6918
7352
|
${input.text.trim()}
|
|
6919
7353
|
`;
|
|
@@ -6954,7 +7388,7 @@ ${input.text.trim()}
|
|
|
6954
7388
|
|
|
6955
7389
|
// src/commands/ingest.ts
|
|
6956
7390
|
import { readFile as readFile24, writeFile as writeFile14, mkdir as mkdir13 } from "fs/promises";
|
|
6957
|
-
import { join as
|
|
7391
|
+
import { join as join36 } from "path";
|
|
6958
7392
|
import { createHash as createHash5 } from "crypto";
|
|
6959
7393
|
var ALLOWED_TYPES = /* @__PURE__ */ new Set(["entity", "concept", "comparison", "query"]);
|
|
6960
7394
|
var TYPE_DIR = {
|
|
@@ -7128,8 +7562,8 @@ async function runIngest(input) {
|
|
|
7128
7562
|
const rawRelPath = `raw/articles/${slug}.md`;
|
|
7129
7563
|
const typedDir = TYPE_DIR[input.type] ?? `${input.type}s`;
|
|
7130
7564
|
const typedRelPath = `${typedDir}/${slug}.md`;
|
|
7131
|
-
const rawAbsPath =
|
|
7132
|
-
const typedAbsPath =
|
|
7565
|
+
const rawAbsPath = join36(input.vault, rawRelPath);
|
|
7566
|
+
const typedAbsPath = join36(input.vault, typedRelPath);
|
|
7133
7567
|
const identity = assessSourceIdentity({
|
|
7134
7568
|
rawPath: rawRelPath,
|
|
7135
7569
|
sourceUrl: sourceUrl ?? void 0,
|
|
@@ -7211,7 +7645,7 @@ async function runIngest(input) {
|
|
|
7211
7645
|
};
|
|
7212
7646
|
}
|
|
7213
7647
|
try {
|
|
7214
|
-
await mkdir13(
|
|
7648
|
+
await mkdir13(join36(input.vault, "raw", "articles"), { recursive: true });
|
|
7215
7649
|
await writeFile14(rawAbsPath, rawContent, "utf8");
|
|
7216
7650
|
} catch (e) {
|
|
7217
7651
|
return {
|
|
@@ -7220,7 +7654,7 @@ async function runIngest(input) {
|
|
|
7220
7654
|
};
|
|
7221
7655
|
}
|
|
7222
7656
|
try {
|
|
7223
|
-
await mkdir13(
|
|
7657
|
+
await mkdir13(join36(input.vault, typedDir), { recursive: true });
|
|
7224
7658
|
await writeFile14(typedAbsPath, typedContent, "utf8");
|
|
7225
7659
|
} catch (e) {
|
|
7226
7660
|
return {
|
|
@@ -7399,12 +7833,12 @@ ${body}`;
|
|
|
7399
7833
|
}
|
|
7400
7834
|
|
|
7401
7835
|
// src/commands/sync.ts
|
|
7402
|
-
import { existsSync as
|
|
7403
|
-
import { join as
|
|
7836
|
+
import { existsSync as existsSync16 } from "fs";
|
|
7837
|
+
import { join as join38 } from "path";
|
|
7404
7838
|
|
|
7405
7839
|
// src/utils/sync-lock.ts
|
|
7406
|
-
import { existsSync as
|
|
7407
|
-
import { join as
|
|
7840
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync5, readFileSync as readFileSync11, renameSync as renameSync2, unlinkSync as unlinkSync5, writeFileSync as writeFileSync7 } from "fs";
|
|
7841
|
+
import { join as join37 } from "path";
|
|
7408
7842
|
import { createHash as createHash6 } from "crypto";
|
|
7409
7843
|
function getSessionId() {
|
|
7410
7844
|
if (process.env.CLAUDE_SESSION_ID) return process.env.CLAUDE_SESSION_ID;
|
|
@@ -7412,13 +7846,13 @@ function getSessionId() {
|
|
|
7412
7846
|
return process.pid.toString();
|
|
7413
7847
|
}
|
|
7414
7848
|
function lockPath(vault) {
|
|
7415
|
-
return
|
|
7849
|
+
return join37(vault, ".skillwiki", "sync.lock");
|
|
7416
7850
|
}
|
|
7417
7851
|
function readLock(vault) {
|
|
7418
7852
|
const path = lockPath(vault);
|
|
7419
|
-
if (!
|
|
7853
|
+
if (!existsSync15(path)) return null;
|
|
7420
7854
|
try {
|
|
7421
|
-
const raw =
|
|
7855
|
+
const raw = readFileSync11(path, "utf8");
|
|
7422
7856
|
return JSON.parse(raw);
|
|
7423
7857
|
} catch {
|
|
7424
7858
|
return null;
|
|
@@ -7431,9 +7865,9 @@ function isStale(lock, now) {
|
|
|
7431
7865
|
}
|
|
7432
7866
|
function acquireLock(vault, opts = {}) {
|
|
7433
7867
|
const path = lockPath(vault);
|
|
7434
|
-
const dir =
|
|
7435
|
-
if (!
|
|
7436
|
-
|
|
7868
|
+
const dir = join37(vault, ".skillwiki");
|
|
7869
|
+
if (!existsSync15(dir)) {
|
|
7870
|
+
mkdirSync5(dir, { recursive: true });
|
|
7437
7871
|
}
|
|
7438
7872
|
const sessionId = opts.sessionId ?? getSessionId();
|
|
7439
7873
|
const summary = opts.summary ?? "skillwiki sync";
|
|
@@ -7452,7 +7886,7 @@ function acquireLock(vault, opts = {}) {
|
|
|
7452
7886
|
};
|
|
7453
7887
|
try {
|
|
7454
7888
|
const content = JSON.stringify(lock, null, 2) + "\n";
|
|
7455
|
-
|
|
7889
|
+
writeFileSync7(path, content, { flag: "wx" });
|
|
7456
7890
|
return { ok: true, lock };
|
|
7457
7891
|
} catch (e) {
|
|
7458
7892
|
const err3 = e;
|
|
@@ -7472,12 +7906,12 @@ function acquireLock(vault, opts = {}) {
|
|
|
7472
7906
|
function writeLockedFile(path, lock) {
|
|
7473
7907
|
const tmp = path + ".tmp";
|
|
7474
7908
|
const content = JSON.stringify(lock, null, 2) + "\n";
|
|
7475
|
-
|
|
7476
|
-
|
|
7909
|
+
writeFileSync7(tmp, content);
|
|
7910
|
+
renameSync2(tmp, path);
|
|
7477
7911
|
}
|
|
7478
7912
|
function releaseLock(vault, opts = {}) {
|
|
7479
7913
|
const path = lockPath(vault);
|
|
7480
|
-
if (!
|
|
7914
|
+
if (!existsSync15(path)) {
|
|
7481
7915
|
return { released: false };
|
|
7482
7916
|
}
|
|
7483
7917
|
const sessionId = opts.sessionId ?? getSessionId();
|
|
@@ -7506,7 +7940,7 @@ function releaseLock(vault, opts = {}) {
|
|
|
7506
7940
|
function runSyncStatus(input) {
|
|
7507
7941
|
const vault = input.vault;
|
|
7508
7942
|
const includeStashes = input.includeStashes ?? false;
|
|
7509
|
-
if (!
|
|
7943
|
+
if (!existsSync16(join38(vault, ".git"))) {
|
|
7510
7944
|
return {
|
|
7511
7945
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
7512
7946
|
result: ok({
|
|
@@ -7584,7 +8018,7 @@ function runSyncStatus(input) {
|
|
|
7584
8018
|
}
|
|
7585
8019
|
async function runSyncPush(input) {
|
|
7586
8020
|
const vault = input.vault;
|
|
7587
|
-
if (!
|
|
8021
|
+
if (!existsSync16(join38(vault, ".git"))) {
|
|
7588
8022
|
return {
|
|
7589
8023
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
7590
8024
|
result: err("NOT_A_GIT_REPO", { path: vault })
|
|
@@ -7707,7 +8141,7 @@ function enableGitLongPathsOnWindows(vault) {
|
|
|
7707
8141
|
}
|
|
7708
8142
|
async function runSyncPull(input) {
|
|
7709
8143
|
const vault = input.vault;
|
|
7710
|
-
if (!
|
|
8144
|
+
if (!existsSync16(join38(vault, ".git"))) {
|
|
7711
8145
|
return {
|
|
7712
8146
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
7713
8147
|
result: err("NOT_A_GIT_REPO", { path: vault })
|
|
@@ -7879,7 +8313,7 @@ function runSyncPeers(input) {
|
|
|
7879
8313
|
}
|
|
7880
8314
|
function runSyncLock(input) {
|
|
7881
8315
|
const vault = input.vault;
|
|
7882
|
-
if (!
|
|
8316
|
+
if (!existsSync16(vault)) {
|
|
7883
8317
|
return {
|
|
7884
8318
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
7885
8319
|
result: err("VAULT_PATH_INVALID", { path: vault })
|
|
@@ -7914,7 +8348,7 @@ function runSyncLock(input) {
|
|
|
7914
8348
|
}
|
|
7915
8349
|
function runSyncUnlock(input) {
|
|
7916
8350
|
const vault = input.vault;
|
|
7917
|
-
if (!
|
|
8351
|
+
if (!existsSync16(vault)) {
|
|
7918
8352
|
return {
|
|
7919
8353
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
7920
8354
|
result: err("VAULT_PATH_INVALID", { path: vault })
|
|
@@ -7947,8 +8381,8 @@ function runSyncUnlock(input) {
|
|
|
7947
8381
|
}
|
|
7948
8382
|
|
|
7949
8383
|
// src/commands/backup.ts
|
|
7950
|
-
import { statSync as statSync5, readdirSync as readdirSync3, readFileSync as
|
|
7951
|
-
import { join as
|
|
8384
|
+
import { statSync as statSync5, readdirSync as readdirSync3, readFileSync as readFileSync12, mkdirSync as mkdirSync6, writeFileSync as writeFileSync8 } from "fs";
|
|
8385
|
+
import { join as join39, relative as relative3, dirname as dirname13 } from "path";
|
|
7952
8386
|
import { PutObjectCommand, HeadObjectCommand, ListObjectsV2Command, GetObjectCommand, DeleteObjectsCommand } from "@aws-sdk/client-s3";
|
|
7953
8387
|
|
|
7954
8388
|
// src/utils/s3-client.ts
|
|
@@ -7972,7 +8406,7 @@ var SKIP_DIRS2 = /* @__PURE__ */ new Set([".git", ".obsidian", "_archive", "node
|
|
|
7972
8406
|
function* walkMarkdown(dir, base) {
|
|
7973
8407
|
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
7974
8408
|
if (SKIP_DIRS2.has(entry.name)) continue;
|
|
7975
|
-
const full =
|
|
8409
|
+
const full = join39(dir, entry.name);
|
|
7976
8410
|
if (entry.isDirectory()) {
|
|
7977
8411
|
yield* walkMarkdown(full, base);
|
|
7978
8412
|
} else if (entry.name.endsWith(".md")) {
|
|
@@ -7995,7 +8429,7 @@ async function runBackupSync(input) {
|
|
|
7995
8429
|
let failed = 0;
|
|
7996
8430
|
const files = [...walkMarkdown(input.vault, input.vault)];
|
|
7997
8431
|
for (const relPath of files) {
|
|
7998
|
-
const absPath =
|
|
8432
|
+
const absPath = join39(input.vault, relPath);
|
|
7999
8433
|
const localStat = statSync5(absPath);
|
|
8000
8434
|
let needsUpload = true;
|
|
8001
8435
|
try {
|
|
@@ -8014,7 +8448,7 @@ async function runBackupSync(input) {
|
|
|
8014
8448
|
continue;
|
|
8015
8449
|
}
|
|
8016
8450
|
try {
|
|
8017
|
-
const body =
|
|
8451
|
+
const body = readFileSync12(absPath);
|
|
8018
8452
|
await client.send(new PutObjectCommand({ Bucket: input.bucket, Key: relPath, Body: body }));
|
|
8019
8453
|
uploaded++;
|
|
8020
8454
|
} catch {
|
|
@@ -8071,7 +8505,7 @@ async function runBackupRestore(input) {
|
|
|
8071
8505
|
const objects = list.Contents ?? [];
|
|
8072
8506
|
for (const obj of objects) {
|
|
8073
8507
|
if (!obj.Key) continue;
|
|
8074
|
-
const localPath =
|
|
8508
|
+
const localPath = join39(target, obj.Key);
|
|
8075
8509
|
try {
|
|
8076
8510
|
const localStat = statSync5(localPath);
|
|
8077
8511
|
if (obj.LastModified && localStat.mtime > obj.LastModified) {
|
|
@@ -8084,8 +8518,8 @@ async function runBackupRestore(input) {
|
|
|
8084
8518
|
const resp = await client.send(new GetObjectCommand({ Bucket: input.bucket, Key: obj.Key }));
|
|
8085
8519
|
const body = await resp.Body?.transformToByteArray();
|
|
8086
8520
|
if (body) {
|
|
8087
|
-
|
|
8088
|
-
|
|
8521
|
+
mkdirSync6(dirname13(localPath), { recursive: true });
|
|
8522
|
+
writeFileSync8(localPath, Buffer.from(body));
|
|
8089
8523
|
downloaded++;
|
|
8090
8524
|
}
|
|
8091
8525
|
} catch {
|
|
@@ -8117,11 +8551,11 @@ async function runBackupRestore(input) {
|
|
|
8117
8551
|
}
|
|
8118
8552
|
|
|
8119
8553
|
// src/commands/status.ts
|
|
8120
|
-
import { existsSync as
|
|
8554
|
+
import { existsSync as existsSync17, statSync as statSync6 } from "fs";
|
|
8121
8555
|
import { readFile as readFile25 } from "fs/promises";
|
|
8122
|
-
import { join as
|
|
8556
|
+
import { join as join40 } from "path";
|
|
8123
8557
|
async function runStatus(input) {
|
|
8124
|
-
if (!
|
|
8558
|
+
if (!existsSync17(input.vault)) {
|
|
8125
8559
|
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { vault: input.vault }) };
|
|
8126
8560
|
}
|
|
8127
8561
|
const scan = await scanVault(input.vault);
|
|
@@ -8146,7 +8580,7 @@ async function runStatus(input) {
|
|
|
8146
8580
|
const compound = scan.data.compound.length;
|
|
8147
8581
|
let schemaVersion = "v1";
|
|
8148
8582
|
try {
|
|
8149
|
-
const schemaContent = await readFile25(
|
|
8583
|
+
const schemaContent = await readFile25(join40(input.vault, "SCHEMA.md"), "utf8");
|
|
8150
8584
|
const versionMatch = schemaContent.match(/version:\s*["']?([^"'\s\n]+)/i);
|
|
8151
8585
|
if (versionMatch) schemaVersion = versionMatch[1];
|
|
8152
8586
|
} catch {
|
|
@@ -8207,7 +8641,7 @@ async function runStatus(input) {
|
|
|
8207
8641
|
|
|
8208
8642
|
// src/commands/seed.ts
|
|
8209
8643
|
import { mkdir as mkdir14, writeFile as writeFile15, stat as stat8 } from "fs/promises";
|
|
8210
|
-
import { join as
|
|
8644
|
+
import { join as join41 } from "path";
|
|
8211
8645
|
var TODAY = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
8212
8646
|
var EXAMPLE_PAGES = {
|
|
8213
8647
|
"entities/example-project.md": `---
|
|
@@ -8276,29 +8710,29 @@ Real sources are immutable after ingestion \u2014 never edit them.
|
|
|
8276
8710
|
`;
|
|
8277
8711
|
async function runSeed(input) {
|
|
8278
8712
|
try {
|
|
8279
|
-
await stat8(
|
|
8713
|
+
await stat8(join41(input.vault, "SCHEMA.md"));
|
|
8280
8714
|
} catch {
|
|
8281
8715
|
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { root: input.vault, reason: "SCHEMA.md missing \u2014 run `skillwiki init` first" }) };
|
|
8282
8716
|
}
|
|
8283
8717
|
const created = [];
|
|
8284
8718
|
const skipped = [];
|
|
8285
8719
|
for (const [relPath, content] of Object.entries(EXAMPLE_PAGES)) {
|
|
8286
|
-
const absPath =
|
|
8720
|
+
const absPath = join41(input.vault, relPath);
|
|
8287
8721
|
try {
|
|
8288
8722
|
await stat8(absPath);
|
|
8289
8723
|
skipped.push(relPath);
|
|
8290
8724
|
} catch {
|
|
8291
|
-
await mkdir14(
|
|
8725
|
+
await mkdir14(join41(absPath, ".."), { recursive: true });
|
|
8292
8726
|
await writeFile15(absPath, content, "utf8");
|
|
8293
8727
|
created.push(relPath);
|
|
8294
8728
|
}
|
|
8295
8729
|
}
|
|
8296
|
-
const rawPath =
|
|
8730
|
+
const rawPath = join41(input.vault, "raw", "articles", "example-source.md");
|
|
8297
8731
|
try {
|
|
8298
8732
|
await stat8(rawPath);
|
|
8299
8733
|
skipped.push("raw/articles/example-source.md");
|
|
8300
8734
|
} catch {
|
|
8301
|
-
await mkdir14(
|
|
8735
|
+
await mkdir14(join41(rawPath, ".."), { recursive: true });
|
|
8302
8736
|
await writeFile15(rawPath, EXAMPLE_RAW, "utf8");
|
|
8303
8737
|
created.push("raw/articles/example-source.md");
|
|
8304
8738
|
}
|
|
@@ -8322,8 +8756,8 @@ async function runSeed(input) {
|
|
|
8322
8756
|
|
|
8323
8757
|
// src/commands/canvas.ts
|
|
8324
8758
|
import { readFile as readFile26, writeFile as writeFile16 } from "fs/promises";
|
|
8325
|
-
import { existsSync as
|
|
8326
|
-
import { join as
|
|
8759
|
+
import { existsSync as existsSync18 } from "fs";
|
|
8760
|
+
import { join as join42 } from "path";
|
|
8327
8761
|
var NODE_WIDTH = 240;
|
|
8328
8762
|
var NODE_HEIGHT = 60;
|
|
8329
8763
|
var COLUMN_SPACING = 400;
|
|
@@ -8401,8 +8835,8 @@ function buildCanvasEdges(adjacency) {
|
|
|
8401
8835
|
return edges;
|
|
8402
8836
|
}
|
|
8403
8837
|
async function runCanvasGenerate(input) {
|
|
8404
|
-
const graphPath = input.graphPath ??
|
|
8405
|
-
if (!
|
|
8838
|
+
const graphPath = input.graphPath ?? join42(input.vault, ".skillwiki", "graph.json");
|
|
8839
|
+
if (!existsSync18(graphPath)) {
|
|
8406
8840
|
return {
|
|
8407
8841
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
8408
8842
|
result: err("FILE_NOT_FOUND", {
|
|
@@ -8439,7 +8873,7 @@ async function runCanvasGenerate(input) {
|
|
|
8439
8873
|
const nodes = buildCanvasNodes(paths);
|
|
8440
8874
|
const edges = buildCanvasEdges(graph.adjacency);
|
|
8441
8875
|
const canvas = { nodes, edges };
|
|
8442
|
-
const outPath =
|
|
8876
|
+
const outPath = join42(input.vault, "vault-graph.canvas");
|
|
8443
8877
|
try {
|
|
8444
8878
|
await writeFile16(outPath, JSON.stringify(canvas, null, 2));
|
|
8445
8879
|
} catch (e) {
|
|
@@ -8462,7 +8896,7 @@ written: ${outPath}`
|
|
|
8462
8896
|
|
|
8463
8897
|
// src/commands/query.ts
|
|
8464
8898
|
import { readFile as readFile27, stat as stat9 } from "fs/promises";
|
|
8465
|
-
import { join as
|
|
8899
|
+
import { join as join43 } from "path";
|
|
8466
8900
|
var W_KEYWORD = 2;
|
|
8467
8901
|
var W_SOURCE_OVERLAP = 4;
|
|
8468
8902
|
var W_WIKILINK = 3;
|
|
@@ -8583,7 +9017,7 @@ function computeKeywordScore(terms, title, tags, body) {
|
|
|
8583
9017
|
return score;
|
|
8584
9018
|
}
|
|
8585
9019
|
async function loadOrBuildGraph(vault) {
|
|
8586
|
-
const graphPath =
|
|
9020
|
+
const graphPath = join43(vault, ".skillwiki", "graph.json");
|
|
8587
9021
|
let needsBuild = false;
|
|
8588
9022
|
try {
|
|
8589
9023
|
const fileStat = await stat9(graphPath);
|
|
@@ -8605,14 +9039,14 @@ async function loadOrBuildGraph(vault) {
|
|
|
8605
9039
|
}
|
|
8606
9040
|
|
|
8607
9041
|
// src/utils/auto-commit.ts
|
|
8608
|
-
import { existsSync as
|
|
8609
|
-
import { join as
|
|
9042
|
+
import { existsSync as existsSync19 } from "fs";
|
|
9043
|
+
import { join as join44 } from "path";
|
|
8610
9044
|
async function postCommit(vault, exitCode) {
|
|
8611
9045
|
if (exitCode !== 0) return;
|
|
8612
9046
|
const home = process.env.HOME ?? "";
|
|
8613
9047
|
const dotenv = await parseDotenvFile(configPath(home));
|
|
8614
9048
|
if (dotenv["AUTO_COMMIT"] === "false") return;
|
|
8615
|
-
if (!
|
|
9049
|
+
if (!existsSync19(join44(vault, ".git"))) return;
|
|
8616
9050
|
const lastOps = readLastOp(vault);
|
|
8617
9051
|
if (lastOps.length === 0) return;
|
|
8618
9052
|
const porcelain = git(vault, ["status", "--porcelain"]);
|
|
@@ -8645,10 +9079,10 @@ var pkg = readCliPackageJson();
|
|
|
8645
9079
|
var program = new Command2();
|
|
8646
9080
|
program.name("skillwiki").description("Deterministic helpers for CodeWiki skills").version(pkg.version);
|
|
8647
9081
|
program.option("--human", "render terminal-readable output instead of JSON");
|
|
8648
|
-
async function emit(r, vault) {
|
|
9082
|
+
async function emit(r, vault, opts) {
|
|
8649
9083
|
if (program.opts().human) printHuman(r.result);
|
|
8650
9084
|
else printJson(r.result);
|
|
8651
|
-
if (vault) await postCommit(vault, r.exitCode);
|
|
9085
|
+
if (vault && opts?.postCommit !== false) await postCommit(vault, r.exitCode);
|
|
8652
9086
|
process.exit(r.exitCode);
|
|
8653
9087
|
}
|
|
8654
9088
|
program.command("hash <file>").description("compute SHA-256 hash of a vault page body").action(async (file) => emit(await runHash({ file })));
|
|
@@ -8663,7 +9097,7 @@ program.command("validate <file>").description("validate vault page frontmatter
|
|
|
8663
9097
|
emit(await runValidate({ file, apply: !!opts.apply, vault }), vault);
|
|
8664
9098
|
});
|
|
8665
9099
|
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) => {
|
|
8666
|
-
const out = opts.out ??
|
|
9100
|
+
const out = opts.out ?? join45(vault, ".skillwiki", "graph.json");
|
|
8667
9101
|
emit(await runGraphBuild({ vault, out }), vault);
|
|
8668
9102
|
});
|
|
8669
9103
|
var canvasCmd = program.command("canvas").description("manage Obsidian canvas files");
|
|
@@ -8794,9 +9228,20 @@ program.command("log-append [vault]").description("append a single entry to the
|
|
|
8794
9228
|
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
8795
9229
|
else emit(await runLogAppend({ vault: v.vault, content: opts.content }), v.vault);
|
|
8796
9230
|
});
|
|
8797
|
-
program.command("lint [vault]").description("run all vault health checks").option("--days <n>", "stale threshold", (s) => parseInt(s, 10), 90).option("--lines <n>", "pagesize threshold", (s) => parseInt(s, 10), 200).option("--log-threshold <n>", "log rotation threshold", (s) => parseInt(s, 10), 500).option("--fix", "auto-fix supported lint violations").option("--only <bucket>", "run only the specified lint bucket").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
9231
|
+
program.command("lint [vault]").description("run all vault health checks").option("--days <n>", "stale threshold", (s) => parseInt(s, 10), 90).option("--lines <n>", "pagesize threshold", (s) => parseInt(s, 10), 200).option("--log-threshold <n>", "log rotation threshold", (s) => parseInt(s, 10), 500).option("--fix", "auto-fix supported lint violations").option("--only <bucket>", "run only the specified lint bucket").option("--summary", "emit bounded bucket counts instead of full item arrays", false).option("--examples <n>", "example count per bucket in summary mode", (s) => parseInt(s, 10), 3).option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
8798
9232
|
const v = await resolveVaultArg(vault, opts.wiki);
|
|
8799
9233
|
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
9234
|
+
else if (opts.summary) emit(await runLint({
|
|
9235
|
+
vault: v.vault,
|
|
9236
|
+
source: vault ? "flag" : void 0,
|
|
9237
|
+
days: opts.days,
|
|
9238
|
+
lines: opts.lines,
|
|
9239
|
+
logThreshold: opts.logThreshold,
|
|
9240
|
+
fix: opts.fix ?? false,
|
|
9241
|
+
only: opts.only,
|
|
9242
|
+
summary: true,
|
|
9243
|
+
examplesLimit: opts.examples
|
|
9244
|
+
}), v.vault);
|
|
8800
9245
|
else emit(await runLint({
|
|
8801
9246
|
vault: v.vault,
|
|
8802
9247
|
source: vault ? "flag" : void 0,
|
|
@@ -8807,6 +9252,27 @@ program.command("lint [vault]").description("run all vault health checks").optio
|
|
|
8807
9252
|
only: opts.only
|
|
8808
9253
|
}), v.vault);
|
|
8809
9254
|
});
|
|
9255
|
+
program.command("health [vault]").description("bounded whole-system wiki health report").option("--wiki <name>", "wiki profile name").option("--sync <mode>", "vault-sync policy: optional|required|off", "optional").option("--no-fail", "always exit 0 while reporting status in JSON").option("--out <path>", "write JSON result envelope to this path").option("--examples <n>", "example count per lint bucket", (s) => parseInt(s, 10), 3).action(async (vault, opts) => {
|
|
9256
|
+
const sync = String(opts.sync ?? "optional");
|
|
9257
|
+
if (!["optional", "required", "off"].includes(sync)) {
|
|
9258
|
+
emit({ exitCode: ExitCode.USAGE, result: { ok: false, error: "USAGE", detail: "--sync must be optional, required, or off" } });
|
|
9259
|
+
}
|
|
9260
|
+
const v = await resolveVaultArg(vault, opts.wiki);
|
|
9261
|
+
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
9262
|
+
else emit(await runHealth({
|
|
9263
|
+
vault: v.vault,
|
|
9264
|
+
vaultSource: vault ? "flag" : "resolved",
|
|
9265
|
+
home: process.env.HOME ?? "",
|
|
9266
|
+
envValue: process.env.WIKI_PATH,
|
|
9267
|
+
argv: process.argv,
|
|
9268
|
+
currentVersion: pkg.version,
|
|
9269
|
+
cwd: process.cwd(),
|
|
9270
|
+
sync,
|
|
9271
|
+
noFail: opts.fail === false,
|
|
9272
|
+
out: opts.out,
|
|
9273
|
+
examplesLimit: opts.examples
|
|
9274
|
+
}), void 0, { postCommit: false });
|
|
9275
|
+
});
|
|
8810
9276
|
var configCmd = program.command("config").description("manage skillwiki configuration");
|
|
8811
9277
|
configCmd.command("get <key>").description("print the value of a config key").action(async (key) => emit(await runConfigGet({ key, home: process.env.HOME ?? "" })));
|
|
8812
9278
|
configCmd.command("set <key> <value>").description("set a config key value").action(async (key, value) => emit(await runConfigSet({ key, value, home: process.env.HOME ?? "" })));
|