skillwiki 0.3.0 → 0.4.1
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 +318 -206
- package/package.json +1 -1
- package/skills/.claude-plugin/plugin.json +1 -1
- package/skills/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
|
|
10
10
|
// src/cli.ts
|
|
11
11
|
import { readFileSync as readFileSync9 } from "fs";
|
|
12
|
-
import { join as
|
|
12
|
+
import { join as join38 } from "path";
|
|
13
13
|
import { Command } from "commander";
|
|
14
14
|
|
|
15
15
|
// ../shared/src/exit-codes.ts
|
|
@@ -1985,7 +1985,9 @@ async function runStale(input) {
|
|
|
1985
1985
|
const meta = transcriptMeta.get(t.relPath);
|
|
1986
1986
|
if (!meta) continue;
|
|
1987
1987
|
if (CLAIMABLE_KINDS.has(meta.kind) && meta.project) {
|
|
1988
|
-
|
|
1988
|
+
const projectSlug = extractSlug(meta.project);
|
|
1989
|
+
const hint = `skillwiki claim ${t.relPath} --project ${projectSlug}`;
|
|
1990
|
+
unclaimedTranscripts.push({ path: t.relPath, reason: `${meta.kind} for ${meta.project} \u2014 no work item`, hint });
|
|
1989
1991
|
}
|
|
1990
1992
|
}
|
|
1991
1993
|
const doneWorkItems = [];
|
|
@@ -2084,7 +2086,8 @@ async function runStale(input) {
|
|
|
2084
2086
|
const hintLines = [];
|
|
2085
2087
|
if (stale.length > 0) hintLines.push(`stale_pages: ${stale.length}`, ...stale.map((p) => ` ${p.page}: ${p.reason}`));
|
|
2086
2088
|
if (staleTranscripts.length > 0) hintLines.push(`stale_transcripts: ${staleTranscripts.length}`, ...staleTranscripts.map((t) => ` ${t.path}: ${t.reason}`));
|
|
2087
|
-
if (unclaimedTranscripts.length > 0) hintLines.push(`unclaimed_transcripts: ${unclaimedTranscripts.length}`, ...unclaimedTranscripts.map((t) => ` ${t.path}: ${t.reason}`
|
|
2089
|
+
if (unclaimedTranscripts.length > 0) hintLines.push(`unclaimed_transcripts: ${unclaimedTranscripts.length}`, ...unclaimedTranscripts.map((t) => ` ${t.path}: ${t.reason}${t.hint ? `
|
|
2090
|
+
hint: ${t.hint}` : ""}`));
|
|
2088
2091
|
if (incompleteWorkItems.length > 0) hintLines.push(`incomplete_work_items: ${incompleteWorkItems.length}`, ...incompleteWorkItems.map((w) => ` ${w.path}: ${w.reason}`));
|
|
2089
2092
|
if (doneWorkItems.length > 0) hintLines.push(`done_work_items: ${doneWorkItems.length}`, ...doneWorkItems.map((w) => ` ${w.path}: ${w.reason}`));
|
|
2090
2093
|
if (archived.length > 0) hintLines.push(`archived: ${archived.length}`, ...archived.map((a) => ` ${a}`));
|
|
@@ -2100,6 +2103,104 @@ async function runStale(input) {
|
|
|
2100
2103
|
}) };
|
|
2101
2104
|
}
|
|
2102
2105
|
|
|
2106
|
+
// src/commands/claim.ts
|
|
2107
|
+
import { mkdir as mkdir7, writeFile as writeFile6, readFile as readFile11 } from "fs/promises";
|
|
2108
|
+
import { existsSync as existsSync2, statSync } from "fs";
|
|
2109
|
+
import { join as join14 } from "path";
|
|
2110
|
+
function extractDate(filename) {
|
|
2111
|
+
const m = filename.match(/^(\d{4}-\d{2}-\d{2})/);
|
|
2112
|
+
return m?.[1] ?? "";
|
|
2113
|
+
}
|
|
2114
|
+
function extractSlugFromFilename(filename) {
|
|
2115
|
+
return filename.replace(/^\d{4}-\d{2}-\d{2}-/, "").replace(/^(task|bug|idea|note|observation)-/, "").replace(/\.md$/, "");
|
|
2116
|
+
}
|
|
2117
|
+
function extractProjectSlug(projectField) {
|
|
2118
|
+
return projectField.replace(/^\[\[/, "").replace(/\]\]$/, "").replace(/^"|"$/g, "");
|
|
2119
|
+
}
|
|
2120
|
+
async function runClaim(input) {
|
|
2121
|
+
if (!existsSync2(input.vault) || !statSync(input.vault).isDirectory()) {
|
|
2122
|
+
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { path: input.vault }) };
|
|
2123
|
+
}
|
|
2124
|
+
const absTranscript = join14(input.vault, input.transcript);
|
|
2125
|
+
if (!existsSync2(absTranscript)) {
|
|
2126
|
+
return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: input.transcript }) };
|
|
2127
|
+
}
|
|
2128
|
+
const content = await readFile11(absTranscript, "utf8");
|
|
2129
|
+
const fm = extractFrontmatter(content);
|
|
2130
|
+
let projectSlug = input.project;
|
|
2131
|
+
if (!projectSlug && fm.ok && typeof fm.data.project === "string") {
|
|
2132
|
+
projectSlug = extractProjectSlug(fm.data.project);
|
|
2133
|
+
}
|
|
2134
|
+
if (!projectSlug) {
|
|
2135
|
+
return {
|
|
2136
|
+
exitCode: ExitCode.SCHEME_REJECTED,
|
|
2137
|
+
result: err("SCHEME_REJECTED", { message: "No project specified. Use --project or set project in transcript frontmatter." })
|
|
2138
|
+
};
|
|
2139
|
+
}
|
|
2140
|
+
const projectDir = join14(input.vault, "projects", projectSlug);
|
|
2141
|
+
if (!existsSync2(projectDir) || !statSync(projectDir).isDirectory()) {
|
|
2142
|
+
return {
|
|
2143
|
+
exitCode: ExitCode.PROJECT_NOT_FOUND,
|
|
2144
|
+
result: err("PROJECT_NOT_FOUND", { project: projectSlug })
|
|
2145
|
+
};
|
|
2146
|
+
}
|
|
2147
|
+
const filename = input.transcript.split("/").pop();
|
|
2148
|
+
const date = extractDate(filename);
|
|
2149
|
+
if (!date) {
|
|
2150
|
+
return {
|
|
2151
|
+
exitCode: ExitCode.SCHEME_REJECTED,
|
|
2152
|
+
result: err("SCHEME_REJECTED", { message: `Cannot extract date from filename: ${filename}` })
|
|
2153
|
+
};
|
|
2154
|
+
}
|
|
2155
|
+
const workSlug = input.slug || extractSlugFromFilename(filename);
|
|
2156
|
+
const dirName = `${date}-${workSlug}`;
|
|
2157
|
+
const workDir = join14(projectDir, "work", dirName);
|
|
2158
|
+
const relWorkDir = `projects/${projectSlug}/work/${dirName}`;
|
|
2159
|
+
const relSpecPath = `${relWorkDir}/spec.md`;
|
|
2160
|
+
if (existsSync2(workDir)) {
|
|
2161
|
+
return {
|
|
2162
|
+
exitCode: ExitCode.OK,
|
|
2163
|
+
result: ok({
|
|
2164
|
+
workItemPath: relWorkDir,
|
|
2165
|
+
specPath: relSpecPath,
|
|
2166
|
+
source: input.transcript,
|
|
2167
|
+
humanHint: `work item already exists: ${relWorkDir}`
|
|
2168
|
+
})
|
|
2169
|
+
};
|
|
2170
|
+
}
|
|
2171
|
+
await mkdir7(workDir, { recursive: true });
|
|
2172
|
+
const kind = fm.ok && typeof fm.data.kind === "string" ? fm.data.kind : "task";
|
|
2173
|
+
const specLines = [
|
|
2174
|
+
"---",
|
|
2175
|
+
`source: ${input.transcript}`,
|
|
2176
|
+
`status: planned`,
|
|
2177
|
+
`kind: ${kind}`,
|
|
2178
|
+
`created: ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`,
|
|
2179
|
+
"---",
|
|
2180
|
+
"",
|
|
2181
|
+
`# ${workSlug}`,
|
|
2182
|
+
"",
|
|
2183
|
+
`Claimed from ${input.transcript}`,
|
|
2184
|
+
""
|
|
2185
|
+
];
|
|
2186
|
+
await writeFile6(join14(workDir, "spec.md"), specLines.join("\n"), "utf8");
|
|
2187
|
+
appendLastOp(input.vault, {
|
|
2188
|
+
operation: "claim",
|
|
2189
|
+
summary: `claimed ${input.transcript} \u2192 ${relWorkDir}`,
|
|
2190
|
+
files: [relSpecPath],
|
|
2191
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
2192
|
+
});
|
|
2193
|
+
return {
|
|
2194
|
+
exitCode: ExitCode.OK,
|
|
2195
|
+
result: ok({
|
|
2196
|
+
workItemPath: relWorkDir,
|
|
2197
|
+
specPath: relSpecPath,
|
|
2198
|
+
source: input.transcript,
|
|
2199
|
+
humanHint: `claimed ${input.transcript} \u2192 ${relWorkDir}`
|
|
2200
|
+
})
|
|
2201
|
+
};
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2103
2204
|
// src/commands/pagesize.ts
|
|
2104
2205
|
async function runPagesize(input) {
|
|
2105
2206
|
const scan = await scanVault(input.vault);
|
|
@@ -2117,19 +2218,19 @@ async function runPagesize(input) {
|
|
|
2117
2218
|
}
|
|
2118
2219
|
|
|
2119
2220
|
// src/commands/log-rotate.ts
|
|
2120
|
-
import { readFile as
|
|
2121
|
-
import { join as
|
|
2221
|
+
import { readFile as readFile12, rename as rename3, writeFile as writeFile7, stat as stat5 } from "fs/promises";
|
|
2222
|
+
import { join as join15 } from "path";
|
|
2122
2223
|
var ENTRY_RE = /^## \[(\d{4})-\d{2}-\d{2}\]/gm;
|
|
2123
2224
|
async function runLogRotate(input) {
|
|
2124
2225
|
try {
|
|
2125
|
-
await stat5(
|
|
2226
|
+
await stat5(join15(input.vault, "SCHEMA.md"));
|
|
2126
2227
|
} catch {
|
|
2127
2228
|
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { vault: input.vault }) };
|
|
2128
2229
|
}
|
|
2129
|
-
const logPath =
|
|
2230
|
+
const logPath = join15(input.vault, "log.md");
|
|
2130
2231
|
let logText;
|
|
2131
2232
|
try {
|
|
2132
|
-
logText = await
|
|
2233
|
+
logText = await readFile12(logPath, "utf8");
|
|
2133
2234
|
} catch {
|
|
2134
2235
|
return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: logPath }) };
|
|
2135
2236
|
}
|
|
@@ -2146,7 +2247,7 @@ async function runLogRotate(input) {
|
|
|
2146
2247
|
}
|
|
2147
2248
|
const newestYear = matches[matches.length - 1][1];
|
|
2148
2249
|
const rotatedName = `log-${newestYear}.md`;
|
|
2149
|
-
const rotatedPath =
|
|
2250
|
+
const rotatedPath = join15(input.vault, rotatedName);
|
|
2150
2251
|
try {
|
|
2151
2252
|
await rename3(logPath, rotatedPath);
|
|
2152
2253
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
@@ -2158,7 +2259,7 @@ Chronological action log. Newest entries last. Skill writes append entries; lint
|
|
|
2158
2259
|
|
|
2159
2260
|
- Previous log moved to ${rotatedName}
|
|
2160
2261
|
`;
|
|
2161
|
-
await
|
|
2262
|
+
await writeFile7(logPath, fresh, "utf8");
|
|
2162
2263
|
} catch (e) {
|
|
2163
2264
|
return { exitCode: ExitCode.WRITE_FAILED, result: err("WRITE_FAILED", { message: String(e) }) };
|
|
2164
2265
|
}
|
|
@@ -2172,9 +2273,9 @@ Chronological action log. Newest entries last. Skill writes append entries; lint
|
|
|
2172
2273
|
}
|
|
2173
2274
|
|
|
2174
2275
|
// src/commands/lint.ts
|
|
2175
|
-
import { existsSync as
|
|
2176
|
-
import { readFile as
|
|
2177
|
-
import { join as
|
|
2276
|
+
import { existsSync as existsSync3 } from "fs";
|
|
2277
|
+
import { readFile as readFile14, writeFile as writeFile8 } from "fs/promises";
|
|
2278
|
+
import { join as join18 } from "path";
|
|
2178
2279
|
|
|
2179
2280
|
// src/commands/topic-map-check.ts
|
|
2180
2281
|
var DEFAULT_THRESHOLD = 200;
|
|
@@ -2196,13 +2297,13 @@ async function runTopicMapCheck(input) {
|
|
|
2196
2297
|
}
|
|
2197
2298
|
|
|
2198
2299
|
// src/commands/index-link-format.ts
|
|
2199
|
-
import { readFile as
|
|
2200
|
-
import { join as
|
|
2300
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
2301
|
+
import { join as join16 } from "path";
|
|
2201
2302
|
var MD_LINK_RE = /\[[^\[\]]+\]\([^)]+\.md\)/;
|
|
2202
2303
|
async function runIndexLinkFormat(input) {
|
|
2203
2304
|
let text = "";
|
|
2204
2305
|
try {
|
|
2205
|
-
text = await
|
|
2306
|
+
text = await readFile13(join16(input.vault, "index.md"), "utf8");
|
|
2206
2307
|
} catch {
|
|
2207
2308
|
}
|
|
2208
2309
|
const markdown_links = [];
|
|
@@ -2216,7 +2317,7 @@ ${markdown_links.map((l) => ` line ${l.line}: ${l.text}`).join("\n")}`;
|
|
|
2216
2317
|
|
|
2217
2318
|
// src/commands/dedup.ts
|
|
2218
2319
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
2219
|
-
import { join as
|
|
2320
|
+
import { join as join17 } from "path";
|
|
2220
2321
|
async function runDedup(input) {
|
|
2221
2322
|
const scan = await scanVault(input.vault);
|
|
2222
2323
|
if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
|
|
@@ -2244,7 +2345,7 @@ async function runDedup(input) {
|
|
|
2244
2345
|
}
|
|
2245
2346
|
}
|
|
2246
2347
|
for (const page of scan.data.typedKnowledge) {
|
|
2247
|
-
const text = readFileSync3(
|
|
2348
|
+
const text = readFileSync3(join17(input.vault, page.relPath), "utf-8");
|
|
2248
2349
|
let updated = text;
|
|
2249
2350
|
let changed = false;
|
|
2250
2351
|
for (const [oldPath, newPath] of replacements) {
|
|
@@ -2262,12 +2363,12 @@ async function runDedup(input) {
|
|
|
2262
2363
|
}
|
|
2263
2364
|
}
|
|
2264
2365
|
if (changed) {
|
|
2265
|
-
writeFileSync2(
|
|
2366
|
+
writeFileSync2(join17(input.vault, page.relPath), updated);
|
|
2266
2367
|
rewired.push(page.relPath);
|
|
2267
2368
|
}
|
|
2268
2369
|
}
|
|
2269
2370
|
for (const [oldPath] of replacements) {
|
|
2270
|
-
const fullPath =
|
|
2371
|
+
const fullPath = join17(input.vault, oldPath);
|
|
2271
2372
|
try {
|
|
2272
2373
|
unlinkSync2(fullPath);
|
|
2273
2374
|
removed.push(oldPath);
|
|
@@ -2414,7 +2515,7 @@ async function runLint(input) {
|
|
|
2414
2515
|
let rawPath = entry.replace(/^"/, "").replace(/"$/, "").replace(/^'/, "").replace(/'$/, "");
|
|
2415
2516
|
rawPath = rawPath.replace(/^\^\[/, "").replace(/\]$/, "");
|
|
2416
2517
|
if (!rawPath.startsWith("raw/") && !rawPath.startsWith("_archive/raw/")) continue;
|
|
2417
|
-
if (!
|
|
2518
|
+
if (!existsSync3(join18(input.vault, rawPath)) && !existsSync3(join18(input.vault, rawPath + ".md")) && !rawPath.startsWith("_archive/") && !existsSync3(join18(input.vault, "_archive", rawPath)) && !existsSync3(join18(input.vault, "_archive", rawPath + ".md"))) {
|
|
2418
2519
|
brokenSourceFlags.push(`${page.relPath}: ${rawPath}`);
|
|
2419
2520
|
}
|
|
2420
2521
|
}
|
|
@@ -2466,9 +2567,20 @@ async function runLint(input) {
|
|
|
2466
2567
|
workItemDirs.set(dir, pages);
|
|
2467
2568
|
}
|
|
2468
2569
|
for (const [dir, pages] of workItemDirs) {
|
|
2469
|
-
const
|
|
2570
|
+
const specPage = pages.find((p) => p.relPath.endsWith("/spec.md"));
|
|
2470
2571
|
const hasPlan = pages.some((p) => p.relPath.endsWith("/plan.md"));
|
|
2471
|
-
|
|
2572
|
+
let specStatus;
|
|
2573
|
+
let specStarted;
|
|
2574
|
+
if (specPage) {
|
|
2575
|
+
const text = await readPage(specPage);
|
|
2576
|
+
const fm = extractFrontmatter(text);
|
|
2577
|
+
if (fm.ok) {
|
|
2578
|
+
specStatus = fm.data.status;
|
|
2579
|
+
specStarted = fm.data.started;
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
const isClosed = specStatus === "completed" || specStatus === "abandoned";
|
|
2583
|
+
if (specPage && !hasPlan && !isClosed) {
|
|
2472
2584
|
const lastSegment = dir.split("/").pop();
|
|
2473
2585
|
const dateMatch = lastSegment.match(/^(\d{4}-\d{2}-\d{2})/);
|
|
2474
2586
|
if (dateMatch) {
|
|
@@ -2478,13 +2590,8 @@ async function runLint(input) {
|
|
|
2478
2590
|
}
|
|
2479
2591
|
}
|
|
2480
2592
|
}
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
const text = await readPage(page);
|
|
2484
|
-
const fm = extractFrontmatter(text);
|
|
2485
|
-
if (fm.ok && fm.data.status === "in-progress" && !fm.data.started) {
|
|
2486
|
-
workItemHealth.push(`${page.relPath}: in-progress without started date`);
|
|
2487
|
-
}
|
|
2593
|
+
if (specPage && specStatus === "in-progress" && !specStarted) {
|
|
2594
|
+
workItemHealth.push(`${specPage.relPath}: in-progress without started date`);
|
|
2488
2595
|
}
|
|
2489
2596
|
}
|
|
2490
2597
|
if (workItemHealth.length > 0) buckets.work_item_health = workItemHealth;
|
|
@@ -2499,11 +2606,11 @@ async function runLint(input) {
|
|
|
2499
2606
|
const slugMatch = String(entry).match(/\[\[([^\]]+)\]\]/);
|
|
2500
2607
|
if (!slugMatch) continue;
|
|
2501
2608
|
const slug = slugMatch[1];
|
|
2502
|
-
const knowledgePath =
|
|
2503
|
-
if (!
|
|
2609
|
+
const knowledgePath = join18(input.vault, "projects", slug, "knowledge.md");
|
|
2610
|
+
if (!existsSync3(knowledgePath)) continue;
|
|
2504
2611
|
const pageRef = page.relPath.replace(/\.md$/, "");
|
|
2505
2612
|
try {
|
|
2506
|
-
const knowledgeContent = await
|
|
2613
|
+
const knowledgeContent = await readFile14(knowledgePath, "utf8");
|
|
2507
2614
|
if (!knowledgeContent.includes(`[[${pageRef}]]`)) {
|
|
2508
2615
|
orphanedProjectPages.push(`${page.relPath}: not in projects/${slug}/knowledge.md`);
|
|
2509
2616
|
}
|
|
@@ -2518,7 +2625,7 @@ async function runLint(input) {
|
|
|
2518
2625
|
for (const relPath of legacyPages) {
|
|
2519
2626
|
try {
|
|
2520
2627
|
const absPath = `${input.vault}/${relPath}`;
|
|
2521
|
-
const raw = await
|
|
2628
|
+
const raw = await readFile14(absPath, "utf8");
|
|
2522
2629
|
const split = splitFrontmatter(raw);
|
|
2523
2630
|
if (!split.ok) {
|
|
2524
2631
|
unresolved.push(relPath);
|
|
@@ -2596,7 +2703,7 @@ async function runLint(input) {
|
|
|
2596
2703
|
${rawFm}
|
|
2597
2704
|
---
|
|
2598
2705
|
${newBody}`;
|
|
2599
|
-
await
|
|
2706
|
+
await writeFile8(absPath, newContent, "utf8");
|
|
2600
2707
|
fixed.push(relPath);
|
|
2601
2708
|
} catch {
|
|
2602
2709
|
unresolved.push(relPath);
|
|
@@ -2613,7 +2720,7 @@ ${newBody}`;
|
|
|
2613
2720
|
for (const relPath of noOverview) {
|
|
2614
2721
|
try {
|
|
2615
2722
|
const absPath = `${input.vault}/${relPath}`;
|
|
2616
|
-
const raw = await
|
|
2723
|
+
const raw = await readFile14(absPath, "utf8");
|
|
2617
2724
|
const split = splitFrontmatter(raw);
|
|
2618
2725
|
if (!split.ok) {
|
|
2619
2726
|
unresolved.push(relPath);
|
|
@@ -2634,7 +2741,7 @@ ${rawFm}
|
|
|
2634
2741
|
${overviewSection}
|
|
2635
2742
|
|
|
2636
2743
|
${trimmedBody}`;
|
|
2637
|
-
await
|
|
2744
|
+
await writeFile8(absPath, newContent, "utf8");
|
|
2638
2745
|
fixed.push(relPath);
|
|
2639
2746
|
} catch {
|
|
2640
2747
|
unresolved.push(relPath);
|
|
@@ -2650,7 +2757,7 @@ ${trimmedBody}`;
|
|
|
2650
2757
|
for (const relPath of missingTldrFlags) {
|
|
2651
2758
|
try {
|
|
2652
2759
|
const absPath = `${input.vault}/${relPath}`;
|
|
2653
|
-
const raw = await
|
|
2760
|
+
const raw = await readFile14(absPath, "utf8");
|
|
2654
2761
|
const split = splitFrontmatter(raw);
|
|
2655
2762
|
if (!split.ok) {
|
|
2656
2763
|
unresolved.push(relPath);
|
|
@@ -2668,7 +2775,7 @@ ${rawFm}
|
|
|
2668
2775
|
- Pending summary.
|
|
2669
2776
|
|
|
2670
2777
|
${trimmedBody}`;
|
|
2671
|
-
await
|
|
2778
|
+
await writeFile8(absPath, newContent, "utf8");
|
|
2672
2779
|
fixed.push(relPath);
|
|
2673
2780
|
} catch {
|
|
2674
2781
|
unresolved.push(relPath);
|
|
@@ -2686,7 +2793,7 @@ ${trimmedBody}`;
|
|
|
2686
2793
|
for (const relPath of wikilinkCitationFlags) {
|
|
2687
2794
|
try {
|
|
2688
2795
|
const absPath = `${input.vault}/${relPath}`;
|
|
2689
|
-
const raw = await
|
|
2796
|
+
const raw = await readFile14(absPath, "utf8");
|
|
2690
2797
|
const split = splitFrontmatter(raw);
|
|
2691
2798
|
if (!split.ok) {
|
|
2692
2799
|
unresolved.push(relPath);
|
|
@@ -2750,7 +2857,7 @@ ${trimmedBody}`;
|
|
|
2750
2857
|
${rawFm}
|
|
2751
2858
|
---
|
|
2752
2859
|
${newBody}`;
|
|
2753
|
-
await
|
|
2860
|
+
await writeFile8(absPath, newContent, "utf8");
|
|
2754
2861
|
wikilinkFixed.push(relPath);
|
|
2755
2862
|
} catch {
|
|
2756
2863
|
unresolved.push(relPath);
|
|
@@ -2807,14 +2914,14 @@ ${newBody}`;
|
|
|
2807
2914
|
}
|
|
2808
2915
|
|
|
2809
2916
|
// src/commands/config.ts
|
|
2810
|
-
import { readFile as
|
|
2811
|
-
import { existsSync as
|
|
2812
|
-
import { join as
|
|
2917
|
+
import { readFile as readFile15 } from "fs/promises";
|
|
2918
|
+
import { existsSync as existsSync4 } from "fs";
|
|
2919
|
+
import { join as join19 } from "path";
|
|
2813
2920
|
function validateKey(key) {
|
|
2814
2921
|
return CONFIG_KEYS.includes(key) || isValidWikiProfileKey(key);
|
|
2815
2922
|
}
|
|
2816
2923
|
function configPath(home) {
|
|
2817
|
-
return
|
|
2924
|
+
return join19(home, ".skillwiki", ".env");
|
|
2818
2925
|
}
|
|
2819
2926
|
async function runConfigGet(input) {
|
|
2820
2927
|
if (!validateKey(input.key)) {
|
|
@@ -2832,7 +2939,7 @@ async function runConfigSet(input) {
|
|
|
2832
2939
|
try {
|
|
2833
2940
|
let originalContent;
|
|
2834
2941
|
try {
|
|
2835
|
-
originalContent = await
|
|
2942
|
+
originalContent = await readFile15(filePath, "utf8");
|
|
2836
2943
|
} catch {
|
|
2837
2944
|
}
|
|
2838
2945
|
const existing = originalContent !== void 0 ? parseDotenvText(originalContent) : {};
|
|
@@ -2864,17 +2971,17 @@ async function runConfigList(input) {
|
|
|
2864
2971
|
}
|
|
2865
2972
|
async function runConfigPath(input) {
|
|
2866
2973
|
const filePath = configPath(input.home);
|
|
2867
|
-
return { exitCode: ExitCode.OK, result: ok({ path: filePath, exists:
|
|
2974
|
+
return { exitCode: ExitCode.OK, result: ok({ path: filePath, exists: existsSync4(filePath), humanHint: filePath }) };
|
|
2868
2975
|
}
|
|
2869
2976
|
|
|
2870
2977
|
// src/commands/doctor.ts
|
|
2871
|
-
import { existsSync as
|
|
2872
|
-
import { join as
|
|
2978
|
+
import { existsSync as existsSync6, lstatSync, readlinkSync, readdirSync, statSync as statSync2 } from "fs";
|
|
2979
|
+
import { join as join22, resolve as resolve4 } from "path";
|
|
2873
2980
|
import { execSync } from "child_process";
|
|
2874
2981
|
|
|
2875
2982
|
// src/utils/auto-update.ts
|
|
2876
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as
|
|
2877
|
-
import { join as
|
|
2983
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
|
|
2984
|
+
import { join as join20, dirname as dirname8 } from "path";
|
|
2878
2985
|
import { spawn } from "child_process";
|
|
2879
2986
|
|
|
2880
2987
|
// src/utils/update-consts.ts
|
|
@@ -2885,7 +2992,7 @@ var CLI_DISABLE_FLAG = "--no-update-notifier";
|
|
|
2885
2992
|
|
|
2886
2993
|
// src/utils/auto-update.ts
|
|
2887
2994
|
function cachePath(home) {
|
|
2888
|
-
return
|
|
2995
|
+
return join20(home, ".skillwiki", CACHE_FILENAME);
|
|
2889
2996
|
}
|
|
2890
2997
|
function readCacheRaw(home) {
|
|
2891
2998
|
try {
|
|
@@ -2923,7 +3030,7 @@ function triggerAutoUpdate(home, currentVersion) {
|
|
|
2923
3030
|
const { isStale } = readCache(home);
|
|
2924
3031
|
if (!isStale) return;
|
|
2925
3032
|
const bgScript = new URL("../auto-update-bg.js", import.meta.url).pathname;
|
|
2926
|
-
if (!
|
|
3033
|
+
if (!existsSync5(bgScript)) return;
|
|
2927
3034
|
const child = spawn(process.execPath, [bgScript, home, currentVersion], {
|
|
2928
3035
|
detached: true,
|
|
2929
3036
|
stdio: "ignore"
|
|
@@ -2935,12 +3042,12 @@ function triggerAutoUpdate(home, currentVersion) {
|
|
|
2935
3042
|
|
|
2936
3043
|
// src/utils/plugin-registry.ts
|
|
2937
3044
|
import { readFileSync as readFileSync5 } from "fs";
|
|
2938
|
-
import { join as
|
|
2939
|
-
var REGISTRY_PATH =
|
|
3045
|
+
import { join as join21 } from "path";
|
|
3046
|
+
var REGISTRY_PATH = join21(".claude", "plugins", "installed_plugins.json");
|
|
2940
3047
|
var PLUGIN_KEY = "skillwiki@llm-wiki";
|
|
2941
3048
|
function readInstalledPlugins(home) {
|
|
2942
3049
|
try {
|
|
2943
|
-
const raw = readFileSync5(
|
|
3050
|
+
const raw = readFileSync5(join21(home, REGISTRY_PATH), "utf8");
|
|
2944
3051
|
return JSON.parse(raw);
|
|
2945
3052
|
} catch {
|
|
2946
3053
|
return null;
|
|
@@ -2983,13 +3090,13 @@ function detectCliChannels(argv, home) {
|
|
|
2983
3090
|
}
|
|
2984
3091
|
const plugin = findPlugin(home);
|
|
2985
3092
|
if (plugin) {
|
|
2986
|
-
const pluginBin =
|
|
2987
|
-
if (
|
|
3093
|
+
const pluginBin = join22(plugin.installPath, "bin", "skillwiki");
|
|
3094
|
+
if (existsSync6(pluginBin)) {
|
|
2988
3095
|
channels.push({ name: "plugin", path: pluginBin, isDevLink: false });
|
|
2989
3096
|
}
|
|
2990
3097
|
}
|
|
2991
|
-
const installBin =
|
|
2992
|
-
if (
|
|
3098
|
+
const installBin = join22(home, ".claude", "skills", "bin", "skillwiki");
|
|
3099
|
+
if (existsSync6(installBin)) {
|
|
2993
3100
|
channels.push({ name: "install", path: installBin, isDevLink: false });
|
|
2994
3101
|
}
|
|
2995
3102
|
return channels;
|
|
@@ -3041,7 +3148,7 @@ function checkCliChannels(argv, home) {
|
|
|
3041
3148
|
}
|
|
3042
3149
|
async function checkConfigFile(home) {
|
|
3043
3150
|
const cfgPath = configPath(home);
|
|
3044
|
-
if (!
|
|
3151
|
+
if (!existsSync6(cfgPath)) {
|
|
3045
3152
|
return check("warn", "config_file", "Config file exists", `${cfgPath} not found`);
|
|
3046
3153
|
}
|
|
3047
3154
|
try {
|
|
@@ -3056,7 +3163,7 @@ function checkWikiPathExists(resolvedPath) {
|
|
|
3056
3163
|
if (resolvedPath === void 0) {
|
|
3057
3164
|
return check("error", "wiki_path_exists", "Vault directory exists", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
3058
3165
|
}
|
|
3059
|
-
if (
|
|
3166
|
+
if (existsSync6(resolvedPath) && statSync2(resolvedPath).isDirectory()) {
|
|
3060
3167
|
return check("pass", "wiki_path_exists", "Vault directory exists", resolvedPath);
|
|
3061
3168
|
}
|
|
3062
3169
|
return check("error", "wiki_path_exists", "Vault directory exists", `${resolvedPath} does not exist or is not a directory`);
|
|
@@ -3065,13 +3172,13 @@ function checkVaultStructure(resolvedPath) {
|
|
|
3065
3172
|
if (resolvedPath === void 0) {
|
|
3066
3173
|
return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
3067
3174
|
}
|
|
3068
|
-
if (!
|
|
3175
|
+
if (!existsSync6(resolvedPath)) {
|
|
3069
3176
|
return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 vault directory does not exist");
|
|
3070
3177
|
}
|
|
3071
3178
|
const missing = [];
|
|
3072
|
-
if (!
|
|
3179
|
+
if (!existsSync6(join22(resolvedPath, "SCHEMA.md"))) missing.push("SCHEMA.md");
|
|
3073
3180
|
for (const dir of ["raw", "entities", "concepts", "meta"]) {
|
|
3074
|
-
if (!
|
|
3181
|
+
if (!existsSync6(join22(resolvedPath, dir))) missing.push(dir + "/");
|
|
3075
3182
|
}
|
|
3076
3183
|
if (missing.length === 0) {
|
|
3077
3184
|
return check("pass", "vault_structure", "Vault structure valid", "All required files and directories present");
|
|
@@ -3079,8 +3186,8 @@ function checkVaultStructure(resolvedPath) {
|
|
|
3079
3186
|
return check("warn", "vault_structure", "Vault structure valid", `Missing: ${missing.join(", ")} \u2014 run \`skillwiki init\` to add CodeWiki structure`);
|
|
3080
3187
|
}
|
|
3081
3188
|
function checkSkillsInstalled(home, cwd) {
|
|
3082
|
-
const srcDir = cwd ?
|
|
3083
|
-
if (srcDir &&
|
|
3189
|
+
const srcDir = cwd ? join22(cwd, "packages", "skills") : void 0;
|
|
3190
|
+
if (srcDir && existsSync6(srcDir)) {
|
|
3084
3191
|
const found = findSkillMd(srcDir);
|
|
3085
3192
|
if (found.length > 0) {
|
|
3086
3193
|
return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (source)`);
|
|
@@ -3093,8 +3200,8 @@ function checkSkillsInstalled(home, cwd) {
|
|
|
3093
3200
|
return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (plugin v${plugin.version})`);
|
|
3094
3201
|
}
|
|
3095
3202
|
}
|
|
3096
|
-
const skillsDir =
|
|
3097
|
-
if (
|
|
3203
|
+
const skillsDir = join22(home, ".claude", "skills");
|
|
3204
|
+
if (existsSync6(skillsDir)) {
|
|
3098
3205
|
const found = findSkillMd(skillsDir);
|
|
3099
3206
|
if (found.length > 0) {
|
|
3100
3207
|
return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (CLI install)`);
|
|
@@ -3104,10 +3211,10 @@ function checkSkillsInstalled(home, cwd) {
|
|
|
3104
3211
|
}
|
|
3105
3212
|
function checkDuplicateSkills(home) {
|
|
3106
3213
|
const plugin = findPlugin(home);
|
|
3107
|
-
const skillsDir =
|
|
3214
|
+
const skillsDir = join22(home, ".claude", "skills");
|
|
3108
3215
|
const agentSkillDirs = [
|
|
3109
|
-
{ label: "~/.codex/skills/", path:
|
|
3110
|
-
{ label: "~/.agents/skills/", path:
|
|
3216
|
+
{ label: "~/.codex/skills/", path: join22(home, ".codex", "skills") },
|
|
3217
|
+
{ label: "~/.agents/skills/", path: join22(home, ".agents", "skills") }
|
|
3111
3218
|
];
|
|
3112
3219
|
if (!plugin) {
|
|
3113
3220
|
return check("pass", "skills_duplicate", "Skills not duplicated", "Single install channel");
|
|
@@ -3184,8 +3291,8 @@ async function checkProfiles(home) {
|
|
|
3184
3291
|
}
|
|
3185
3292
|
async function checkProjectLocalOverride(cwd) {
|
|
3186
3293
|
const dir = cwd ?? process.cwd();
|
|
3187
|
-
const envPath =
|
|
3188
|
-
if (
|
|
3294
|
+
const envPath = join22(dir, ".skillwiki", ".env");
|
|
3295
|
+
if (existsSync6(envPath)) {
|
|
3189
3296
|
return check("pass", "project_local", "Project-local config", `Found: ${envPath}`);
|
|
3190
3297
|
}
|
|
3191
3298
|
return check("pass", "project_local", "Project-local config", "None");
|
|
@@ -3194,7 +3301,7 @@ function checkVaultGitRemote(resolvedPath) {
|
|
|
3194
3301
|
if (resolvedPath === void 0) {
|
|
3195
3302
|
return check("error", "vault_git_remote", "Vault git remote", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
3196
3303
|
}
|
|
3197
|
-
if (!
|
|
3304
|
+
if (!existsSync6(join22(resolvedPath, ".git"))) {
|
|
3198
3305
|
return check("warn", "vault_git_remote", "Vault git remote", "Vault is not a git repository \u2014 sync features unavailable");
|
|
3199
3306
|
}
|
|
3200
3307
|
try {
|
|
@@ -3217,9 +3324,9 @@ function checkObsidianTemplates(resolvedPath) {
|
|
|
3217
3324
|
return check("error", "obsidian_templates", "Obsidian templates", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
3218
3325
|
}
|
|
3219
3326
|
const missing = [];
|
|
3220
|
-
if (!
|
|
3221
|
-
if (!
|
|
3222
|
-
if (!
|
|
3327
|
+
if (!existsSync6(join22(resolvedPath, "_Templates"))) missing.push("_Templates/");
|
|
3328
|
+
if (!existsSync6(join22(resolvedPath, ".obsidian", "templates.json"))) missing.push(".obsidian/templates.json");
|
|
3329
|
+
if (!existsSync6(join22(resolvedPath, ".obsidian", "app.json"))) missing.push(".obsidian/app.json");
|
|
3223
3330
|
if (missing.length === 0) {
|
|
3224
3331
|
return check("pass", "obsidian_templates", "Obsidian templates", "Template folder and config present");
|
|
3225
3332
|
}
|
|
@@ -3229,8 +3336,8 @@ function checkDotStoreClean(resolvedPath) {
|
|
|
3229
3336
|
if (resolvedPath === void 0) {
|
|
3230
3337
|
return check("error", "dsstore_clean", "No .DS_Store in raw/", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
3231
3338
|
}
|
|
3232
|
-
const rawDir =
|
|
3233
|
-
if (!
|
|
3339
|
+
const rawDir = join22(resolvedPath, "raw");
|
|
3340
|
+
if (!existsSync6(rawDir)) {
|
|
3234
3341
|
return check("pass", "dsstore_clean", "No .DS_Store in raw/", "raw/ directory not found \u2014 check skipped");
|
|
3235
3342
|
}
|
|
3236
3343
|
const found = [];
|
|
@@ -3245,7 +3352,7 @@ function checkDotStoreClean(resolvedPath) {
|
|
|
3245
3352
|
if (entry.name === ".DS_Store") {
|
|
3246
3353
|
found.push(rel ? `${rel}/.DS_Store` : ".DS_Store");
|
|
3247
3354
|
} else if (entry.isDirectory()) {
|
|
3248
|
-
walk2(
|
|
3355
|
+
walk2(join22(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name);
|
|
3249
3356
|
}
|
|
3250
3357
|
}
|
|
3251
3358
|
})(rawDir, "");
|
|
@@ -3258,7 +3365,7 @@ function checkSyncLastPush(resolvedPath) {
|
|
|
3258
3365
|
if (resolvedPath === void 0) {
|
|
3259
3366
|
return check("error", "sync_last_push", "Vault sync recency", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
3260
3367
|
}
|
|
3261
|
-
if (!
|
|
3368
|
+
if (!existsSync6(join22(resolvedPath, ".git"))) {
|
|
3262
3369
|
return check("pass", "sync_last_push", "Vault sync recency", "No git repo \u2014 sync check skipped");
|
|
3263
3370
|
}
|
|
3264
3371
|
let timestamp;
|
|
@@ -3300,9 +3407,9 @@ function findSkillMd(dir) {
|
|
|
3300
3407
|
}
|
|
3301
3408
|
for (const entry of entries) {
|
|
3302
3409
|
if (entry.isFile() && entry.name === "SKILL.md") {
|
|
3303
|
-
results.push(
|
|
3410
|
+
results.push(join22(dir, entry.name));
|
|
3304
3411
|
} else if (entry.isDirectory()) {
|
|
3305
|
-
results.push(...findSkillMd(
|
|
3412
|
+
results.push(...findSkillMd(join22(dir, entry.name)));
|
|
3306
3413
|
}
|
|
3307
3414
|
}
|
|
3308
3415
|
return results;
|
|
@@ -3316,7 +3423,7 @@ function findSkillNames(dir) {
|
|
|
3316
3423
|
return results;
|
|
3317
3424
|
}
|
|
3318
3425
|
for (const entry of entries) {
|
|
3319
|
-
if (entry.isDirectory() &&
|
|
3426
|
+
if (entry.isDirectory() && existsSync6(join22(dir, entry.name, "SKILL.md"))) {
|
|
3320
3427
|
results.push(entry.name);
|
|
3321
3428
|
}
|
|
3322
3429
|
}
|
|
@@ -3369,8 +3476,8 @@ async function runDoctor(input) {
|
|
|
3369
3476
|
}
|
|
3370
3477
|
|
|
3371
3478
|
// src/commands/archive.ts
|
|
3372
|
-
import { rename as rename4, mkdir as
|
|
3373
|
-
import { join as
|
|
3479
|
+
import { rename as rename4, mkdir as mkdir8, readFile as readFile16, writeFile as writeFile9 } from "fs/promises";
|
|
3480
|
+
import { join as join23, dirname as dirname9 } from "path";
|
|
3374
3481
|
async function runArchive(input) {
|
|
3375
3482
|
const scan = await scanVault(input.vault);
|
|
3376
3483
|
if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
|
|
@@ -3386,25 +3493,25 @@ async function runArchive(input) {
|
|
|
3386
3493
|
}
|
|
3387
3494
|
if (!relPath) return { exitCode: ExitCode.ARCHIVE_TARGET_NOT_FOUND, result: err("ARCHIVE_TARGET_NOT_FOUND", { page: input.page }) };
|
|
3388
3495
|
if (relPath.startsWith("_archive/")) return { exitCode: ExitCode.ARCHIVE_ALREADY_ARCHIVED, result: err("ARCHIVE_ALREADY_ARCHIVED", { page: relPath }) };
|
|
3389
|
-
const archivePath =
|
|
3390
|
-
await
|
|
3496
|
+
const archivePath = join23("_archive", relPath).replace(/\\/g, "/");
|
|
3497
|
+
await mkdir8(dirname9(join23(input.vault, archivePath)), { recursive: true });
|
|
3391
3498
|
let indexUpdated = false;
|
|
3392
3499
|
if (!isRaw) {
|
|
3393
|
-
const indexPath =
|
|
3500
|
+
const indexPath = join23(input.vault, "index.md");
|
|
3394
3501
|
try {
|
|
3395
|
-
const idx = await
|
|
3502
|
+
const idx = await readFile16(indexPath, "utf8");
|
|
3396
3503
|
const slug = relPath.replace(/\.md$/, "").split("/").pop();
|
|
3397
3504
|
const originalLines = idx.split("\n");
|
|
3398
3505
|
const filtered = originalLines.filter((l) => !l.includes(`[[${slug}]]`));
|
|
3399
3506
|
if (filtered.length !== originalLines.length) {
|
|
3400
|
-
await
|
|
3507
|
+
await writeFile9(indexPath, filtered.join("\n"), "utf8");
|
|
3401
3508
|
indexUpdated = true;
|
|
3402
3509
|
}
|
|
3403
3510
|
} catch (e) {
|
|
3404
3511
|
if (e instanceof Error && "code" in e && e.code !== "ENOENT") throw e;
|
|
3405
3512
|
}
|
|
3406
3513
|
}
|
|
3407
|
-
await rename4(
|
|
3514
|
+
await rename4(join23(input.vault, relPath), join23(input.vault, archivePath));
|
|
3408
3515
|
appendLastOp(input.vault, {
|
|
3409
3516
|
operation: "archive",
|
|
3410
3517
|
summary: `moved ${relPath} to ${archivePath}`,
|
|
@@ -3416,7 +3523,7 @@ async function runArchive(input) {
|
|
|
3416
3523
|
|
|
3417
3524
|
// src/commands/drift.ts
|
|
3418
3525
|
import { createHash as createHash2 } from "crypto";
|
|
3419
|
-
import { writeFile as
|
|
3526
|
+
import { writeFile as writeFile10 } from "fs/promises";
|
|
3420
3527
|
|
|
3421
3528
|
// src/utils/fetch.ts
|
|
3422
3529
|
async function controlledFetch(url, opts) {
|
|
@@ -3503,7 +3610,7 @@ async function runDrift(input) {
|
|
|
3503
3610
|
${newFm}
|
|
3504
3611
|
---
|
|
3505
3612
|
${body}`;
|
|
3506
|
-
await
|
|
3613
|
+
await writeFile10(raw.absPath, newText, "utf8");
|
|
3507
3614
|
results.push({
|
|
3508
3615
|
raw_path: raw.relPath,
|
|
3509
3616
|
source_url: sourceUrl,
|
|
@@ -3546,7 +3653,7 @@ ${body}`;
|
|
|
3546
3653
|
}
|
|
3547
3654
|
|
|
3548
3655
|
// src/commands/migrate-citations.ts
|
|
3549
|
-
import { writeFile as
|
|
3656
|
+
import { writeFile as writeFile11 } from "fs/promises";
|
|
3550
3657
|
var MARKER_RE2 = /\^\[(raw\/[^\]]+)\]/g;
|
|
3551
3658
|
function moveMarkersToParagraphEnd(body) {
|
|
3552
3659
|
const lines = body.split("\n");
|
|
@@ -3669,7 +3776,7 @@ ${migratedBody}${newFooter}`;
|
|
|
3669
3776
|
continue;
|
|
3670
3777
|
}
|
|
3671
3778
|
if (!input.dryRun) {
|
|
3672
|
-
await
|
|
3779
|
+
await writeFile11(page.absPath, newText, "utf8");
|
|
3673
3780
|
}
|
|
3674
3781
|
migrated.push(page.relPath);
|
|
3675
3782
|
}
|
|
@@ -3699,7 +3806,7 @@ ${migratedBody}${newFooter}`;
|
|
|
3699
3806
|
}
|
|
3700
3807
|
|
|
3701
3808
|
// src/commands/frontmatter-fix.ts
|
|
3702
|
-
import { writeFile as
|
|
3809
|
+
import { writeFile as writeFile12 } from "fs/promises";
|
|
3703
3810
|
function isoToday() {
|
|
3704
3811
|
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
3705
3812
|
}
|
|
@@ -3741,7 +3848,7 @@ ${newBody}`;
|
|
|
3741
3848
|
continue;
|
|
3742
3849
|
}
|
|
3743
3850
|
if (!input.dryRun) {
|
|
3744
|
-
await
|
|
3851
|
+
await writeFile12(page.absPath, newText, "utf8");
|
|
3745
3852
|
}
|
|
3746
3853
|
fixed.push(page.relPath);
|
|
3747
3854
|
}
|
|
@@ -3774,14 +3881,14 @@ ${newBody}`;
|
|
|
3774
3881
|
// src/commands/update.ts
|
|
3775
3882
|
import { execSync as execSync2 } from "child_process";
|
|
3776
3883
|
import { readFileSync as readFileSync6 } from "fs";
|
|
3777
|
-
import { join as
|
|
3884
|
+
import { join as join24 } from "path";
|
|
3778
3885
|
function resolveGlobalSkillsRoot() {
|
|
3779
3886
|
try {
|
|
3780
3887
|
const globalRoot = execSync2("npm root -g", {
|
|
3781
3888
|
encoding: "utf8",
|
|
3782
3889
|
timeout: 5e3
|
|
3783
3890
|
}).trim();
|
|
3784
|
-
return
|
|
3891
|
+
return join24(globalRoot, "skillwiki", "skills");
|
|
3785
3892
|
} catch {
|
|
3786
3893
|
return null;
|
|
3787
3894
|
}
|
|
@@ -3807,7 +3914,7 @@ async function runUpdate(input) {
|
|
|
3807
3914
|
);
|
|
3808
3915
|
const currentVersion = pkg2.version;
|
|
3809
3916
|
const tag = input.distTag ?? "latest";
|
|
3810
|
-
const target =
|
|
3917
|
+
const target = join24(input.home, ".claude", "skills");
|
|
3811
3918
|
let latest;
|
|
3812
3919
|
try {
|
|
3813
3920
|
latest = execSync2(`npm view skillwiki@${tag} version`, {
|
|
@@ -3877,16 +3984,16 @@ async function runUpdate(input) {
|
|
|
3877
3984
|
|
|
3878
3985
|
// src/commands/self-update.ts
|
|
3879
3986
|
import { execSync as execSync3 } from "child_process";
|
|
3880
|
-
import { existsSync as
|
|
3881
|
-
import { join as
|
|
3987
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
|
|
3988
|
+
import { join as join25 } from "path";
|
|
3882
3989
|
var DEFAULT_SOURCE_ROOT_SUFFIX = "/Desktop/code/llm-wiki";
|
|
3883
3990
|
async function runSelfUpdate(input) {
|
|
3884
3991
|
const currentVersion = JSON.parse(
|
|
3885
3992
|
readFileSync7(new URL("../../package.json", import.meta.url), "utf8")
|
|
3886
3993
|
).version;
|
|
3887
3994
|
const sourceRoot = input.sourceRoot ?? `${input.home}${DEFAULT_SOURCE_ROOT_SUFFIX}`;
|
|
3888
|
-
const localPkgPath =
|
|
3889
|
-
const hasLocalSource =
|
|
3995
|
+
const localPkgPath = join25(sourceRoot, "packages", "cli", "package.json");
|
|
3996
|
+
const hasLocalSource = existsSync7(localPkgPath);
|
|
3890
3997
|
if (input.check) {
|
|
3891
3998
|
let availableVersion = null;
|
|
3892
3999
|
let source;
|
|
@@ -4017,10 +4124,10 @@ async function runSelfUpdate(input) {
|
|
|
4017
4124
|
}
|
|
4018
4125
|
|
|
4019
4126
|
// src/commands/transcripts.ts
|
|
4020
|
-
import { readdir as readdir5, stat as stat6, readFile as
|
|
4021
|
-
import { join as
|
|
4127
|
+
import { readdir as readdir5, stat as stat6, readFile as readFile17 } from "fs/promises";
|
|
4128
|
+
import { join as join26 } from "path";
|
|
4022
4129
|
async function runTranscripts(input) {
|
|
4023
|
-
const dir =
|
|
4130
|
+
const dir = join26(input.vault, "raw", "transcripts");
|
|
4024
4131
|
let entries;
|
|
4025
4132
|
try {
|
|
4026
4133
|
entries = await readdir5(dir, { withFileTypes: true });
|
|
@@ -4030,8 +4137,8 @@ async function runTranscripts(input) {
|
|
|
4030
4137
|
const transcripts = [];
|
|
4031
4138
|
for (const entry of entries) {
|
|
4032
4139
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
4033
|
-
const filePath =
|
|
4034
|
-
const content = await
|
|
4140
|
+
const filePath = join26(dir, entry.name);
|
|
4141
|
+
const content = await readFile17(filePath, "utf8");
|
|
4035
4142
|
const fm = extractFrontmatter(content);
|
|
4036
4143
|
if (!fm.ok) continue;
|
|
4037
4144
|
const ingested = typeof fm.data.ingested === "string" ? fm.data.ingested : "";
|
|
@@ -4048,12 +4155,12 @@ async function runTranscripts(input) {
|
|
|
4048
4155
|
}
|
|
4049
4156
|
|
|
4050
4157
|
// src/commands/project-index.ts
|
|
4051
|
-
import { readdir as readdir6, readFile as
|
|
4052
|
-
import { join as
|
|
4158
|
+
import { readdir as readdir6, readFile as readFile18, writeFile as writeFile13, mkdir as mkdir9 } from "fs/promises";
|
|
4159
|
+
import { join as join27, dirname as dirname10 } from "path";
|
|
4053
4160
|
var LAYER2_DIRS = ["entities", "concepts", "comparisons", "queries", "meta"];
|
|
4054
4161
|
async function runProjectIndex(input) {
|
|
4055
4162
|
const slug = input.slug;
|
|
4056
|
-
const projectDir =
|
|
4163
|
+
const projectDir = join27(input.vault, "projects", slug);
|
|
4057
4164
|
try {
|
|
4058
4165
|
await readdir6(projectDir);
|
|
4059
4166
|
} catch {
|
|
@@ -4064,15 +4171,15 @@ async function runProjectIndex(input) {
|
|
|
4064
4171
|
}
|
|
4065
4172
|
const wikilinkPattern = `[[${slug}]]`;
|
|
4066
4173
|
const entries = [];
|
|
4067
|
-
const compoundDir =
|
|
4174
|
+
const compoundDir = join27(input.vault, "projects", slug, "compound");
|
|
4068
4175
|
try {
|
|
4069
4176
|
const compoundFiles = await readdir6(compoundDir, { withFileTypes: true });
|
|
4070
4177
|
for (const entry of compoundFiles) {
|
|
4071
4178
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
4072
|
-
const filePath =
|
|
4179
|
+
const filePath = join27(compoundDir, entry.name);
|
|
4073
4180
|
let text;
|
|
4074
4181
|
try {
|
|
4075
|
-
text = await
|
|
4182
|
+
text = await readFile18(filePath, "utf8");
|
|
4076
4183
|
} catch {
|
|
4077
4184
|
continue;
|
|
4078
4185
|
}
|
|
@@ -4089,16 +4196,16 @@ async function runProjectIndex(input) {
|
|
|
4089
4196
|
for (const dir of LAYER2_DIRS) {
|
|
4090
4197
|
let files;
|
|
4091
4198
|
try {
|
|
4092
|
-
files = await readdir6(
|
|
4199
|
+
files = await readdir6(join27(input.vault, dir), { withFileTypes: true });
|
|
4093
4200
|
} catch {
|
|
4094
4201
|
continue;
|
|
4095
4202
|
}
|
|
4096
4203
|
for (const entry of files) {
|
|
4097
4204
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
4098
|
-
const filePath =
|
|
4205
|
+
const filePath = join27(input.vault, dir, entry.name);
|
|
4099
4206
|
let text;
|
|
4100
4207
|
try {
|
|
4101
|
-
text = await
|
|
4208
|
+
text = await readFile18(filePath, "utf8");
|
|
4102
4209
|
} catch {
|
|
4103
4210
|
continue;
|
|
4104
4211
|
}
|
|
@@ -4119,11 +4226,11 @@ async function runProjectIndex(input) {
|
|
|
4119
4226
|
const tb = typeOrder[b.type] ?? 99;
|
|
4120
4227
|
return ta !== tb ? ta - tb : a.title.localeCompare(b.title);
|
|
4121
4228
|
});
|
|
4122
|
-
const indexPath =
|
|
4229
|
+
const indexPath = join27(projectDir, "knowledge.md");
|
|
4123
4230
|
let existing = false;
|
|
4124
4231
|
let stale = false;
|
|
4125
4232
|
try {
|
|
4126
|
-
const existingText = await
|
|
4233
|
+
const existingText = await readFile18(indexPath, "utf8");
|
|
4127
4234
|
existing = true;
|
|
4128
4235
|
const existingEntries = existingText.split("\n").filter((l) => l.startsWith("- [["));
|
|
4129
4236
|
const existingPages = new Set(existingEntries.map((l) => {
|
|
@@ -4163,8 +4270,8 @@ Autogenerated by \`skillwiki project-index\` on ${today}.
|
|
|
4163
4270
|
}
|
|
4164
4271
|
if (input.apply) {
|
|
4165
4272
|
try {
|
|
4166
|
-
await
|
|
4167
|
-
await
|
|
4273
|
+
await mkdir9(dirname10(indexPath), { recursive: true });
|
|
4274
|
+
await writeFile13(indexPath, body, "utf8");
|
|
4168
4275
|
} catch (e) {
|
|
4169
4276
|
return {
|
|
4170
4277
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -4192,10 +4299,10 @@ ${entries.map((e) => ` ${e.type}: [[${e.page.replace(/\.md$/, "")}]] \u2014 ${e
|
|
|
4192
4299
|
}
|
|
4193
4300
|
|
|
4194
4301
|
// src/commands/compound.ts
|
|
4195
|
-
import { writeFile as
|
|
4196
|
-
import { join as
|
|
4197
|
-
import { existsSync as
|
|
4198
|
-
import { readFile as
|
|
4302
|
+
import { writeFile as writeFile14, mkdir as mkdir10, readdir as readdir7, unlink as unlink2 } from "fs/promises";
|
|
4303
|
+
import { join as join28 } from "path";
|
|
4304
|
+
import { existsSync as existsSync8 } from "fs";
|
|
4305
|
+
import { readFile as readFile19 } from "fs/promises";
|
|
4199
4306
|
var RETRO_HEADING_RE = /^## \[(\d{4}-\d{2}-\d{2})(?:\s+[^\]]+)?\] retro \| loop cycle(?: (\d+))?: (.+)$/;
|
|
4200
4307
|
var FIELD_RE = {
|
|
4201
4308
|
improve: /^-\s+\*?\*?Improve:?\*?\*?\s*(.+)$/m,
|
|
@@ -4293,17 +4400,17 @@ function extractRetroFields(date, cycleName, block) {
|
|
|
4293
4400
|
};
|
|
4294
4401
|
}
|
|
4295
4402
|
async function runCompound(input) {
|
|
4296
|
-
const logPath =
|
|
4403
|
+
const logPath = join28(input.vault, "log.md");
|
|
4297
4404
|
let logText;
|
|
4298
4405
|
try {
|
|
4299
|
-
logText = await
|
|
4406
|
+
logText = await readFile19(logPath, "utf8");
|
|
4300
4407
|
} catch {
|
|
4301
4408
|
return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: logPath }) };
|
|
4302
4409
|
}
|
|
4303
4410
|
const entries = parseRetroEntries(logText);
|
|
4304
4411
|
const promoted = [];
|
|
4305
4412
|
const skipped = [];
|
|
4306
|
-
const compoundDir =
|
|
4413
|
+
const compoundDir = join28(input.vault, "projects", input.project, "compound");
|
|
4307
4414
|
for (const entry of entries) {
|
|
4308
4415
|
const generalizeValue = entry.generalize.trim();
|
|
4309
4416
|
if (!/^yes/i.test(generalizeValue)) {
|
|
@@ -4311,8 +4418,8 @@ async function runCompound(input) {
|
|
|
4311
4418
|
continue;
|
|
4312
4419
|
}
|
|
4313
4420
|
const slug = slugify(entry.cycleName);
|
|
4314
|
-
const compoundPath =
|
|
4315
|
-
if (
|
|
4421
|
+
const compoundPath = join28(compoundDir, `${slug}.md`);
|
|
4422
|
+
if (existsSync8(compoundPath)) {
|
|
4316
4423
|
skipped.push(entry.date);
|
|
4317
4424
|
continue;
|
|
4318
4425
|
}
|
|
@@ -4350,10 +4457,10 @@ async function runCompound(input) {
|
|
|
4350
4457
|
].join("\n");
|
|
4351
4458
|
const content = frontmatter + "\n" + body;
|
|
4352
4459
|
if (!input.dryRun) {
|
|
4353
|
-
if (!
|
|
4354
|
-
await
|
|
4460
|
+
if (!existsSync8(compoundDir)) {
|
|
4461
|
+
await mkdir10(compoundDir, { recursive: true });
|
|
4355
4462
|
}
|
|
4356
|
-
await
|
|
4463
|
+
await writeFile14(compoundPath, content, "utf8");
|
|
4357
4464
|
}
|
|
4358
4465
|
promoted.push(`${slug}.md`);
|
|
4359
4466
|
}
|
|
@@ -4372,16 +4479,16 @@ async function runCompound(input) {
|
|
|
4372
4479
|
};
|
|
4373
4480
|
}
|
|
4374
4481
|
async function runCompoundDelete(input) {
|
|
4375
|
-
const projectDir =
|
|
4376
|
-
if (!
|
|
4482
|
+
const projectDir = join28(input.vault, "projects", input.project);
|
|
4483
|
+
if (!existsSync8(projectDir)) {
|
|
4377
4484
|
return {
|
|
4378
4485
|
exitCode: ExitCode.PROJECT_NOT_FOUND,
|
|
4379
4486
|
result: err("PROJECT_NOT_FOUND", { slug: input.project, path: projectDir })
|
|
4380
4487
|
};
|
|
4381
4488
|
}
|
|
4382
4489
|
const entryName = input.entry.replace(/\.md$/, "");
|
|
4383
|
-
const compoundPath =
|
|
4384
|
-
if (!
|
|
4490
|
+
const compoundPath = join28(projectDir, "compound", `${entryName}.md`);
|
|
4491
|
+
if (!existsSync8(compoundPath)) {
|
|
4385
4492
|
return {
|
|
4386
4493
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
4387
4494
|
result: err("FILE_NOT_FOUND", { path: compoundPath })
|
|
@@ -4414,8 +4521,8 @@ knowledge.md regenerated`
|
|
|
4414
4521
|
};
|
|
4415
4522
|
}
|
|
4416
4523
|
async function runCompoundList(input) {
|
|
4417
|
-
const compoundDir =
|
|
4418
|
-
if (!
|
|
4524
|
+
const compoundDir = join28(input.vault, "projects", input.project, "compound");
|
|
4525
|
+
if (!existsSync8(compoundDir)) {
|
|
4419
4526
|
return {
|
|
4420
4527
|
exitCode: ExitCode.OK,
|
|
4421
4528
|
result: ok({
|
|
@@ -4445,10 +4552,10 @@ could not read compound directory`
|
|
|
4445
4552
|
const entries = [];
|
|
4446
4553
|
for (const dirent of dirents) {
|
|
4447
4554
|
if (!dirent.isFile() || !dirent.name.endsWith(".md")) continue;
|
|
4448
|
-
const filePath =
|
|
4555
|
+
const filePath = join28(compoundDir, dirent.name);
|
|
4449
4556
|
let text;
|
|
4450
4557
|
try {
|
|
4451
|
-
text = await
|
|
4558
|
+
text = await readFile19(filePath, "utf8");
|
|
4452
4559
|
} catch {
|
|
4453
4560
|
continue;
|
|
4454
4561
|
}
|
|
@@ -4477,9 +4584,9 @@ no compound entries found`;
|
|
|
4477
4584
|
}
|
|
4478
4585
|
|
|
4479
4586
|
// src/commands/observe.ts
|
|
4480
|
-
import { mkdir as
|
|
4481
|
-
import { existsSync as
|
|
4482
|
-
import { join as
|
|
4587
|
+
import { mkdir as mkdir11, writeFile as writeFile15 } from "fs/promises";
|
|
4588
|
+
import { existsSync as existsSync9, statSync as statSync3 } from "fs";
|
|
4589
|
+
import { join as join29 } from "path";
|
|
4483
4590
|
import { createHash as createHash3 } from "crypto";
|
|
4484
4591
|
var ALLOWED_KINDS = /* @__PURE__ */ new Set(["note", "bug", "task", "idea", "session-log"]);
|
|
4485
4592
|
function slugify2(text) {
|
|
@@ -4502,15 +4609,15 @@ async function runObserve(input) {
|
|
|
4502
4609
|
result: err("SCHEME_REJECTED", { message: "Text must not be empty" })
|
|
4503
4610
|
};
|
|
4504
4611
|
}
|
|
4505
|
-
if (!
|
|
4612
|
+
if (!existsSync9(input.vault) || !statSync3(input.vault).isDirectory()) {
|
|
4506
4613
|
return {
|
|
4507
4614
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
4508
4615
|
result: err("VAULT_PATH_INVALID", { path: input.vault })
|
|
4509
4616
|
};
|
|
4510
4617
|
}
|
|
4511
|
-
const transcriptsDir =
|
|
4618
|
+
const transcriptsDir = join29(input.vault, "raw", "transcripts");
|
|
4512
4619
|
try {
|
|
4513
|
-
await
|
|
4620
|
+
await mkdir11(transcriptsDir, { recursive: true });
|
|
4514
4621
|
} catch {
|
|
4515
4622
|
return {
|
|
4516
4623
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
@@ -4520,7 +4627,7 @@ async function runObserve(input) {
|
|
|
4520
4627
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
4521
4628
|
const slug = slugify2(input.text);
|
|
4522
4629
|
const fileName = `${today}-observation-${slug}.md`;
|
|
4523
|
-
const filePath =
|
|
4630
|
+
const filePath = join29(transcriptsDir, fileName);
|
|
4524
4631
|
const body = `
|
|
4525
4632
|
${input.text.trim()}
|
|
4526
4633
|
`;
|
|
@@ -4538,7 +4645,7 @@ ${input.text.trim()}
|
|
|
4538
4645
|
frontmatterLines.push("---");
|
|
4539
4646
|
const content = frontmatterLines.join("\n") + body;
|
|
4540
4647
|
try {
|
|
4541
|
-
await
|
|
4648
|
+
await writeFile15(filePath, content, "utf8");
|
|
4542
4649
|
} catch (e) {
|
|
4543
4650
|
return {
|
|
4544
4651
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -4560,8 +4667,8 @@ ${input.text.trim()}
|
|
|
4560
4667
|
}
|
|
4561
4668
|
|
|
4562
4669
|
// src/commands/ingest.ts
|
|
4563
|
-
import { readFile as
|
|
4564
|
-
import { join as
|
|
4670
|
+
import { readFile as readFile20, writeFile as writeFile16, mkdir as mkdir12 } from "fs/promises";
|
|
4671
|
+
import { join as join30 } from "path";
|
|
4565
4672
|
import { createHash as createHash4 } from "crypto";
|
|
4566
4673
|
var ALLOWED_TYPES = /* @__PURE__ */ new Set(["entity", "concept", "comparison", "query"]);
|
|
4567
4674
|
var TYPE_DIR = {
|
|
@@ -4720,7 +4827,7 @@ async function runIngest(input) {
|
|
|
4720
4827
|
sourceContent = fetchResult.data.body;
|
|
4721
4828
|
} else {
|
|
4722
4829
|
try {
|
|
4723
|
-
sourceContent = await
|
|
4830
|
+
sourceContent = await readFile20(input.source, "utf8");
|
|
4724
4831
|
} catch {
|
|
4725
4832
|
return {
|
|
4726
4833
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
@@ -4735,8 +4842,8 @@ async function runIngest(input) {
|
|
|
4735
4842
|
const rawRelPath = `raw/articles/${slug}.md`;
|
|
4736
4843
|
const typedDir = TYPE_DIR[input.type] ?? `${input.type}s`;
|
|
4737
4844
|
const typedRelPath = `${typedDir}/${slug}.md`;
|
|
4738
|
-
const rawAbsPath =
|
|
4739
|
-
const typedAbsPath =
|
|
4845
|
+
const rawAbsPath = join30(input.vault, rawRelPath);
|
|
4846
|
+
const typedAbsPath = join30(input.vault, typedRelPath);
|
|
4740
4847
|
const rawContent = buildRawContent(sourceUrl, today, sha256, sourceContent);
|
|
4741
4848
|
const typedContent = buildTypedContent(
|
|
4742
4849
|
input.title,
|
|
@@ -4799,8 +4906,8 @@ async function runIngest(input) {
|
|
|
4799
4906
|
};
|
|
4800
4907
|
}
|
|
4801
4908
|
try {
|
|
4802
|
-
await
|
|
4803
|
-
await
|
|
4909
|
+
await mkdir12(join30(input.vault, "raw", "articles"), { recursive: true });
|
|
4910
|
+
await writeFile16(rawAbsPath, rawContent, "utf8");
|
|
4804
4911
|
} catch (e) {
|
|
4805
4912
|
return {
|
|
4806
4913
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -4808,8 +4915,8 @@ async function runIngest(input) {
|
|
|
4808
4915
|
};
|
|
4809
4916
|
}
|
|
4810
4917
|
try {
|
|
4811
|
-
await
|
|
4812
|
-
await
|
|
4918
|
+
await mkdir12(join30(input.vault, typedDir), { recursive: true });
|
|
4919
|
+
await writeFile16(typedAbsPath, typedContent, "utf8");
|
|
4813
4920
|
} catch (e) {
|
|
4814
4921
|
return {
|
|
4815
4922
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -4840,7 +4947,7 @@ async function runIngest(input) {
|
|
|
4840
4947
|
}
|
|
4841
4948
|
|
|
4842
4949
|
// src/commands/tag-sync.ts
|
|
4843
|
-
import { writeFile as
|
|
4950
|
+
import { writeFile as writeFile17 } from "fs/promises";
|
|
4844
4951
|
var ENUM_MIRRORS = {
|
|
4845
4952
|
provenance: ["research", "project", "mixed"],
|
|
4846
4953
|
confidence: ["high", "medium", "low"]
|
|
@@ -4955,7 +5062,7 @@ ${newFm}
|
|
|
4955
5062
|
---
|
|
4956
5063
|
${body}`;
|
|
4957
5064
|
if (!input.dryRun) {
|
|
4958
|
-
await
|
|
5065
|
+
await writeFile17(page.absPath, newText, "utf8");
|
|
4959
5066
|
}
|
|
4960
5067
|
synced.push(page.relPath);
|
|
4961
5068
|
}
|
|
@@ -4984,11 +5091,11 @@ ${body}`;
|
|
|
4984
5091
|
}
|
|
4985
5092
|
|
|
4986
5093
|
// src/commands/sync.ts
|
|
4987
|
-
import { existsSync as
|
|
4988
|
-
import { join as
|
|
5094
|
+
import { existsSync as existsSync10 } from "fs";
|
|
5095
|
+
import { join as join31 } from "path";
|
|
4989
5096
|
function runSyncStatus(input) {
|
|
4990
5097
|
const vault = input.vault;
|
|
4991
|
-
if (!
|
|
5098
|
+
if (!existsSync10(join31(vault, ".git"))) {
|
|
4992
5099
|
return {
|
|
4993
5100
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
4994
5101
|
result: ok({
|
|
@@ -5057,7 +5164,7 @@ function runSyncStatus(input) {
|
|
|
5057
5164
|
}
|
|
5058
5165
|
async function runSyncPush(input) {
|
|
5059
5166
|
const vault = input.vault;
|
|
5060
|
-
if (!
|
|
5167
|
+
if (!existsSync10(join31(vault, ".git"))) {
|
|
5061
5168
|
return {
|
|
5062
5169
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
5063
5170
|
result: err("NOT_A_GIT_REPO", { path: vault })
|
|
@@ -5142,7 +5249,7 @@ async function runSyncPush(input) {
|
|
|
5142
5249
|
}
|
|
5143
5250
|
async function runSyncPull(input) {
|
|
5144
5251
|
const vault = input.vault;
|
|
5145
|
-
if (!
|
|
5252
|
+
if (!existsSync10(join31(vault, ".git"))) {
|
|
5146
5253
|
return {
|
|
5147
5254
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
5148
5255
|
result: err("NOT_A_GIT_REPO", { path: vault })
|
|
@@ -5217,8 +5324,8 @@ async function runSyncPull(input) {
|
|
|
5217
5324
|
}
|
|
5218
5325
|
|
|
5219
5326
|
// src/commands/backup.ts
|
|
5220
|
-
import { statSync as
|
|
5221
|
-
import { join as
|
|
5327
|
+
import { statSync as statSync4, readdirSync as readdirSync2, readFileSync as readFileSync8, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
5328
|
+
import { join as join32, relative as relative3, dirname as dirname11 } from "path";
|
|
5222
5329
|
import { PutObjectCommand, HeadObjectCommand, ListObjectsV2Command, GetObjectCommand, DeleteObjectsCommand } from "@aws-sdk/client-s3";
|
|
5223
5330
|
|
|
5224
5331
|
// src/utils/s3-client.ts
|
|
@@ -5242,7 +5349,7 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([".git", ".obsidian", "_archive", "node_
|
|
|
5242
5349
|
function* walkMarkdown(dir, base) {
|
|
5243
5350
|
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
5244
5351
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
5245
|
-
const full =
|
|
5352
|
+
const full = join32(dir, entry.name);
|
|
5246
5353
|
if (entry.isDirectory()) {
|
|
5247
5354
|
yield* walkMarkdown(full, base);
|
|
5248
5355
|
} else if (entry.name.endsWith(".md")) {
|
|
@@ -5265,8 +5372,8 @@ async function runBackupSync(input) {
|
|
|
5265
5372
|
let failed = 0;
|
|
5266
5373
|
const files = [...walkMarkdown(input.vault, input.vault)];
|
|
5267
5374
|
for (const relPath of files) {
|
|
5268
|
-
const absPath =
|
|
5269
|
-
const localStat =
|
|
5375
|
+
const absPath = join32(input.vault, relPath);
|
|
5376
|
+
const localStat = statSync4(absPath);
|
|
5270
5377
|
let needsUpload = true;
|
|
5271
5378
|
try {
|
|
5272
5379
|
const head = await client.send(new HeadObjectCommand({ Bucket: input.bucket, Key: relPath }));
|
|
@@ -5341,9 +5448,9 @@ async function runBackupRestore(input) {
|
|
|
5341
5448
|
const objects = list.Contents ?? [];
|
|
5342
5449
|
for (const obj of objects) {
|
|
5343
5450
|
if (!obj.Key) continue;
|
|
5344
|
-
const localPath =
|
|
5451
|
+
const localPath = join32(target, obj.Key);
|
|
5345
5452
|
try {
|
|
5346
|
-
const localStat =
|
|
5453
|
+
const localStat = statSync4(localPath);
|
|
5347
5454
|
if (obj.LastModified && localStat.mtime > obj.LastModified) {
|
|
5348
5455
|
conflicts++;
|
|
5349
5456
|
continue;
|
|
@@ -5387,11 +5494,11 @@ async function runBackupRestore(input) {
|
|
|
5387
5494
|
}
|
|
5388
5495
|
|
|
5389
5496
|
// src/commands/status.ts
|
|
5390
|
-
import { existsSync as
|
|
5391
|
-
import { readFile as
|
|
5392
|
-
import { join as
|
|
5497
|
+
import { existsSync as existsSync11, statSync as statSync5 } from "fs";
|
|
5498
|
+
import { readFile as readFile21 } from "fs/promises";
|
|
5499
|
+
import { join as join33 } from "path";
|
|
5393
5500
|
async function runStatus(input) {
|
|
5394
|
-
if (!
|
|
5501
|
+
if (!existsSync11(input.vault)) {
|
|
5395
5502
|
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { vault: input.vault }) };
|
|
5396
5503
|
}
|
|
5397
5504
|
const scan = await scanVault(input.vault);
|
|
@@ -5416,7 +5523,7 @@ async function runStatus(input) {
|
|
|
5416
5523
|
const compound = scan.data.compound.length;
|
|
5417
5524
|
let schemaVersion = "v1";
|
|
5418
5525
|
try {
|
|
5419
|
-
const schemaContent = await
|
|
5526
|
+
const schemaContent = await readFile21(join33(input.vault, "SCHEMA.md"), "utf8");
|
|
5420
5527
|
const versionMatch = schemaContent.match(/version:\s*["']?([^"'\s\n]+)/i);
|
|
5421
5528
|
if (versionMatch) schemaVersion = versionMatch[1];
|
|
5422
5529
|
} catch {
|
|
@@ -5432,7 +5539,7 @@ async function runStatus(input) {
|
|
|
5432
5539
|
let maxTime = 0;
|
|
5433
5540
|
for (const page of allPages) {
|
|
5434
5541
|
try {
|
|
5435
|
-
const st =
|
|
5542
|
+
const st = statSync5(page.absPath);
|
|
5436
5543
|
if (st.mtimeMs > maxTime) {
|
|
5437
5544
|
maxTime = st.mtimeMs;
|
|
5438
5545
|
lastModified = st.mtime.toISOString();
|
|
@@ -5476,8 +5583,8 @@ async function runStatus(input) {
|
|
|
5476
5583
|
}
|
|
5477
5584
|
|
|
5478
5585
|
// src/commands/seed.ts
|
|
5479
|
-
import { mkdir as
|
|
5480
|
-
import { join as
|
|
5586
|
+
import { mkdir as mkdir13, writeFile as writeFile18, stat as stat7 } from "fs/promises";
|
|
5587
|
+
import { join as join34 } from "path";
|
|
5481
5588
|
var TODAY = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
5482
5589
|
var EXAMPLE_PAGES = {
|
|
5483
5590
|
"entities/example-project.md": `---
|
|
@@ -5546,30 +5653,30 @@ Real sources are immutable after ingestion \u2014 never edit them.
|
|
|
5546
5653
|
`;
|
|
5547
5654
|
async function runSeed(input) {
|
|
5548
5655
|
try {
|
|
5549
|
-
await stat7(
|
|
5656
|
+
await stat7(join34(input.vault, "SCHEMA.md"));
|
|
5550
5657
|
} catch {
|
|
5551
5658
|
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { root: input.vault, reason: "SCHEMA.md missing \u2014 run `skillwiki init` first" }) };
|
|
5552
5659
|
}
|
|
5553
5660
|
const created = [];
|
|
5554
5661
|
const skipped = [];
|
|
5555
5662
|
for (const [relPath, content] of Object.entries(EXAMPLE_PAGES)) {
|
|
5556
|
-
const absPath =
|
|
5663
|
+
const absPath = join34(input.vault, relPath);
|
|
5557
5664
|
try {
|
|
5558
5665
|
await stat7(absPath);
|
|
5559
5666
|
skipped.push(relPath);
|
|
5560
5667
|
} catch {
|
|
5561
|
-
await
|
|
5562
|
-
await
|
|
5668
|
+
await mkdir13(join34(absPath, ".."), { recursive: true });
|
|
5669
|
+
await writeFile18(absPath, content, "utf8");
|
|
5563
5670
|
created.push(relPath);
|
|
5564
5671
|
}
|
|
5565
5672
|
}
|
|
5566
|
-
const rawPath =
|
|
5673
|
+
const rawPath = join34(input.vault, "raw", "articles", "example-source.md");
|
|
5567
5674
|
try {
|
|
5568
5675
|
await stat7(rawPath);
|
|
5569
5676
|
skipped.push("raw/articles/example-source.md");
|
|
5570
5677
|
} catch {
|
|
5571
|
-
await
|
|
5572
|
-
await
|
|
5678
|
+
await mkdir13(join34(rawPath, ".."), { recursive: true });
|
|
5679
|
+
await writeFile18(rawPath, EXAMPLE_RAW, "utf8");
|
|
5573
5680
|
created.push("raw/articles/example-source.md");
|
|
5574
5681
|
}
|
|
5575
5682
|
if (created.length > 0) {
|
|
@@ -5591,9 +5698,9 @@ async function runSeed(input) {
|
|
|
5591
5698
|
}
|
|
5592
5699
|
|
|
5593
5700
|
// src/commands/canvas.ts
|
|
5594
|
-
import { readFile as
|
|
5595
|
-
import { existsSync as
|
|
5596
|
-
import { join as
|
|
5701
|
+
import { readFile as readFile22, writeFile as writeFile19 } from "fs/promises";
|
|
5702
|
+
import { existsSync as existsSync12 } from "fs";
|
|
5703
|
+
import { join as join35 } from "path";
|
|
5597
5704
|
var NODE_WIDTH = 240;
|
|
5598
5705
|
var NODE_HEIGHT = 60;
|
|
5599
5706
|
var COLUMN_SPACING = 400;
|
|
@@ -5671,8 +5778,8 @@ function buildCanvasEdges(adjacency) {
|
|
|
5671
5778
|
return edges;
|
|
5672
5779
|
}
|
|
5673
5780
|
async function runCanvasGenerate(input) {
|
|
5674
|
-
const graphPath = input.graphPath ??
|
|
5675
|
-
if (!
|
|
5781
|
+
const graphPath = input.graphPath ?? join35(input.vault, ".skillwiki", "graph.json");
|
|
5782
|
+
if (!existsSync12(graphPath)) {
|
|
5676
5783
|
return {
|
|
5677
5784
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
5678
5785
|
result: err("FILE_NOT_FOUND", {
|
|
@@ -5683,7 +5790,7 @@ async function runCanvasGenerate(input) {
|
|
|
5683
5790
|
}
|
|
5684
5791
|
let raw;
|
|
5685
5792
|
try {
|
|
5686
|
-
raw = await
|
|
5793
|
+
raw = await readFile22(graphPath, "utf8");
|
|
5687
5794
|
} catch (e) {
|
|
5688
5795
|
return {
|
|
5689
5796
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
@@ -5709,9 +5816,9 @@ async function runCanvasGenerate(input) {
|
|
|
5709
5816
|
const nodes = buildCanvasNodes(paths);
|
|
5710
5817
|
const edges = buildCanvasEdges(graph.adjacency);
|
|
5711
5818
|
const canvas = { nodes, edges };
|
|
5712
|
-
const outPath =
|
|
5819
|
+
const outPath = join35(input.vault, "vault-graph.canvas");
|
|
5713
5820
|
try {
|
|
5714
|
-
await
|
|
5821
|
+
await writeFile19(outPath, JSON.stringify(canvas, null, 2));
|
|
5715
5822
|
} catch (e) {
|
|
5716
5823
|
return {
|
|
5717
5824
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -5731,8 +5838,8 @@ written: ${outPath}`
|
|
|
5731
5838
|
}
|
|
5732
5839
|
|
|
5733
5840
|
// src/commands/query.ts
|
|
5734
|
-
import { readFile as
|
|
5735
|
-
import { join as
|
|
5841
|
+
import { readFile as readFile23, stat as stat8 } from "fs/promises";
|
|
5842
|
+
import { join as join36 } from "path";
|
|
5736
5843
|
var W_KEYWORD = 2;
|
|
5737
5844
|
var W_SOURCE_OVERLAP = 4;
|
|
5738
5845
|
var W_WIKILINK = 3;
|
|
@@ -5853,7 +5960,7 @@ function computeKeywordScore(terms, title, tags, body) {
|
|
|
5853
5960
|
return score;
|
|
5854
5961
|
}
|
|
5855
5962
|
async function loadOrBuildGraph(vault) {
|
|
5856
|
-
const graphPath =
|
|
5963
|
+
const graphPath = join36(vault, ".skillwiki", "graph.json");
|
|
5857
5964
|
let needsBuild = false;
|
|
5858
5965
|
try {
|
|
5859
5966
|
const fileStat = await stat8(graphPath);
|
|
@@ -5867,7 +5974,7 @@ async function loadOrBuildGraph(vault) {
|
|
|
5867
5974
|
if (buildResult.exitCode !== 0) return null;
|
|
5868
5975
|
}
|
|
5869
5976
|
try {
|
|
5870
|
-
const raw = await
|
|
5977
|
+
const raw = await readFile23(graphPath, "utf8");
|
|
5871
5978
|
return JSON.parse(raw);
|
|
5872
5979
|
} catch {
|
|
5873
5980
|
return null;
|
|
@@ -5875,14 +5982,14 @@ async function loadOrBuildGraph(vault) {
|
|
|
5875
5982
|
}
|
|
5876
5983
|
|
|
5877
5984
|
// src/utils/auto-commit.ts
|
|
5878
|
-
import { existsSync as
|
|
5879
|
-
import { join as
|
|
5985
|
+
import { existsSync as existsSync13 } from "fs";
|
|
5986
|
+
import { join as join37 } from "path";
|
|
5880
5987
|
async function postCommit(vault, exitCode) {
|
|
5881
5988
|
if (exitCode !== 0) return;
|
|
5882
5989
|
const home = process.env.HOME ?? "";
|
|
5883
5990
|
const dotenv = await parseDotenvFile(configPath(home));
|
|
5884
5991
|
if (dotenv["AUTO_COMMIT"] === "false") return;
|
|
5885
|
-
if (!
|
|
5992
|
+
if (!existsSync13(join37(vault, ".git"))) return;
|
|
5886
5993
|
const lastOps = readLastOp(vault);
|
|
5887
5994
|
if (lastOps.length === 0) return;
|
|
5888
5995
|
const porcelain = git(vault, ["status", "--porcelain"]);
|
|
@@ -5933,7 +6040,7 @@ program.command("validate <file>").description("validate vault page frontmatter
|
|
|
5933
6040
|
emit(await runValidate({ file, apply: !!opts.apply, vault }), vault);
|
|
5934
6041
|
});
|
|
5935
6042
|
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) => {
|
|
5936
|
-
const out = opts.out ??
|
|
6043
|
+
const out = opts.out ?? join38(vault, ".skillwiki", "graph.json");
|
|
5937
6044
|
emit(await runGraphBuild({ vault, out }), vault);
|
|
5938
6045
|
});
|
|
5939
6046
|
var canvasCmd = program.command("canvas").description("manage Obsidian canvas files");
|
|
@@ -6044,6 +6151,11 @@ program.command("stale [vault]").description("identify stale transcripts and inc
|
|
|
6044
6151
|
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
6045
6152
|
else emit(await runStale({ vault: v.vault, days: opts.days, archive: !!opts.archive, forceScan: !!opts.forceScan }), v.vault);
|
|
6046
6153
|
});
|
|
6154
|
+
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) => {
|
|
6155
|
+
const v = await resolveVaultArg(vault, opts.wiki);
|
|
6156
|
+
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
6157
|
+
else emit(await runClaim({ vault: v.vault, transcript, project: opts.project, slug: opts.slug }), v.vault);
|
|
6158
|
+
});
|
|
6047
6159
|
program.command("pagesize [vault]").description("report page sizes and flag oversized pages").option("--lines <n>", "max body lines", (s) => parseInt(s, 10), 200).option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
6048
6160
|
const v = await resolveVaultArg(vault, opts.wiki);
|
|
6049
6161
|
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|