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/cli.js CHANGED
@@ -92,7 +92,7 @@ import {
92
92
  taxonomyCommentForPage,
93
93
  upsertIndexEntry,
94
94
  writeDotenv
95
- } from "./chunk-2PENIQ3A.js";
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 join25 } from "path";
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 mkdir5, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
1363
- import { join as join8, dirname as dirname5 } from "path";
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 = join8("_archive", relPath).replace(/\\/g, "/");
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 readFile5(join8(input.vault, "index.md"), "utf8");
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 = join8(input.vault, ref.page);
1451
- const text = await readFile5(absPath, "utf8");
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 writeFile4(absPath, `---
1504
+ await writeFile5(absPath, `---
1463
1505
  ${fmRewritten}
1464
1506
  ---${split.data.body}`, "utf8");
1465
1507
  }
1466
1508
  }
1467
1509
  }
1468
- await mkdir5(dirname5(join8(input.vault, archivePath)), { recursive: true });
1510
+ await mkdir6(dirname5(join9(input.vault, archivePath)), { recursive: true });
1469
1511
  let indexUpdated = false;
1470
1512
  if (!isRaw) {
1471
- const indexPath = join8(input.vault, "index.md");
1513
+ const indexPath = join9(input.vault, "index.md");
1472
1514
  try {
1473
- const idx = await readFile5(indexPath, "utf8");
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 writeFile4(indexPath, filtered.join("\n"), "utf8");
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(join8(input.vault, relPath), join8(input.vault, archivePath));
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 join9 } from "path";
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 join9(globalRoot, "skillwiki", "skills");
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 = join9(input.home, ".claude", "skills");
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 join10 } from "path";
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 = join10(sourceRoot, "packages", "cli", "package.json");
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 readdir3, stat as stat3, readFile as readFile6 } from "fs/promises";
2080
- import { join as join11 } from "path";
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 = join11(input.vault, "raw", "transcripts");
2231
+ const dir = join13(input.vault, "raw", "transcripts");
2083
2232
  let entries;
2084
2233
  try {
2085
- entries = await readdir3(dir, { withFileTypes: true });
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 = join11(dir, entry.name);
2093
- const content = await readFile6(filePath, "utf8");
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 writeFile5, mkdir as mkdir6, readdir as readdir4, unlink as unlink2 } from "fs/promises";
2111
- import { join as join12 } from "path";
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 readFile7 } from "fs/promises";
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 = join12(input.vault, "log.md");
2360
+ const logPath = join14(input.vault, "log.md");
2212
2361
  let logText;
2213
2362
  try {
2214
- logText = await readFile7(logPath, "utf8");
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 = join12(input.vault, "projects", input.project, "compound");
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 = join12(compoundDir, `${slug}.md`);
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 mkdir6(compoundDir, { recursive: true });
2418
+ await mkdir7(compoundDir, { recursive: true });
2270
2419
  }
2271
- await writeFile5(compoundPath, content, "utf8");
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 = join12(input.vault, "projects", input.project);
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 = join12(projectDir, "compound", `${entryName}.md`);
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 unlink2(compoundPath);
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 = join12(input.vault, "projects", input.project, "compound");
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 readdir4(compoundDir, { withFileTypes: true });
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 = join12(compoundDir, dirent.name);
2512
+ const filePath = join14(compoundDir, dirent.name);
2364
2513
  let text;
2365
2514
  try {
2366
- text = await readFile7(filePath, "utf8");
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 mkdir7, readFile as readFile8, writeFile as writeFile6 } from "fs/promises";
2396
- import { join as join13, relative, sep } from "path";
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(join13(cwd, ".skillwiki", ".env"));
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 readFile8(file, "utf8");
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(join13(vault, "meta", "session-pins.md"));
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(join13(vault, ".skillwiki", "health.json"));
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(join13(vault, ".skillwiki", "memory", project, "topics.json"));
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 = join13(vault, "meta", "latest-session-brief.md");
2730
- const cacheMdPath = join13(vault, ".skillwiki", "session-brief.md");
2731
- const cacheJsonPath = join13(vault, ".skillwiki", "session-brief.json");
2732
- await mkdir7(join13(vault, "meta"), { recursive: true });
2733
- await mkdir7(join13(vault, ".skillwiki"), { recursive: true });
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 writeFile6(metaPath, committed, "utf8");
2739
- await writeFile6(cacheMdPath, input.brief, "utf8");
2740
- await writeFile6(cacheJsonPath, `${JSON.stringify({
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 = join13(vault, "index.md");
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 writeFile6(indexPath, lines.join("\n"), "utf8");
2958
+ await writeFile8(indexPath, lines.join("\n"), "utf8");
2810
2959
  return true;
2811
2960
  }
2812
2961
  async function appendMaterialLog(vault, today) {
2813
- const logPath = join13(vault, "log.md");
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 writeFile6(logPath, text.trimEnd() + entry + "\n", "utf8");
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 readFile8(path, "utf8");
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 readFile10, open, unlink as unlink3, mkdir as mkdir8 } from "fs/promises";
2866
- import { join as join15 } from "path";
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 readFile9 } from "fs/promises";
2873
- import { join as join14, resolve as resolve3 } from "path";
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 readFile9(input.draftPath, "utf8");
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 = join14(vault, "SCHEMA.md");
3153
+ const schemaPath = join16(vault, "SCHEMA.md");
3005
3154
  let schemaText;
3006
3155
  try {
3007
- schemaText = await readFile9(schemaPath, "utf8");
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
- readFile9(input.targetPath, "utf8"),
3038
- readFile9(schemaPath, "utf8")
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 readFile9(targetPath, "utf8") !== content);
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 = join14(vault, "SCHEMA.md");
3276
+ const schemaPath = join16(vault, "SCHEMA.md");
3128
3277
  let schemaText;
3129
3278
  try {
3130
- schemaText = await readFile9(schemaPath, "utf8");
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 = join14(vault, "index.md");
3291
+ const indexPath = join16(vault, "index.md");
3143
3292
  let indexText;
3144
3293
  try {
3145
- indexText = await readFile9(indexPath, "utf8");
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 = join14(vault, "log.md");
3305
+ const logPath = join16(vault, "log.md");
3157
3306
  let logText;
3158
3307
  try {
3159
- logText = await readFile9(logPath, "utf8");
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 readFile10(input.path, "utf8");
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 unlink3(lock.data);
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 readFile10(input.source, "utf8");
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 = join15(input.vault, rawRelPath);
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 mkdir8(join15(input.vault, typedDir), { recursive: true });
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: join15(input.vault, typedDir), message: String(error) })
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 mkdir8(join15(input.vault, "raw", "articles"), { recursive: true });
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: join15(input.vault, "raw", "articles"), message: String(error) })
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 readFile11 } from "fs/promises";
3847
- import { join as join16, posix } from "path";
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 readFile11(path, "utf8");
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 ? join16(input.vault, page) : void 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 readFile11(schemaPath, "utf8")) };
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 = join16(input.vault, "SCHEMA.md");
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 readFile11(schemaPath, "utf8");
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(join16(input.vault, "SCHEMA.md"));
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 join17 } from "path";
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(join17(vault, ".git"))) {
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(join17(vault, ".git"))) {
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(join17(vault, ".git"))) {
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 join18, relative as relative2, dirname as dirname6 } from "path";
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 = join18(dir, entry.name);
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 = join18(input.vault, relPath);
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 = join18(target, obj.Key);
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 readFile12 } from "fs/promises";
4786
- import { join as join19 } from "path";
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 readFile12(join19(input.vault, "SCHEMA.md"), "utf8");
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 mkdir9, writeFile as writeFile7, stat as stat4 } from "fs/promises";
4874
- import { join as join20 } from "path";
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(join20(input.vault, "SCHEMA.md"));
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 = join20(input.vault, relPath);
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 mkdir9(join20(absPath, ".."), { recursive: true });
4956
- await writeFile7(absPath, content, "utf8");
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 = join20(input.vault, "raw", "articles", "example-source.md");
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 mkdir9(join20(rawPath, ".."), { recursive: true });
4966
- await writeFile7(rawPath, EXAMPLE_RAW, "utf8");
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 readFile13, writeFile as writeFile8 } from "fs/promises";
5137
+ import { readFile as readFile15, writeFile as writeFile10 } from "fs/promises";
4989
5138
  import { existsSync as existsSync7 } from "fs";
4990
- import { join as join21 } from "path";
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 ?? join21(input.vault, ".skillwiki", "graph.json");
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 readFile13(graphPath, "utf8");
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 = join21(input.vault, "vault-graph.canvas");
5255
+ const outPath = join23(input.vault, "vault-graph.canvas");
5107
5256
  try {
5108
- await writeFile8(outPath, JSON.stringify(canvas, null, 2));
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 join22 } from "path";
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 ? join22(vault, FLEET_REL_PATH) : void 0);
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 join23 } from "path";
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(join23(vault, ".git"))) return;
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 join24, resolve as resolvePath } from "path";
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 = join24(home, ".skillwiki", ".env");
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 ?? join25(vault, ".skillwiki", "graph.json");
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 });