skillwiki 0.8.5-beta.3 → 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 +619 -136
- 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,8 +14,7 @@ import {
|
|
|
14
14
|
} from "./chunk-TPS5XD2J.js";
|
|
15
15
|
|
|
16
16
|
// src/cli.ts
|
|
17
|
-
import {
|
|
18
|
-
import { join as join44 } from "path";
|
|
17
|
+
import { join as join45 } from "path";
|
|
19
18
|
import { Command as Command2 } from "commander";
|
|
20
19
|
|
|
21
20
|
// ../shared/src/exit-codes.ts
|
|
@@ -3048,8 +3047,9 @@ function buildCliSurface() {
|
|
|
3048
3047
|
program2.command("pagesize").option("--lines <n>").option("--wiki <name>");
|
|
3049
3048
|
program2.command("log-rotate").option("--threshold <n>").option("--apply").option("--wiki <name>");
|
|
3050
3049
|
program2.command("log-append").requiredOption("--content <text>").option("--wiki <name>");
|
|
3051
|
-
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>");
|
|
3052
3051
|
program2.command("config");
|
|
3052
|
+
program2.command("health").option("--wiki <name>").option("--sync <mode>").option("--no-fail").option("--out <path>").option("--examples <n>");
|
|
3053
3053
|
program2.command("doctor");
|
|
3054
3054
|
program2.command("status").option("--wiki <name>");
|
|
3055
3055
|
program2.command("archive").option("--wiki <name>").option("--cascade").option("--apply");
|
|
@@ -3272,6 +3272,58 @@ var ERROR_ORDER = ["broken_wikilinks", "invalid_frontmatter", "raw_source_identi
|
|
|
3272
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"];
|
|
3273
3273
|
var INFO_ORDER = ["bridges", "sparse_community", "page_structure", "topic_map_recommended", "frontmatter_wikilink", "wikilink_citation", "missing_tldr", "stale_sections", "cli_refs"];
|
|
3274
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
|
+
}
|
|
3275
3327
|
async function runLint(input) {
|
|
3276
3328
|
if (input.only && !KNOWN_BUCKETS.includes(input.only)) {
|
|
3277
3329
|
return {
|
|
@@ -3904,17 +3956,18 @@ ${newBody}`;
|
|
|
3904
3956
|
let fExit = ExitCode.OK;
|
|
3905
3957
|
if (fSummary.errors > 0) fExit = ExitCode.LINT_HAS_ERRORS;
|
|
3906
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
|
+
};
|
|
3907
3968
|
return {
|
|
3908
3969
|
exitCode: fExit,
|
|
3909
|
-
result: ok(
|
|
3910
|
-
vault: { path: input.vault, source: input.source ?? "resolved" },
|
|
3911
|
-
summary: fSummary,
|
|
3912
|
-
by_severity: filtered,
|
|
3913
|
-
fixed,
|
|
3914
|
-
unresolved,
|
|
3915
|
-
humanHint: `--only ${input.only}
|
|
3916
|
-
${match.length === 0 ? "0 violations" : match.map((b) => ` ${b.kind}: ${b.items.length}`).join("\n")}`
|
|
3917
|
-
})
|
|
3970
|
+
result: ok(input.summary ? summarizeLintOutput(output2, input.examplesLimit) : output2)
|
|
3918
3971
|
};
|
|
3919
3972
|
}
|
|
3920
3973
|
const summary = {
|
|
@@ -3942,19 +3995,31 @@ ${match.length === 0 ? "0 violations" : match.map((b) => ` ${b.kind}: ${b.items
|
|
|
3942
3995
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3943
3996
|
});
|
|
3944
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
|
+
};
|
|
3945
4006
|
return {
|
|
3946
4007
|
exitCode,
|
|
3947
|
-
result: ok(
|
|
3948
|
-
vault: { path: input.vault, source: input.source ?? "resolved" },
|
|
3949
|
-
summary,
|
|
3950
|
-
by_severity: { error: errorOut, warning: warningOut, info: infoOut },
|
|
3951
|
-
fixed,
|
|
3952
|
-
unresolved,
|
|
3953
|
-
humanHint: hintLines.join("\n")
|
|
3954
|
-
})
|
|
4008
|
+
result: ok(input.summary ? summarizeLintOutput(output, input.examplesLimit) : output)
|
|
3955
4009
|
};
|
|
3956
4010
|
}
|
|
3957
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
|
+
|
|
3958
4023
|
// src/commands/config.ts
|
|
3959
4024
|
import { readFile as readFile18 } from "fs/promises";
|
|
3960
4025
|
import { existsSync as existsSync6 } from "fs";
|
|
@@ -4016,12 +4081,6 @@ async function runConfigPath(input) {
|
|
|
4016
4081
|
return { exitCode: ExitCode.OK, result: ok({ path: filePath, exists: existsSync6(filePath), humanHint: filePath }) };
|
|
4017
4082
|
}
|
|
4018
4083
|
|
|
4019
|
-
// src/commands/doctor.ts
|
|
4020
|
-
import { existsSync as existsSync10, lstatSync, readlinkSync, readdirSync as readdirSync2, statSync as statSync3, readFileSync as readFileSync7 } from "fs";
|
|
4021
|
-
import { join as join27, resolve as resolve5 } from "path";
|
|
4022
|
-
import { execSync as execSync2 } from "child_process";
|
|
4023
|
-
import { platform as platform2 } from "os";
|
|
4024
|
-
|
|
4025
4084
|
// src/utils/auto-update.ts
|
|
4026
4085
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync7, mkdirSync as mkdirSync3 } from "fs";
|
|
4027
4086
|
import { join as join24, dirname as dirname9 } from "path";
|
|
@@ -5624,9 +5683,383 @@ async function runDoctor(input) {
|
|
|
5624
5683
|
return { exitCode, result: ok({ checks, summary, humanHint }) };
|
|
5625
5684
|
}
|
|
5626
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
|
+
|
|
5627
6060
|
// src/commands/archive.ts
|
|
5628
6061
|
import { rename as rename7, mkdir as mkdir9, readFile as readFile20, writeFile as writeFile10 } from "fs/promises";
|
|
5629
|
-
import { join as
|
|
6062
|
+
import { join as join29, dirname as dirname11 } from "path";
|
|
5630
6063
|
function countWikilinks(body, slug) {
|
|
5631
6064
|
const escaped = slug.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5632
6065
|
const re = new RegExp(`\\[\\[${escaped}(?:[|#][^\\]]*)?\\]\\]`, "g");
|
|
@@ -5654,7 +6087,7 @@ async function runArchive(input) {
|
|
|
5654
6087
|
if (!relPath) return { exitCode: ExitCode.ARCHIVE_TARGET_NOT_FOUND, result: err("ARCHIVE_TARGET_NOT_FOUND", { page: input.page }) };
|
|
5655
6088
|
if (relPath.startsWith("_archive/")) return { exitCode: ExitCode.ARCHIVE_ALREADY_ARCHIVED, result: err("ARCHIVE_ALREADY_ARCHIVED", { page: relPath }) };
|
|
5656
6089
|
const slug = relPath.replace(/\.md$/, "").split("/").pop();
|
|
5657
|
-
const archivePath =
|
|
6090
|
+
const archivePath = join29("_archive", relPath).replace(/\\/g, "/");
|
|
5658
6091
|
let cascade;
|
|
5659
6092
|
if (input.cascade) {
|
|
5660
6093
|
const wikilinkRefs = [];
|
|
@@ -5678,7 +6111,7 @@ async function runArchive(input) {
|
|
|
5678
6111
|
const indexRefs = [];
|
|
5679
6112
|
if (!isRaw) {
|
|
5680
6113
|
try {
|
|
5681
|
-
const idx = await readFile20(
|
|
6114
|
+
const idx = await readFile20(join29(input.vault, "index.md"), "utf8");
|
|
5682
6115
|
idx.split("\n").forEach((line, i) => {
|
|
5683
6116
|
if (line.includes(`[[${slug}]]`)) indexRefs.push({ line: i + 1, text: line });
|
|
5684
6117
|
});
|
|
@@ -5704,7 +6137,7 @@ async function runArchive(input) {
|
|
|
5704
6137
|
}
|
|
5705
6138
|
if (input.cascade && input.apply && cascade) {
|
|
5706
6139
|
for (const ref of cascade.source_array_refs) {
|
|
5707
|
-
const absPath =
|
|
6140
|
+
const absPath = join29(input.vault, ref.page);
|
|
5708
6141
|
const text = await readFile20(absPath, "utf8");
|
|
5709
6142
|
const split = splitFrontmatter(text);
|
|
5710
6143
|
if (!split.ok) continue;
|
|
@@ -5722,10 +6155,10 @@ ${fmRewritten}
|
|
|
5722
6155
|
}
|
|
5723
6156
|
}
|
|
5724
6157
|
}
|
|
5725
|
-
await mkdir9(
|
|
6158
|
+
await mkdir9(dirname11(join29(input.vault, archivePath)), { recursive: true });
|
|
5726
6159
|
let indexUpdated = false;
|
|
5727
6160
|
if (!isRaw) {
|
|
5728
|
-
const indexPath =
|
|
6161
|
+
const indexPath = join29(input.vault, "index.md");
|
|
5729
6162
|
try {
|
|
5730
6163
|
const idx = await readFile20(indexPath, "utf8");
|
|
5731
6164
|
const originalLines = idx.split("\n");
|
|
@@ -5738,7 +6171,7 @@ ${fmRewritten}
|
|
|
5738
6171
|
if (e instanceof Error && "code" in e && e.code !== "ENOENT") throw e;
|
|
5739
6172
|
}
|
|
5740
6173
|
}
|
|
5741
|
-
await rename7(
|
|
6174
|
+
await rename7(join29(input.vault, relPath), join29(input.vault, archivePath));
|
|
5742
6175
|
appendLastOp(input.vault, {
|
|
5743
6176
|
operation: input.cascade ? "archive-cascade" : "archive",
|
|
5744
6177
|
summary: `moved ${relPath} to ${archivePath}${input.cascade ? ` (cascade: ${cascade?.source_array_refs.length ?? 0} source arrays updated)` : ""}`,
|
|
@@ -6147,15 +6580,37 @@ ${newBody}`;
|
|
|
6147
6580
|
|
|
6148
6581
|
// src/commands/update.ts
|
|
6149
6582
|
import { execSync as execSync3 } from "child_process";
|
|
6150
|
-
import {
|
|
6151
|
-
|
|
6583
|
+
import { join as join30 } from "path";
|
|
6584
|
+
|
|
6585
|
+
// src/utils/package-info.ts
|
|
6586
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
6587
|
+
function packageJsonCandidateUrls(baseUrl = import.meta.url) {
|
|
6588
|
+
return [
|
|
6589
|
+
new URL("../package.json", baseUrl),
|
|
6590
|
+
new URL("../../package.json", baseUrl)
|
|
6591
|
+
];
|
|
6592
|
+
}
|
|
6593
|
+
function readCliPackageJson(baseUrl = import.meta.url) {
|
|
6594
|
+
for (const url of packageJsonCandidateUrls(baseUrl)) {
|
|
6595
|
+
try {
|
|
6596
|
+
const pkg2 = JSON.parse(readFileSync9(url, "utf8"));
|
|
6597
|
+
if (typeof pkg2.version === "string") {
|
|
6598
|
+
return { ...pkg2, version: pkg2.version };
|
|
6599
|
+
}
|
|
6600
|
+
} catch {
|
|
6601
|
+
}
|
|
6602
|
+
}
|
|
6603
|
+
throw new Error(`Could not locate skillwiki package.json from ${baseUrl}`);
|
|
6604
|
+
}
|
|
6605
|
+
|
|
6606
|
+
// src/commands/update.ts
|
|
6152
6607
|
function resolveGlobalSkillsRoot() {
|
|
6153
6608
|
try {
|
|
6154
6609
|
const globalRoot = execSync3("npm root -g", {
|
|
6155
6610
|
encoding: "utf8",
|
|
6156
6611
|
timeout: 5e3
|
|
6157
6612
|
}).trim();
|
|
6158
|
-
return
|
|
6613
|
+
return join30(globalRoot, "skillwiki", "skills");
|
|
6159
6614
|
} catch {
|
|
6160
6615
|
return null;
|
|
6161
6616
|
}
|
|
@@ -6176,12 +6631,10 @@ async function refreshInstalledSkills(target) {
|
|
|
6176
6631
|
}
|
|
6177
6632
|
}
|
|
6178
6633
|
async function runUpdate(input) {
|
|
6179
|
-
const pkg2 =
|
|
6180
|
-
readFileSync8(new URL("../../package.json", import.meta.url), "utf8")
|
|
6181
|
-
);
|
|
6634
|
+
const pkg2 = readCliPackageJson();
|
|
6182
6635
|
const currentVersion = pkg2.version;
|
|
6183
6636
|
const tag = normalizeDistTag(input.distTag);
|
|
6184
|
-
const target =
|
|
6637
|
+
const target = join30(input.home, ".claude", "skills");
|
|
6185
6638
|
let latest;
|
|
6186
6639
|
try {
|
|
6187
6640
|
latest = execSync3(`npm view skillwiki@${tag} version`, {
|
|
@@ -6252,24 +6705,22 @@ async function runUpdate(input) {
|
|
|
6252
6705
|
|
|
6253
6706
|
// src/commands/self-update.ts
|
|
6254
6707
|
import { execSync as execSync4 } from "child_process";
|
|
6255
|
-
import { existsSync as
|
|
6256
|
-
import { join as
|
|
6708
|
+
import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
|
|
6709
|
+
import { join as join31 } from "path";
|
|
6257
6710
|
var DEFAULT_SOURCE_ROOT_SUFFIX = "/Desktop/code/llm-wiki";
|
|
6258
6711
|
async function runSelfUpdate(input) {
|
|
6259
|
-
const currentVersion =
|
|
6260
|
-
readFileSync9(new URL("../../package.json", import.meta.url), "utf8")
|
|
6261
|
-
).version;
|
|
6712
|
+
const currentVersion = readCliPackageJson().version;
|
|
6262
6713
|
const sourceRoot = input.sourceRoot ?? `${input.home}${DEFAULT_SOURCE_ROOT_SUFFIX}`;
|
|
6263
6714
|
const distTag = normalizeDistTag(input.distTag);
|
|
6264
|
-
const localPkgPath =
|
|
6265
|
-
const hasLocalSource =
|
|
6715
|
+
const localPkgPath = join31(sourceRoot, "packages", "cli", "package.json");
|
|
6716
|
+
const hasLocalSource = existsSync12(localPkgPath);
|
|
6266
6717
|
if (input.check) {
|
|
6267
6718
|
let availableVersion = null;
|
|
6268
6719
|
let source;
|
|
6269
6720
|
if (hasLocalSource) {
|
|
6270
6721
|
source = "local";
|
|
6271
6722
|
try {
|
|
6272
|
-
availableVersion = JSON.parse(
|
|
6723
|
+
availableVersion = JSON.parse(readFileSync10(localPkgPath, "utf8")).version ?? null;
|
|
6273
6724
|
} catch {
|
|
6274
6725
|
availableVersion = null;
|
|
6275
6726
|
}
|
|
@@ -6327,7 +6778,7 @@ async function runSelfUpdate(input) {
|
|
|
6327
6778
|
}
|
|
6328
6779
|
const newVersion = (() => {
|
|
6329
6780
|
try {
|
|
6330
|
-
return JSON.parse(
|
|
6781
|
+
return JSON.parse(readFileSync10(localPkgPath, "utf8")).version ?? "unknown";
|
|
6331
6782
|
} catch {
|
|
6332
6783
|
return "unknown";
|
|
6333
6784
|
}
|
|
@@ -6394,9 +6845,9 @@ async function runSelfUpdate(input) {
|
|
|
6394
6845
|
|
|
6395
6846
|
// src/commands/transcripts.ts
|
|
6396
6847
|
import { readdir as readdir5, stat as stat7, readFile as readFile21 } from "fs/promises";
|
|
6397
|
-
import { join as
|
|
6848
|
+
import { join as join32 } from "path";
|
|
6398
6849
|
async function runTranscripts(input) {
|
|
6399
|
-
const dir =
|
|
6850
|
+
const dir = join32(input.vault, "raw", "transcripts");
|
|
6400
6851
|
let entries;
|
|
6401
6852
|
try {
|
|
6402
6853
|
entries = await readdir5(dir, { withFileTypes: true });
|
|
@@ -6406,7 +6857,7 @@ async function runTranscripts(input) {
|
|
|
6406
6857
|
const transcripts = [];
|
|
6407
6858
|
for (const entry of entries) {
|
|
6408
6859
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
6409
|
-
const filePath =
|
|
6860
|
+
const filePath = join32(dir, entry.name);
|
|
6410
6861
|
const content = await readFile21(filePath, "utf8");
|
|
6411
6862
|
const fm = extractFrontmatter(content);
|
|
6412
6863
|
if (!fm.ok) continue;
|
|
@@ -6425,11 +6876,11 @@ async function runTranscripts(input) {
|
|
|
6425
6876
|
|
|
6426
6877
|
// src/commands/project-index.ts
|
|
6427
6878
|
import { readdir as readdir6, readFile as readFile22, writeFile as writeFile11, mkdir as mkdir10 } from "fs/promises";
|
|
6428
|
-
import { join as
|
|
6879
|
+
import { join as join33, dirname as dirname12 } from "path";
|
|
6429
6880
|
var LAYER2_DIRS = ["entities", "concepts", "comparisons", "queries", "meta"];
|
|
6430
6881
|
async function runProjectIndex(input) {
|
|
6431
6882
|
const slug = input.slug;
|
|
6432
|
-
const projectDir =
|
|
6883
|
+
const projectDir = join33(input.vault, "projects", slug);
|
|
6433
6884
|
try {
|
|
6434
6885
|
await readdir6(projectDir);
|
|
6435
6886
|
} catch {
|
|
@@ -6440,12 +6891,12 @@ async function runProjectIndex(input) {
|
|
|
6440
6891
|
}
|
|
6441
6892
|
const wikilinkPattern = `[[${slug}]]`;
|
|
6442
6893
|
const entries = [];
|
|
6443
|
-
const compoundDir =
|
|
6894
|
+
const compoundDir = join33(input.vault, "projects", slug, "compound");
|
|
6444
6895
|
try {
|
|
6445
6896
|
const compoundFiles = await readdir6(compoundDir, { withFileTypes: true });
|
|
6446
6897
|
for (const entry of compoundFiles) {
|
|
6447
6898
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
6448
|
-
const filePath =
|
|
6899
|
+
const filePath = join33(compoundDir, entry.name);
|
|
6449
6900
|
let text;
|
|
6450
6901
|
try {
|
|
6451
6902
|
text = await readFile22(filePath, "utf8");
|
|
@@ -6465,13 +6916,13 @@ async function runProjectIndex(input) {
|
|
|
6465
6916
|
for (const dir of LAYER2_DIRS) {
|
|
6466
6917
|
let files;
|
|
6467
6918
|
try {
|
|
6468
|
-
files = await readdir6(
|
|
6919
|
+
files = await readdir6(join33(input.vault, dir), { withFileTypes: true });
|
|
6469
6920
|
} catch {
|
|
6470
6921
|
continue;
|
|
6471
6922
|
}
|
|
6472
6923
|
for (const entry of files) {
|
|
6473
6924
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
6474
|
-
const filePath =
|
|
6925
|
+
const filePath = join33(input.vault, dir, entry.name);
|
|
6475
6926
|
let text;
|
|
6476
6927
|
try {
|
|
6477
6928
|
text = await readFile22(filePath, "utf8");
|
|
@@ -6495,7 +6946,7 @@ async function runProjectIndex(input) {
|
|
|
6495
6946
|
const tb = typeOrder[b.type] ?? 99;
|
|
6496
6947
|
return ta !== tb ? ta - tb : a.title.localeCompare(b.title);
|
|
6497
6948
|
});
|
|
6498
|
-
const indexPath =
|
|
6949
|
+
const indexPath = join33(projectDir, "knowledge.md");
|
|
6499
6950
|
let existing = false;
|
|
6500
6951
|
let stale = false;
|
|
6501
6952
|
try {
|
|
@@ -6539,7 +6990,7 @@ Autogenerated by \`skillwiki project-index\` on ${today}.
|
|
|
6539
6990
|
}
|
|
6540
6991
|
if (input.apply) {
|
|
6541
6992
|
try {
|
|
6542
|
-
await mkdir10(
|
|
6993
|
+
await mkdir10(dirname12(indexPath), { recursive: true });
|
|
6543
6994
|
await writeFile11(indexPath, body, "utf8");
|
|
6544
6995
|
} catch (e) {
|
|
6545
6996
|
return {
|
|
@@ -6569,8 +7020,8 @@ ${entries.map((e) => ` ${e.type}: [[${e.page.replace(/\.md$/, "")}]] \u2014 ${e
|
|
|
6569
7020
|
|
|
6570
7021
|
// src/commands/compound.ts
|
|
6571
7022
|
import { writeFile as writeFile12, mkdir as mkdir11, readdir as readdir7, unlink as unlink4 } from "fs/promises";
|
|
6572
|
-
import { join as
|
|
6573
|
-
import { existsSync as
|
|
7023
|
+
import { join as join34 } from "path";
|
|
7024
|
+
import { existsSync as existsSync13 } from "fs";
|
|
6574
7025
|
import { readFile as readFile23 } from "fs/promises";
|
|
6575
7026
|
var RETRO_HEADING_RE = /^## \[(\d{4}-\d{2}-\d{2})(?:\s+[^\]]+)?\] retro \| loop cycle(?: (\d+))?: (.+)$/;
|
|
6576
7027
|
var FIELD_RE = {
|
|
@@ -6669,7 +7120,7 @@ function extractRetroFields(date, cycleName, block) {
|
|
|
6669
7120
|
};
|
|
6670
7121
|
}
|
|
6671
7122
|
async function runCompound(input) {
|
|
6672
|
-
const logPath =
|
|
7123
|
+
const logPath = join34(input.vault, "log.md");
|
|
6673
7124
|
let logText;
|
|
6674
7125
|
try {
|
|
6675
7126
|
logText = await readFile23(logPath, "utf8");
|
|
@@ -6679,7 +7130,7 @@ async function runCompound(input) {
|
|
|
6679
7130
|
const entries = parseRetroEntries(logText);
|
|
6680
7131
|
const promoted = [];
|
|
6681
7132
|
const skipped = [];
|
|
6682
|
-
const compoundDir =
|
|
7133
|
+
const compoundDir = join34(input.vault, "projects", input.project, "compound");
|
|
6683
7134
|
for (const entry of entries) {
|
|
6684
7135
|
const generalizeValue = entry.generalize.trim();
|
|
6685
7136
|
if (!/^yes/i.test(generalizeValue)) {
|
|
@@ -6687,8 +7138,8 @@ async function runCompound(input) {
|
|
|
6687
7138
|
continue;
|
|
6688
7139
|
}
|
|
6689
7140
|
const slug = slugify(entry.cycleName);
|
|
6690
|
-
const compoundPath =
|
|
6691
|
-
if (
|
|
7141
|
+
const compoundPath = join34(compoundDir, `${slug}.md`);
|
|
7142
|
+
if (existsSync13(compoundPath)) {
|
|
6692
7143
|
skipped.push(entry.date);
|
|
6693
7144
|
continue;
|
|
6694
7145
|
}
|
|
@@ -6726,7 +7177,7 @@ async function runCompound(input) {
|
|
|
6726
7177
|
].join("\n");
|
|
6727
7178
|
const content = frontmatter + "\n" + body;
|
|
6728
7179
|
if (!input.dryRun) {
|
|
6729
|
-
if (!
|
|
7180
|
+
if (!existsSync13(compoundDir)) {
|
|
6730
7181
|
await mkdir11(compoundDir, { recursive: true });
|
|
6731
7182
|
}
|
|
6732
7183
|
await writeFile12(compoundPath, content, "utf8");
|
|
@@ -6748,16 +7199,16 @@ async function runCompound(input) {
|
|
|
6748
7199
|
};
|
|
6749
7200
|
}
|
|
6750
7201
|
async function runCompoundDelete(input) {
|
|
6751
|
-
const projectDir =
|
|
6752
|
-
if (!
|
|
7202
|
+
const projectDir = join34(input.vault, "projects", input.project);
|
|
7203
|
+
if (!existsSync13(projectDir)) {
|
|
6753
7204
|
return {
|
|
6754
7205
|
exitCode: ExitCode.PROJECT_NOT_FOUND,
|
|
6755
7206
|
result: err("PROJECT_NOT_FOUND", { slug: input.project, path: projectDir })
|
|
6756
7207
|
};
|
|
6757
7208
|
}
|
|
6758
7209
|
const entryName = input.entry.replace(/\.md$/, "");
|
|
6759
|
-
const compoundPath =
|
|
6760
|
-
if (!
|
|
7210
|
+
const compoundPath = join34(projectDir, "compound", `${entryName}.md`);
|
|
7211
|
+
if (!existsSync13(compoundPath)) {
|
|
6761
7212
|
return {
|
|
6762
7213
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
6763
7214
|
result: err("FILE_NOT_FOUND", { path: compoundPath })
|
|
@@ -6790,8 +7241,8 @@ knowledge.md regenerated`
|
|
|
6790
7241
|
};
|
|
6791
7242
|
}
|
|
6792
7243
|
async function runCompoundList(input) {
|
|
6793
|
-
const compoundDir =
|
|
6794
|
-
if (!
|
|
7244
|
+
const compoundDir = join34(input.vault, "projects", input.project, "compound");
|
|
7245
|
+
if (!existsSync13(compoundDir)) {
|
|
6795
7246
|
return {
|
|
6796
7247
|
exitCode: ExitCode.OK,
|
|
6797
7248
|
result: ok({
|
|
@@ -6821,7 +7272,7 @@ could not read compound directory`
|
|
|
6821
7272
|
const entries = [];
|
|
6822
7273
|
for (const dirent of dirents) {
|
|
6823
7274
|
if (!dirent.isFile() || !dirent.name.endsWith(".md")) continue;
|
|
6824
|
-
const filePath =
|
|
7275
|
+
const filePath = join34(compoundDir, dirent.name);
|
|
6825
7276
|
let text;
|
|
6826
7277
|
try {
|
|
6827
7278
|
text = await readFile23(filePath, "utf8");
|
|
@@ -6854,8 +7305,8 @@ no compound entries found`;
|
|
|
6854
7305
|
|
|
6855
7306
|
// src/commands/observe.ts
|
|
6856
7307
|
import { mkdir as mkdir12, writeFile as writeFile13 } from "fs/promises";
|
|
6857
|
-
import { existsSync as
|
|
6858
|
-
import { join as
|
|
7308
|
+
import { existsSync as existsSync14, statSync as statSync4 } from "fs";
|
|
7309
|
+
import { join as join35 } from "path";
|
|
6859
7310
|
import { createHash as createHash4 } from "crypto";
|
|
6860
7311
|
var ALLOWED_KINDS = /* @__PURE__ */ new Set(["note", "bug", "task", "idea", "session-log"]);
|
|
6861
7312
|
function slugify2(text) {
|
|
@@ -6878,13 +7329,13 @@ async function runObserve(input) {
|
|
|
6878
7329
|
result: err("SCHEME_REJECTED", { message: "Text must not be empty" })
|
|
6879
7330
|
};
|
|
6880
7331
|
}
|
|
6881
|
-
if (!
|
|
7332
|
+
if (!existsSync14(input.vault) || !statSync4(input.vault).isDirectory()) {
|
|
6882
7333
|
return {
|
|
6883
7334
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
6884
7335
|
result: err("VAULT_PATH_INVALID", { path: input.vault })
|
|
6885
7336
|
};
|
|
6886
7337
|
}
|
|
6887
|
-
const transcriptsDir =
|
|
7338
|
+
const transcriptsDir = join35(input.vault, "raw", "transcripts");
|
|
6888
7339
|
try {
|
|
6889
7340
|
await mkdir12(transcriptsDir, { recursive: true });
|
|
6890
7341
|
} catch {
|
|
@@ -6896,7 +7347,7 @@ async function runObserve(input) {
|
|
|
6896
7347
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
6897
7348
|
const slug = slugify2(input.text);
|
|
6898
7349
|
const fileName = `${today}-observation-${slug}.md`;
|
|
6899
|
-
const filePath =
|
|
7350
|
+
const filePath = join35(transcriptsDir, fileName);
|
|
6900
7351
|
const body = `
|
|
6901
7352
|
${input.text.trim()}
|
|
6902
7353
|
`;
|
|
@@ -6937,7 +7388,7 @@ ${input.text.trim()}
|
|
|
6937
7388
|
|
|
6938
7389
|
// src/commands/ingest.ts
|
|
6939
7390
|
import { readFile as readFile24, writeFile as writeFile14, mkdir as mkdir13 } from "fs/promises";
|
|
6940
|
-
import { join as
|
|
7391
|
+
import { join as join36 } from "path";
|
|
6941
7392
|
import { createHash as createHash5 } from "crypto";
|
|
6942
7393
|
var ALLOWED_TYPES = /* @__PURE__ */ new Set(["entity", "concept", "comparison", "query"]);
|
|
6943
7394
|
var TYPE_DIR = {
|
|
@@ -7111,8 +7562,8 @@ async function runIngest(input) {
|
|
|
7111
7562
|
const rawRelPath = `raw/articles/${slug}.md`;
|
|
7112
7563
|
const typedDir = TYPE_DIR[input.type] ?? `${input.type}s`;
|
|
7113
7564
|
const typedRelPath = `${typedDir}/${slug}.md`;
|
|
7114
|
-
const rawAbsPath =
|
|
7115
|
-
const typedAbsPath =
|
|
7565
|
+
const rawAbsPath = join36(input.vault, rawRelPath);
|
|
7566
|
+
const typedAbsPath = join36(input.vault, typedRelPath);
|
|
7116
7567
|
const identity = assessSourceIdentity({
|
|
7117
7568
|
rawPath: rawRelPath,
|
|
7118
7569
|
sourceUrl: sourceUrl ?? void 0,
|
|
@@ -7194,7 +7645,7 @@ async function runIngest(input) {
|
|
|
7194
7645
|
};
|
|
7195
7646
|
}
|
|
7196
7647
|
try {
|
|
7197
|
-
await mkdir13(
|
|
7648
|
+
await mkdir13(join36(input.vault, "raw", "articles"), { recursive: true });
|
|
7198
7649
|
await writeFile14(rawAbsPath, rawContent, "utf8");
|
|
7199
7650
|
} catch (e) {
|
|
7200
7651
|
return {
|
|
@@ -7203,7 +7654,7 @@ async function runIngest(input) {
|
|
|
7203
7654
|
};
|
|
7204
7655
|
}
|
|
7205
7656
|
try {
|
|
7206
|
-
await mkdir13(
|
|
7657
|
+
await mkdir13(join36(input.vault, typedDir), { recursive: true });
|
|
7207
7658
|
await writeFile14(typedAbsPath, typedContent, "utf8");
|
|
7208
7659
|
} catch (e) {
|
|
7209
7660
|
return {
|
|
@@ -7382,12 +7833,12 @@ ${body}`;
|
|
|
7382
7833
|
}
|
|
7383
7834
|
|
|
7384
7835
|
// src/commands/sync.ts
|
|
7385
|
-
import { existsSync as
|
|
7386
|
-
import { join as
|
|
7836
|
+
import { existsSync as existsSync16 } from "fs";
|
|
7837
|
+
import { join as join38 } from "path";
|
|
7387
7838
|
|
|
7388
7839
|
// src/utils/sync-lock.ts
|
|
7389
|
-
import { existsSync as
|
|
7390
|
-
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";
|
|
7391
7842
|
import { createHash as createHash6 } from "crypto";
|
|
7392
7843
|
function getSessionId() {
|
|
7393
7844
|
if (process.env.CLAUDE_SESSION_ID) return process.env.CLAUDE_SESSION_ID;
|
|
@@ -7395,13 +7846,13 @@ function getSessionId() {
|
|
|
7395
7846
|
return process.pid.toString();
|
|
7396
7847
|
}
|
|
7397
7848
|
function lockPath(vault) {
|
|
7398
|
-
return
|
|
7849
|
+
return join37(vault, ".skillwiki", "sync.lock");
|
|
7399
7850
|
}
|
|
7400
7851
|
function readLock(vault) {
|
|
7401
7852
|
const path = lockPath(vault);
|
|
7402
|
-
if (!
|
|
7853
|
+
if (!existsSync15(path)) return null;
|
|
7403
7854
|
try {
|
|
7404
|
-
const raw =
|
|
7855
|
+
const raw = readFileSync11(path, "utf8");
|
|
7405
7856
|
return JSON.parse(raw);
|
|
7406
7857
|
} catch {
|
|
7407
7858
|
return null;
|
|
@@ -7414,9 +7865,9 @@ function isStale(lock, now) {
|
|
|
7414
7865
|
}
|
|
7415
7866
|
function acquireLock(vault, opts = {}) {
|
|
7416
7867
|
const path = lockPath(vault);
|
|
7417
|
-
const dir =
|
|
7418
|
-
if (!
|
|
7419
|
-
|
|
7868
|
+
const dir = join37(vault, ".skillwiki");
|
|
7869
|
+
if (!existsSync15(dir)) {
|
|
7870
|
+
mkdirSync5(dir, { recursive: true });
|
|
7420
7871
|
}
|
|
7421
7872
|
const sessionId = opts.sessionId ?? getSessionId();
|
|
7422
7873
|
const summary = opts.summary ?? "skillwiki sync";
|
|
@@ -7435,7 +7886,7 @@ function acquireLock(vault, opts = {}) {
|
|
|
7435
7886
|
};
|
|
7436
7887
|
try {
|
|
7437
7888
|
const content = JSON.stringify(lock, null, 2) + "\n";
|
|
7438
|
-
|
|
7889
|
+
writeFileSync7(path, content, { flag: "wx" });
|
|
7439
7890
|
return { ok: true, lock };
|
|
7440
7891
|
} catch (e) {
|
|
7441
7892
|
const err3 = e;
|
|
@@ -7455,12 +7906,12 @@ function acquireLock(vault, opts = {}) {
|
|
|
7455
7906
|
function writeLockedFile(path, lock) {
|
|
7456
7907
|
const tmp = path + ".tmp";
|
|
7457
7908
|
const content = JSON.stringify(lock, null, 2) + "\n";
|
|
7458
|
-
|
|
7459
|
-
|
|
7909
|
+
writeFileSync7(tmp, content);
|
|
7910
|
+
renameSync2(tmp, path);
|
|
7460
7911
|
}
|
|
7461
7912
|
function releaseLock(vault, opts = {}) {
|
|
7462
7913
|
const path = lockPath(vault);
|
|
7463
|
-
if (!
|
|
7914
|
+
if (!existsSync15(path)) {
|
|
7464
7915
|
return { released: false };
|
|
7465
7916
|
}
|
|
7466
7917
|
const sessionId = opts.sessionId ?? getSessionId();
|
|
@@ -7489,7 +7940,7 @@ function releaseLock(vault, opts = {}) {
|
|
|
7489
7940
|
function runSyncStatus(input) {
|
|
7490
7941
|
const vault = input.vault;
|
|
7491
7942
|
const includeStashes = input.includeStashes ?? false;
|
|
7492
|
-
if (!
|
|
7943
|
+
if (!existsSync16(join38(vault, ".git"))) {
|
|
7493
7944
|
return {
|
|
7494
7945
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
7495
7946
|
result: ok({
|
|
@@ -7567,7 +8018,7 @@ function runSyncStatus(input) {
|
|
|
7567
8018
|
}
|
|
7568
8019
|
async function runSyncPush(input) {
|
|
7569
8020
|
const vault = input.vault;
|
|
7570
|
-
if (!
|
|
8021
|
+
if (!existsSync16(join38(vault, ".git"))) {
|
|
7571
8022
|
return {
|
|
7572
8023
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
7573
8024
|
result: err("NOT_A_GIT_REPO", { path: vault })
|
|
@@ -7690,7 +8141,7 @@ function enableGitLongPathsOnWindows(vault) {
|
|
|
7690
8141
|
}
|
|
7691
8142
|
async function runSyncPull(input) {
|
|
7692
8143
|
const vault = input.vault;
|
|
7693
|
-
if (!
|
|
8144
|
+
if (!existsSync16(join38(vault, ".git"))) {
|
|
7694
8145
|
return {
|
|
7695
8146
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
7696
8147
|
result: err("NOT_A_GIT_REPO", { path: vault })
|
|
@@ -7862,7 +8313,7 @@ function runSyncPeers(input) {
|
|
|
7862
8313
|
}
|
|
7863
8314
|
function runSyncLock(input) {
|
|
7864
8315
|
const vault = input.vault;
|
|
7865
|
-
if (!
|
|
8316
|
+
if (!existsSync16(vault)) {
|
|
7866
8317
|
return {
|
|
7867
8318
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
7868
8319
|
result: err("VAULT_PATH_INVALID", { path: vault })
|
|
@@ -7897,7 +8348,7 @@ function runSyncLock(input) {
|
|
|
7897
8348
|
}
|
|
7898
8349
|
function runSyncUnlock(input) {
|
|
7899
8350
|
const vault = input.vault;
|
|
7900
|
-
if (!
|
|
8351
|
+
if (!existsSync16(vault)) {
|
|
7901
8352
|
return {
|
|
7902
8353
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
7903
8354
|
result: err("VAULT_PATH_INVALID", { path: vault })
|
|
@@ -7930,8 +8381,8 @@ function runSyncUnlock(input) {
|
|
|
7930
8381
|
}
|
|
7931
8382
|
|
|
7932
8383
|
// src/commands/backup.ts
|
|
7933
|
-
import { statSync as statSync5, readdirSync as readdirSync3, readFileSync as
|
|
7934
|
-
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";
|
|
7935
8386
|
import { PutObjectCommand, HeadObjectCommand, ListObjectsV2Command, GetObjectCommand, DeleteObjectsCommand } from "@aws-sdk/client-s3";
|
|
7936
8387
|
|
|
7937
8388
|
// src/utils/s3-client.ts
|
|
@@ -7955,7 +8406,7 @@ var SKIP_DIRS2 = /* @__PURE__ */ new Set([".git", ".obsidian", "_archive", "node
|
|
|
7955
8406
|
function* walkMarkdown(dir, base) {
|
|
7956
8407
|
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
7957
8408
|
if (SKIP_DIRS2.has(entry.name)) continue;
|
|
7958
|
-
const full =
|
|
8409
|
+
const full = join39(dir, entry.name);
|
|
7959
8410
|
if (entry.isDirectory()) {
|
|
7960
8411
|
yield* walkMarkdown(full, base);
|
|
7961
8412
|
} else if (entry.name.endsWith(".md")) {
|
|
@@ -7978,7 +8429,7 @@ async function runBackupSync(input) {
|
|
|
7978
8429
|
let failed = 0;
|
|
7979
8430
|
const files = [...walkMarkdown(input.vault, input.vault)];
|
|
7980
8431
|
for (const relPath of files) {
|
|
7981
|
-
const absPath =
|
|
8432
|
+
const absPath = join39(input.vault, relPath);
|
|
7982
8433
|
const localStat = statSync5(absPath);
|
|
7983
8434
|
let needsUpload = true;
|
|
7984
8435
|
try {
|
|
@@ -7997,7 +8448,7 @@ async function runBackupSync(input) {
|
|
|
7997
8448
|
continue;
|
|
7998
8449
|
}
|
|
7999
8450
|
try {
|
|
8000
|
-
const body =
|
|
8451
|
+
const body = readFileSync12(absPath);
|
|
8001
8452
|
await client.send(new PutObjectCommand({ Bucket: input.bucket, Key: relPath, Body: body }));
|
|
8002
8453
|
uploaded++;
|
|
8003
8454
|
} catch {
|
|
@@ -8054,7 +8505,7 @@ async function runBackupRestore(input) {
|
|
|
8054
8505
|
const objects = list.Contents ?? [];
|
|
8055
8506
|
for (const obj of objects) {
|
|
8056
8507
|
if (!obj.Key) continue;
|
|
8057
|
-
const localPath =
|
|
8508
|
+
const localPath = join39(target, obj.Key);
|
|
8058
8509
|
try {
|
|
8059
8510
|
const localStat = statSync5(localPath);
|
|
8060
8511
|
if (obj.LastModified && localStat.mtime > obj.LastModified) {
|
|
@@ -8067,8 +8518,8 @@ async function runBackupRestore(input) {
|
|
|
8067
8518
|
const resp = await client.send(new GetObjectCommand({ Bucket: input.bucket, Key: obj.Key }));
|
|
8068
8519
|
const body = await resp.Body?.transformToByteArray();
|
|
8069
8520
|
if (body) {
|
|
8070
|
-
|
|
8071
|
-
|
|
8521
|
+
mkdirSync6(dirname13(localPath), { recursive: true });
|
|
8522
|
+
writeFileSync8(localPath, Buffer.from(body));
|
|
8072
8523
|
downloaded++;
|
|
8073
8524
|
}
|
|
8074
8525
|
} catch {
|
|
@@ -8100,11 +8551,11 @@ async function runBackupRestore(input) {
|
|
|
8100
8551
|
}
|
|
8101
8552
|
|
|
8102
8553
|
// src/commands/status.ts
|
|
8103
|
-
import { existsSync as
|
|
8554
|
+
import { existsSync as existsSync17, statSync as statSync6 } from "fs";
|
|
8104
8555
|
import { readFile as readFile25 } from "fs/promises";
|
|
8105
|
-
import { join as
|
|
8556
|
+
import { join as join40 } from "path";
|
|
8106
8557
|
async function runStatus(input) {
|
|
8107
|
-
if (!
|
|
8558
|
+
if (!existsSync17(input.vault)) {
|
|
8108
8559
|
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { vault: input.vault }) };
|
|
8109
8560
|
}
|
|
8110
8561
|
const scan = await scanVault(input.vault);
|
|
@@ -8129,7 +8580,7 @@ async function runStatus(input) {
|
|
|
8129
8580
|
const compound = scan.data.compound.length;
|
|
8130
8581
|
let schemaVersion = "v1";
|
|
8131
8582
|
try {
|
|
8132
|
-
const schemaContent = await readFile25(
|
|
8583
|
+
const schemaContent = await readFile25(join40(input.vault, "SCHEMA.md"), "utf8");
|
|
8133
8584
|
const versionMatch = schemaContent.match(/version:\s*["']?([^"'\s\n]+)/i);
|
|
8134
8585
|
if (versionMatch) schemaVersion = versionMatch[1];
|
|
8135
8586
|
} catch {
|
|
@@ -8190,7 +8641,7 @@ async function runStatus(input) {
|
|
|
8190
8641
|
|
|
8191
8642
|
// src/commands/seed.ts
|
|
8192
8643
|
import { mkdir as mkdir14, writeFile as writeFile15, stat as stat8 } from "fs/promises";
|
|
8193
|
-
import { join as
|
|
8644
|
+
import { join as join41 } from "path";
|
|
8194
8645
|
var TODAY = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
8195
8646
|
var EXAMPLE_PAGES = {
|
|
8196
8647
|
"entities/example-project.md": `---
|
|
@@ -8259,29 +8710,29 @@ Real sources are immutable after ingestion \u2014 never edit them.
|
|
|
8259
8710
|
`;
|
|
8260
8711
|
async function runSeed(input) {
|
|
8261
8712
|
try {
|
|
8262
|
-
await stat8(
|
|
8713
|
+
await stat8(join41(input.vault, "SCHEMA.md"));
|
|
8263
8714
|
} catch {
|
|
8264
8715
|
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { root: input.vault, reason: "SCHEMA.md missing \u2014 run `skillwiki init` first" }) };
|
|
8265
8716
|
}
|
|
8266
8717
|
const created = [];
|
|
8267
8718
|
const skipped = [];
|
|
8268
8719
|
for (const [relPath, content] of Object.entries(EXAMPLE_PAGES)) {
|
|
8269
|
-
const absPath =
|
|
8720
|
+
const absPath = join41(input.vault, relPath);
|
|
8270
8721
|
try {
|
|
8271
8722
|
await stat8(absPath);
|
|
8272
8723
|
skipped.push(relPath);
|
|
8273
8724
|
} catch {
|
|
8274
|
-
await mkdir14(
|
|
8725
|
+
await mkdir14(join41(absPath, ".."), { recursive: true });
|
|
8275
8726
|
await writeFile15(absPath, content, "utf8");
|
|
8276
8727
|
created.push(relPath);
|
|
8277
8728
|
}
|
|
8278
8729
|
}
|
|
8279
|
-
const rawPath =
|
|
8730
|
+
const rawPath = join41(input.vault, "raw", "articles", "example-source.md");
|
|
8280
8731
|
try {
|
|
8281
8732
|
await stat8(rawPath);
|
|
8282
8733
|
skipped.push("raw/articles/example-source.md");
|
|
8283
8734
|
} catch {
|
|
8284
|
-
await mkdir14(
|
|
8735
|
+
await mkdir14(join41(rawPath, ".."), { recursive: true });
|
|
8285
8736
|
await writeFile15(rawPath, EXAMPLE_RAW, "utf8");
|
|
8286
8737
|
created.push("raw/articles/example-source.md");
|
|
8287
8738
|
}
|
|
@@ -8305,8 +8756,8 @@ async function runSeed(input) {
|
|
|
8305
8756
|
|
|
8306
8757
|
// src/commands/canvas.ts
|
|
8307
8758
|
import { readFile as readFile26, writeFile as writeFile16 } from "fs/promises";
|
|
8308
|
-
import { existsSync as
|
|
8309
|
-
import { join as
|
|
8759
|
+
import { existsSync as existsSync18 } from "fs";
|
|
8760
|
+
import { join as join42 } from "path";
|
|
8310
8761
|
var NODE_WIDTH = 240;
|
|
8311
8762
|
var NODE_HEIGHT = 60;
|
|
8312
8763
|
var COLUMN_SPACING = 400;
|
|
@@ -8384,8 +8835,8 @@ function buildCanvasEdges(adjacency) {
|
|
|
8384
8835
|
return edges;
|
|
8385
8836
|
}
|
|
8386
8837
|
async function runCanvasGenerate(input) {
|
|
8387
|
-
const graphPath = input.graphPath ??
|
|
8388
|
-
if (!
|
|
8838
|
+
const graphPath = input.graphPath ?? join42(input.vault, ".skillwiki", "graph.json");
|
|
8839
|
+
if (!existsSync18(graphPath)) {
|
|
8389
8840
|
return {
|
|
8390
8841
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
8391
8842
|
result: err("FILE_NOT_FOUND", {
|
|
@@ -8422,7 +8873,7 @@ async function runCanvasGenerate(input) {
|
|
|
8422
8873
|
const nodes = buildCanvasNodes(paths);
|
|
8423
8874
|
const edges = buildCanvasEdges(graph.adjacency);
|
|
8424
8875
|
const canvas = { nodes, edges };
|
|
8425
|
-
const outPath =
|
|
8876
|
+
const outPath = join42(input.vault, "vault-graph.canvas");
|
|
8426
8877
|
try {
|
|
8427
8878
|
await writeFile16(outPath, JSON.stringify(canvas, null, 2));
|
|
8428
8879
|
} catch (e) {
|
|
@@ -8445,7 +8896,7 @@ written: ${outPath}`
|
|
|
8445
8896
|
|
|
8446
8897
|
// src/commands/query.ts
|
|
8447
8898
|
import { readFile as readFile27, stat as stat9 } from "fs/promises";
|
|
8448
|
-
import { join as
|
|
8899
|
+
import { join as join43 } from "path";
|
|
8449
8900
|
var W_KEYWORD = 2;
|
|
8450
8901
|
var W_SOURCE_OVERLAP = 4;
|
|
8451
8902
|
var W_WIKILINK = 3;
|
|
@@ -8566,7 +9017,7 @@ function computeKeywordScore(terms, title, tags, body) {
|
|
|
8566
9017
|
return score;
|
|
8567
9018
|
}
|
|
8568
9019
|
async function loadOrBuildGraph(vault) {
|
|
8569
|
-
const graphPath =
|
|
9020
|
+
const graphPath = join43(vault, ".skillwiki", "graph.json");
|
|
8570
9021
|
let needsBuild = false;
|
|
8571
9022
|
try {
|
|
8572
9023
|
const fileStat = await stat9(graphPath);
|
|
@@ -8588,14 +9039,14 @@ async function loadOrBuildGraph(vault) {
|
|
|
8588
9039
|
}
|
|
8589
9040
|
|
|
8590
9041
|
// src/utils/auto-commit.ts
|
|
8591
|
-
import { existsSync as
|
|
8592
|
-
import { join as
|
|
9042
|
+
import { existsSync as existsSync19 } from "fs";
|
|
9043
|
+
import { join as join44 } from "path";
|
|
8593
9044
|
async function postCommit(vault, exitCode) {
|
|
8594
9045
|
if (exitCode !== 0) return;
|
|
8595
9046
|
const home = process.env.HOME ?? "";
|
|
8596
9047
|
const dotenv = await parseDotenvFile(configPath(home));
|
|
8597
9048
|
if (dotenv["AUTO_COMMIT"] === "false") return;
|
|
8598
|
-
if (!
|
|
9049
|
+
if (!existsSync19(join44(vault, ".git"))) return;
|
|
8599
9050
|
const lastOps = readLastOp(vault);
|
|
8600
9051
|
if (lastOps.length === 0) return;
|
|
8601
9052
|
const porcelain = git(vault, ["status", "--porcelain"]);
|
|
@@ -8624,14 +9075,14 @@ async function postCommit(vault, exitCode) {
|
|
|
8624
9075
|
}
|
|
8625
9076
|
|
|
8626
9077
|
// src/cli.ts
|
|
8627
|
-
var pkg =
|
|
9078
|
+
var pkg = readCliPackageJson();
|
|
8628
9079
|
var program = new Command2();
|
|
8629
9080
|
program.name("skillwiki").description("Deterministic helpers for CodeWiki skills").version(pkg.version);
|
|
8630
9081
|
program.option("--human", "render terminal-readable output instead of JSON");
|
|
8631
|
-
async function emit(r, vault) {
|
|
9082
|
+
async function emit(r, vault, opts) {
|
|
8632
9083
|
if (program.opts().human) printHuman(r.result);
|
|
8633
9084
|
else printJson(r.result);
|
|
8634
|
-
if (vault) await postCommit(vault, r.exitCode);
|
|
9085
|
+
if (vault && opts?.postCommit !== false) await postCommit(vault, r.exitCode);
|
|
8635
9086
|
process.exit(r.exitCode);
|
|
8636
9087
|
}
|
|
8637
9088
|
program.command("hash <file>").description("compute SHA-256 hash of a vault page body").action(async (file) => emit(await runHash({ file })));
|
|
@@ -8646,7 +9097,7 @@ program.command("validate <file>").description("validate vault page frontmatter
|
|
|
8646
9097
|
emit(await runValidate({ file, apply: !!opts.apply, vault }), vault);
|
|
8647
9098
|
});
|
|
8648
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) => {
|
|
8649
|
-
const out = opts.out ??
|
|
9100
|
+
const out = opts.out ?? join45(vault, ".skillwiki", "graph.json");
|
|
8650
9101
|
emit(await runGraphBuild({ vault, out }), vault);
|
|
8651
9102
|
});
|
|
8652
9103
|
var canvasCmd = program.command("canvas").description("manage Obsidian canvas files");
|
|
@@ -8777,9 +9228,20 @@ program.command("log-append [vault]").description("append a single entry to the
|
|
|
8777
9228
|
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
8778
9229
|
else emit(await runLogAppend({ vault: v.vault, content: opts.content }), v.vault);
|
|
8779
9230
|
});
|
|
8780
|
-
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) => {
|
|
8781
9232
|
const v = await resolveVaultArg(vault, opts.wiki);
|
|
8782
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);
|
|
8783
9245
|
else emit(await runLint({
|
|
8784
9246
|
vault: v.vault,
|
|
8785
9247
|
source: vault ? "flag" : void 0,
|
|
@@ -8790,6 +9252,27 @@ program.command("lint [vault]").description("run all vault health checks").optio
|
|
|
8790
9252
|
only: opts.only
|
|
8791
9253
|
}), v.vault);
|
|
8792
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
|
+
});
|
|
8793
9276
|
var configCmd = program.command("config").description("manage skillwiki configuration");
|
|
8794
9277
|
configCmd.command("get <key>").description("print the value of a config key").action(async (key) => emit(await runConfigGet({ key, home: process.env.HOME ?? "" })));
|
|
8795
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 ?? "" })));
|