skillwiki 0.9.62 → 0.9.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-2PENIQ3A.js → chunk-TUFQZ5K4.js} +1 -0
- package/dist/cli.js +293 -128
- package/dist/skillwiki-mcp.js +1 -1
- package/package.json +1 -1
- package/skills/.claude-plugin/plugin.json +2 -2
- package/skills/.codex-plugin/plugin.json +3 -3
- package/skills/package.json +1 -1
- package/skills/skills/using-skillwiki/SKILL.md +16 -2
- package/skills/skills/wiki-archive/SKILL.md +18 -4
- package/skills/skills/wiki-remove/SKILL.md +96 -0
- package/skills/using-skillwiki/SKILL.md +16 -2
- package/skills/wiki-archive/SKILL.md +18 -4
- package/skills/wiki-remove/SKILL.md +96 -0
package/dist/cli.js
CHANGED
|
@@ -92,7 +92,7 @@ import {
|
|
|
92
92
|
taxonomyCommentForPage,
|
|
93
93
|
upsertIndexEntry,
|
|
94
94
|
writeDotenv
|
|
95
|
-
} from "./chunk-
|
|
95
|
+
} from "./chunk-TUFQZ5K4.js";
|
|
96
96
|
import {
|
|
97
97
|
normalizeDistTag,
|
|
98
98
|
readCache,
|
|
@@ -102,7 +102,7 @@ import {
|
|
|
102
102
|
} from "./chunk-7I2TPIV5.js";
|
|
103
103
|
|
|
104
104
|
// src/cli.ts
|
|
105
|
-
import { join as
|
|
105
|
+
import { join as join27 } from "path";
|
|
106
106
|
import { Command } from "commander";
|
|
107
107
|
|
|
108
108
|
// src/utils/output.ts
|
|
@@ -1359,8 +1359,50 @@ async function runHealth(input) {
|
|
|
1359
1359
|
}
|
|
1360
1360
|
|
|
1361
1361
|
// src/commands/archive.ts
|
|
1362
|
-
import { rename as rename2, mkdir as
|
|
1363
|
-
import { join as
|
|
1362
|
+
import { rename as rename2, mkdir as mkdir6, readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
|
|
1363
|
+
import { join as join9, dirname as dirname5 } from "path";
|
|
1364
|
+
|
|
1365
|
+
// src/utils/delete-intent.ts
|
|
1366
|
+
import { mkdir as mkdir5, writeFile as writeFile4, readdir as readdir3, readFile as readFile5 } from "fs/promises";
|
|
1367
|
+
import { join as join8 } from "path";
|
|
1368
|
+
var DELETE_INTENT_SCHEMA = "vault-delete-intent/v1";
|
|
1369
|
+
var DELETE_INTENT_DIR = "meta/delete-intents";
|
|
1370
|
+
function normalizeVaultRelPath(path) {
|
|
1371
|
+
const p = path.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
1372
|
+
if (!p || p.includes("..") || p.startsWith(".git/")) {
|
|
1373
|
+
throw new Error(`invalid vault-relative path: ${path}`);
|
|
1374
|
+
}
|
|
1375
|
+
return p;
|
|
1376
|
+
}
|
|
1377
|
+
function pathToIntentFilename(path) {
|
|
1378
|
+
const p = normalizeVaultRelPath(path);
|
|
1379
|
+
return `${p.replace(/\//g, "__")}.json`;
|
|
1380
|
+
}
|
|
1381
|
+
function intentHostId() {
|
|
1382
|
+
return process.env.SKILLWIKI_HOST_ID ?? process.env.AGENT_HOST_ID ?? "unknown";
|
|
1383
|
+
}
|
|
1384
|
+
function buildDeleteIntent(input) {
|
|
1385
|
+
return {
|
|
1386
|
+
schema: DELETE_INTENT_SCHEMA,
|
|
1387
|
+
path: normalizeVaultRelPath(input.path),
|
|
1388
|
+
action: input.action,
|
|
1389
|
+
created: input.created ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1390
|
+
host: input.host ?? intentHostId(),
|
|
1391
|
+
actor: input.actor,
|
|
1392
|
+
reason: input.reason,
|
|
1393
|
+
source: input.source,
|
|
1394
|
+
expires: input.expires ?? null
|
|
1395
|
+
};
|
|
1396
|
+
}
|
|
1397
|
+
async function writeDeleteIntent(vault, intent) {
|
|
1398
|
+
const dir = join8(vault, DELETE_INTENT_DIR);
|
|
1399
|
+
await mkdir5(dir, { recursive: true });
|
|
1400
|
+
const rel = `${DELETE_INTENT_DIR}/${pathToIntentFilename(intent.path)}`;
|
|
1401
|
+
await writeFile4(join8(vault, rel), JSON.stringify(intent, null, 2) + "\n", "utf8");
|
|
1402
|
+
return rel;
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
// src/commands/archive.ts
|
|
1364
1406
|
function countWikilinks(body, slug) {
|
|
1365
1407
|
const escaped = slug.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1366
1408
|
const re = new RegExp(`\\[\\[${escaped}(?:[|#][^\\]]*)?\\]\\]`, "g");
|
|
@@ -1394,7 +1436,7 @@ async function runArchive(input) {
|
|
|
1394
1436
|
if (!relPath) return { exitCode: ExitCode.ARCHIVE_TARGET_NOT_FOUND, result: err("ARCHIVE_TARGET_NOT_FOUND", { page: input.page }) };
|
|
1395
1437
|
if (relPath.startsWith("_archive/")) return { exitCode: ExitCode.ARCHIVE_ALREADY_ARCHIVED, result: err("ARCHIVE_ALREADY_ARCHIVED", { page: relPath }) };
|
|
1396
1438
|
const slug = relPath.replace(/\.md$/, "").split("/").pop();
|
|
1397
|
-
const archivePath =
|
|
1439
|
+
const archivePath = join9("_archive", relPath).replace(/\\/g, "/");
|
|
1398
1440
|
const remoteRoot = normalizeRemoteRoot(input.remote);
|
|
1399
1441
|
const remoteObjectPath = buildRemoteObjectPath(remoteRoot, relPath);
|
|
1400
1442
|
let cascade;
|
|
@@ -1420,7 +1462,7 @@ async function runArchive(input) {
|
|
|
1420
1462
|
const indexRefs = [];
|
|
1421
1463
|
if (!isRaw) {
|
|
1422
1464
|
try {
|
|
1423
|
-
const idx = await
|
|
1465
|
+
const idx = await readFile6(join9(input.vault, "index.md"), "utf8");
|
|
1424
1466
|
idx.split("\n").forEach((line, i) => {
|
|
1425
1467
|
if (line.includes(`[[${slug}]]`)) indexRefs.push({ line: i + 1, text: line });
|
|
1426
1468
|
});
|
|
@@ -1447,8 +1489,8 @@ async function runArchive(input) {
|
|
|
1447
1489
|
}
|
|
1448
1490
|
if (input.cascade && input.apply && cascade) {
|
|
1449
1491
|
for (const ref of cascade.source_array_refs) {
|
|
1450
|
-
const absPath =
|
|
1451
|
-
const text = await
|
|
1492
|
+
const absPath = join9(input.vault, ref.page);
|
|
1493
|
+
const text = await readFile6(absPath, "utf8");
|
|
1452
1494
|
const split = splitFrontmatter(text);
|
|
1453
1495
|
if (!split.ok) continue;
|
|
1454
1496
|
const before = split.data.rawFrontmatter;
|
|
@@ -1459,33 +1501,40 @@ async function runArchive(input) {
|
|
|
1459
1501
|
);
|
|
1460
1502
|
if (fmRewritten === before) continue;
|
|
1461
1503
|
if (!arraysEqual(ref.sources_after, ref.sources_before)) {
|
|
1462
|
-
await
|
|
1504
|
+
await writeFile5(absPath, `---
|
|
1463
1505
|
${fmRewritten}
|
|
1464
1506
|
---${split.data.body}`, "utf8");
|
|
1465
1507
|
}
|
|
1466
1508
|
}
|
|
1467
1509
|
}
|
|
1468
|
-
await
|
|
1510
|
+
await mkdir6(dirname5(join9(input.vault, archivePath)), { recursive: true });
|
|
1469
1511
|
let indexUpdated = false;
|
|
1470
1512
|
if (!isRaw) {
|
|
1471
|
-
const indexPath =
|
|
1513
|
+
const indexPath = join9(input.vault, "index.md");
|
|
1472
1514
|
try {
|
|
1473
|
-
const idx = await
|
|
1515
|
+
const idx = await readFile6(indexPath, "utf8");
|
|
1474
1516
|
const originalLines = idx.split("\n");
|
|
1475
1517
|
const filtered = originalLines.filter((l) => !l.includes(`[[${slug}]]`));
|
|
1476
1518
|
if (filtered.length !== originalLines.length) {
|
|
1477
|
-
await
|
|
1519
|
+
await writeFile5(indexPath, filtered.join("\n"), "utf8");
|
|
1478
1520
|
indexUpdated = true;
|
|
1479
1521
|
}
|
|
1480
1522
|
} catch (e) {
|
|
1481
1523
|
if (e instanceof Error && "code" in e && e.code !== "ENOENT") throw e;
|
|
1482
1524
|
}
|
|
1483
1525
|
}
|
|
1484
|
-
await rename2(
|
|
1526
|
+
await rename2(join9(input.vault, relPath), join9(input.vault, archivePath));
|
|
1527
|
+
const archiveIntent = buildDeleteIntent({
|
|
1528
|
+
path: relPath,
|
|
1529
|
+
action: "archive",
|
|
1530
|
+
actor: "skillwiki-cli",
|
|
1531
|
+
source: "cli"
|
|
1532
|
+
});
|
|
1533
|
+
const tombstonePath = await writeDeleteIntent(input.vault, archiveIntent);
|
|
1485
1534
|
appendLastOp(input.vault, {
|
|
1486
1535
|
operation: input.cascade ? "archive-cascade" : "archive",
|
|
1487
|
-
summary: `moved ${relPath} to ${archivePath}${input.cascade ? ` (cascade: ${cascade?.source_array_refs.length ?? 0} source arrays updated)` : ""}`,
|
|
1488
|
-
files: [relPath],
|
|
1536
|
+
summary: `moved ${relPath} to ${archivePath}${input.cascade ? ` (cascade: ${cascade?.source_array_refs.length ?? 0} source arrays updated)` : ""}; tombstone ${tombstonePath}`,
|
|
1537
|
+
files: [relPath, archivePath, tombstonePath],
|
|
1489
1538
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1490
1539
|
});
|
|
1491
1540
|
let remote;
|
|
@@ -1514,6 +1563,106 @@ ${fmRewritten}
|
|
|
1514
1563
|
};
|
|
1515
1564
|
}
|
|
1516
1565
|
|
|
1566
|
+
// src/commands/remove.ts
|
|
1567
|
+
import { unlink as unlink2, readFile as readFile7, writeFile as writeFile6, access } from "fs/promises";
|
|
1568
|
+
import { join as join10 } from "path";
|
|
1569
|
+
async function pathExists(abs) {
|
|
1570
|
+
try {
|
|
1571
|
+
await access(abs);
|
|
1572
|
+
return true;
|
|
1573
|
+
} catch {
|
|
1574
|
+
return false;
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
async function runRemove(input) {
|
|
1578
|
+
if (input.remoteDelete && !input.remote) {
|
|
1579
|
+
return { exitCode: ExitCode.USAGE, result: err("USAGE", { message: "--remote-delete requires --remote" }) };
|
|
1580
|
+
}
|
|
1581
|
+
if (input.remoteDelete && !isValidRemoteDeleteCap(input.maxRemoteDeletes)) {
|
|
1582
|
+
return { exitCode: ExitCode.USAGE, result: err("USAGE", { message: "--max-remote-deletes must be a positive integer" }) };
|
|
1583
|
+
}
|
|
1584
|
+
const scan = await scanVault(input.vault);
|
|
1585
|
+
if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
|
|
1586
|
+
const lookup = (pages) => {
|
|
1587
|
+
if (input.page.includes("/")) return pages.find((p) => p.relPath === input.page)?.relPath;
|
|
1588
|
+
return pages.find((p) => p.relPath.replace(/\.md$/, "").split("/").pop() === input.page)?.relPath;
|
|
1589
|
+
};
|
|
1590
|
+
let relPath = lookup(scan.data.typedKnowledge) ?? lookup(scan.data.raw) ?? null;
|
|
1591
|
+
if (!relPath) {
|
|
1592
|
+
try {
|
|
1593
|
+
const candidate = normalizeVaultRelPath(input.page);
|
|
1594
|
+
if (await pathExists(join10(input.vault, candidate))) {
|
|
1595
|
+
relPath = candidate;
|
|
1596
|
+
}
|
|
1597
|
+
} catch {
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
if (!relPath) {
|
|
1601
|
+
return {
|
|
1602
|
+
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
1603
|
+
result: err("FILE_NOT_FOUND", { page: input.page })
|
|
1604
|
+
};
|
|
1605
|
+
}
|
|
1606
|
+
if (relPath.startsWith("_archive/")) {
|
|
1607
|
+
return {
|
|
1608
|
+
exitCode: ExitCode.USAGE,
|
|
1609
|
+
result: err("USAGE", { message: "refusing to remove path already under _archive/; use restore or leave archived" })
|
|
1610
|
+
};
|
|
1611
|
+
}
|
|
1612
|
+
const remoteRoot = normalizeRemoteRoot(input.remote);
|
|
1613
|
+
const remoteObjectPath = buildRemoteObjectPath(remoteRoot, relPath);
|
|
1614
|
+
const slug = relPath.replace(/\.md$/, "").split("/").pop() ?? relPath;
|
|
1615
|
+
let indexUpdated = false;
|
|
1616
|
+
if (relPath.endsWith(".md") && !relPath.startsWith("raw/")) {
|
|
1617
|
+
const indexPath = join10(input.vault, "index.md");
|
|
1618
|
+
try {
|
|
1619
|
+
const idx = await readFile7(indexPath, "utf8");
|
|
1620
|
+
const originalLines = idx.split("\n");
|
|
1621
|
+
const filtered = originalLines.filter((l) => !l.includes(`[[${slug}]]`));
|
|
1622
|
+
if (filtered.length !== originalLines.length) {
|
|
1623
|
+
await writeFile6(indexPath, filtered.join("\n"), "utf8");
|
|
1624
|
+
indexUpdated = true;
|
|
1625
|
+
}
|
|
1626
|
+
} catch (e) {
|
|
1627
|
+
if (e instanceof Error && "code" in e && e.code !== "ENOENT") throw e;
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
const intent = buildDeleteIntent({
|
|
1631
|
+
path: relPath,
|
|
1632
|
+
action: "remove",
|
|
1633
|
+
actor: "skillwiki-cli",
|
|
1634
|
+
source: "cli",
|
|
1635
|
+
reason: input.reason
|
|
1636
|
+
});
|
|
1637
|
+
const tombstonePath = await writeDeleteIntent(input.vault, intent);
|
|
1638
|
+
await unlink2(join10(input.vault, relPath));
|
|
1639
|
+
appendLastOp(input.vault, {
|
|
1640
|
+
operation: "remove",
|
|
1641
|
+
summary: `removed ${relPath} (tombstone ${tombstonePath})`,
|
|
1642
|
+
files: [relPath, tombstonePath],
|
|
1643
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1644
|
+
});
|
|
1645
|
+
let remote;
|
|
1646
|
+
if (remoteObjectPath) {
|
|
1647
|
+
const pruned = await planAndMaybePruneRemoteObjects([remoteObjectPath], input);
|
|
1648
|
+
if (!pruned.ok) {
|
|
1649
|
+
return { exitCode: ExitCode.SYNC_PUSH_FAILED, result: pruned };
|
|
1650
|
+
}
|
|
1651
|
+
remote = pruned.data;
|
|
1652
|
+
}
|
|
1653
|
+
const remoteNote = remote ? ` (remote ${input.remoteDelete ? `deleted ${remote.deleted.length}` : `planned ${remote.plannedDeletes.length}`})` : "";
|
|
1654
|
+
return {
|
|
1655
|
+
exitCode: ExitCode.OK,
|
|
1656
|
+
result: ok({
|
|
1657
|
+
removed: relPath,
|
|
1658
|
+
tombstone_path: tombstonePath,
|
|
1659
|
+
index_updated: indexUpdated,
|
|
1660
|
+
...remote ? { remote } : {},
|
|
1661
|
+
humanHint: `removed ${relPath}; tombstone ${tombstonePath}${indexUpdated ? " (index updated)" : ""}${remoteNote}`
|
|
1662
|
+
})
|
|
1663
|
+
};
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1517
1666
|
// src/commands/drift.ts
|
|
1518
1667
|
import { createHash as createHash2 } from "crypto";
|
|
1519
1668
|
|
|
@@ -1825,14 +1974,14 @@ ${migratedBody}${newFooter}`;
|
|
|
1825
1974
|
|
|
1826
1975
|
// src/commands/update.ts
|
|
1827
1976
|
import { execSync } from "child_process";
|
|
1828
|
-
import { join as
|
|
1977
|
+
import { join as join11 } from "path";
|
|
1829
1978
|
function resolveGlobalSkillsRoot() {
|
|
1830
1979
|
try {
|
|
1831
1980
|
const globalRoot = execSync("npm root -g", {
|
|
1832
1981
|
encoding: "utf8",
|
|
1833
1982
|
timeout: 5e3
|
|
1834
1983
|
}).trim();
|
|
1835
|
-
return
|
|
1984
|
+
return join11(globalRoot, "skillwiki", "skills");
|
|
1836
1985
|
} catch {
|
|
1837
1986
|
return null;
|
|
1838
1987
|
}
|
|
@@ -1860,7 +2009,7 @@ async function runUpdate(input) {
|
|
|
1860
2009
|
const pkg2 = readCliPackageJson();
|
|
1861
2010
|
const currentVersion = pkg2.version;
|
|
1862
2011
|
const tag = normalizeDistTag(input.distTag);
|
|
1863
|
-
const target =
|
|
2012
|
+
const target = join11(input.home, ".claude", "skills");
|
|
1864
2013
|
let latest;
|
|
1865
2014
|
try {
|
|
1866
2015
|
latest = execSync(`npm view skillwiki@${tag} version`, {
|
|
@@ -1938,13 +2087,13 @@ async function runUpdate(input) {
|
|
|
1938
2087
|
// src/commands/self-update.ts
|
|
1939
2088
|
import { execSync as execSync2 } from "child_process";
|
|
1940
2089
|
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
1941
|
-
import { join as
|
|
2090
|
+
import { join as join12 } from "path";
|
|
1942
2091
|
var DEFAULT_SOURCE_ROOT_SUFFIX = "/Desktop/code/llm-wiki";
|
|
1943
2092
|
async function runSelfUpdate(input) {
|
|
1944
2093
|
const currentVersion = readCliPackageJson().version;
|
|
1945
2094
|
const sourceRoot = input.sourceRoot ?? `${input.home}${DEFAULT_SOURCE_ROOT_SUFFIX}`;
|
|
1946
2095
|
const distTag = normalizeDistTag(input.distTag);
|
|
1947
|
-
const localPkgPath =
|
|
2096
|
+
const localPkgPath = join12(sourceRoot, "packages", "cli", "package.json");
|
|
1948
2097
|
const hasLocalSource = existsSync3(localPkgPath);
|
|
1949
2098
|
if (input.check) {
|
|
1950
2099
|
let availableVersion = null;
|
|
@@ -2076,21 +2225,21 @@ async function runSelfUpdate(input) {
|
|
|
2076
2225
|
}
|
|
2077
2226
|
|
|
2078
2227
|
// src/commands/transcripts.ts
|
|
2079
|
-
import { readdir as
|
|
2080
|
-
import { join as
|
|
2228
|
+
import { readdir as readdir4, stat as stat3, readFile as readFile8 } from "fs/promises";
|
|
2229
|
+
import { join as join13 } from "path";
|
|
2081
2230
|
async function runTranscripts(input) {
|
|
2082
|
-
const dir =
|
|
2231
|
+
const dir = join13(input.vault, "raw", "transcripts");
|
|
2083
2232
|
let entries;
|
|
2084
2233
|
try {
|
|
2085
|
-
entries = await
|
|
2234
|
+
entries = await readdir4(dir, { withFileTypes: true });
|
|
2086
2235
|
} catch {
|
|
2087
2236
|
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: { ok: false, error: "VAULT_PATH_INVALID", detail: `raw/transcripts/ not found: ${dir}` } };
|
|
2088
2237
|
}
|
|
2089
2238
|
const transcripts = [];
|
|
2090
2239
|
for (const entry of entries) {
|
|
2091
2240
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
2092
|
-
const filePath =
|
|
2093
|
-
const content = await
|
|
2241
|
+
const filePath = join13(dir, entry.name);
|
|
2242
|
+
const content = await readFile8(filePath, "utf8");
|
|
2094
2243
|
const fm = extractFrontmatter(content);
|
|
2095
2244
|
if (!fm.ok) continue;
|
|
2096
2245
|
const ingested = typeof fm.data.ingested === "string" ? fm.data.ingested : "";
|
|
@@ -2107,10 +2256,10 @@ async function runTranscripts(input) {
|
|
|
2107
2256
|
}
|
|
2108
2257
|
|
|
2109
2258
|
// src/commands/compound.ts
|
|
2110
|
-
import { writeFile as
|
|
2111
|
-
import { join as
|
|
2259
|
+
import { writeFile as writeFile7, mkdir as mkdir7, readdir as readdir5, unlink as unlink3 } from "fs/promises";
|
|
2260
|
+
import { join as join14 } from "path";
|
|
2112
2261
|
import { existsSync as existsSync4 } from "fs";
|
|
2113
|
-
import { readFile as
|
|
2262
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
2114
2263
|
var RETRO_HEADING_RE = /^## \[(\d{4}-\d{2}-\d{2})(?:\s+[^\]]+)?\] retro \| loop cycle(?: (\d+))?: (.+)$/;
|
|
2115
2264
|
var FIELD_RE = {
|
|
2116
2265
|
improve: /^-\s+\*?\*?Improve:?\*?\*?\s*(.+)$/m,
|
|
@@ -2208,17 +2357,17 @@ function extractRetroFields(date, cycleName, block) {
|
|
|
2208
2357
|
};
|
|
2209
2358
|
}
|
|
2210
2359
|
async function runCompound(input) {
|
|
2211
|
-
const logPath =
|
|
2360
|
+
const logPath = join14(input.vault, "log.md");
|
|
2212
2361
|
let logText;
|
|
2213
2362
|
try {
|
|
2214
|
-
logText = await
|
|
2363
|
+
logText = await readFile9(logPath, "utf8");
|
|
2215
2364
|
} catch {
|
|
2216
2365
|
return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: logPath }) };
|
|
2217
2366
|
}
|
|
2218
2367
|
const entries = parseRetroEntries(logText);
|
|
2219
2368
|
const promoted = [];
|
|
2220
2369
|
const skipped = [];
|
|
2221
|
-
const compoundDir =
|
|
2370
|
+
const compoundDir = join14(input.vault, "projects", input.project, "compound");
|
|
2222
2371
|
for (const entry of entries) {
|
|
2223
2372
|
const generalizeValue = entry.generalize.trim();
|
|
2224
2373
|
if (!/^yes/i.test(generalizeValue)) {
|
|
@@ -2226,7 +2375,7 @@ async function runCompound(input) {
|
|
|
2226
2375
|
continue;
|
|
2227
2376
|
}
|
|
2228
2377
|
const slug = slugify(entry.cycleName);
|
|
2229
|
-
const compoundPath =
|
|
2378
|
+
const compoundPath = join14(compoundDir, `${slug}.md`);
|
|
2230
2379
|
if (existsSync4(compoundPath)) {
|
|
2231
2380
|
skipped.push(entry.date);
|
|
2232
2381
|
continue;
|
|
@@ -2266,9 +2415,9 @@ async function runCompound(input) {
|
|
|
2266
2415
|
const content = frontmatter + "\n" + body;
|
|
2267
2416
|
if (!input.dryRun) {
|
|
2268
2417
|
if (!existsSync4(compoundDir)) {
|
|
2269
|
-
await
|
|
2418
|
+
await mkdir7(compoundDir, { recursive: true });
|
|
2270
2419
|
}
|
|
2271
|
-
await
|
|
2420
|
+
await writeFile7(compoundPath, content, "utf8");
|
|
2272
2421
|
}
|
|
2273
2422
|
promoted.push(`${slug}.md`);
|
|
2274
2423
|
}
|
|
@@ -2287,7 +2436,7 @@ async function runCompound(input) {
|
|
|
2287
2436
|
};
|
|
2288
2437
|
}
|
|
2289
2438
|
async function runCompoundDelete(input) {
|
|
2290
|
-
const projectDir =
|
|
2439
|
+
const projectDir = join14(input.vault, "projects", input.project);
|
|
2291
2440
|
if (!existsSync4(projectDir)) {
|
|
2292
2441
|
return {
|
|
2293
2442
|
exitCode: ExitCode.PROJECT_NOT_FOUND,
|
|
@@ -2295,7 +2444,7 @@ async function runCompoundDelete(input) {
|
|
|
2295
2444
|
};
|
|
2296
2445
|
}
|
|
2297
2446
|
const entryName = input.entry.replace(/\.md$/, "");
|
|
2298
|
-
const compoundPath =
|
|
2447
|
+
const compoundPath = join14(projectDir, "compound", `${entryName}.md`);
|
|
2299
2448
|
if (!existsSync4(compoundPath)) {
|
|
2300
2449
|
return {
|
|
2301
2450
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
@@ -2303,7 +2452,7 @@ async function runCompoundDelete(input) {
|
|
|
2303
2452
|
};
|
|
2304
2453
|
}
|
|
2305
2454
|
try {
|
|
2306
|
-
await
|
|
2455
|
+
await unlink3(compoundPath);
|
|
2307
2456
|
} catch (e) {
|
|
2308
2457
|
return {
|
|
2309
2458
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -2329,7 +2478,7 @@ knowledge.md regenerated`
|
|
|
2329
2478
|
};
|
|
2330
2479
|
}
|
|
2331
2480
|
async function runCompoundList(input) {
|
|
2332
|
-
const compoundDir =
|
|
2481
|
+
const compoundDir = join14(input.vault, "projects", input.project, "compound");
|
|
2333
2482
|
if (!existsSync4(compoundDir)) {
|
|
2334
2483
|
return {
|
|
2335
2484
|
exitCode: ExitCode.OK,
|
|
@@ -2344,7 +2493,7 @@ no compound directory found`
|
|
|
2344
2493
|
}
|
|
2345
2494
|
let dirents;
|
|
2346
2495
|
try {
|
|
2347
|
-
dirents = await
|
|
2496
|
+
dirents = await readdir5(compoundDir, { withFileTypes: true });
|
|
2348
2497
|
} catch {
|
|
2349
2498
|
return {
|
|
2350
2499
|
exitCode: ExitCode.OK,
|
|
@@ -2360,10 +2509,10 @@ could not read compound directory`
|
|
|
2360
2509
|
const entries = [];
|
|
2361
2510
|
for (const dirent of dirents) {
|
|
2362
2511
|
if (!dirent.isFile() || !dirent.name.endsWith(".md")) continue;
|
|
2363
|
-
const filePath =
|
|
2512
|
+
const filePath = join14(compoundDir, dirent.name);
|
|
2364
2513
|
let text;
|
|
2365
2514
|
try {
|
|
2366
|
-
text = await
|
|
2515
|
+
text = await readFile9(filePath, "utf8");
|
|
2367
2516
|
} catch {
|
|
2368
2517
|
continue;
|
|
2369
2518
|
}
|
|
@@ -2392,8 +2541,8 @@ no compound entries found`;
|
|
|
2392
2541
|
}
|
|
2393
2542
|
|
|
2394
2543
|
// src/commands/session-brief.ts
|
|
2395
|
-
import { mkdir as
|
|
2396
|
-
import { join as
|
|
2544
|
+
import { mkdir as mkdir8, readFile as readFile10, writeFile as writeFile8 } from "fs/promises";
|
|
2545
|
+
import { join as join15, relative, sep } from "path";
|
|
2397
2546
|
var MAX_WORDS = 900;
|
|
2398
2547
|
async function runSessionBrief(input) {
|
|
2399
2548
|
const scan = await scanVault(input.vault);
|
|
@@ -2493,7 +2642,7 @@ async function resolveProject(input) {
|
|
|
2493
2642
|
const envProject = input.env?.SKILLWIKI_PROJECT;
|
|
2494
2643
|
if (envProject) return envProject;
|
|
2495
2644
|
const cwd = input.cwd ?? process.cwd();
|
|
2496
|
-
const projectDotenv = await readProjectSlug(
|
|
2645
|
+
const projectDotenv = await readProjectSlug(join15(cwd, ".skillwiki", ".env"));
|
|
2497
2646
|
if (projectDotenv) return projectDotenv;
|
|
2498
2647
|
const inferred = inferProjectFromPath(input.vault, cwd);
|
|
2499
2648
|
if (inferred) return inferred;
|
|
@@ -2502,7 +2651,7 @@ async function resolveProject(input) {
|
|
|
2502
2651
|
async function readProjectSlug(file) {
|
|
2503
2652
|
let text;
|
|
2504
2653
|
try {
|
|
2505
|
-
text = await
|
|
2654
|
+
text = await readFile10(file, "utf8");
|
|
2506
2655
|
} catch {
|
|
2507
2656
|
return void 0;
|
|
2508
2657
|
}
|
|
@@ -2579,7 +2728,7 @@ async function loadTrendDigests(typedPages) {
|
|
|
2579
2728
|
return out;
|
|
2580
2729
|
}
|
|
2581
2730
|
async function loadSessionPins(vault, project) {
|
|
2582
|
-
const text = await readIfExists(
|
|
2731
|
+
const text = await readIfExists(join15(vault, "meta", "session-pins.md"));
|
|
2583
2732
|
if (!text) return [];
|
|
2584
2733
|
const fm = extractFrontmatter(text);
|
|
2585
2734
|
if (!fm.ok) return [];
|
|
@@ -2696,7 +2845,7 @@ function satelliteHealthWarnings(warning) {
|
|
|
2696
2845
|
return warning ? [warning] : [];
|
|
2697
2846
|
}
|
|
2698
2847
|
async function loadHealthWarnings(vault) {
|
|
2699
|
-
const text = await readIfExists(
|
|
2848
|
+
const text = await readIfExists(join15(vault, ".skillwiki", "health.json"));
|
|
2700
2849
|
if (!text) return [];
|
|
2701
2850
|
try {
|
|
2702
2851
|
const parsed = JSON.parse(text);
|
|
@@ -2709,7 +2858,7 @@ async function loadHealthWarnings(vault) {
|
|
|
2709
2858
|
}
|
|
2710
2859
|
}
|
|
2711
2860
|
async function loadMemoryTopics(vault, project) {
|
|
2712
|
-
const text = await readIfExists(
|
|
2861
|
+
const text = await readIfExists(join15(vault, ".skillwiki", "memory", project, "topics.json"));
|
|
2713
2862
|
if (!text) return [];
|
|
2714
2863
|
try {
|
|
2715
2864
|
const parsed = JSON.parse(text);
|
|
@@ -2726,18 +2875,18 @@ async function loadMemoryTopics(vault, project) {
|
|
|
2726
2875
|
}
|
|
2727
2876
|
}
|
|
2728
2877
|
async function writeBriefArtifacts(vault, input) {
|
|
2729
|
-
const metaPath =
|
|
2730
|
-
const cacheMdPath =
|
|
2731
|
-
const cacheJsonPath =
|
|
2732
|
-
await
|
|
2733
|
-
await
|
|
2878
|
+
const metaPath = join15(vault, "meta", "latest-session-brief.md");
|
|
2879
|
+
const cacheMdPath = join15(vault, ".skillwiki", "session-brief.md");
|
|
2880
|
+
const cacheJsonPath = join15(vault, ".skillwiki", "session-brief.json");
|
|
2881
|
+
await mkdir8(join15(vault, "meta"), { recursive: true });
|
|
2882
|
+
await mkdir8(join15(vault, ".skillwiki"), { recursive: true });
|
|
2734
2883
|
const committed = renderCommittedBrief(input);
|
|
2735
2884
|
const previousComparable = comparableBrief(await readIfExists(metaPath));
|
|
2736
2885
|
const nextComparable = comparableBrief(committed);
|
|
2737
2886
|
const materialChange = previousComparable !== nextComparable;
|
|
2738
|
-
await
|
|
2739
|
-
await
|
|
2740
|
-
await
|
|
2887
|
+
await writeFile8(metaPath, committed, "utf8");
|
|
2888
|
+
await writeFile8(cacheMdPath, input.brief, "utf8");
|
|
2889
|
+
await writeFile8(cacheJsonPath, `${JSON.stringify({
|
|
2741
2890
|
project: input.project,
|
|
2742
2891
|
brief: input.brief,
|
|
2743
2892
|
word_count: input.wordCount,
|
|
@@ -2791,7 +2940,7 @@ function renderCommittedBrief(input) {
|
|
|
2791
2940
|
].filter((line) => line !== "").join("\n");
|
|
2792
2941
|
}
|
|
2793
2942
|
async function ensureIndexEntry(vault) {
|
|
2794
|
-
const indexPath =
|
|
2943
|
+
const indexPath = join15(vault, "index.md");
|
|
2795
2944
|
let text = await readIfExists(indexPath);
|
|
2796
2945
|
if (!text) return false;
|
|
2797
2946
|
if (text.includes("[[meta/latest-session-brief]]")) return false;
|
|
@@ -2806,21 +2955,21 @@ async function ensureIndexEntry(vault) {
|
|
|
2806
2955
|
while (insertAt < lines.length && !lines[insertAt].startsWith("## ")) insertAt++;
|
|
2807
2956
|
lines.splice(insertAt, 0, entry);
|
|
2808
2957
|
}
|
|
2809
|
-
await
|
|
2958
|
+
await writeFile8(indexPath, lines.join("\n"), "utf8");
|
|
2810
2959
|
return true;
|
|
2811
2960
|
}
|
|
2812
2961
|
async function appendMaterialLog(vault, today) {
|
|
2813
|
-
const logPath =
|
|
2962
|
+
const logPath = join15(vault, "log.md");
|
|
2814
2963
|
const text = await readIfExists(logPath);
|
|
2815
2964
|
if (!text) return false;
|
|
2816
2965
|
const entry = `
|
|
2817
2966
|
## [${today}] session-brief | refreshed: meta/latest-session-brief.md`;
|
|
2818
|
-
await
|
|
2967
|
+
await writeFile8(logPath, text.trimEnd() + entry + "\n", "utf8");
|
|
2819
2968
|
return true;
|
|
2820
2969
|
}
|
|
2821
2970
|
async function readIfExists(path) {
|
|
2822
2971
|
try {
|
|
2823
|
-
return await
|
|
2972
|
+
return await readFile10(path, "utf8");
|
|
2824
2973
|
} catch {
|
|
2825
2974
|
return "";
|
|
2826
2975
|
}
|
|
@@ -2862,15 +3011,15 @@ function dateFromPath(path) {
|
|
|
2862
3011
|
}
|
|
2863
3012
|
|
|
2864
3013
|
// src/commands/ingest.ts
|
|
2865
|
-
import { readFile as
|
|
2866
|
-
import { join as
|
|
3014
|
+
import { readFile as readFile12, open, unlink as unlink4, mkdir as mkdir9 } from "fs/promises";
|
|
3015
|
+
import { join as join17 } from "path";
|
|
2867
3016
|
import { createHash as createHash4 } from "crypto";
|
|
2868
3017
|
|
|
2869
3018
|
// src/commands/page-publish.ts
|
|
2870
3019
|
import { createHash as createHash3 } from "crypto";
|
|
2871
3020
|
import { realpathSync } from "fs";
|
|
2872
|
-
import { readFile as
|
|
2873
|
-
import { join as
|
|
3021
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
3022
|
+
import { join as join16, resolve as resolve3 } from "path";
|
|
2874
3023
|
var DEFAULT_DEPS = { afterStage: async () => void 0 };
|
|
2875
3024
|
function errorExitCode(error) {
|
|
2876
3025
|
switch (error) {
|
|
@@ -2930,7 +3079,7 @@ function preparePagePublicationFromContent(input) {
|
|
|
2930
3079
|
async function preparePagePublication(input) {
|
|
2931
3080
|
let content;
|
|
2932
3081
|
try {
|
|
2933
|
-
content = await
|
|
3082
|
+
content = await readFile11(input.draftPath, "utf8");
|
|
2934
3083
|
} catch (error) {
|
|
2935
3084
|
return err("FILE_NOT_FOUND", { path: input.draftPath, message: String(error) });
|
|
2936
3085
|
}
|
|
@@ -3001,10 +3150,10 @@ async function runLockedPrimaryStages(input, vault, deps) {
|
|
|
3001
3150
|
ExitCode.VAULT_PATH_INVALID
|
|
3002
3151
|
);
|
|
3003
3152
|
}
|
|
3004
|
-
const schemaPath =
|
|
3153
|
+
const schemaPath = join16(vault, "SCHEMA.md");
|
|
3005
3154
|
let schemaText;
|
|
3006
3155
|
try {
|
|
3007
|
-
schemaText = await
|
|
3156
|
+
schemaText = await readFile11(schemaPath, "utf8");
|
|
3008
3157
|
} catch (error) {
|
|
3009
3158
|
return lockedFailure("schema", state, err("WRITE_FAILED", { message: String(error) }));
|
|
3010
3159
|
}
|
|
@@ -3034,8 +3183,8 @@ async function runLockedPrimaryStages(input, vault, deps) {
|
|
|
3034
3183
|
let visibleSchema;
|
|
3035
3184
|
try {
|
|
3036
3185
|
[visible, visibleSchema] = await Promise.all([
|
|
3037
|
-
|
|
3038
|
-
|
|
3186
|
+
readFile11(input.targetPath, "utf8"),
|
|
3187
|
+
readFile11(schemaPath, "utf8")
|
|
3039
3188
|
]);
|
|
3040
3189
|
} catch (error) {
|
|
3041
3190
|
return lockedFailure("verify", state, err("WRITE_FAILED", { message: String(error) }));
|
|
@@ -3117,17 +3266,17 @@ function renderPublicationLog(input, added) {
|
|
|
3117
3266
|
}
|
|
3118
3267
|
async function readPageChanged(targetPath, content) {
|
|
3119
3268
|
try {
|
|
3120
|
-
return ok(await
|
|
3269
|
+
return ok(await readFile11(targetPath, "utf8") !== content);
|
|
3121
3270
|
} catch (error) {
|
|
3122
3271
|
if (error.code === "ENOENT") return ok(true);
|
|
3123
3272
|
return err("WRITE_FAILED", { path: targetPath, message: String(error) });
|
|
3124
3273
|
}
|
|
3125
3274
|
}
|
|
3126
3275
|
async function previewPreparedPagePublication(input, vault) {
|
|
3127
|
-
const schemaPath =
|
|
3276
|
+
const schemaPath = join16(vault, "SCHEMA.md");
|
|
3128
3277
|
let schemaText;
|
|
3129
3278
|
try {
|
|
3130
|
-
schemaText = await
|
|
3279
|
+
schemaText = await readFile11(schemaPath, "utf8");
|
|
3131
3280
|
} catch (error) {
|
|
3132
3281
|
const result = err("FILE_NOT_FOUND", { path: schemaPath, message: String(error) });
|
|
3133
3282
|
return { exitCode: ExitCode.FILE_NOT_FOUND, result };
|
|
@@ -3139,10 +3288,10 @@ async function previewPreparedPagePublication(input, vault) {
|
|
|
3139
3288
|
if (!reconciled.ok) return { exitCode: errorExitCode(reconciled.error), result: reconciled };
|
|
3140
3289
|
const pageChanged = await readPageChanged(input.targetPath, input.page.content);
|
|
3141
3290
|
if (!pageChanged.ok) return { exitCode: errorExitCode(pageChanged.error), result: pageChanged };
|
|
3142
|
-
const indexPath =
|
|
3291
|
+
const indexPath = join16(vault, "index.md");
|
|
3143
3292
|
let indexText;
|
|
3144
3293
|
try {
|
|
3145
|
-
indexText = await
|
|
3294
|
+
indexText = await readFile11(indexPath, "utf8");
|
|
3146
3295
|
} catch (error) {
|
|
3147
3296
|
const result = err("FILE_NOT_FOUND", { path: indexPath, message: String(error) });
|
|
3148
3297
|
return { exitCode: ExitCode.FILE_NOT_FOUND, result };
|
|
@@ -3153,10 +3302,10 @@ async function previewPreparedPagePublication(input, vault) {
|
|
|
3153
3302
|
type: input.page.type
|
|
3154
3303
|
});
|
|
3155
3304
|
if (!index.ok) return { exitCode: errorExitCode(index.error), result: index };
|
|
3156
|
-
const logPath =
|
|
3305
|
+
const logPath = join16(vault, "log.md");
|
|
3157
3306
|
let logText;
|
|
3158
3307
|
try {
|
|
3159
|
-
logText = await
|
|
3308
|
+
logText = await readFile11(logPath, "utf8");
|
|
3160
3309
|
} catch (error) {
|
|
3161
3310
|
const result = err("FILE_NOT_FOUND", { path: logPath, message: String(error) });
|
|
3162
3311
|
return { exitCode: ExitCode.FILE_NOT_FOUND, result };
|
|
@@ -3352,7 +3501,7 @@ function buildTypedContent(title, ingested, type, tags, rawRelPath, provenance)
|
|
|
3352
3501
|
}
|
|
3353
3502
|
async function resolveRawCapture(input) {
|
|
3354
3503
|
try {
|
|
3355
|
-
const existing = await
|
|
3504
|
+
const existing = await readFile12(input.path, "utf8");
|
|
3356
3505
|
const frontmatter = extractFrontmatter(existing);
|
|
3357
3506
|
if (!frontmatter.ok) {
|
|
3358
3507
|
return err("INGEST_VALIDATION_FAILED", {
|
|
@@ -3401,7 +3550,7 @@ async function writeResolvedRaw(input) {
|
|
|
3401
3550
|
return written.ok ? ok({ changed: written.data.changed, capture: resolved.data }) : written;
|
|
3402
3551
|
} finally {
|
|
3403
3552
|
try {
|
|
3404
|
-
await
|
|
3553
|
+
await unlink4(lock.data);
|
|
3405
3554
|
} catch {
|
|
3406
3555
|
}
|
|
3407
3556
|
}
|
|
@@ -3491,7 +3640,7 @@ async function runIngest(input) {
|
|
|
3491
3640
|
sourceContent = fetchResult.data.body;
|
|
3492
3641
|
} else {
|
|
3493
3642
|
try {
|
|
3494
|
-
sourceContent = await
|
|
3643
|
+
sourceContent = await readFile12(input.source, "utf8");
|
|
3495
3644
|
} catch {
|
|
3496
3645
|
return {
|
|
3497
3646
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
@@ -3516,7 +3665,7 @@ async function runIngest(input) {
|
|
|
3516
3665
|
const rawRelPath = `raw/articles/${slug}.md`;
|
|
3517
3666
|
const typedDir = TYPE_DIR[input.type] ?? `${input.type}s`;
|
|
3518
3667
|
const typedRelPath = `${typedDir}/${slug}.md`;
|
|
3519
|
-
const rawAbsPath =
|
|
3668
|
+
const rawAbsPath = join17(input.vault, rawRelPath);
|
|
3520
3669
|
const identity = assessSourceIdentity({
|
|
3521
3670
|
rawPath: rawRelPath,
|
|
3522
3671
|
sourceUrl: sourceUrl ?? void 0,
|
|
@@ -3560,11 +3709,11 @@ async function runIngest(input) {
|
|
|
3560
3709
|
);
|
|
3561
3710
|
if (!input.dryRun) {
|
|
3562
3711
|
try {
|
|
3563
|
-
await
|
|
3712
|
+
await mkdir9(join17(input.vault, typedDir), { recursive: true });
|
|
3564
3713
|
} catch (error) {
|
|
3565
3714
|
return {
|
|
3566
3715
|
exitCode: ExitCode.WRITE_FAILED,
|
|
3567
|
-
result: err("WRITE_FAILED", { path:
|
|
3716
|
+
result: err("WRITE_FAILED", { path: join17(input.vault, typedDir), message: String(error) })
|
|
3568
3717
|
};
|
|
3569
3718
|
}
|
|
3570
3719
|
}
|
|
@@ -3611,11 +3760,11 @@ async function runIngest(input) {
|
|
|
3611
3760
|
};
|
|
3612
3761
|
}
|
|
3613
3762
|
try {
|
|
3614
|
-
await
|
|
3763
|
+
await mkdir9(join17(input.vault, "raw", "articles"), { recursive: true });
|
|
3615
3764
|
} catch (error) {
|
|
3616
3765
|
return {
|
|
3617
3766
|
exitCode: ExitCode.WRITE_FAILED,
|
|
3618
|
-
result: err("WRITE_FAILED", { path:
|
|
3767
|
+
result: err("WRITE_FAILED", { path: join17(input.vault, "raw", "articles"), message: String(error) })
|
|
3619
3768
|
};
|
|
3620
3769
|
}
|
|
3621
3770
|
const rawWrite = await writeResolvedRaw({
|
|
@@ -3843,8 +3992,8 @@ ${body}`;
|
|
|
3843
3992
|
}
|
|
3844
3993
|
|
|
3845
3994
|
// src/commands/tag-reconcile.ts
|
|
3846
|
-
import { readFile as
|
|
3847
|
-
import { join as
|
|
3995
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
3996
|
+
import { join as join18, posix } from "path";
|
|
3848
3997
|
var TYPED_TARGET_RE = /^(entities|concepts|comparisons|queries|meta)\/[a-z0-9][a-z0-9./_-]*\.md$/;
|
|
3849
3998
|
function errorExitCode2(error) {
|
|
3850
3999
|
switch (error) {
|
|
@@ -3887,7 +4036,7 @@ function asTagArray(frontmatter, path) {
|
|
|
3887
4036
|
async function readTagsFromFile(path) {
|
|
3888
4037
|
let text;
|
|
3889
4038
|
try {
|
|
3890
|
-
text = await
|
|
4039
|
+
text = await readFile13(path, "utf8");
|
|
3891
4040
|
} catch (error) {
|
|
3892
4041
|
if (error.code === "ENOENT") {
|
|
3893
4042
|
return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path }) };
|
|
@@ -3908,7 +4057,7 @@ async function resolveRequestedTags(input, page) {
|
|
|
3908
4057
|
result: err("INVALID_FRONTMATTER", { message: "explicit tags must be an array of strings" })
|
|
3909
4058
|
};
|
|
3910
4059
|
}
|
|
3911
|
-
const source = input.from ?? (explicit.length === 0 ?
|
|
4060
|
+
const source = input.from ?? (explicit.length === 0 ? join18(input.vault, page) : void 0);
|
|
3912
4061
|
if (!source) return { exitCode: ExitCode.OK, result: ok({ tags: [...new Set(explicit)].sort() }) };
|
|
3913
4062
|
const sourced = await readTagsFromFile(source);
|
|
3914
4063
|
if (!sourced.result.ok) return { exitCode: sourced.exitCode, result: sourced.result };
|
|
@@ -3919,7 +4068,7 @@ async function resolveRequestedTags(input, page) {
|
|
|
3919
4068
|
}
|
|
3920
4069
|
async function readSchema(schemaPath) {
|
|
3921
4070
|
try {
|
|
3922
|
-
return { exitCode: ExitCode.OK, result: ok(await
|
|
4071
|
+
return { exitCode: ExitCode.OK, result: ok(await readFile13(schemaPath, "utf8")) };
|
|
3923
4072
|
} catch (error) {
|
|
3924
4073
|
if (error.code === "ENOENT") {
|
|
3925
4074
|
return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: schemaPath }) };
|
|
@@ -3947,7 +4096,7 @@ function previewResult(page, tags, reconciled, dryRun, filesChanged) {
|
|
|
3947
4096
|
};
|
|
3948
4097
|
}
|
|
3949
4098
|
async function reconcileTagsWhileLocked(input, page, tags, comment) {
|
|
3950
|
-
const schemaPath =
|
|
4099
|
+
const schemaPath = join18(input.vault, "SCHEMA.md");
|
|
3951
4100
|
const current = await readSchema(schemaPath);
|
|
3952
4101
|
if (!current.result.ok) return { exitCode: current.exitCode, result: current.result };
|
|
3953
4102
|
const next = reconcileTaxonomyDocument(current.result.data, { tags, comment });
|
|
@@ -3958,7 +4107,7 @@ async function reconcileTagsWhileLocked(input, page, tags, comment) {
|
|
|
3958
4107
|
}
|
|
3959
4108
|
let verifiedText;
|
|
3960
4109
|
try {
|
|
3961
|
-
verifiedText = await
|
|
4110
|
+
verifiedText = await readFile13(schemaPath, "utf8");
|
|
3962
4111
|
} catch (error) {
|
|
3963
4112
|
return {
|
|
3964
4113
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -3984,7 +4133,7 @@ async function runTagReconcile(input) {
|
|
|
3984
4133
|
const comment = taxonomyCommentForPage(page.data, date, input.reason);
|
|
3985
4134
|
if (!comment.ok) return { exitCode: ExitCode.SCHEME_REJECTED, result: comment };
|
|
3986
4135
|
if (!input.write) {
|
|
3987
|
-
const schema = await readSchema(
|
|
4136
|
+
const schema = await readSchema(join18(input.vault, "SCHEMA.md"));
|
|
3988
4137
|
if (!schema.result.ok) return { exitCode: schema.exitCode, result: schema.result };
|
|
3989
4138
|
const preview = reconcileTaxonomyDocument(schema.result.data, { tags, comment: comment.data });
|
|
3990
4139
|
return previewResult(page.data, tags, preview, true, []);
|
|
@@ -4037,7 +4186,7 @@ async function runTagReconcile(input) {
|
|
|
4037
4186
|
|
|
4038
4187
|
// src/commands/sync.ts
|
|
4039
4188
|
import { existsSync as existsSync5 } from "fs";
|
|
4040
|
-
import { join as
|
|
4189
|
+
import { join as join19 } from "path";
|
|
4041
4190
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
4042
4191
|
|
|
4043
4192
|
// src/utils/git.ts
|
|
@@ -4104,7 +4253,7 @@ function refHasPath(vault, ref, path) {
|
|
|
4104
4253
|
function runSyncStatus(input) {
|
|
4105
4254
|
const vault = input.vault;
|
|
4106
4255
|
const includeStashes = input.includeStashes ?? false;
|
|
4107
|
-
if (!existsSync5(
|
|
4256
|
+
if (!existsSync5(join19(vault, ".git"))) {
|
|
4108
4257
|
return {
|
|
4109
4258
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
4110
4259
|
result: ok({
|
|
@@ -4211,7 +4360,7 @@ function runSyncStatus(input) {
|
|
|
4211
4360
|
}
|
|
4212
4361
|
async function runSyncPush(input) {
|
|
4213
4362
|
const vault = input.vault;
|
|
4214
|
-
if (!existsSync5(
|
|
4363
|
+
if (!existsSync5(join19(vault, ".git"))) {
|
|
4215
4364
|
return {
|
|
4216
4365
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
4217
4366
|
result: err("NOT_A_GIT_REPO", { path: vault })
|
|
@@ -4371,7 +4520,7 @@ function enableGitLongPathsOnWindows(vault) {
|
|
|
4371
4520
|
}
|
|
4372
4521
|
async function runSyncPull(input) {
|
|
4373
4522
|
const vault = input.vault;
|
|
4374
|
-
if (!existsSync5(
|
|
4523
|
+
if (!existsSync5(join19(vault, ".git"))) {
|
|
4375
4524
|
return {
|
|
4376
4525
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
4377
4526
|
result: err("NOT_A_GIT_REPO", { path: vault })
|
|
@@ -4612,7 +4761,7 @@ function runSyncUnlock(input) {
|
|
|
4612
4761
|
|
|
4613
4762
|
// src/commands/backup.ts
|
|
4614
4763
|
import { statSync as statSync2, readdirSync, readFileSync as readFileSync4, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
4615
|
-
import { join as
|
|
4764
|
+
import { join as join20, relative as relative2, dirname as dirname6 } from "path";
|
|
4616
4765
|
import { PutObjectCommand, HeadObjectCommand, ListObjectsV2Command, GetObjectCommand, DeleteObjectsCommand } from "@aws-sdk/client-s3";
|
|
4617
4766
|
|
|
4618
4767
|
// src/utils/s3-client.ts
|
|
@@ -4636,7 +4785,7 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([".git", ".obsidian", "_archive", "node_
|
|
|
4636
4785
|
function* walkMarkdown(dir, base) {
|
|
4637
4786
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
4638
4787
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
4639
|
-
const full =
|
|
4788
|
+
const full = join20(dir, entry.name);
|
|
4640
4789
|
if (entry.isDirectory()) {
|
|
4641
4790
|
yield* walkMarkdown(full, base);
|
|
4642
4791
|
} else if (entry.name.endsWith(".md")) {
|
|
@@ -4659,7 +4808,7 @@ async function runBackupSync(input) {
|
|
|
4659
4808
|
let failed = 0;
|
|
4660
4809
|
const files = [...walkMarkdown(input.vault, input.vault)];
|
|
4661
4810
|
for (const relPath of files) {
|
|
4662
|
-
const absPath =
|
|
4811
|
+
const absPath = join20(input.vault, relPath);
|
|
4663
4812
|
const localStat = statSync2(absPath);
|
|
4664
4813
|
let needsUpload = true;
|
|
4665
4814
|
try {
|
|
@@ -4735,7 +4884,7 @@ async function runBackupRestore(input) {
|
|
|
4735
4884
|
const objects = list.Contents ?? [];
|
|
4736
4885
|
for (const obj of objects) {
|
|
4737
4886
|
if (!obj.Key) continue;
|
|
4738
|
-
const localPath =
|
|
4887
|
+
const localPath = join20(target, obj.Key);
|
|
4739
4888
|
try {
|
|
4740
4889
|
const localStat = statSync2(localPath);
|
|
4741
4890
|
if (obj.LastModified && localStat.mtime > obj.LastModified) {
|
|
@@ -4782,8 +4931,8 @@ async function runBackupRestore(input) {
|
|
|
4782
4931
|
|
|
4783
4932
|
// src/commands/status.ts
|
|
4784
4933
|
import { existsSync as existsSync6, statSync as statSync3 } from "fs";
|
|
4785
|
-
import { readFile as
|
|
4786
|
-
import { join as
|
|
4934
|
+
import { readFile as readFile14 } from "fs/promises";
|
|
4935
|
+
import { join as join21 } from "path";
|
|
4787
4936
|
async function runStatus(input) {
|
|
4788
4937
|
if (!existsSync6(input.vault)) {
|
|
4789
4938
|
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { vault: input.vault }) };
|
|
@@ -4810,7 +4959,7 @@ async function runStatus(input) {
|
|
|
4810
4959
|
const compound = scan.data.compound.length;
|
|
4811
4960
|
let schemaVersion = "v1";
|
|
4812
4961
|
try {
|
|
4813
|
-
const schemaContent = await
|
|
4962
|
+
const schemaContent = await readFile14(join21(input.vault, "SCHEMA.md"), "utf8");
|
|
4814
4963
|
const versionMatch = schemaContent.match(/version:\s*["']?([^"'\s\n]+)/i);
|
|
4815
4964
|
if (versionMatch) schemaVersion = versionMatch[1];
|
|
4816
4965
|
} catch {
|
|
@@ -4870,8 +5019,8 @@ async function runStatus(input) {
|
|
|
4870
5019
|
}
|
|
4871
5020
|
|
|
4872
5021
|
// src/commands/seed.ts
|
|
4873
|
-
import { mkdir as
|
|
4874
|
-
import { join as
|
|
5022
|
+
import { mkdir as mkdir10, writeFile as writeFile9, stat as stat4 } from "fs/promises";
|
|
5023
|
+
import { join as join22 } from "path";
|
|
4875
5024
|
var TODAY = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
4876
5025
|
var EXAMPLE_PAGES = {
|
|
4877
5026
|
"entities/example-project.md": `---
|
|
@@ -4940,30 +5089,30 @@ Real sources are immutable after ingestion \u2014 never edit them.
|
|
|
4940
5089
|
`;
|
|
4941
5090
|
async function runSeed(input) {
|
|
4942
5091
|
try {
|
|
4943
|
-
await stat4(
|
|
5092
|
+
await stat4(join22(input.vault, "SCHEMA.md"));
|
|
4944
5093
|
} catch {
|
|
4945
5094
|
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { root: input.vault, reason: "SCHEMA.md missing \u2014 run `skillwiki init` first" }) };
|
|
4946
5095
|
}
|
|
4947
5096
|
const created = [];
|
|
4948
5097
|
const skipped = [];
|
|
4949
5098
|
for (const [relPath, content] of Object.entries(EXAMPLE_PAGES)) {
|
|
4950
|
-
const absPath =
|
|
5099
|
+
const absPath = join22(input.vault, relPath);
|
|
4951
5100
|
try {
|
|
4952
5101
|
await stat4(absPath);
|
|
4953
5102
|
skipped.push(relPath);
|
|
4954
5103
|
} catch {
|
|
4955
|
-
await
|
|
4956
|
-
await
|
|
5104
|
+
await mkdir10(join22(absPath, ".."), { recursive: true });
|
|
5105
|
+
await writeFile9(absPath, content, "utf8");
|
|
4957
5106
|
created.push(relPath);
|
|
4958
5107
|
}
|
|
4959
5108
|
}
|
|
4960
|
-
const rawPath =
|
|
5109
|
+
const rawPath = join22(input.vault, "raw", "articles", "example-source.md");
|
|
4961
5110
|
try {
|
|
4962
5111
|
await stat4(rawPath);
|
|
4963
5112
|
skipped.push("raw/articles/example-source.md");
|
|
4964
5113
|
} catch {
|
|
4965
|
-
await
|
|
4966
|
-
await
|
|
5114
|
+
await mkdir10(join22(rawPath, ".."), { recursive: true });
|
|
5115
|
+
await writeFile9(rawPath, EXAMPLE_RAW, "utf8");
|
|
4967
5116
|
created.push("raw/articles/example-source.md");
|
|
4968
5117
|
}
|
|
4969
5118
|
if (created.length > 0) {
|
|
@@ -4985,9 +5134,9 @@ async function runSeed(input) {
|
|
|
4985
5134
|
}
|
|
4986
5135
|
|
|
4987
5136
|
// src/commands/canvas.ts
|
|
4988
|
-
import { readFile as
|
|
5137
|
+
import { readFile as readFile15, writeFile as writeFile10 } from "fs/promises";
|
|
4989
5138
|
import { existsSync as existsSync7 } from "fs";
|
|
4990
|
-
import { join as
|
|
5139
|
+
import { join as join23 } from "path";
|
|
4991
5140
|
var NODE_WIDTH = 240;
|
|
4992
5141
|
var NODE_HEIGHT = 60;
|
|
4993
5142
|
var COLUMN_SPACING = 400;
|
|
@@ -5065,7 +5214,7 @@ function buildCanvasEdges(adjacency) {
|
|
|
5065
5214
|
return edges;
|
|
5066
5215
|
}
|
|
5067
5216
|
async function runCanvasGenerate(input) {
|
|
5068
|
-
const graphPath = input.graphPath ??
|
|
5217
|
+
const graphPath = input.graphPath ?? join23(input.vault, ".skillwiki", "graph.json");
|
|
5069
5218
|
if (!existsSync7(graphPath)) {
|
|
5070
5219
|
return {
|
|
5071
5220
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
@@ -5077,7 +5226,7 @@ async function runCanvasGenerate(input) {
|
|
|
5077
5226
|
}
|
|
5078
5227
|
let raw;
|
|
5079
5228
|
try {
|
|
5080
|
-
raw = await
|
|
5229
|
+
raw = await readFile15(graphPath, "utf8");
|
|
5081
5230
|
} catch (e) {
|
|
5082
5231
|
return {
|
|
5083
5232
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
@@ -5103,9 +5252,9 @@ async function runCanvasGenerate(input) {
|
|
|
5103
5252
|
const nodes = buildCanvasNodes(paths);
|
|
5104
5253
|
const edges = buildCanvasEdges(graph.adjacency);
|
|
5105
5254
|
const canvas = { nodes, edges };
|
|
5106
|
-
const outPath =
|
|
5255
|
+
const outPath = join23(input.vault, "vault-graph.canvas");
|
|
5107
5256
|
try {
|
|
5108
|
-
await
|
|
5257
|
+
await writeFile10(outPath, JSON.stringify(canvas, null, 2));
|
|
5109
5258
|
} catch (e) {
|
|
5110
5259
|
return {
|
|
5111
5260
|
exitCode: ExitCode.WRITE_FAILED,
|
|
@@ -5128,7 +5277,7 @@ written: ${outPath}`
|
|
|
5128
5277
|
import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
|
|
5129
5278
|
import { execSync as nodeExecSync } from "child_process";
|
|
5130
5279
|
import { hostname as nodeHostname, platform as nodePlatform } from "os";
|
|
5131
|
-
import { join as
|
|
5280
|
+
import { join as join24 } from "path";
|
|
5132
5281
|
var SSH_TIMEOUT_MS = 15e3;
|
|
5133
5282
|
var TIMER_UNIT = "agent-memory-trends.timer";
|
|
5134
5283
|
var SERVICE_UNIT = "agent-memory-trends.service";
|
|
@@ -5358,7 +5507,7 @@ async function runFleetHealth(input) {
|
|
|
5358
5507
|
const home = input.home ?? env.HOME ?? "";
|
|
5359
5508
|
const osHostname = input.osHostname ?? env.HOSTNAME ?? nodeHostname();
|
|
5360
5509
|
const vault = input.vault ?? env.WIKI_PATH;
|
|
5361
|
-
const file = input.file ?? (vault ?
|
|
5510
|
+
const file = input.file ?? (vault ? join24(vault, FLEET_REL_PATH) : void 0);
|
|
5362
5511
|
if (!file) {
|
|
5363
5512
|
return {
|
|
5364
5513
|
exitCode: ExitCode.NO_VAULT_CONFIGURED,
|
|
@@ -5443,14 +5592,14 @@ async function runFleetHealth(input) {
|
|
|
5443
5592
|
|
|
5444
5593
|
// src/utils/auto-commit.ts
|
|
5445
5594
|
import { existsSync as existsSync9 } from "fs";
|
|
5446
|
-
import { join as
|
|
5595
|
+
import { join as join25 } from "path";
|
|
5447
5596
|
async function postCommit(vault, exitCode) {
|
|
5448
5597
|
if (exitCode !== 0) return;
|
|
5449
5598
|
const home = process.env.HOME ?? "";
|
|
5450
5599
|
const dotenv = await parseDotenvFile(configPath(home));
|
|
5451
5600
|
const autoCommit = process.env.AUTO_COMMIT ?? dotenv["AUTO_COMMIT"];
|
|
5452
5601
|
if (autoCommit === "false") return;
|
|
5453
|
-
if (!existsSync9(
|
|
5602
|
+
if (!existsSync9(join25(vault, ".git"))) return;
|
|
5454
5603
|
const lastOps = readLastOp(vault);
|
|
5455
5604
|
if (lastOps.length === 0) return;
|
|
5456
5605
|
const porcelain = git(vault, ["status", "--porcelain", "--", ...VAULT_COMMIT_PATHSPEC]);
|
|
@@ -5475,7 +5624,7 @@ async function postCommit(vault, exitCode) {
|
|
|
5475
5624
|
|
|
5476
5625
|
// src/utils/protected-vault-write-guard.ts
|
|
5477
5626
|
import { readFileSync as readFileSync6 } from "fs";
|
|
5478
|
-
import { join as
|
|
5627
|
+
import { join as join26, resolve as resolvePath } from "path";
|
|
5479
5628
|
async function guardProtectedVaultWrite(input) {
|
|
5480
5629
|
const env = input.env ?? process.env;
|
|
5481
5630
|
const home = input.home ?? process.env.HOME ?? "";
|
|
@@ -5541,7 +5690,7 @@ async function resolveLiveVaultPath(input) {
|
|
|
5541
5690
|
return resolved.ok ? resolved.data.path : void 0;
|
|
5542
5691
|
}
|
|
5543
5692
|
function resolveSnapshotWorktree(home) {
|
|
5544
|
-
const skillwikiEnv =
|
|
5693
|
+
const skillwikiEnv = join26(home, ".skillwiki", ".env");
|
|
5545
5694
|
const explicitWorktree = readEnvKey(skillwikiEnv, ["vault_sync.snapshot_worktree"]);
|
|
5546
5695
|
if (explicitWorktree) return explicitWorktree;
|
|
5547
5696
|
const snapshotProfile = readEnvKey(skillwikiEnv, ["vault_sync.snapshot_profile"]);
|
|
@@ -5611,7 +5760,7 @@ program.command("validate <file>").description("validate vault page frontmatter
|
|
|
5611
5760
|
emit(await runValidate({ file, apply: !!opts.apply, vault }), vault);
|
|
5612
5761
|
});
|
|
5613
5762
|
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) => {
|
|
5614
|
-
const out = opts.out ??
|
|
5763
|
+
const out = opts.out ?? join27(vault, ".skillwiki", "graph.json");
|
|
5615
5764
|
return emitGuardedVaultWrite(vault, "graph build", () => runGraphBuild({ vault, out }));
|
|
5616
5765
|
});
|
|
5617
5766
|
var canvasCmd = program.command("canvas").description("manage Obsidian canvas files");
|
|
@@ -5910,6 +6059,22 @@ program.command("archive <page> [vault]").description("archive a typed-knowledge
|
|
|
5910
6059
|
})
|
|
5911
6060
|
);
|
|
5912
6061
|
});
|
|
6062
|
+
program.command("remove <page> [vault]").description("remove a vault path and write a delete-intent tombstone").option("--wiki <name>", "wiki profile name").option("--remote <remote>", "rclone remote root to prune the live path, for example seaweed-wiki:cloud/wiki").option("--remote-delete", "delete the live path from the remote after local remove", false).option("--max-remote-deletes <n>", "maximum remote object deletes allowed", "1").option("--reason <text>", "stored on the delete-intent tombstone").action(async (page, vault, opts) => {
|
|
6063
|
+
const v = await resolveVaultArg(vault, opts.wiki);
|
|
6064
|
+
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|
|
6065
|
+
else return emitGuardedVaultWrite(
|
|
6066
|
+
v.vault,
|
|
6067
|
+
"remove",
|
|
6068
|
+
() => runRemove({
|
|
6069
|
+
vault: v.vault,
|
|
6070
|
+
page,
|
|
6071
|
+
remote: opts.remote,
|
|
6072
|
+
remoteDelete: !!opts.remoteDelete,
|
|
6073
|
+
maxRemoteDeletes: Number.parseInt(opts.maxRemoteDeletes, 10),
|
|
6074
|
+
reason: opts.reason
|
|
6075
|
+
})
|
|
6076
|
+
);
|
|
6077
|
+
});
|
|
5913
6078
|
program.command("drift [vault]").description("detect content drift in raw sources").option("--apply", "update sha256 in drifted sources").option("--new <date>", "list raw files ingested on/after this date (YYYY-MM-DD)").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
|
|
5914
6079
|
const v = await resolveVaultArg(vault, opts.wiki);
|
|
5915
6080
|
if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
|