skillwiki 0.5.1 → 0.5.4
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
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
|
|
10
10
|
// src/cli.ts
|
|
11
11
|
import { readFileSync as readFileSync10 } from "fs";
|
|
12
|
-
import { join as
|
|
12
|
+
import { join as join39 } from "path";
|
|
13
13
|
import { Command as Command2 } from "commander";
|
|
14
14
|
|
|
15
15
|
// ../shared/src/exit-codes.ts
|
|
@@ -59,7 +59,9 @@ var ExitCode = {
|
|
|
59
59
|
SYNC_PUSH_FAILED: 42,
|
|
60
60
|
SYNC_PULL_FAILED: 43,
|
|
61
61
|
BACKUP_SYNC_FAILED: 44,
|
|
62
|
-
BACKUP_RESTORE_CONFLICTS: 45
|
|
62
|
+
BACKUP_RESTORE_CONFLICTS: 45,
|
|
63
|
+
USAGE: 46,
|
|
64
|
+
BODY_TRUNCATION_GUARD: 47
|
|
63
65
|
};
|
|
64
66
|
|
|
65
67
|
// ../shared/src/json-output.ts
|
|
@@ -91,7 +93,8 @@ var TypedKnowledgeSchema = z.object({
|
|
|
91
93
|
contradictions: z.array(z.string()).optional(),
|
|
92
94
|
provenance: z.enum(["research", "project", "mixed"]).optional(),
|
|
93
95
|
provenance_projects: z.array(wikilink).optional(),
|
|
94
|
-
work_items: z.array(wikilink).optional()
|
|
96
|
+
work_items: z.array(wikilink).optional(),
|
|
97
|
+
stale_ttl: z.number().int().positive().optional()
|
|
95
98
|
}).superRefine((v, ctx) => {
|
|
96
99
|
if (v.provenance && v.provenance !== "research" && (!v.provenance_projects || v.provenance_projects.length === 0)) {
|
|
97
100
|
ctx.addIssue({ code: z.ZodIssueCode.custom, path: ["provenance_projects"], message: "required when provenance != research" });
|
|
@@ -305,11 +308,11 @@ async function runHash(input) {
|
|
|
305
308
|
}
|
|
306
309
|
const split = splitFrontmatter(text);
|
|
307
310
|
if (!split.ok) return { exitCode: ExitCode.MISSING_CLOSING_DELIMITER, result: split };
|
|
308
|
-
const
|
|
309
|
-
const sha256 = createHash("sha256").update(
|
|
311
|
+
const bodyBytes2 = Buffer.from(split.data.body, "utf8");
|
|
312
|
+
const sha256 = createHash("sha256").update(bodyBytes2).digest("hex");
|
|
310
313
|
return {
|
|
311
314
|
exitCode: ExitCode.OK,
|
|
312
|
-
result: ok({ path: input.file, sha256, byte_count:
|
|
315
|
+
result: ok({ path: input.file, sha256, byte_count: bodyBytes2.byteLength, humanHint: sha256 })
|
|
313
316
|
};
|
|
314
317
|
}
|
|
315
318
|
|
|
@@ -1004,8 +1007,10 @@ function isLegacyCitationStyle(body) {
|
|
|
1004
1007
|
return false;
|
|
1005
1008
|
}
|
|
1006
1009
|
function hasOrphanedCitations(body) {
|
|
1007
|
-
const
|
|
1010
|
+
const noFm = body.replace(FRONTMATTER, "");
|
|
1011
|
+
const stripped = stripFences(noFm);
|
|
1008
1012
|
const lines = stripped.split("\n");
|
|
1013
|
+
const rawLines = noFm.split("\n");
|
|
1009
1014
|
let inSources = false;
|
|
1010
1015
|
let sourcesEnded = false;
|
|
1011
1016
|
let sourcesStartLine = -1;
|
|
@@ -1025,10 +1030,13 @@ function hasOrphanedCitations(body) {
|
|
|
1025
1030
|
}
|
|
1026
1031
|
continue;
|
|
1027
1032
|
}
|
|
1028
|
-
const isListItem = /^\s*[-*]\s+/.test(line);
|
|
1033
|
+
const isListItem = /^\s*(?:[-*]|\d+\.)\s+/.test(line);
|
|
1029
1034
|
const hasMarker = /\^\[raw\//.test(line);
|
|
1035
|
+
const hasBacktickRawPath = /`raw\/[^`]+`/.test(rawLines[i]);
|
|
1030
1036
|
if (isListItem && hasMarker) {
|
|
1031
1037
|
lastNonBlankInSources = i;
|
|
1038
|
+
} else if (isListItem && hasBacktickRawPath) {
|
|
1039
|
+
lastNonBlankInSources = i;
|
|
1032
1040
|
} else if (hasMarker && !isListItem) {
|
|
1033
1041
|
return true;
|
|
1034
1042
|
} else {
|
|
@@ -1835,6 +1843,42 @@ async function runIndexCheck(input) {
|
|
|
1835
1843
|
// src/commands/stale.ts
|
|
1836
1844
|
import { readdir as readdir4, rename as rename2, mkdir as mkdir6, readFile as readFile10 } from "fs/promises";
|
|
1837
1845
|
import { join as join13 } from "path";
|
|
1846
|
+
|
|
1847
|
+
// src/parsers/expiry-annotations.ts
|
|
1848
|
+
var HEADING_RE = /^#{1,6}\s+(.+)$/;
|
|
1849
|
+
var ANNOTATION_RE = /^<!--\s*expires:\s*(\d{4}-\d{2}-\d{2})(?:\s+refresh:\s*(weekly|monthly|quarterly))?(?:\s+source:\s*(\S+))?\s*-->$/;
|
|
1850
|
+
var VALID_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
1851
|
+
function isValidDate(s) {
|
|
1852
|
+
if (!VALID_DATE_RE.test(s)) return false;
|
|
1853
|
+
const d = /* @__PURE__ */ new Date(s + "T00:00:00Z");
|
|
1854
|
+
return !Number.isNaN(d.getTime()) && s === d.toISOString().slice(0, 10);
|
|
1855
|
+
}
|
|
1856
|
+
function parseExpiryAnnotations(content, pagePath) {
|
|
1857
|
+
const lines = content.split("\n");
|
|
1858
|
+
const annotations = [];
|
|
1859
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1860
|
+
const match = lines[i].match(ANNOTATION_RE);
|
|
1861
|
+
if (!match) continue;
|
|
1862
|
+
const expires = match[1];
|
|
1863
|
+
if (!isValidDate(expires)) continue;
|
|
1864
|
+
if (i === 0) continue;
|
|
1865
|
+
const prevLine = lines[i - 1];
|
|
1866
|
+
const headingMatch = prevLine.match(HEADING_RE);
|
|
1867
|
+
if (!headingMatch) continue;
|
|
1868
|
+
annotations.push({
|
|
1869
|
+
page: pagePath,
|
|
1870
|
+
heading: headingMatch[1].trim(),
|
|
1871
|
+
line: i + 1,
|
|
1872
|
+
// 1-indexed
|
|
1873
|
+
expires,
|
|
1874
|
+
refresh: match[2],
|
|
1875
|
+
source: match[3]
|
|
1876
|
+
});
|
|
1877
|
+
}
|
|
1878
|
+
return annotations;
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
// src/commands/stale.ts
|
|
1838
1882
|
function daysSince(isoDate2) {
|
|
1839
1883
|
return Math.floor((Date.now() - Date.parse(isoDate2)) / 864e5);
|
|
1840
1884
|
}
|
|
@@ -1852,6 +1896,12 @@ async function runStale(input) {
|
|
|
1852
1896
|
projectSlugs = (await readdir4(projectsDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
1853
1897
|
} catch {
|
|
1854
1898
|
}
|
|
1899
|
+
if (input.project) {
|
|
1900
|
+
if (!projectSlugs.includes(input.project)) {
|
|
1901
|
+
return { exitCode: ExitCode.USAGE, result: { ok: false, error: "UNKNOWN_PROJECT", detail: `Project "${input.project}" not found. Available: ${projectSlugs.join(", ") || "(none)"}` } };
|
|
1902
|
+
}
|
|
1903
|
+
projectSlugs = [input.project];
|
|
1904
|
+
}
|
|
1855
1905
|
for (const slug of projectSlugs) {
|
|
1856
1906
|
const workPath = join13(projectsDir, slug, "work");
|
|
1857
1907
|
let entries;
|
|
@@ -1893,9 +1943,10 @@ async function runStale(input) {
|
|
|
1893
1943
|
function extractSlug(projectField) {
|
|
1894
1944
|
return projectField.replace(/^\[\[/, "").replace(/\]\]$/, "").replace(/^"|"$/g, "");
|
|
1895
1945
|
}
|
|
1946
|
+
const TERMINAL_STATUSES = /* @__PURE__ */ new Set(["completed", "abandoned", "done", "invalid"]);
|
|
1896
1947
|
const KIND_FROM_FILENAME = /^(?:\d{4}-\d{2}-\d{2})-(task|bug|idea|note|observation)-.+\.md$/;
|
|
1897
1948
|
const LOOP_CYCLE_PATTERN = /loop-cycle-/;
|
|
1898
|
-
|
|
1949
|
+
let transcripts = scan.data.raw.filter((p) => p.relPath.startsWith("raw/transcripts/") && p.relPath.endsWith(".md"));
|
|
1899
1950
|
const claimedPaths = /* @__PURE__ */ new Set();
|
|
1900
1951
|
const transcriptMeta = /* @__PURE__ */ new Map();
|
|
1901
1952
|
for (const t of transcripts) {
|
|
@@ -1904,11 +1955,12 @@ async function runStale(input) {
|
|
|
1904
1955
|
const fm = extractFrontmatter(content);
|
|
1905
1956
|
let kind = fm.ok && typeof fm.data.kind === "string" ? fm.data.kind : "";
|
|
1906
1957
|
let project = fm.ok && typeof fm.data.project === "string" ? fm.data.project : "";
|
|
1958
|
+
if (input.project && !project.includes(input.project)) continue;
|
|
1907
1959
|
let inferred = false;
|
|
1908
1960
|
if (input.forceScan && !kind) {
|
|
1909
|
-
const
|
|
1910
|
-
if (!LOOP_CYCLE_PATTERN.test(
|
|
1911
|
-
const m =
|
|
1961
|
+
const basename2 = t.relPath.split("/").pop();
|
|
1962
|
+
if (!LOOP_CYCLE_PATTERN.test(basename2)) {
|
|
1963
|
+
const m = basename2.match(KIND_FROM_FILENAME);
|
|
1912
1964
|
if (m) {
|
|
1913
1965
|
kind = m[1];
|
|
1914
1966
|
inferred = true;
|
|
@@ -1948,7 +2000,7 @@ async function runStale(input) {
|
|
|
1948
2000
|
const overlap = wWords.filter((w) => tWords.has(w)).length;
|
|
1949
2001
|
if (dirName.includes(tSlug) || tSlug.includes(wSlug) || overlap >= 1) {
|
|
1950
2002
|
claimedPaths.add(t.relPath);
|
|
1951
|
-
if (status
|
|
2003
|
+
if (TERMINAL_STATUSES.has(status)) {
|
|
1952
2004
|
staleTranscripts.push({ path: t.relPath, reason: `work item projects/${slug}/work/${dirName} is ${status}` });
|
|
1953
2005
|
}
|
|
1954
2006
|
break;
|
|
@@ -1958,7 +2010,7 @@ async function runStale(input) {
|
|
|
1958
2010
|
for (const [dir, status] of workDirs) {
|
|
1959
2011
|
if (dir.split("/").pop().startsWith(datePrefix)) {
|
|
1960
2012
|
claimedPaths.add(t.relPath);
|
|
1961
|
-
if (status
|
|
2013
|
+
if (TERMINAL_STATUSES.has(status)) {
|
|
1962
2014
|
staleTranscripts.push({ path: t.relPath, reason: `work item ${dir} is ${status}` });
|
|
1963
2015
|
}
|
|
1964
2016
|
break;
|
|
@@ -2003,10 +2055,8 @@ async function runStale(input) {
|
|
|
2003
2055
|
continue;
|
|
2004
2056
|
}
|
|
2005
2057
|
const hasSpec = files.includes("spec.md"), hasPlan = files.includes("plan.md"), hasWI = files.includes("work-item.md");
|
|
2006
|
-
if (status
|
|
2007
|
-
doneWorkItems.push({ path: relDir, reason: "completed \u2014 should be archived
|
|
2008
|
-
} else if (status === "invalid") {
|
|
2009
|
-
doneWorkItems.push({ path: relDir, reason: "invalid \u2014 should be archived" });
|
|
2058
|
+
if (TERMINAL_STATUSES.has(status)) {
|
|
2059
|
+
doneWorkItems.push({ path: relDir, reason: `${status || "completed"} \u2014 should be archived` });
|
|
2010
2060
|
} else if (hasSpec && !hasPlan) {
|
|
2011
2061
|
incompleteWorkItems.push({ path: relDir, reason: "has spec but no plan" });
|
|
2012
2062
|
} else if (hasWI && !hasSpec && !hasPlan) {
|
|
@@ -2019,16 +2069,53 @@ async function runStale(input) {
|
|
|
2019
2069
|
const text = await readFile10(join13(input.vault, page.relPath), "utf8");
|
|
2020
2070
|
const fm = extractFrontmatter(text);
|
|
2021
2071
|
if (fm.ok && typeof fm.data.updated === "string") {
|
|
2072
|
+
if (input.project) {
|
|
2073
|
+
const pp = fm.data.provenance_projects;
|
|
2074
|
+
const linked = Array.isArray(pp) && pp.some((p) => String(p).includes(input.project));
|
|
2075
|
+
if (!linked) continue;
|
|
2076
|
+
}
|
|
2022
2077
|
const age = daysSince(fm.data.updated);
|
|
2023
|
-
|
|
2024
|
-
|
|
2078
|
+
const threshold = typeof fm.data.stale_ttl === "number" && fm.data.stale_ttl > 0 ? fm.data.stale_ttl : input.days;
|
|
2079
|
+
if (age >= threshold) {
|
|
2080
|
+
stale.push({ page: page.relPath, reason: `updated ${age} days ago (threshold: ${threshold})` });
|
|
2025
2081
|
}
|
|
2026
2082
|
}
|
|
2027
2083
|
} catch {
|
|
2028
2084
|
}
|
|
2029
2085
|
}
|
|
2086
|
+
const staleSections = [];
|
|
2087
|
+
for (const page of scan.data.typedKnowledge) {
|
|
2088
|
+
try {
|
|
2089
|
+
const text = await readFile10(join13(input.vault, page.relPath), "utf8");
|
|
2090
|
+
const projectFilter = input.project;
|
|
2091
|
+
if (projectFilter) {
|
|
2092
|
+
const fm = extractFrontmatter(text);
|
|
2093
|
+
if (fm.ok) {
|
|
2094
|
+
const pp = fm.data.provenance_projects;
|
|
2095
|
+
const linked = Array.isArray(pp) && pp.some((p) => String(p).includes(projectFilter));
|
|
2096
|
+
if (!linked) continue;
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
const annotations = parseExpiryAnnotations(text, page.relPath);
|
|
2100
|
+
for (const ann of annotations) {
|
|
2101
|
+
if (daysSince(ann.expires) >= 0) {
|
|
2102
|
+
staleSections.push({
|
|
2103
|
+
page: ann.page,
|
|
2104
|
+
heading: ann.heading,
|
|
2105
|
+
line: ann.line,
|
|
2106
|
+
expires: ann.expires,
|
|
2107
|
+
refresh: ann.refresh,
|
|
2108
|
+
source: ann.source,
|
|
2109
|
+
reason: `section "${ann.heading}" expired on ${ann.expires}`
|
|
2110
|
+
});
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
} catch {
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
2030
2117
|
if (input.archive) {
|
|
2031
|
-
const archiveDir = join13(input.vault, "_archive",
|
|
2118
|
+
const archiveDir = join13(input.vault, "_archive", today);
|
|
2032
2119
|
await mkdir6(archiveDir, { recursive: true });
|
|
2033
2120
|
const citedRawPaths = /* @__PURE__ */ new Set();
|
|
2034
2121
|
for (const page of scan.data.typedKnowledge) {
|
|
@@ -2082,7 +2169,7 @@ async function runStale(input) {
|
|
|
2082
2169
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2083
2170
|
});
|
|
2084
2171
|
}
|
|
2085
|
-
const total = stale.length + staleTranscripts.length + unclaimedTranscripts.length + incompleteWorkItems.length + doneWorkItems.length;
|
|
2172
|
+
const total = stale.length + staleTranscripts.length + unclaimedTranscripts.length + incompleteWorkItems.length + doneWorkItems.length + staleSections.length;
|
|
2086
2173
|
const hintLines = [];
|
|
2087
2174
|
if (stale.length > 0) hintLines.push(`stale_pages: ${stale.length}`, ...stale.map((p) => ` ${p.page}: ${p.reason}`));
|
|
2088
2175
|
if (staleTranscripts.length > 0) hintLines.push(`stale_transcripts: ${staleTranscripts.length}`, ...staleTranscripts.map((t) => ` ${t.path}: ${t.reason}`));
|
|
@@ -2090,6 +2177,7 @@ async function runStale(input) {
|
|
|
2090
2177
|
hint: ${t.hint}` : ""}`));
|
|
2091
2178
|
if (incompleteWorkItems.length > 0) hintLines.push(`incomplete_work_items: ${incompleteWorkItems.length}`, ...incompleteWorkItems.map((w) => ` ${w.path}: ${w.reason}`));
|
|
2092
2179
|
if (doneWorkItems.length > 0) hintLines.push(`done_work_items: ${doneWorkItems.length}`, ...doneWorkItems.map((w) => ` ${w.path}: ${w.reason}`));
|
|
2180
|
+
if (staleSections.length > 0) hintLines.push(`stale_sections: ${staleSections.length}`, ...staleSections.map((s) => ` ${s.page}#${s.heading}: ${s.reason}`));
|
|
2093
2181
|
if (archived.length > 0) hintLines.push(`archived: ${archived.length}`, ...archived.map((a) => ` ${a}`));
|
|
2094
2182
|
if (hintLines.length === 0) hintLines.push("no stale transcripts or incomplete work items");
|
|
2095
2183
|
return { exitCode: total > 0 ? ExitCode.STALE_PAGE : ExitCode.OK, result: ok({
|
|
@@ -2098,6 +2186,7 @@ async function runStale(input) {
|
|
|
2098
2186
|
unclaimed_transcripts: unclaimedTranscripts,
|
|
2099
2187
|
incomplete_work_items: incompleteWorkItems,
|
|
2100
2188
|
done_work_items: doneWorkItems,
|
|
2189
|
+
stale_sections: staleSections,
|
|
2101
2190
|
archived,
|
|
2102
2191
|
humanHint: hintLines.join("\n")
|
|
2103
2192
|
}) };
|
|
@@ -2274,8 +2363,8 @@ Chronological action log. Newest entries last. Skill writes append entries; lint
|
|
|
2274
2363
|
|
|
2275
2364
|
// src/commands/lint.ts
|
|
2276
2365
|
import { existsSync as existsSync3 } from "fs";
|
|
2277
|
-
import { readFile as
|
|
2278
|
-
import { join as
|
|
2366
|
+
import { readFile as readFile15 } from "fs/promises";
|
|
2367
|
+
import { join as join19 } from "path";
|
|
2279
2368
|
|
|
2280
2369
|
// src/commands/topic-map-check.ts
|
|
2281
2370
|
var DEFAULT_THRESHOLD = 200;
|
|
@@ -2402,6 +2491,81 @@ async function runDedup(input) {
|
|
|
2402
2491
|
};
|
|
2403
2492
|
}
|
|
2404
2493
|
|
|
2494
|
+
// src/utils/safe-write.ts
|
|
2495
|
+
import { open, readFile as readFile14, rename as rename4, unlink as unlink2, writeFile as writeFile8 } from "fs/promises";
|
|
2496
|
+
import { randomBytes } from "crypto";
|
|
2497
|
+
import { dirname as dirname7, basename, join as join18 } from "path";
|
|
2498
|
+
var DEFAULT_MIN_BODY_RATIO = 0.5;
|
|
2499
|
+
var DEFAULT_MIN_OLD_BODY_BYTES = 200;
|
|
2500
|
+
function bodyBytes(text) {
|
|
2501
|
+
const split = splitFrontmatter(text);
|
|
2502
|
+
if (!split.ok) return Buffer.byteLength(text, "utf8");
|
|
2503
|
+
return Buffer.byteLength(split.data.body, "utf8");
|
|
2504
|
+
}
|
|
2505
|
+
async function readIfExists(absPath) {
|
|
2506
|
+
try {
|
|
2507
|
+
return await readFile14(absPath, "utf8");
|
|
2508
|
+
} catch (e) {
|
|
2509
|
+
if (e.code === "ENOENT") return null;
|
|
2510
|
+
throw e;
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
async function safeWritePage(absPath, newContent, opts = {}) {
|
|
2514
|
+
const minRatio = opts.minBodyRatio === void 0 ? DEFAULT_MIN_BODY_RATIO : opts.minBodyRatio;
|
|
2515
|
+
const minOldBytes = opts.minOldBodyBytes ?? DEFAULT_MIN_OLD_BODY_BYTES;
|
|
2516
|
+
let oldContent;
|
|
2517
|
+
try {
|
|
2518
|
+
oldContent = await readIfExists(absPath);
|
|
2519
|
+
} catch (e) {
|
|
2520
|
+
return err("WRITE_FAILED", { path: absPath, phase: "read-existing", message: String(e) });
|
|
2521
|
+
}
|
|
2522
|
+
const isNew = oldContent === null;
|
|
2523
|
+
const oldBodyBytes = isNew ? 0 : bodyBytes(oldContent);
|
|
2524
|
+
const newBodyBytes = bodyBytes(newContent);
|
|
2525
|
+
const bodyRatio = oldBodyBytes > 0 ? newBodyBytes / oldBodyBytes : null;
|
|
2526
|
+
let guardSkippedSmall = false;
|
|
2527
|
+
if (!isNew && minRatio !== null && bodyRatio !== null && bodyRatio < minRatio) {
|
|
2528
|
+
if (oldBodyBytes < minOldBytes) {
|
|
2529
|
+
guardSkippedSmall = true;
|
|
2530
|
+
} else {
|
|
2531
|
+
return err("BODY_TRUNCATION_GUARD", {
|
|
2532
|
+
path: absPath,
|
|
2533
|
+
oldBodyBytes,
|
|
2534
|
+
newBodyBytes,
|
|
2535
|
+
bodyRatio,
|
|
2536
|
+
minBodyRatio: minRatio,
|
|
2537
|
+
hint: "Refusing to write \u2014 new body lost too much content. Likely a parse-modify-serialize bug or a write race. Verify the page source before retrying."
|
|
2538
|
+
});
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
if (!isNew && oldContent === newContent) {
|
|
2542
|
+
return ok({ isNew: false, oldBodyBytes, newBodyBytes, bodyRatio, guardSkippedSmall });
|
|
2543
|
+
}
|
|
2544
|
+
const dir = dirname7(absPath);
|
|
2545
|
+
const tmpName = `.${basename(absPath)}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
|
|
2546
|
+
const tmpPath = join18(dir, tmpName);
|
|
2547
|
+
try {
|
|
2548
|
+
const handle = await open(tmpPath, "w");
|
|
2549
|
+
try {
|
|
2550
|
+
await handle.writeFile(newContent, "utf8");
|
|
2551
|
+
try {
|
|
2552
|
+
await handle.sync();
|
|
2553
|
+
} catch {
|
|
2554
|
+
}
|
|
2555
|
+
} finally {
|
|
2556
|
+
await handle.close();
|
|
2557
|
+
}
|
|
2558
|
+
await rename4(tmpPath, absPath);
|
|
2559
|
+
return ok({ isNew, oldBodyBytes, newBodyBytes, bodyRatio, guardSkippedSmall });
|
|
2560
|
+
} catch (e) {
|
|
2561
|
+
try {
|
|
2562
|
+
await unlink2(tmpPath);
|
|
2563
|
+
} catch {
|
|
2564
|
+
}
|
|
2565
|
+
return err("WRITE_FAILED", { path: absPath, phase: "atomic-write", message: String(e) });
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2405
2569
|
// src/commands/raw-body-dedup.ts
|
|
2406
2570
|
import { createHash as createHash2 } from "crypto";
|
|
2407
2571
|
async function runRawBodyDedup(vault) {
|
|
@@ -2466,11 +2630,11 @@ function buildCliSurface() {
|
|
|
2466
2630
|
program2.command("index-check").option("--wiki <name>");
|
|
2467
2631
|
program2.command("index-link-format").option("--wiki <name>");
|
|
2468
2632
|
program2.command("topic-map-check").option("--threshold <n>").option("--wiki <name>");
|
|
2469
|
-
program2.command("stale").option("--archive").option("--days <n>").option("--force-scan").option("--wiki <name>");
|
|
2633
|
+
program2.command("stale").option("--archive").option("--days <n>").option("--force-scan").option("--project <slug>").option("--refresh").option("--wiki <name>");
|
|
2470
2634
|
program2.command("claim").option("--project <slug>").option("--slug <slug>").option("--wiki <name>");
|
|
2471
2635
|
program2.command("pagesize").option("--lines <n>").option("--wiki <name>");
|
|
2472
2636
|
program2.command("log-rotate").option("--threshold <n>").option("--apply").option("--wiki <name>");
|
|
2473
|
-
program2.command("lint").option("--days <n>").option("--lines <n>").option("--log-threshold <n>").option("--fix").option("--wiki <name>");
|
|
2637
|
+
program2.command("lint").option("--days <n>").option("--lines <n>").option("--log-threshold <n>").option("--fix").option("--only <bucket>").option("--wiki <name>");
|
|
2474
2638
|
program2.command("config");
|
|
2475
2639
|
program2.command("doctor");
|
|
2476
2640
|
program2.command("status").option("--wiki <name>");
|
|
@@ -2615,8 +2779,8 @@ function extractSourceEntries(rawFm) {
|
|
|
2615
2779
|
return entries;
|
|
2616
2780
|
}
|
|
2617
2781
|
var ERROR_ORDER = ["broken_wikilinks", "invalid_frontmatter", "raw_dedup", "broken_sources", "tag_not_in_taxonomy"];
|
|
2618
|
-
var WARNING_ORDER = ["raw_body_duplicate", "raw_subdirectory_duplicate", "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"];
|
|
2619
|
-
var INFO_ORDER = ["bridges", "page_structure", "topic_map_recommended", "frontmatter_wikilink", "wikilink_citation", "missing_tldr", "
|
|
2782
|
+
var WARNING_ORDER = ["raw_body_duplicate", "raw_subdirectory_duplicate", "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"];
|
|
2783
|
+
var INFO_ORDER = ["bridges", "page_structure", "topic_map_recommended", "frontmatter_wikilink", "wikilink_citation", "missing_tldr", "stale_sections", "cli_refs"];
|
|
2620
2784
|
async function runLint(input) {
|
|
2621
2785
|
const buckets = {};
|
|
2622
2786
|
const fixed = [];
|
|
@@ -2725,7 +2889,7 @@ async function runLint(input) {
|
|
|
2725
2889
|
let rawPath = entry.replace(/^"/, "").replace(/"$/, "").replace(/^'/, "").replace(/'$/, "");
|
|
2726
2890
|
rawPath = rawPath.replace(/^\^\[/, "").replace(/\]$/, "");
|
|
2727
2891
|
if (!rawPath.startsWith("raw/") && !rawPath.startsWith("_archive/raw/")) continue;
|
|
2728
|
-
if (!existsSync3(
|
|
2892
|
+
if (!existsSync3(join19(input.vault, rawPath)) && !existsSync3(join19(input.vault, rawPath + ".md")) && !rawPath.startsWith("_archive/") && !existsSync3(join19(input.vault, "_archive", rawPath)) && !existsSync3(join19(input.vault, "_archive", rawPath + ".md"))) {
|
|
2729
2893
|
brokenSourceFlags.push(`${page.relPath}: ${rawPath}`);
|
|
2730
2894
|
}
|
|
2731
2895
|
}
|
|
@@ -2785,7 +2949,7 @@ async function runLint(input) {
|
|
|
2785
2949
|
const text = await readPage(specPage);
|
|
2786
2950
|
const fm = extractFrontmatter(text);
|
|
2787
2951
|
if (fm.ok) {
|
|
2788
|
-
specStatus = fm.data.status;
|
|
2952
|
+
specStatus = typeof fm.data.status === "string" ? fm.data.status : void 0;
|
|
2789
2953
|
specStarted = fm.data.started;
|
|
2790
2954
|
}
|
|
2791
2955
|
}
|
|
@@ -2816,11 +2980,11 @@ async function runLint(input) {
|
|
|
2816
2980
|
const slugMatch = String(entry).match(/\[\[([^\]]+)\]\]/);
|
|
2817
2981
|
if (!slugMatch) continue;
|
|
2818
2982
|
const slug = slugMatch[1];
|
|
2819
|
-
const knowledgePath =
|
|
2983
|
+
const knowledgePath = join19(input.vault, "projects", slug, "knowledge.md");
|
|
2820
2984
|
if (!existsSync3(knowledgePath)) continue;
|
|
2821
2985
|
const pageRef = page.relPath.replace(/\.md$/, "");
|
|
2822
2986
|
try {
|
|
2823
|
-
const knowledgeContent = await
|
|
2987
|
+
const knowledgeContent = await readFile15(knowledgePath, "utf8");
|
|
2824
2988
|
if (!knowledgeContent.includes(`[[${pageRef}]]`)) {
|
|
2825
2989
|
orphanedProjectPages.push(`${page.relPath}: not in projects/${slug}/knowledge.md`);
|
|
2826
2990
|
}
|
|
@@ -2840,13 +3004,34 @@ async function runLint(input) {
|
|
|
2840
3004
|
}
|
|
2841
3005
|
}
|
|
2842
3006
|
if (cliRefFlags.length > 0) buckets.cli_refs = cliRefFlags;
|
|
3007
|
+
const staleSectionFlags = [];
|
|
3008
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
3009
|
+
const approachingThreshold = 7;
|
|
3010
|
+
for (const page of scan.data.typedKnowledge) {
|
|
3011
|
+
try {
|
|
3012
|
+
const text = await readPage(page);
|
|
3013
|
+
const annotations = parseExpiryAnnotations(text, page.relPath);
|
|
3014
|
+
for (const ann of annotations) {
|
|
3015
|
+
if (ann.expires < today) {
|
|
3016
|
+
staleSectionFlags.push(`${page.relPath}: section "${ann.heading}" expired on ${ann.expires}`);
|
|
3017
|
+
} else {
|
|
3018
|
+
const daysUntilExpiry = Math.floor((Date.parse(ann.expires) - Date.now()) / 864e5);
|
|
3019
|
+
if (daysUntilExpiry <= approachingThreshold) {
|
|
3020
|
+
staleSectionFlags.push(`${page.relPath}: section "${ann.heading}" expires in ${daysUntilExpiry} day(s) (${ann.expires})`);
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
} catch {
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
if (staleSectionFlags.length > 0) buckets.stale_sections = staleSectionFlags;
|
|
2843
3028
|
if (input.fix && legacyPages.length > 0) {
|
|
2844
3029
|
const FENCE_RE2 = /```[\s\S]*?```/g;
|
|
2845
3030
|
const INLINE_MARKER = /\^\[raw\/[^\]]+\]/g;
|
|
2846
3031
|
for (const relPath of legacyPages) {
|
|
2847
3032
|
try {
|
|
2848
3033
|
const absPath = `${input.vault}/${relPath}`;
|
|
2849
|
-
const raw = await
|
|
3034
|
+
const raw = await readFile15(absPath, "utf8");
|
|
2850
3035
|
const split = splitFrontmatter(raw);
|
|
2851
3036
|
if (!split.ok) {
|
|
2852
3037
|
unresolved.push(relPath);
|
|
@@ -2924,7 +3109,11 @@ async function runLint(input) {
|
|
|
2924
3109
|
${rawFm}
|
|
2925
3110
|
---
|
|
2926
3111
|
${newBody}`;
|
|
2927
|
-
await
|
|
3112
|
+
const w = await safeWritePage(absPath, newContent);
|
|
3113
|
+
if (!w.ok) {
|
|
3114
|
+
unresolved.push(relPath);
|
|
3115
|
+
continue;
|
|
3116
|
+
}
|
|
2928
3117
|
fixed.push(relPath);
|
|
2929
3118
|
} catch {
|
|
2930
3119
|
unresolved.push(relPath);
|
|
@@ -2941,7 +3130,7 @@ ${newBody}`;
|
|
|
2941
3130
|
for (const relPath of noOverview) {
|
|
2942
3131
|
try {
|
|
2943
3132
|
const absPath = `${input.vault}/${relPath}`;
|
|
2944
|
-
const raw = await
|
|
3133
|
+
const raw = await readFile15(absPath, "utf8");
|
|
2945
3134
|
const split = splitFrontmatter(raw);
|
|
2946
3135
|
if (!split.ok) {
|
|
2947
3136
|
unresolved.push(relPath);
|
|
@@ -2962,7 +3151,11 @@ ${rawFm}
|
|
|
2962
3151
|
${overviewSection}
|
|
2963
3152
|
|
|
2964
3153
|
${trimmedBody}`;
|
|
2965
|
-
await
|
|
3154
|
+
const w = await safeWritePage(absPath, newContent);
|
|
3155
|
+
if (!w.ok) {
|
|
3156
|
+
unresolved.push(relPath);
|
|
3157
|
+
continue;
|
|
3158
|
+
}
|
|
2966
3159
|
fixed.push(relPath);
|
|
2967
3160
|
} catch {
|
|
2968
3161
|
unresolved.push(relPath);
|
|
@@ -2978,7 +3171,7 @@ ${trimmedBody}`;
|
|
|
2978
3171
|
for (const relPath of missingTldrFlags) {
|
|
2979
3172
|
try {
|
|
2980
3173
|
const absPath = `${input.vault}/${relPath}`;
|
|
2981
|
-
const raw = await
|
|
3174
|
+
const raw = await readFile15(absPath, "utf8");
|
|
2982
3175
|
const split = splitFrontmatter(raw);
|
|
2983
3176
|
if (!split.ok) {
|
|
2984
3177
|
unresolved.push(relPath);
|
|
@@ -3006,7 +3199,11 @@ ${trimmedBody}`;
|
|
|
3006
3199
|
const newContent = `---
|
|
3007
3200
|
${trimmedFm}---
|
|
3008
3201
|
${lines.join("\n")}`;
|
|
3009
|
-
await
|
|
3202
|
+
const w = await safeWritePage(absPath, newContent);
|
|
3203
|
+
if (!w.ok) {
|
|
3204
|
+
unresolved.push(relPath);
|
|
3205
|
+
continue;
|
|
3206
|
+
}
|
|
3010
3207
|
fixed.push(relPath);
|
|
3011
3208
|
} catch {
|
|
3012
3209
|
unresolved.push(relPath);
|
|
@@ -3024,7 +3221,7 @@ ${lines.join("\n")}`;
|
|
|
3024
3221
|
for (const relPath of wikilinkCitationFlags) {
|
|
3025
3222
|
try {
|
|
3026
3223
|
const absPath = `${input.vault}/${relPath}`;
|
|
3027
|
-
const raw = await
|
|
3224
|
+
const raw = await readFile15(absPath, "utf8");
|
|
3028
3225
|
const split = splitFrontmatter(raw);
|
|
3029
3226
|
if (!split.ok) {
|
|
3030
3227
|
unresolved.push(relPath);
|
|
@@ -3088,7 +3285,11 @@ ${lines.join("\n")}`;
|
|
|
3088
3285
|
${rawFm}
|
|
3089
3286
|
---
|
|
3090
3287
|
${newBody}`;
|
|
3091
|
-
await
|
|
3288
|
+
const w = await safeWritePage(absPath, newContent);
|
|
3289
|
+
if (!w.ok) {
|
|
3290
|
+
unresolved.push(relPath);
|
|
3291
|
+
continue;
|
|
3292
|
+
}
|
|
3092
3293
|
wikilinkFixed.push(relPath);
|
|
3093
3294
|
} catch {
|
|
3094
3295
|
unresolved.push(relPath);
|
|
@@ -3106,6 +3307,38 @@ ${newBody}`;
|
|
|
3106
3307
|
const errorOut = ERROR_ORDER.flatMap((k) => buckets[k] ? [{ kind: k, items: buckets[k] }] : []);
|
|
3107
3308
|
const warningOut = WARNING_ORDER.flatMap((k) => buckets[k] ? [{ kind: k, items: buckets[k] }] : []);
|
|
3108
3309
|
const infoOut = INFO_ORDER.flatMap((k) => buckets[k] ? [{ kind: k, items: buckets[k] }] : []);
|
|
3310
|
+
if (input.only) {
|
|
3311
|
+
const allKnown = [...ERROR_ORDER, ...WARNING_ORDER, ...INFO_ORDER];
|
|
3312
|
+
if (!allKnown.includes(input.only)) {
|
|
3313
|
+
return {
|
|
3314
|
+
exitCode: ExitCode.USAGE,
|
|
3315
|
+
result: { ok: false, error: "UNKNOWN_BUCKET", detail: `Unknown bucket "${input.only}". Valid: ${allKnown.join(", ")}` }
|
|
3316
|
+
};
|
|
3317
|
+
}
|
|
3318
|
+
const match = [...errorOut, ...warningOut, ...infoOut].filter((b) => b.kind === input.only);
|
|
3319
|
+
const severity = ERROR_ORDER.includes(input.only) ? "error" : WARNING_ORDER.includes(input.only) ? "warning" : "info";
|
|
3320
|
+
const filtered = severity === "error" ? { error: match, warning: [], info: [] } : severity === "warning" ? { error: [], warning: match, info: [] } : { error: [], warning: [], info: match };
|
|
3321
|
+
const fSummary = {
|
|
3322
|
+
errors: filtered.error.reduce((n, b) => n + b.items.length, 0),
|
|
3323
|
+
warnings: filtered.warning.reduce((n, b) => n + b.items.length, 0),
|
|
3324
|
+
info: filtered.info.reduce((n, b) => n + b.items.length, 0)
|
|
3325
|
+
};
|
|
3326
|
+
let fExit = ExitCode.OK;
|
|
3327
|
+
if (fSummary.errors > 0) fExit = ExitCode.LINT_HAS_ERRORS;
|
|
3328
|
+
else if (fSummary.warnings > 0 || fSummary.info > 0) fExit = ExitCode.LINT_HAS_WARNINGS;
|
|
3329
|
+
return {
|
|
3330
|
+
exitCode: fExit,
|
|
3331
|
+
result: ok({
|
|
3332
|
+
vault: { path: input.vault, source: input.source ?? "resolved" },
|
|
3333
|
+
summary: fSummary,
|
|
3334
|
+
by_severity: filtered,
|
|
3335
|
+
fixed,
|
|
3336
|
+
unresolved,
|
|
3337
|
+
humanHint: `--only ${input.only}
|
|
3338
|
+
${match.length === 0 ? "0 violations" : match.map((b) => ` ${b.kind}: ${b.items.length}`).join("\n")}`
|
|
3339
|
+
})
|
|
3340
|
+
};
|
|
3341
|
+
}
|
|
3109
3342
|
const summary = {
|
|
3110
3343
|
errors: errorOut.reduce((n, b) => n + b.items.length, 0),
|
|
3111
3344
|
warnings: warningOut.reduce((n, b) => n + b.items.length, 0),
|
|
@@ -3145,14 +3378,14 @@ ${newBody}`;
|
|
|
3145
3378
|
}
|
|
3146
3379
|
|
|
3147
3380
|
// src/commands/config.ts
|
|
3148
|
-
import { readFile as
|
|
3381
|
+
import { readFile as readFile16 } from "fs/promises";
|
|
3149
3382
|
import { existsSync as existsSync4 } from "fs";
|
|
3150
|
-
import { join as
|
|
3383
|
+
import { join as join20 } from "path";
|
|
3151
3384
|
function validateKey(key) {
|
|
3152
3385
|
return CONFIG_KEYS.includes(key) || isValidWikiProfileKey(key);
|
|
3153
3386
|
}
|
|
3154
3387
|
function configPath(home) {
|
|
3155
|
-
return
|
|
3388
|
+
return join20(home, ".skillwiki", ".env");
|
|
3156
3389
|
}
|
|
3157
3390
|
async function runConfigGet(input) {
|
|
3158
3391
|
if (!validateKey(input.key)) {
|
|
@@ -3170,7 +3403,7 @@ async function runConfigSet(input) {
|
|
|
3170
3403
|
try {
|
|
3171
3404
|
let originalContent;
|
|
3172
3405
|
try {
|
|
3173
|
-
originalContent = await
|
|
3406
|
+
originalContent = await readFile16(filePath, "utf8");
|
|
3174
3407
|
} catch {
|
|
3175
3408
|
}
|
|
3176
3409
|
const existing = originalContent !== void 0 ? parseDotenvText(originalContent) : {};
|
|
@@ -3207,13 +3440,13 @@ async function runConfigPath(input) {
|
|
|
3207
3440
|
|
|
3208
3441
|
// src/commands/doctor.ts
|
|
3209
3442
|
import { existsSync as existsSync6, lstatSync, readlinkSync, readdirSync, readFileSync as readFileSync6, statSync as statSync2 } from "fs";
|
|
3210
|
-
import { join as
|
|
3443
|
+
import { join as join23, resolve as resolve4 } from "path";
|
|
3211
3444
|
import { execSync } from "child_process";
|
|
3212
3445
|
import { platform } from "os";
|
|
3213
3446
|
|
|
3214
3447
|
// src/utils/auto-update.ts
|
|
3215
3448
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
|
|
3216
|
-
import { join as
|
|
3449
|
+
import { join as join21, dirname as dirname8 } from "path";
|
|
3217
3450
|
import { spawn } from "child_process";
|
|
3218
3451
|
|
|
3219
3452
|
// src/utils/update-consts.ts
|
|
@@ -3224,7 +3457,7 @@ var CLI_DISABLE_FLAG = "--no-update-notifier";
|
|
|
3224
3457
|
|
|
3225
3458
|
// src/utils/auto-update.ts
|
|
3226
3459
|
function cachePath(home) {
|
|
3227
|
-
return
|
|
3460
|
+
return join21(home, ".skillwiki", CACHE_FILENAME);
|
|
3228
3461
|
}
|
|
3229
3462
|
function readCacheRaw(home) {
|
|
3230
3463
|
try {
|
|
@@ -3274,12 +3507,12 @@ function triggerAutoUpdate(home, currentVersion) {
|
|
|
3274
3507
|
|
|
3275
3508
|
// src/utils/plugin-registry.ts
|
|
3276
3509
|
import { readFileSync as readFileSync5 } from "fs";
|
|
3277
|
-
import { join as
|
|
3278
|
-
var REGISTRY_PATH =
|
|
3510
|
+
import { join as join22 } from "path";
|
|
3511
|
+
var REGISTRY_PATH = join22(".claude", "plugins", "installed_plugins.json");
|
|
3279
3512
|
var PLUGIN_KEY = "skillwiki@llm-wiki";
|
|
3280
3513
|
function readInstalledPlugins(home) {
|
|
3281
3514
|
try {
|
|
3282
|
-
const raw = readFileSync5(
|
|
3515
|
+
const raw = readFileSync5(join22(home, REGISTRY_PATH), "utf8");
|
|
3283
3516
|
return JSON.parse(raw);
|
|
3284
3517
|
} catch {
|
|
3285
3518
|
return null;
|
|
@@ -3322,12 +3555,12 @@ function detectCliChannels(argv, home) {
|
|
|
3322
3555
|
}
|
|
3323
3556
|
const plugin = findPlugin(home);
|
|
3324
3557
|
if (plugin) {
|
|
3325
|
-
const pluginBin =
|
|
3558
|
+
const pluginBin = join23(plugin.installPath, "bin", "skillwiki");
|
|
3326
3559
|
if (existsSync6(pluginBin)) {
|
|
3327
3560
|
channels.push({ name: "plugin", path: pluginBin, isDevLink: false });
|
|
3328
3561
|
}
|
|
3329
3562
|
}
|
|
3330
|
-
const installBin =
|
|
3563
|
+
const installBin = join23(home, ".claude", "skills", "bin", "skillwiki");
|
|
3331
3564
|
if (existsSync6(installBin)) {
|
|
3332
3565
|
channels.push({ name: "install", path: installBin, isDevLink: false });
|
|
3333
3566
|
}
|
|
@@ -3408,9 +3641,9 @@ function checkVaultStructure(resolvedPath) {
|
|
|
3408
3641
|
return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 vault directory does not exist");
|
|
3409
3642
|
}
|
|
3410
3643
|
const missing = [];
|
|
3411
|
-
if (!existsSync6(
|
|
3644
|
+
if (!existsSync6(join23(resolvedPath, "SCHEMA.md"))) missing.push("SCHEMA.md");
|
|
3412
3645
|
for (const dir of ["raw", "entities", "concepts", "meta"]) {
|
|
3413
|
-
if (!existsSync6(
|
|
3646
|
+
if (!existsSync6(join23(resolvedPath, dir))) missing.push(dir + "/");
|
|
3414
3647
|
}
|
|
3415
3648
|
if (missing.length === 0) {
|
|
3416
3649
|
return check("pass", "vault_structure", "Vault structure valid", "All required files and directories present");
|
|
@@ -3418,7 +3651,7 @@ function checkVaultStructure(resolvedPath) {
|
|
|
3418
3651
|
return check("warn", "vault_structure", "Vault structure valid", `Missing: ${missing.join(", ")} \u2014 run \`skillwiki init\` to add CodeWiki structure`);
|
|
3419
3652
|
}
|
|
3420
3653
|
function checkSkillsInstalled(home, cwd) {
|
|
3421
|
-
const srcDir = cwd ?
|
|
3654
|
+
const srcDir = cwd ? join23(cwd, "packages", "skills") : void 0;
|
|
3422
3655
|
if (srcDir && existsSync6(srcDir)) {
|
|
3423
3656
|
const found = findSkillMd(srcDir);
|
|
3424
3657
|
if (found.length > 0) {
|
|
@@ -3432,7 +3665,7 @@ function checkSkillsInstalled(home, cwd) {
|
|
|
3432
3665
|
return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (plugin v${plugin.version})`);
|
|
3433
3666
|
}
|
|
3434
3667
|
}
|
|
3435
|
-
const skillsDir =
|
|
3668
|
+
const skillsDir = join23(home, ".claude", "skills");
|
|
3436
3669
|
if (existsSync6(skillsDir)) {
|
|
3437
3670
|
const found = findSkillMd(skillsDir);
|
|
3438
3671
|
if (found.length > 0) {
|
|
@@ -3443,10 +3676,10 @@ function checkSkillsInstalled(home, cwd) {
|
|
|
3443
3676
|
}
|
|
3444
3677
|
function checkDuplicateSkills(home) {
|
|
3445
3678
|
const plugin = findPlugin(home);
|
|
3446
|
-
const skillsDir =
|
|
3679
|
+
const skillsDir = join23(home, ".claude", "skills");
|
|
3447
3680
|
const agentSkillDirs = [
|
|
3448
|
-
{ label: "~/.codex/skills/", path:
|
|
3449
|
-
{ label: "~/.agents/skills/", path:
|
|
3681
|
+
{ label: "~/.codex/skills/", path: join23(home, ".codex", "skills") },
|
|
3682
|
+
{ label: "~/.agents/skills/", path: join23(home, ".agents", "skills") }
|
|
3450
3683
|
];
|
|
3451
3684
|
if (!plugin) {
|
|
3452
3685
|
return check("pass", "skills_duplicate", "Skills not duplicated", "Single install channel");
|
|
@@ -3523,7 +3756,7 @@ async function checkProfiles(home) {
|
|
|
3523
3756
|
}
|
|
3524
3757
|
async function checkProjectLocalOverride(cwd) {
|
|
3525
3758
|
const dir = cwd ?? process.cwd();
|
|
3526
|
-
const envPath =
|
|
3759
|
+
const envPath = join23(dir, ".skillwiki", ".env");
|
|
3527
3760
|
if (existsSync6(envPath)) {
|
|
3528
3761
|
return check("pass", "project_local", "Project-local config", `Found: ${envPath}`);
|
|
3529
3762
|
}
|
|
@@ -3533,7 +3766,7 @@ function checkVaultGitRemote(resolvedPath) {
|
|
|
3533
3766
|
if (resolvedPath === void 0) {
|
|
3534
3767
|
return check("error", "vault_git_remote", "Vault git remote", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
3535
3768
|
}
|
|
3536
|
-
if (!existsSync6(
|
|
3769
|
+
if (!existsSync6(join23(resolvedPath, ".git"))) {
|
|
3537
3770
|
return check("warn", "vault_git_remote", "Vault git remote", "Vault is not a git repository \u2014 sync features unavailable");
|
|
3538
3771
|
}
|
|
3539
3772
|
try {
|
|
@@ -3556,9 +3789,9 @@ function checkObsidianTemplates(resolvedPath) {
|
|
|
3556
3789
|
return check("error", "obsidian_templates", "Obsidian templates", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
3557
3790
|
}
|
|
3558
3791
|
const missing = [];
|
|
3559
|
-
if (!existsSync6(
|
|
3560
|
-
if (!existsSync6(
|
|
3561
|
-
if (!existsSync6(
|
|
3792
|
+
if (!existsSync6(join23(resolvedPath, "_Templates"))) missing.push("_Templates/");
|
|
3793
|
+
if (!existsSync6(join23(resolvedPath, ".obsidian", "templates.json"))) missing.push(".obsidian/templates.json");
|
|
3794
|
+
if (!existsSync6(join23(resolvedPath, ".obsidian", "app.json"))) missing.push(".obsidian/app.json");
|
|
3562
3795
|
if (missing.length === 0) {
|
|
3563
3796
|
return check("pass", "obsidian_templates", "Obsidian templates", "Template folder and config present");
|
|
3564
3797
|
}
|
|
@@ -3568,7 +3801,7 @@ function checkDotStoreClean(resolvedPath) {
|
|
|
3568
3801
|
if (resolvedPath === void 0) {
|
|
3569
3802
|
return check("error", "dsstore_clean", "No .DS_Store in raw/", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
3570
3803
|
}
|
|
3571
|
-
const rawDir =
|
|
3804
|
+
const rawDir = join23(resolvedPath, "raw");
|
|
3572
3805
|
if (!existsSync6(rawDir)) {
|
|
3573
3806
|
return check("pass", "dsstore_clean", "No .DS_Store in raw/", "raw/ directory not found \u2014 check skipped");
|
|
3574
3807
|
}
|
|
@@ -3584,7 +3817,7 @@ function checkDotStoreClean(resolvedPath) {
|
|
|
3584
3817
|
if (entry.name === ".DS_Store") {
|
|
3585
3818
|
found.push(rel ? `${rel}/.DS_Store` : ".DS_Store");
|
|
3586
3819
|
} else if (entry.isDirectory()) {
|
|
3587
|
-
walk2(
|
|
3820
|
+
walk2(join23(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name);
|
|
3588
3821
|
}
|
|
3589
3822
|
}
|
|
3590
3823
|
})(rawDir, "");
|
|
@@ -3597,7 +3830,7 @@ function checkSyncLastPush(resolvedPath) {
|
|
|
3597
3830
|
if (resolvedPath === void 0) {
|
|
3598
3831
|
return check("error", "sync_last_push", "Vault sync recency", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
3599
3832
|
}
|
|
3600
|
-
if (!existsSync6(
|
|
3833
|
+
if (!existsSync6(join23(resolvedPath, ".git"))) {
|
|
3601
3834
|
return check("pass", "sync_last_push", "Vault sync recency", "No git repo \u2014 sync check skipped");
|
|
3602
3835
|
}
|
|
3603
3836
|
let timestamp;
|
|
@@ -3671,7 +3904,7 @@ function checkS3MountPerf(resolvedPath) {
|
|
|
3671
3904
|
if (!mountPoint) {
|
|
3672
3905
|
return check("pass", "s3_mount_perf", "S3 mount performance", "local disk");
|
|
3673
3906
|
}
|
|
3674
|
-
const conceptsDir =
|
|
3907
|
+
const conceptsDir = join23(resolvedPath, "concepts");
|
|
3675
3908
|
if (!existsSync6(conceptsDir)) {
|
|
3676
3909
|
return check("pass", "s3_mount_perf", "S3 mount performance", `S3 FUSE mount (${mountPoint}), no concepts/ to benchmark`);
|
|
3677
3910
|
}
|
|
@@ -3721,9 +3954,9 @@ function findSkillMd(dir) {
|
|
|
3721
3954
|
}
|
|
3722
3955
|
for (const entry of entries) {
|
|
3723
3956
|
if (entry.isFile() && entry.name === "SKILL.md") {
|
|
3724
|
-
results.push(
|
|
3957
|
+
results.push(join23(dir, entry.name));
|
|
3725
3958
|
} else if (entry.isDirectory()) {
|
|
3726
|
-
results.push(...findSkillMd(
|
|
3959
|
+
results.push(...findSkillMd(join23(dir, entry.name)));
|
|
3727
3960
|
}
|
|
3728
3961
|
}
|
|
3729
3962
|
return results;
|
|
@@ -3737,7 +3970,7 @@ function findSkillNames(dir) {
|
|
|
3737
3970
|
return results;
|
|
3738
3971
|
}
|
|
3739
3972
|
for (const entry of entries) {
|
|
3740
|
-
if (entry.isDirectory() && existsSync6(
|
|
3973
|
+
if (entry.isDirectory() && existsSync6(join23(dir, entry.name, "SKILL.md"))) {
|
|
3741
3974
|
results.push(entry.name);
|
|
3742
3975
|
}
|
|
3743
3976
|
}
|
|
@@ -3791,8 +4024,8 @@ async function runDoctor(input) {
|
|
|
3791
4024
|
}
|
|
3792
4025
|
|
|
3793
4026
|
// src/commands/archive.ts
|
|
3794
|
-
import { rename as
|
|
3795
|
-
import { join as
|
|
4027
|
+
import { rename as rename5, mkdir as mkdir8, readFile as readFile17, writeFile as writeFile9 } from "fs/promises";
|
|
4028
|
+
import { join as join24, dirname as dirname9 } from "path";
|
|
3796
4029
|
async function runArchive(input) {
|
|
3797
4030
|
const scan = await scanVault(input.vault);
|
|
3798
4031
|
if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
|
|
@@ -3808,13 +4041,13 @@ async function runArchive(input) {
|
|
|
3808
4041
|
}
|
|
3809
4042
|
if (!relPath) return { exitCode: ExitCode.ARCHIVE_TARGET_NOT_FOUND, result: err("ARCHIVE_TARGET_NOT_FOUND", { page: input.page }) };
|
|
3810
4043
|
if (relPath.startsWith("_archive/")) return { exitCode: ExitCode.ARCHIVE_ALREADY_ARCHIVED, result: err("ARCHIVE_ALREADY_ARCHIVED", { page: relPath }) };
|
|
3811
|
-
const archivePath =
|
|
3812
|
-
await mkdir8(dirname9(
|
|
4044
|
+
const archivePath = join24("_archive", relPath).replace(/\\/g, "/");
|
|
4045
|
+
await mkdir8(dirname9(join24(input.vault, archivePath)), { recursive: true });
|
|
3813
4046
|
let indexUpdated = false;
|
|
3814
4047
|
if (!isRaw) {
|
|
3815
|
-
const indexPath =
|
|
4048
|
+
const indexPath = join24(input.vault, "index.md");
|
|
3816
4049
|
try {
|
|
3817
|
-
const idx = await
|
|
4050
|
+
const idx = await readFile17(indexPath, "utf8");
|
|
3818
4051
|
const slug = relPath.replace(/\.md$/, "").split("/").pop();
|
|
3819
4052
|
const originalLines = idx.split("\n");
|
|
3820
4053
|
const filtered = originalLines.filter((l) => !l.includes(`[[${slug}]]`));
|
|
@@ -3826,7 +4059,7 @@ async function runArchive(input) {
|
|
|
3826
4059
|
if (e instanceof Error && "code" in e && e.code !== "ENOENT") throw e;
|
|
3827
4060
|
}
|
|
3828
4061
|
}
|
|
3829
|
-
await
|
|
4062
|
+
await rename5(join24(input.vault, relPath), join24(input.vault, archivePath));
|
|
3830
4063
|
appendLastOp(input.vault, {
|
|
3831
4064
|
operation: "archive",
|
|
3832
4065
|
summary: `moved ${relPath} to ${archivePath}`,
|
|
@@ -3838,7 +4071,6 @@ async function runArchive(input) {
|
|
|
3838
4071
|
|
|
3839
4072
|
// src/commands/drift.ts
|
|
3840
4073
|
import { createHash as createHash3 } from "crypto";
|
|
3841
|
-
import { writeFile as writeFile10 } from "fs/promises";
|
|
3842
4074
|
|
|
3843
4075
|
// src/utils/fetch.ts
|
|
3844
4076
|
async function controlledFetch(url, opts) {
|
|
@@ -3925,7 +4157,7 @@ async function runDrift(input) {
|
|
|
3925
4157
|
${newFm}
|
|
3926
4158
|
---
|
|
3927
4159
|
${body}`;
|
|
3928
|
-
await
|
|
4160
|
+
await safeWritePage(raw.absPath, newText);
|
|
3929
4161
|
results.push({
|
|
3930
4162
|
raw_path: raw.relPath,
|
|
3931
4163
|
source_url: sourceUrl,
|
|
@@ -3968,7 +4200,6 @@ ${body}`;
|
|
|
3968
4200
|
}
|
|
3969
4201
|
|
|
3970
4202
|
// src/commands/migrate-citations.ts
|
|
3971
|
-
import { writeFile as writeFile11 } from "fs/promises";
|
|
3972
4203
|
var MARKER_RE2 = /\^\[(raw\/[^\]]+)\]/g;
|
|
3973
4204
|
function moveMarkersToParagraphEnd(body) {
|
|
3974
4205
|
const lines = body.split("\n");
|
|
@@ -4091,7 +4322,11 @@ ${migratedBody}${newFooter}`;
|
|
|
4091
4322
|
continue;
|
|
4092
4323
|
}
|
|
4093
4324
|
if (!input.dryRun) {
|
|
4094
|
-
await
|
|
4325
|
+
const w = await safeWritePage(page.absPath, newText);
|
|
4326
|
+
if (!w.ok) {
|
|
4327
|
+
skipped.push(page.relPath);
|
|
4328
|
+
continue;
|
|
4329
|
+
}
|
|
4095
4330
|
}
|
|
4096
4331
|
migrated.push(page.relPath);
|
|
4097
4332
|
}
|
|
@@ -4121,7 +4356,6 @@ ${migratedBody}${newFooter}`;
|
|
|
4121
4356
|
}
|
|
4122
4357
|
|
|
4123
4358
|
// src/commands/frontmatter-fix.ts
|
|
4124
|
-
import { writeFile as writeFile12 } from "fs/promises";
|
|
4125
4359
|
function isoToday() {
|
|
4126
4360
|
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
4127
4361
|
}
|
|
@@ -4163,7 +4397,11 @@ ${newBody}`;
|
|
|
4163
4397
|
continue;
|
|
4164
4398
|
}
|
|
4165
4399
|
if (!input.dryRun) {
|
|
4166
|
-
await
|
|
4400
|
+
const w = await safeWritePage(page.absPath, newText);
|
|
4401
|
+
if (!w.ok) {
|
|
4402
|
+
skipped.push(page.relPath);
|
|
4403
|
+
continue;
|
|
4404
|
+
}
|
|
4167
4405
|
}
|
|
4168
4406
|
fixed.push(page.relPath);
|
|
4169
4407
|
}
|
|
@@ -4196,14 +4434,14 @@ ${newBody}`;
|
|
|
4196
4434
|
// src/commands/update.ts
|
|
4197
4435
|
import { execSync as execSync2 } from "child_process";
|
|
4198
4436
|
import { readFileSync as readFileSync7 } from "fs";
|
|
4199
|
-
import { join as
|
|
4437
|
+
import { join as join25 } from "path";
|
|
4200
4438
|
function resolveGlobalSkillsRoot() {
|
|
4201
4439
|
try {
|
|
4202
4440
|
const globalRoot = execSync2("npm root -g", {
|
|
4203
4441
|
encoding: "utf8",
|
|
4204
4442
|
timeout: 5e3
|
|
4205
4443
|
}).trim();
|
|
4206
|
-
return
|
|
4444
|
+
return join25(globalRoot, "skillwiki", "skills");
|
|
4207
4445
|
} catch {
|
|
4208
4446
|
return null;
|
|
4209
4447
|
}
|
|
@@ -4229,7 +4467,7 @@ async function runUpdate(input) {
|
|
|
4229
4467
|
);
|
|
4230
4468
|
const currentVersion = pkg2.version;
|
|
4231
4469
|
const tag = input.distTag ?? "latest";
|
|
4232
|
-
const target =
|
|
4470
|
+
const target = join25(input.home, ".claude", "skills");
|
|
4233
4471
|
let latest;
|
|
4234
4472
|
try {
|
|
4235
4473
|
latest = execSync2(`npm view skillwiki@${tag} version`, {
|
|
@@ -4300,14 +4538,14 @@ async function runUpdate(input) {
|
|
|
4300
4538
|
// src/commands/self-update.ts
|
|
4301
4539
|
import { execSync as execSync3 } from "child_process";
|
|
4302
4540
|
import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
|
|
4303
|
-
import { join as
|
|
4541
|
+
import { join as join26 } from "path";
|
|
4304
4542
|
var DEFAULT_SOURCE_ROOT_SUFFIX = "/Desktop/code/llm-wiki";
|
|
4305
4543
|
async function runSelfUpdate(input) {
|
|
4306
4544
|
const currentVersion = JSON.parse(
|
|
4307
4545
|
readFileSync8(new URL("../../package.json", import.meta.url), "utf8")
|
|
4308
4546
|
).version;
|
|
4309
4547
|
const sourceRoot = input.sourceRoot ?? `${input.home}${DEFAULT_SOURCE_ROOT_SUFFIX}`;
|
|
4310
|
-
const localPkgPath =
|
|
4548
|
+
const localPkgPath = join26(sourceRoot, "packages", "cli", "package.json");
|
|
4311
4549
|
const hasLocalSource = existsSync7(localPkgPath);
|
|
4312
4550
|
if (input.check) {
|
|
4313
4551
|
let availableVersion = null;
|
|
@@ -4439,10 +4677,10 @@ async function runSelfUpdate(input) {
|
|
|
4439
4677
|
}
|
|
4440
4678
|
|
|
4441
4679
|
// src/commands/transcripts.ts
|
|
4442
|
-
import { readdir as readdir5, stat as stat6, readFile as
|
|
4443
|
-
import { join as
|
|
4680
|
+
import { readdir as readdir5, stat as stat6, readFile as readFile18 } from "fs/promises";
|
|
4681
|
+
import { join as join27 } from "path";
|
|
4444
4682
|
async function runTranscripts(input) {
|
|
4445
|
-
const dir =
|
|
4683
|
+
const dir = join27(input.vault, "raw", "transcripts");
|
|
4446
4684
|
let entries;
|
|
4447
4685
|
try {
|
|
4448
4686
|
entries = await readdir5(dir, { withFileTypes: true });
|
|
@@ -4452,8 +4690,8 @@ async function runTranscripts(input) {
|
|
|
4452
4690
|
const transcripts = [];
|
|
4453
4691
|
for (const entry of entries) {
|
|
4454
4692
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
4455
|
-
const filePath =
|
|
4456
|
-
const content = await
|
|
4693
|
+
const filePath = join27(dir, entry.name);
|
|
4694
|
+
const content = await readFile18(filePath, "utf8");
|
|
4457
4695
|
const fm = extractFrontmatter(content);
|
|
4458
4696
|
if (!fm.ok) continue;
|
|
4459
4697
|
const ingested = typeof fm.data.ingested === "string" ? fm.data.ingested : "";
|
|
@@ -4470,12 +4708,12 @@ async function runTranscripts(input) {
|
|
|
4470
4708
|
}
|
|
4471
4709
|
|
|
4472
4710
|
// src/commands/project-index.ts
|
|
4473
|
-
import { readdir as readdir6, readFile as
|
|
4474
|
-
import { join as
|
|
4711
|
+
import { readdir as readdir6, readFile as readFile19, writeFile as writeFile10, mkdir as mkdir9 } from "fs/promises";
|
|
4712
|
+
import { join as join28, dirname as dirname10 } from "path";
|
|
4475
4713
|
var LAYER2_DIRS = ["entities", "concepts", "comparisons", "queries", "meta"];
|
|
4476
4714
|
async function runProjectIndex(input) {
|
|
4477
4715
|
const slug = input.slug;
|
|
4478
|
-
const projectDir =
|
|
4716
|
+
const projectDir = join28(input.vault, "projects", slug);
|
|
4479
4717
|
try {
|
|
4480
4718
|
await readdir6(projectDir);
|
|
4481
4719
|
} catch {
|
|
@@ -4486,15 +4724,15 @@ async function runProjectIndex(input) {
|
|
|
4486
4724
|
}
|
|
4487
4725
|
const wikilinkPattern = `[[${slug}]]`;
|
|
4488
4726
|
const entries = [];
|
|
4489
|
-
const compoundDir =
|
|
4727
|
+
const compoundDir = join28(input.vault, "projects", slug, "compound");
|
|
4490
4728
|
try {
|
|
4491
4729
|
const compoundFiles = await readdir6(compoundDir, { withFileTypes: true });
|
|
4492
4730
|
for (const entry of compoundFiles) {
|
|
4493
4731
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
4494
|
-
const filePath =
|
|
4732
|
+
const filePath = join28(compoundDir, entry.name);
|
|
4495
4733
|
let text;
|
|
4496
4734
|
try {
|
|
4497
|
-
text = await
|
|
4735
|
+
text = await readFile19(filePath, "utf8");
|
|
4498
4736
|
} catch {
|
|
4499
4737
|
continue;
|
|
4500
4738
|
}
|
|
@@ -4511,16 +4749,16 @@ async function runProjectIndex(input) {
|
|
|
4511
4749
|
for (const dir of LAYER2_DIRS) {
|
|
4512
4750
|
let files;
|
|
4513
4751
|
try {
|
|
4514
|
-
files = await readdir6(
|
|
4752
|
+
files = await readdir6(join28(input.vault, dir), { withFileTypes: true });
|
|
4515
4753
|
} catch {
|
|
4516
4754
|
continue;
|
|
4517
4755
|
}
|
|
4518
4756
|
for (const entry of files) {
|
|
4519
4757
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
4520
|
-
const filePath =
|
|
4758
|
+
const filePath = join28(input.vault, dir, entry.name);
|
|
4521
4759
|
let text;
|
|
4522
4760
|
try {
|
|
4523
|
-
text = await
|
|
4761
|
+
text = await readFile19(filePath, "utf8");
|
|
4524
4762
|
} catch {
|
|
4525
4763
|
continue;
|
|
4526
4764
|
}
|
|
@@ -4541,11 +4779,11 @@ async function runProjectIndex(input) {
|
|
|
4541
4779
|
const tb = typeOrder[b.type] ?? 99;
|
|
4542
4780
|
return ta !== tb ? ta - tb : a.title.localeCompare(b.title);
|
|
4543
4781
|
});
|
|
4544
|
-
const indexPath =
|
|
4782
|
+
const indexPath = join28(projectDir, "knowledge.md");
|
|
4545
4783
|
let existing = false;
|
|
4546
4784
|
let stale = false;
|
|
4547
4785
|
try {
|
|
4548
|
-
const existingText = await
|
|
4786
|
+
const existingText = await readFile19(indexPath, "utf8");
|
|
4549
4787
|
existing = true;
|
|
4550
4788
|
const existingEntries = existingText.split("\n").filter((l) => l.startsWith("- [["));
|
|
4551
4789
|
const existingPages = new Set(existingEntries.map((l) => {
|
|
@@ -4586,7 +4824,7 @@ Autogenerated by \`skillwiki project-index\` on ${today}.
|
|
|
4586
4824
|
if (input.apply) {
|
|
4587
4825
|
try {
|
|
4588
4826
|
await mkdir9(dirname10(indexPath), { recursive: true });
|
|
4589
|
-
await
|
|
4827
|
+
await writeFile10(indexPath, body, "utf8");
|
|
4590
4828
|
} catch (e) {
|
|
4591
4829
|
return {
|
|
4592
4830
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -4614,10 +4852,10 @@ ${entries.map((e) => ` ${e.type}: [[${e.page.replace(/\.md$/, "")}]] \u2014 ${e
|
|
|
4614
4852
|
}
|
|
4615
4853
|
|
|
4616
4854
|
// src/commands/compound.ts
|
|
4617
|
-
import { writeFile as
|
|
4618
|
-
import { join as
|
|
4855
|
+
import { writeFile as writeFile11, mkdir as mkdir10, readdir as readdir7, unlink as unlink3 } from "fs/promises";
|
|
4856
|
+
import { join as join29 } from "path";
|
|
4619
4857
|
import { existsSync as existsSync8 } from "fs";
|
|
4620
|
-
import { readFile as
|
|
4858
|
+
import { readFile as readFile20 } from "fs/promises";
|
|
4621
4859
|
var RETRO_HEADING_RE = /^## \[(\d{4}-\d{2}-\d{2})(?:\s+[^\]]+)?\] retro \| loop cycle(?: (\d+))?: (.+)$/;
|
|
4622
4860
|
var FIELD_RE = {
|
|
4623
4861
|
improve: /^-\s+\*?\*?Improve:?\*?\*?\s*(.+)$/m,
|
|
@@ -4715,17 +4953,17 @@ function extractRetroFields(date, cycleName, block) {
|
|
|
4715
4953
|
};
|
|
4716
4954
|
}
|
|
4717
4955
|
async function runCompound(input) {
|
|
4718
|
-
const logPath =
|
|
4956
|
+
const logPath = join29(input.vault, "log.md");
|
|
4719
4957
|
let logText;
|
|
4720
4958
|
try {
|
|
4721
|
-
logText = await
|
|
4959
|
+
logText = await readFile20(logPath, "utf8");
|
|
4722
4960
|
} catch {
|
|
4723
4961
|
return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: logPath }) };
|
|
4724
4962
|
}
|
|
4725
4963
|
const entries = parseRetroEntries(logText);
|
|
4726
4964
|
const promoted = [];
|
|
4727
4965
|
const skipped = [];
|
|
4728
|
-
const compoundDir =
|
|
4966
|
+
const compoundDir = join29(input.vault, "projects", input.project, "compound");
|
|
4729
4967
|
for (const entry of entries) {
|
|
4730
4968
|
const generalizeValue = entry.generalize.trim();
|
|
4731
4969
|
if (!/^yes/i.test(generalizeValue)) {
|
|
@@ -4733,7 +4971,7 @@ async function runCompound(input) {
|
|
|
4733
4971
|
continue;
|
|
4734
4972
|
}
|
|
4735
4973
|
const slug = slugify(entry.cycleName);
|
|
4736
|
-
const compoundPath =
|
|
4974
|
+
const compoundPath = join29(compoundDir, `${slug}.md`);
|
|
4737
4975
|
if (existsSync8(compoundPath)) {
|
|
4738
4976
|
skipped.push(entry.date);
|
|
4739
4977
|
continue;
|
|
@@ -4775,7 +5013,7 @@ async function runCompound(input) {
|
|
|
4775
5013
|
if (!existsSync8(compoundDir)) {
|
|
4776
5014
|
await mkdir10(compoundDir, { recursive: true });
|
|
4777
5015
|
}
|
|
4778
|
-
await
|
|
5016
|
+
await writeFile11(compoundPath, content, "utf8");
|
|
4779
5017
|
}
|
|
4780
5018
|
promoted.push(`${slug}.md`);
|
|
4781
5019
|
}
|
|
@@ -4794,7 +5032,7 @@ async function runCompound(input) {
|
|
|
4794
5032
|
};
|
|
4795
5033
|
}
|
|
4796
5034
|
async function runCompoundDelete(input) {
|
|
4797
|
-
const projectDir =
|
|
5035
|
+
const projectDir = join29(input.vault, "projects", input.project);
|
|
4798
5036
|
if (!existsSync8(projectDir)) {
|
|
4799
5037
|
return {
|
|
4800
5038
|
exitCode: ExitCode.PROJECT_NOT_FOUND,
|
|
@@ -4802,7 +5040,7 @@ async function runCompoundDelete(input) {
|
|
|
4802
5040
|
};
|
|
4803
5041
|
}
|
|
4804
5042
|
const entryName = input.entry.replace(/\.md$/, "");
|
|
4805
|
-
const compoundPath =
|
|
5043
|
+
const compoundPath = join29(projectDir, "compound", `${entryName}.md`);
|
|
4806
5044
|
if (!existsSync8(compoundPath)) {
|
|
4807
5045
|
return {
|
|
4808
5046
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
@@ -4810,7 +5048,7 @@ async function runCompoundDelete(input) {
|
|
|
4810
5048
|
};
|
|
4811
5049
|
}
|
|
4812
5050
|
try {
|
|
4813
|
-
await
|
|
5051
|
+
await unlink3(compoundPath);
|
|
4814
5052
|
} catch (e) {
|
|
4815
5053
|
return {
|
|
4816
5054
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -4836,7 +5074,7 @@ knowledge.md regenerated`
|
|
|
4836
5074
|
};
|
|
4837
5075
|
}
|
|
4838
5076
|
async function runCompoundList(input) {
|
|
4839
|
-
const compoundDir =
|
|
5077
|
+
const compoundDir = join29(input.vault, "projects", input.project, "compound");
|
|
4840
5078
|
if (!existsSync8(compoundDir)) {
|
|
4841
5079
|
return {
|
|
4842
5080
|
exitCode: ExitCode.OK,
|
|
@@ -4867,10 +5105,10 @@ could not read compound directory`
|
|
|
4867
5105
|
const entries = [];
|
|
4868
5106
|
for (const dirent of dirents) {
|
|
4869
5107
|
if (!dirent.isFile() || !dirent.name.endsWith(".md")) continue;
|
|
4870
|
-
const filePath =
|
|
5108
|
+
const filePath = join29(compoundDir, dirent.name);
|
|
4871
5109
|
let text;
|
|
4872
5110
|
try {
|
|
4873
|
-
text = await
|
|
5111
|
+
text = await readFile20(filePath, "utf8");
|
|
4874
5112
|
} catch {
|
|
4875
5113
|
continue;
|
|
4876
5114
|
}
|
|
@@ -4899,9 +5137,9 @@ no compound entries found`;
|
|
|
4899
5137
|
}
|
|
4900
5138
|
|
|
4901
5139
|
// src/commands/observe.ts
|
|
4902
|
-
import { mkdir as mkdir11, writeFile as
|
|
5140
|
+
import { mkdir as mkdir11, writeFile as writeFile12 } from "fs/promises";
|
|
4903
5141
|
import { existsSync as existsSync9, statSync as statSync3 } from "fs";
|
|
4904
|
-
import { join as
|
|
5142
|
+
import { join as join30 } from "path";
|
|
4905
5143
|
import { createHash as createHash4 } from "crypto";
|
|
4906
5144
|
var ALLOWED_KINDS = /* @__PURE__ */ new Set(["note", "bug", "task", "idea", "session-log"]);
|
|
4907
5145
|
function slugify2(text) {
|
|
@@ -4930,7 +5168,7 @@ async function runObserve(input) {
|
|
|
4930
5168
|
result: err("VAULT_PATH_INVALID", { path: input.vault })
|
|
4931
5169
|
};
|
|
4932
5170
|
}
|
|
4933
|
-
const transcriptsDir =
|
|
5171
|
+
const transcriptsDir = join30(input.vault, "raw", "transcripts");
|
|
4934
5172
|
try {
|
|
4935
5173
|
await mkdir11(transcriptsDir, { recursive: true });
|
|
4936
5174
|
} catch {
|
|
@@ -4942,7 +5180,7 @@ async function runObserve(input) {
|
|
|
4942
5180
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
4943
5181
|
const slug = slugify2(input.text);
|
|
4944
5182
|
const fileName = `${today}-observation-${slug}.md`;
|
|
4945
|
-
const filePath =
|
|
5183
|
+
const filePath = join30(transcriptsDir, fileName);
|
|
4946
5184
|
const body = `
|
|
4947
5185
|
${input.text.trim()}
|
|
4948
5186
|
`;
|
|
@@ -4960,7 +5198,7 @@ ${input.text.trim()}
|
|
|
4960
5198
|
frontmatterLines.push("---");
|
|
4961
5199
|
const content = frontmatterLines.join("\n") + body;
|
|
4962
5200
|
try {
|
|
4963
|
-
await
|
|
5201
|
+
await writeFile12(filePath, content, "utf8");
|
|
4964
5202
|
} catch (e) {
|
|
4965
5203
|
return {
|
|
4966
5204
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -4982,8 +5220,8 @@ ${input.text.trim()}
|
|
|
4982
5220
|
}
|
|
4983
5221
|
|
|
4984
5222
|
// src/commands/ingest.ts
|
|
4985
|
-
import { readFile as
|
|
4986
|
-
import { join as
|
|
5223
|
+
import { readFile as readFile21, writeFile as writeFile13, mkdir as mkdir12 } from "fs/promises";
|
|
5224
|
+
import { join as join31 } from "path";
|
|
4987
5225
|
import { createHash as createHash5 } from "crypto";
|
|
4988
5226
|
var ALLOWED_TYPES = /* @__PURE__ */ new Set(["entity", "concept", "comparison", "query"]);
|
|
4989
5227
|
var TYPE_DIR = {
|
|
@@ -5142,7 +5380,7 @@ async function runIngest(input) {
|
|
|
5142
5380
|
sourceContent = fetchResult.data.body;
|
|
5143
5381
|
} else {
|
|
5144
5382
|
try {
|
|
5145
|
-
sourceContent = await
|
|
5383
|
+
sourceContent = await readFile21(input.source, "utf8");
|
|
5146
5384
|
} catch {
|
|
5147
5385
|
return {
|
|
5148
5386
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
@@ -5157,8 +5395,8 @@ async function runIngest(input) {
|
|
|
5157
5395
|
const rawRelPath = `raw/articles/${slug}.md`;
|
|
5158
5396
|
const typedDir = TYPE_DIR[input.type] ?? `${input.type}s`;
|
|
5159
5397
|
const typedRelPath = `${typedDir}/${slug}.md`;
|
|
5160
|
-
const rawAbsPath =
|
|
5161
|
-
const typedAbsPath =
|
|
5398
|
+
const rawAbsPath = join31(input.vault, rawRelPath);
|
|
5399
|
+
const typedAbsPath = join31(input.vault, typedRelPath);
|
|
5162
5400
|
const rawContent = buildRawContent(sourceUrl, today, sha256, sourceContent);
|
|
5163
5401
|
const typedContent = buildTypedContent(
|
|
5164
5402
|
input.title,
|
|
@@ -5221,8 +5459,8 @@ async function runIngest(input) {
|
|
|
5221
5459
|
};
|
|
5222
5460
|
}
|
|
5223
5461
|
try {
|
|
5224
|
-
await mkdir12(
|
|
5225
|
-
await
|
|
5462
|
+
await mkdir12(join31(input.vault, "raw", "articles"), { recursive: true });
|
|
5463
|
+
await writeFile13(rawAbsPath, rawContent, "utf8");
|
|
5226
5464
|
} catch (e) {
|
|
5227
5465
|
return {
|
|
5228
5466
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -5230,8 +5468,8 @@ async function runIngest(input) {
|
|
|
5230
5468
|
};
|
|
5231
5469
|
}
|
|
5232
5470
|
try {
|
|
5233
|
-
await mkdir12(
|
|
5234
|
-
await
|
|
5471
|
+
await mkdir12(join31(input.vault, typedDir), { recursive: true });
|
|
5472
|
+
await writeFile13(typedAbsPath, typedContent, "utf8");
|
|
5235
5473
|
} catch (e) {
|
|
5236
5474
|
return {
|
|
5237
5475
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -5262,7 +5500,6 @@ async function runIngest(input) {
|
|
|
5262
5500
|
}
|
|
5263
5501
|
|
|
5264
5502
|
// src/commands/tag-sync.ts
|
|
5265
|
-
import { writeFile as writeFile17 } from "fs/promises";
|
|
5266
5503
|
var ENUM_MIRRORS = {
|
|
5267
5504
|
provenance: ["research", "project", "mixed"],
|
|
5268
5505
|
confidence: ["high", "medium", "low"]
|
|
@@ -5377,7 +5614,11 @@ ${newFm}
|
|
|
5377
5614
|
---
|
|
5378
5615
|
${body}`;
|
|
5379
5616
|
if (!input.dryRun) {
|
|
5380
|
-
await
|
|
5617
|
+
const w = await safeWritePage(page.absPath, newText);
|
|
5618
|
+
if (!w.ok) {
|
|
5619
|
+
unchanged++;
|
|
5620
|
+
continue;
|
|
5621
|
+
}
|
|
5381
5622
|
}
|
|
5382
5623
|
synced.push(page.relPath);
|
|
5383
5624
|
}
|
|
@@ -5407,10 +5648,10 @@ ${body}`;
|
|
|
5407
5648
|
|
|
5408
5649
|
// src/commands/sync.ts
|
|
5409
5650
|
import { existsSync as existsSync10 } from "fs";
|
|
5410
|
-
import { join as
|
|
5651
|
+
import { join as join32 } from "path";
|
|
5411
5652
|
function runSyncStatus(input) {
|
|
5412
5653
|
const vault = input.vault;
|
|
5413
|
-
if (!existsSync10(
|
|
5654
|
+
if (!existsSync10(join32(vault, ".git"))) {
|
|
5414
5655
|
return {
|
|
5415
5656
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
5416
5657
|
result: ok({
|
|
@@ -5479,7 +5720,7 @@ function runSyncStatus(input) {
|
|
|
5479
5720
|
}
|
|
5480
5721
|
async function runSyncPush(input) {
|
|
5481
5722
|
const vault = input.vault;
|
|
5482
|
-
if (!existsSync10(
|
|
5723
|
+
if (!existsSync10(join32(vault, ".git"))) {
|
|
5483
5724
|
return {
|
|
5484
5725
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
5485
5726
|
result: err("NOT_A_GIT_REPO", { path: vault })
|
|
@@ -5564,7 +5805,7 @@ async function runSyncPush(input) {
|
|
|
5564
5805
|
}
|
|
5565
5806
|
async function runSyncPull(input) {
|
|
5566
5807
|
const vault = input.vault;
|
|
5567
|
-
if (!existsSync10(
|
|
5808
|
+
if (!existsSync10(join32(vault, ".git"))) {
|
|
5568
5809
|
return {
|
|
5569
5810
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
5570
5811
|
result: err("NOT_A_GIT_REPO", { path: vault })
|
|
@@ -5640,7 +5881,7 @@ async function runSyncPull(input) {
|
|
|
5640
5881
|
|
|
5641
5882
|
// src/commands/backup.ts
|
|
5642
5883
|
import { statSync as statSync4, readdirSync as readdirSync2, readFileSync as readFileSync9, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
5643
|
-
import { join as
|
|
5884
|
+
import { join as join33, relative as relative3, dirname as dirname11 } from "path";
|
|
5644
5885
|
import { PutObjectCommand, HeadObjectCommand, ListObjectsV2Command, GetObjectCommand, DeleteObjectsCommand } from "@aws-sdk/client-s3";
|
|
5645
5886
|
|
|
5646
5887
|
// src/utils/s3-client.ts
|
|
@@ -5664,7 +5905,7 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([".git", ".obsidian", "_archive", "node_
|
|
|
5664
5905
|
function* walkMarkdown(dir, base) {
|
|
5665
5906
|
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
5666
5907
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
5667
|
-
const full =
|
|
5908
|
+
const full = join33(dir, entry.name);
|
|
5668
5909
|
if (entry.isDirectory()) {
|
|
5669
5910
|
yield* walkMarkdown(full, base);
|
|
5670
5911
|
} else if (entry.name.endsWith(".md")) {
|
|
@@ -5687,7 +5928,7 @@ async function runBackupSync(input) {
|
|
|
5687
5928
|
let failed = 0;
|
|
5688
5929
|
const files = [...walkMarkdown(input.vault, input.vault)];
|
|
5689
5930
|
for (const relPath of files) {
|
|
5690
|
-
const absPath =
|
|
5931
|
+
const absPath = join33(input.vault, relPath);
|
|
5691
5932
|
const localStat = statSync4(absPath);
|
|
5692
5933
|
let needsUpload = true;
|
|
5693
5934
|
try {
|
|
@@ -5763,7 +6004,7 @@ async function runBackupRestore(input) {
|
|
|
5763
6004
|
const objects = list.Contents ?? [];
|
|
5764
6005
|
for (const obj of objects) {
|
|
5765
6006
|
if (!obj.Key) continue;
|
|
5766
|
-
const localPath =
|
|
6007
|
+
const localPath = join33(target, obj.Key);
|
|
5767
6008
|
try {
|
|
5768
6009
|
const localStat = statSync4(localPath);
|
|
5769
6010
|
if (obj.LastModified && localStat.mtime > obj.LastModified) {
|
|
@@ -5810,8 +6051,8 @@ async function runBackupRestore(input) {
|
|
|
5810
6051
|
|
|
5811
6052
|
// src/commands/status.ts
|
|
5812
6053
|
import { existsSync as existsSync11, statSync as statSync5 } from "fs";
|
|
5813
|
-
import { readFile as
|
|
5814
|
-
import { join as
|
|
6054
|
+
import { readFile as readFile22 } from "fs/promises";
|
|
6055
|
+
import { join as join34 } from "path";
|
|
5815
6056
|
async function runStatus(input) {
|
|
5816
6057
|
if (!existsSync11(input.vault)) {
|
|
5817
6058
|
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { vault: input.vault }) };
|
|
@@ -5838,7 +6079,7 @@ async function runStatus(input) {
|
|
|
5838
6079
|
const compound = scan.data.compound.length;
|
|
5839
6080
|
let schemaVersion = "v1";
|
|
5840
6081
|
try {
|
|
5841
|
-
const schemaContent = await
|
|
6082
|
+
const schemaContent = await readFile22(join34(input.vault, "SCHEMA.md"), "utf8");
|
|
5842
6083
|
const versionMatch = schemaContent.match(/version:\s*["']?([^"'\s\n]+)/i);
|
|
5843
6084
|
if (versionMatch) schemaVersion = versionMatch[1];
|
|
5844
6085
|
} catch {
|
|
@@ -5898,8 +6139,8 @@ async function runStatus(input) {
|
|
|
5898
6139
|
}
|
|
5899
6140
|
|
|
5900
6141
|
// src/commands/seed.ts
|
|
5901
|
-
import { mkdir as mkdir13, writeFile as
|
|
5902
|
-
import { join as
|
|
6142
|
+
import { mkdir as mkdir13, writeFile as writeFile14, stat as stat7 } from "fs/promises";
|
|
6143
|
+
import { join as join35 } from "path";
|
|
5903
6144
|
var TODAY = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
5904
6145
|
var EXAMPLE_PAGES = {
|
|
5905
6146
|
"entities/example-project.md": `---
|
|
@@ -5968,30 +6209,30 @@ Real sources are immutable after ingestion \u2014 never edit them.
|
|
|
5968
6209
|
`;
|
|
5969
6210
|
async function runSeed(input) {
|
|
5970
6211
|
try {
|
|
5971
|
-
await stat7(
|
|
6212
|
+
await stat7(join35(input.vault, "SCHEMA.md"));
|
|
5972
6213
|
} catch {
|
|
5973
6214
|
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { root: input.vault, reason: "SCHEMA.md missing \u2014 run `skillwiki init` first" }) };
|
|
5974
6215
|
}
|
|
5975
6216
|
const created = [];
|
|
5976
6217
|
const skipped = [];
|
|
5977
6218
|
for (const [relPath, content] of Object.entries(EXAMPLE_PAGES)) {
|
|
5978
|
-
const absPath =
|
|
6219
|
+
const absPath = join35(input.vault, relPath);
|
|
5979
6220
|
try {
|
|
5980
6221
|
await stat7(absPath);
|
|
5981
6222
|
skipped.push(relPath);
|
|
5982
6223
|
} catch {
|
|
5983
|
-
await mkdir13(
|
|
5984
|
-
await
|
|
6224
|
+
await mkdir13(join35(absPath, ".."), { recursive: true });
|
|
6225
|
+
await writeFile14(absPath, content, "utf8");
|
|
5985
6226
|
created.push(relPath);
|
|
5986
6227
|
}
|
|
5987
6228
|
}
|
|
5988
|
-
const rawPath =
|
|
6229
|
+
const rawPath = join35(input.vault, "raw", "articles", "example-source.md");
|
|
5989
6230
|
try {
|
|
5990
6231
|
await stat7(rawPath);
|
|
5991
6232
|
skipped.push("raw/articles/example-source.md");
|
|
5992
6233
|
} catch {
|
|
5993
|
-
await mkdir13(
|
|
5994
|
-
await
|
|
6234
|
+
await mkdir13(join35(rawPath, ".."), { recursive: true });
|
|
6235
|
+
await writeFile14(rawPath, EXAMPLE_RAW, "utf8");
|
|
5995
6236
|
created.push("raw/articles/example-source.md");
|
|
5996
6237
|
}
|
|
5997
6238
|
if (created.length > 0) {
|
|
@@ -6013,9 +6254,9 @@ async function runSeed(input) {
|
|
|
6013
6254
|
}
|
|
6014
6255
|
|
|
6015
6256
|
// src/commands/canvas.ts
|
|
6016
|
-
import { readFile as
|
|
6257
|
+
import { readFile as readFile23, writeFile as writeFile15 } from "fs/promises";
|
|
6017
6258
|
import { existsSync as existsSync12 } from "fs";
|
|
6018
|
-
import { join as
|
|
6259
|
+
import { join as join36 } from "path";
|
|
6019
6260
|
var NODE_WIDTH = 240;
|
|
6020
6261
|
var NODE_HEIGHT = 60;
|
|
6021
6262
|
var COLUMN_SPACING = 400;
|
|
@@ -6093,7 +6334,7 @@ function buildCanvasEdges(adjacency) {
|
|
|
6093
6334
|
return edges;
|
|
6094
6335
|
}
|
|
6095
6336
|
async function runCanvasGenerate(input) {
|
|
6096
|
-
const graphPath = input.graphPath ??
|
|
6337
|
+
const graphPath = input.graphPath ?? join36(input.vault, ".skillwiki", "graph.json");
|
|
6097
6338
|
if (!existsSync12(graphPath)) {
|
|
6098
6339
|
return {
|
|
6099
6340
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
@@ -6105,7 +6346,7 @@ async function runCanvasGenerate(input) {
|
|
|
6105
6346
|
}
|
|
6106
6347
|
let raw;
|
|
6107
6348
|
try {
|
|
6108
|
-
raw = await
|
|
6349
|
+
raw = await readFile23(graphPath, "utf8");
|
|
6109
6350
|
} catch (e) {
|
|
6110
6351
|
return {
|
|
6111
6352
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
@@ -6131,9 +6372,9 @@ async function runCanvasGenerate(input) {
|
|
|
6131
6372
|
const nodes = buildCanvasNodes(paths);
|
|
6132
6373
|
const edges = buildCanvasEdges(graph.adjacency);
|
|
6133
6374
|
const canvas = { nodes, edges };
|
|
6134
|
-
const outPath =
|
|
6375
|
+
const outPath = join36(input.vault, "vault-graph.canvas");
|
|
6135
6376
|
try {
|
|
6136
|
-
await
|
|
6377
|
+
await writeFile15(outPath, JSON.stringify(canvas, null, 2));
|
|
6137
6378
|
} catch (e) {
|
|
6138
6379
|
return {
|
|
6139
6380
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -6153,8 +6394,8 @@ written: ${outPath}`
|
|
|
6153
6394
|
}
|
|
6154
6395
|
|
|
6155
6396
|
// src/commands/query.ts
|
|
6156
|
-
import { readFile as
|
|
6157
|
-
import { join as
|
|
6397
|
+
import { readFile as readFile24, stat as stat8 } from "fs/promises";
|
|
6398
|
+
import { join as join37 } from "path";
|
|
6158
6399
|
var W_KEYWORD = 2;
|
|
6159
6400
|
var W_SOURCE_OVERLAP = 4;
|
|
6160
6401
|
var W_WIKILINK = 3;
|
|
@@ -6275,7 +6516,7 @@ function computeKeywordScore(terms, title, tags, body) {
|
|
|
6275
6516
|
return score;
|
|
6276
6517
|
}
|
|
6277
6518
|
async function loadOrBuildGraph(vault) {
|
|
6278
|
-
const graphPath =
|
|
6519
|
+
const graphPath = join37(vault, ".skillwiki", "graph.json");
|
|
6279
6520
|
let needsBuild = false;
|
|
6280
6521
|
try {
|
|
6281
6522
|
const fileStat = await stat8(graphPath);
|
|
@@ -6289,7 +6530,7 @@ async function loadOrBuildGraph(vault) {
|
|
|
6289
6530
|
if (buildResult.exitCode !== 0) return null;
|
|
6290
6531
|
}
|
|
6291
6532
|
try {
|
|
6292
|
-
const raw = await
|
|
6533
|
+
const raw = await readFile24(graphPath, "utf8");
|
|
6293
6534
|
return JSON.parse(raw);
|
|
6294
6535
|
} catch {
|
|
6295
6536
|
return null;
|
|
@@ -6298,13 +6539,13 @@ async function loadOrBuildGraph(vault) {
|
|
|
6298
6539
|
|
|
6299
6540
|
// src/utils/auto-commit.ts
|
|
6300
6541
|
import { existsSync as existsSync13 } from "fs";
|
|
6301
|
-
import { join as
|
|
6542
|
+
import { join as join38 } from "path";
|
|
6302
6543
|
async function postCommit(vault, exitCode) {
|
|
6303
6544
|
if (exitCode !== 0) return;
|
|
6304
6545
|
const home = process.env.HOME ?? "";
|
|
6305
6546
|
const dotenv = await parseDotenvFile(configPath(home));
|
|
6306
6547
|
if (dotenv["AUTO_COMMIT"] === "false") return;
|
|
6307
|
-
if (!existsSync13(
|
|
6548
|
+
if (!existsSync13(join38(vault, ".git"))) return;
|
|
6308
6549
|
const lastOps = readLastOp(vault);
|
|
6309
6550
|
if (lastOps.length === 0) return;
|
|
6310
6551
|
const porcelain = git(vault, ["status", "--porcelain"]);
|
|
@@ -6355,7 +6596,7 @@ program.command("validate <file>").description("validate vault page frontmatter
|
|
|
6355
6596
|
emit(await runValidate({ file, apply: !!opts.apply, vault }), vault);
|
|
6356
6597
|
});
|
|
6357
6598
|
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) => {
|
|
6358
|
-
const out = opts.out ??
|
|
6599
|
+
const out = opts.out ?? join39(vault, ".skillwiki", "graph.json");
|
|
6359
6600
|
emit(await runGraphBuild({ vault, out }), vault);
|
|
6360
6601
|
});
|
|
6361
6602
|
var canvasCmd = program.command("canvas").description("manage Obsidian canvas files");
|
|
@@ -6461,10 +6702,10 @@ program.command("topic-map-check [vault]").description("check whether a topic ma
|
|
|
6461
6702
|
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
6462
6703
|
else emit(await runTopicMapCheck({ vault: v.vault, threshold: opts.threshold }), v.vault);
|
|
6463
6704
|
});
|
|
6464
|
-
program.command("stale [vault]").description("identify stale transcripts and incomplete work items").option("--archive", "move stale items to _archive/", false).option("--days <n>", "staleness threshold in days", (s) => parseInt(s, 10), 3).option("--force-scan", "infer kind/project from filename and content when frontmatter is missing", false).option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
6705
|
+
program.command("stale [vault]").description("identify stale transcripts and incomplete work items").option("--archive", "move stale items to _archive/", false).option("--days <n>", "staleness threshold in days", (s) => parseInt(s, 10), 3).option("--force-scan", "infer kind/project from filename and content when frontmatter is missing", false).option("--project <slug>", "scope to a single project").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
6465
6706
|
const v = await resolveVaultArg(vault, opts.wiki);
|
|
6466
6707
|
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
6467
|
-
else emit(await runStale({ vault: v.vault, days: opts.days, archive: !!opts.archive, forceScan: !!opts.forceScan }), v.vault);
|
|
6708
|
+
else emit(await runStale({ vault: v.vault, days: opts.days, archive: !!opts.archive, forceScan: !!opts.forceScan, project: opts.project }), v.vault);
|
|
6468
6709
|
});
|
|
6469
6710
|
program.command("claim <transcript> [vault]").description("claim an unclaimed transcript by creating a work item with source: link").option("--project <slug>", "project slug (overrides transcript frontmatter)").option("--slug <slug>", "work-item slug (defaults to transcript filename without date/kind prefix)").option("--wiki <name>", "wiki profile name").action(async (transcript, vault, opts) => {
|
|
6470
6711
|
const v = await resolveVaultArg(vault, opts.wiki);
|
|
@@ -6481,7 +6722,7 @@ program.command("log-rotate [vault]").description("rotate or trim the vault log
|
|
|
6481
6722
|
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
6482
6723
|
else emit(await runLogRotate({ vault: v.vault, threshold: opts.threshold, apply: !!opts.apply }), v.vault);
|
|
6483
6724
|
});
|
|
6484
|
-
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 legacy_citation_style violations").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
6725
|
+
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 legacy_citation_style violations").option("--only <bucket>", "run only the specified lint bucket").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
6485
6726
|
const v = await resolveVaultArg(vault, opts.wiki);
|
|
6486
6727
|
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
6487
6728
|
else emit(await runLint({
|
|
@@ -6490,7 +6731,8 @@ program.command("lint [vault]").description("run all vault health checks").optio
|
|
|
6490
6731
|
days: opts.days,
|
|
6491
6732
|
lines: opts.lines,
|
|
6492
6733
|
logThreshold: opts.logThreshold,
|
|
6493
|
-
fix: opts.fix ?? false
|
|
6734
|
+
fix: opts.fix ?? false,
|
|
6735
|
+
only: opts.only
|
|
6494
6736
|
}), v.vault);
|
|
6495
6737
|
});
|
|
6496
6738
|
var configCmd = program.command("config").description("manage skillwiki configuration");
|
|
@@ -6597,7 +6839,10 @@ syncCmd.command("pull [vault]").description("pull remote vault changes and lint"
|
|
|
6597
6839
|
var backupCmd = program.command("backup").description("manage S3-compatible remote backup");
|
|
6598
6840
|
backupCmd.command("sync [vault]").description("sync vault to S3-compatible remote backup").option("--dry-run", "list actions without executing").option("--bucket <name>", "S3 bucket name").option("--endpoint <url>", "S3 endpoint URL").option("--region <region>", "S3 region", "us-east-1").option("--prune", "delete orphaned S3 objects not in vault", false).option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
6599
6841
|
const v = await resolveVaultArg(vault, opts.wiki);
|
|
6600
|
-
if (!v.ok)
|
|
6842
|
+
if (!v.ok) {
|
|
6843
|
+
emit({ exitCode: v.exitCode, result: v.payload });
|
|
6844
|
+
return;
|
|
6845
|
+
}
|
|
6601
6846
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "/tmp";
|
|
6602
6847
|
const dotenv = await parseDotenvFile(configPath(home));
|
|
6603
6848
|
emit(await runBackupSync({
|
|
@@ -6613,7 +6858,10 @@ backupCmd.command("sync [vault]").description("sync vault to S3-compatible remot
|
|
|
6613
6858
|
});
|
|
6614
6859
|
backupCmd.command("restore [vault]").description("restore vault from S3-compatible remote backup").option("--bucket <name>", "S3 bucket name").option("--endpoint <url>", "S3 endpoint URL").option("--region <region>", "S3 region", "us-east-1").option("--target <dir>", "restore target directory (defaults to vault)").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
6615
6860
|
const v = await resolveVaultArg(vault, opts.wiki);
|
|
6616
|
-
if (!v.ok)
|
|
6861
|
+
if (!v.ok) {
|
|
6862
|
+
emit({ exitCode: v.exitCode, result: v.payload });
|
|
6863
|
+
return;
|
|
6864
|
+
}
|
|
6617
6865
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "/tmp";
|
|
6618
6866
|
const dotenv = await parseDotenvFile(configPath(home));
|
|
6619
6867
|
emit(await runBackupRestore({
|