skillwiki 0.10.6 → 0.10.8

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
@@ -26,6 +26,7 @@ import {
26
26
  readCliPackageJson,
27
27
  readLastOp,
28
28
  readLock,
29
+ readLogEvents,
29
30
  readSatelliteLatestRunFromText,
30
31
  reconcileTaxonomyDocument,
31
32
  releaseLock,
@@ -67,9 +68,11 @@ import {
67
68
  runValidate,
68
69
  safeWritePage,
69
70
  satelliteLatestRunPath,
71
+ scanConflictMarkerBlocksInText,
70
72
  taxonomyCommentForPage,
71
- upsertIndexEntry
72
- } from "./chunk-NJFCTMYZ.js";
73
+ upsertIndexEntry,
74
+ writeLogEvent
75
+ } from "./chunk-AVCGIWSL.js";
73
76
  import {
74
77
  normalizeDistTag,
75
78
  readCache,
@@ -95,7 +98,7 @@ import {
95
98
  releaseManagedWriteLock,
96
99
  runManagedWritePreflight,
97
100
  runManagedWriteTransaction
98
- } from "./chunk-4KGCTQM3.js";
101
+ } from "./chunk-3ZVZ2YEE.js";
99
102
  import {
100
103
  FLEET_REL_PATH,
101
104
  git,
@@ -106,16 +109,18 @@ import {
106
109
  loadFleetManifestAndHost,
107
110
  parseDotenvFile,
108
111
  parseDotenvText,
112
+ parseJournalEnv,
109
113
  profileKey,
110
114
  readJournal,
111
115
  resolveFleetHostId,
112
116
  runFleetContext,
113
117
  runFleetValidate,
114
118
  runVaultSyncPullHelper,
119
+ serializeJournalEnv,
115
120
  snapshotterAliasForLocalHost,
116
121
  supersedeStaleReviewRequiredJournals,
117
122
  writeDotenv
118
- } from "./chunk-R6BKJWVC.js";
123
+ } from "./chunk-TYN2IHBY.js";
119
124
  import {
120
125
  ExitCode,
121
126
  MetaSchema,
@@ -126,7 +131,7 @@ import {
126
131
  } from "./chunk-C5OLZRRM.js";
127
132
 
128
133
  // src/cli.ts
129
- import { join as join34 } from "path";
134
+ import { join as join35 } from "path";
130
135
  import { Command } from "commander";
131
136
 
132
137
  // src/utils/output.ts
@@ -466,6 +471,15 @@ async function runInstall(input) {
466
471
  }
467
472
 
468
473
  // src/commands/path.ts
474
+ function toPathOutput(path, source, plain, chain) {
475
+ return {
476
+ path,
477
+ source,
478
+ ...chain ? { chain } : {},
479
+ ...plain ? { plain: true } : {},
480
+ humanHint: plain ? path : `${path} (via ${source})`
481
+ };
482
+ }
469
483
  async function runPath(input) {
470
484
  if (input.initTime) {
471
485
  const r2 = await resolveInitTimePath({
@@ -475,7 +489,10 @@ async function runPath(input) {
475
489
  cwd: input.cwd,
476
490
  explain: input.explain
477
491
  });
478
- return { exitCode: ExitCode.OK, result: ok({ path: r2.path, source: r2.source, ...r2.chain ? { chain: r2.chain } : {}, humanHint: `${r2.path} (via ${r2.source})` }) };
492
+ return {
493
+ exitCode: ExitCode.OK,
494
+ result: ok(toPathOutput(r2.path, r2.source, input.plain, r2.chain))
495
+ };
479
496
  }
480
497
  const r = await resolveRuntimePath({
481
498
  flag: input.flag,
@@ -489,7 +506,10 @@ async function runPath(input) {
489
506
  const exitCode = r.error === "UNKNOWN_WIKI_PROFILE" ? ExitCode.UNKNOWN_WIKI_PROFILE : ExitCode.NO_VAULT_CONFIGURED;
490
507
  return { exitCode, result: r };
491
508
  }
492
- return { exitCode: ExitCode.OK, result: ok({ path: r.data.path, source: r.data.source, ...r.data.chain ? { chain: r.data.chain } : {}, humanHint: `${r.data.path} (via ${r.data.source})` }) };
509
+ return {
510
+ exitCode: ExitCode.OK,
511
+ result: ok(toPathOutput(r.data.path, r.data.source, input.plain, r.data.chain))
512
+ };
493
513
  }
494
514
 
495
515
  // src/utils/lang.ts
@@ -992,7 +1012,7 @@ import { mkdtempSync, writeFileSync, rmSync } from "fs";
992
1012
  import { tmpdir } from "os";
993
1013
  import { join as join8 } from "path";
994
1014
  var ENTRY_RE = /^## \[/m;
995
- var PUBLISH_MARKER_RE = /<!--\s*skillwiki-page-publish:([a-f0-9]{64})\s*-->/i;
1015
+ var OPERATION_MARKER_RE = /<!--\s*skillwiki-(?:page-publish|log-op):([a-f0-9]{64})\s*-->/i;
996
1016
  function splitLog(text) {
997
1017
  const normalized = text.replace(/\r\n/g, "\n");
998
1018
  const match = ENTRY_RE.exec(normalized);
@@ -1006,7 +1026,7 @@ function splitLog(text) {
1006
1026
  return { preamble, entries };
1007
1027
  }
1008
1028
  function publishId(entry) {
1009
- return PUBLISH_MARKER_RE.exec(entry)?.[1]?.toLowerCase();
1029
+ return OPERATION_MARKER_RE.exec(entry)?.[1]?.toLowerCase();
1010
1030
  }
1011
1031
  function unionUnstructured(base, ours, theirs) {
1012
1032
  const dir = mkdtempSync(join8(tmpdir(), "log-union-"));
@@ -1187,21 +1207,21 @@ function rollbackPath(vault, path, stage2, stage3) {
1187
1207
  }
1188
1208
  function hasMarkers(path, text) {
1189
1209
  const lines = text.split(/\r?\n/);
1190
- let open3 = false;
1210
+ let open2 = false;
1191
1211
  let sep2 = false;
1192
1212
  for (const line of lines) {
1193
1213
  if (line.startsWith("<<<<<<< ")) {
1194
- open3 = true;
1214
+ open2 = true;
1195
1215
  sep2 = false;
1196
1216
  continue;
1197
1217
  }
1198
- if (line === "=======" && open3) {
1218
+ if (line === "=======" && open2) {
1199
1219
  sep2 = true;
1200
1220
  continue;
1201
1221
  }
1202
- if (line.startsWith(">>>>>>> ") && open3 && sep2) return true;
1222
+ if (line.startsWith(">>>>>>> ") && open2 && sep2) return true;
1203
1223
  if (line.startsWith(">>>>>>> ")) {
1204
- open3 = false;
1224
+ open2 = false;
1205
1225
  sep2 = false;
1206
1226
  }
1207
1227
  }
@@ -1304,144 +1324,7 @@ async function runDerivedConflictResolution(input) {
1304
1324
 
1305
1325
  // src/commands/log-materialize.ts
1306
1326
  import { readFileSync as readFileSync6 } from "fs";
1307
- import { join as join11 } from "path";
1308
-
1309
- // src/utils/log-events.ts
1310
- import { mkdir as mkdir4, open, readFile as readFile4, readdir as readdir3 } from "fs/promises";
1311
1327
  import { join as join10 } from "path";
1312
- function isPlainObject(value) {
1313
- return typeof value === "object" && value !== null && !Array.isArray(value);
1314
- }
1315
- function canonicalize(value) {
1316
- if (Array.isArray(value)) return value.map(canonicalize);
1317
- if (!isPlainObject(value)) return value;
1318
- const out = {};
1319
- for (const key of Object.keys(value).sort()) {
1320
- out[key] = canonicalize(value[key]);
1321
- }
1322
- return out;
1323
- }
1324
- function eventPathFor(event) {
1325
- const day2 = event.occurred_at.slice(0, 10);
1326
- return `meta/log-events/${day2}/${event.operation_id}.json`;
1327
- }
1328
- function validateLogEvent(event) {
1329
- if (event.schema !== "skillwiki-log-event/v1") {
1330
- return err("SCHEME_REJECTED", { message: "invalid event schema" });
1331
- }
1332
- if (!/^[0-9a-f]{64}$/.test(event.operation_id)) {
1333
- return err("SCHEME_REJECTED", { message: "operation_id must be 64 hex chars" });
1334
- }
1335
- if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(event.occurred_at)) {
1336
- return err("SCHEME_REJECTED", { message: "occurred_at must be UTC ISO with milliseconds" });
1337
- }
1338
- for (const field of ["host_id", "actor", "kind", "target", "note"]) {
1339
- const v = event[field];
1340
- if (typeof v !== "string" || v.trim().length === 0 || v.length > 500) {
1341
- return err("SCHEME_REJECTED", { message: `invalid ${field}` });
1342
- }
1343
- }
1344
- if (!isPlainObject(event.metadata)) {
1345
- return err("SCHEME_REJECTED", { message: "metadata must be a plain object" });
1346
- }
1347
- const sensitive = scanSensitiveContent(JSON.stringify(event));
1348
- if (sensitive.length > 0) {
1349
- return err("SENSITIVE_CONTENT_DETECTED", { findings: sensitive });
1350
- }
1351
- return ok({
1352
- schema: "skillwiki-log-event/v1",
1353
- operation_id: event.operation_id,
1354
- occurred_at: event.occurred_at,
1355
- host_id: event.host_id,
1356
- actor: event.actor,
1357
- kind: event.kind,
1358
- target: event.target,
1359
- note: event.note,
1360
- metadata: canonicalize(event.metadata)
1361
- });
1362
- }
1363
- function canonicalEventJson(event) {
1364
- const ordered = {
1365
- schema: event.schema,
1366
- operation_id: event.operation_id,
1367
- occurred_at: event.occurred_at,
1368
- host_id: event.host_id,
1369
- actor: event.actor,
1370
- kind: event.kind,
1371
- target: event.target,
1372
- note: event.note,
1373
- metadata: event.metadata
1374
- };
1375
- return `${JSON.stringify(ordered, null, 2)}
1376
- `;
1377
- }
1378
- async function writeLogEvent(vault, event) {
1379
- const validated = validateLogEvent(event);
1380
- if (!validated.ok) return validated;
1381
- const rel = eventPathFor(validated.data);
1382
- const abs = join10(vault, rel);
1383
- await mkdir4(join10(vault, "meta", "log-events", validated.data.occurred_at.slice(0, 10)), {
1384
- recursive: true
1385
- });
1386
- const body = canonicalEventJson(validated.data);
1387
- try {
1388
- const handle = await open(abs, "wx");
1389
- try {
1390
- await handle.writeFile(body, "utf8");
1391
- } finally {
1392
- await handle.close();
1393
- }
1394
- return ok({ path: rel, created: true });
1395
- } catch (error) {
1396
- if (error.code === "EEXIST") {
1397
- let existing;
1398
- try {
1399
- existing = await readFile4(abs, "utf8");
1400
- } catch (readErr) {
1401
- return err("WRITE_FAILED", { path: rel, message: String(readErr) });
1402
- }
1403
- if (existing === body) return ok({ path: rel, created: false });
1404
- return err("EVENT_IDENTITY_COLLISION", { path: rel });
1405
- }
1406
- return err("WRITE_FAILED", { path: rel, message: String(error) });
1407
- }
1408
- }
1409
- async function readLogEvents(vault) {
1410
- const root = join10(vault, "meta", "log-events");
1411
- let days;
1412
- try {
1413
- days = (await readdir3(root, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name).sort();
1414
- } catch {
1415
- return ok([]);
1416
- }
1417
- const events = [];
1418
- for (const day2 of days) {
1419
- const files = (await readdir3(join10(root, day2))).filter((f) => f.endsWith(".json")).sort();
1420
- for (const file of files) {
1421
- const text = await readFile4(join10(root, day2, file), "utf8");
1422
- let parsed;
1423
- try {
1424
- parsed = JSON.parse(text);
1425
- } catch {
1426
- return err("SCHEME_REJECTED", { path: `meta/log-events/${day2}/${file}`, message: "invalid JSON" });
1427
- }
1428
- const validated = validateLogEvent(parsed);
1429
- if (!validated.ok) return validated;
1430
- if (eventPathFor(validated.data) !== `meta/log-events/${day2}/${file}`) {
1431
- return err("SCHEME_REJECTED", {
1432
- path: `meta/log-events/${day2}/${file}`,
1433
- message: "path/identity mismatch"
1434
- });
1435
- }
1436
- events.push(validated.data);
1437
- }
1438
- }
1439
- events.sort((a, b) => {
1440
- if (a.occurred_at !== b.occurred_at) return a.occurred_at < b.occurred_at ? -1 : 1;
1441
- return a.operation_id < b.operation_id ? -1 : a.operation_id > b.operation_id ? 1 : 0;
1442
- });
1443
- return ok(events);
1444
- }
1445
1328
 
1446
1329
  // src/utils/log-projection.ts
1447
1330
  function day(event) {
@@ -1547,7 +1430,7 @@ async function runLogMaterialize(input) {
1547
1430
  const text = renderLogProjection(events.data);
1548
1431
  let current = "";
1549
1432
  try {
1550
- current = readFileSync6(join11(input.vault, "log.md"), "utf8");
1433
+ current = readFileSync6(join10(input.vault, "log.md"), "utf8");
1551
1434
  } catch {
1552
1435
  current = "";
1553
1436
  }
@@ -1591,9 +1474,9 @@ async function runLogMaterialize(input) {
1591
1474
  })
1592
1475
  };
1593
1476
  }
1594
- const written = await atomicWriteText(join11(input.vault, "log.md"), text);
1477
+ const written = await atomicWriteText(join10(input.vault, "log.md"), text);
1595
1478
  if (!written.ok) return { exitCode: ExitCode.WRITE_FAILED, result: written };
1596
- const installed = readFileSync6(join11(input.vault, "log.md"), "utf8");
1479
+ const installed = readFileSync6(join10(input.vault, "log.md"), "utf8");
1597
1480
  if (installed !== text) {
1598
1481
  return {
1599
1482
  exitCode: ExitCode.WRITE_FAILED,
@@ -1613,7 +1496,7 @@ async function runLogMaterialize(input) {
1613
1496
 
1614
1497
  // src/commands/log-migrate-legacy.ts
1615
1498
  import { readFileSync as readFileSync7 } from "fs";
1616
- import { join as join12 } from "path";
1499
+ import { join as join11 } from "path";
1617
1500
 
1618
1501
  // src/utils/operation-id.ts
1619
1502
  import { createHash as createHash2 } from "crypto";
@@ -1642,7 +1525,7 @@ function splitLegacyBlocks(text) {
1642
1525
  async function runLogMigrateLegacy(input) {
1643
1526
  let text = "";
1644
1527
  try {
1645
- text = readFileSync7(join12(input.vault, "log.md"), "utf8");
1528
+ text = readFileSync7(join11(input.vault, "log.md"), "utf8");
1646
1529
  } catch {
1647
1530
  return {
1648
1531
  exitCode: ExitCode.OK,
@@ -1699,7 +1582,7 @@ async function runLogMigrateLegacy(input) {
1699
1582
 
1700
1583
  // src/commands/projections-materialize.ts
1701
1584
  import { readFileSync as readFileSync8 } from "fs";
1702
- import { join as join13 } from "path";
1585
+ import { join as join12 } from "path";
1703
1586
  var defaultDeps = {
1704
1587
  writeText: (path, text) => atomicWriteText(path, text)
1705
1588
  };
@@ -1715,11 +1598,11 @@ async function runProjectionsMaterialize(input, deps = defaultDeps) {
1715
1598
  let curIndex = "";
1716
1599
  let curLog = "";
1717
1600
  try {
1718
- curIndex = readFileSync8(join13(input.vault, "index.md"), "utf8");
1601
+ curIndex = readFileSync8(join12(input.vault, "index.md"), "utf8");
1719
1602
  } catch {
1720
1603
  }
1721
1604
  try {
1722
- curLog = readFileSync8(join13(input.vault, "log.md"), "utf8");
1605
+ curLog = readFileSync8(join12(input.vault, "log.md"), "utf8");
1723
1606
  } catch {
1724
1607
  }
1725
1608
  const indexDrift = curIndex !== indexProj.data.text;
@@ -1753,8 +1636,8 @@ async function runProjectionsMaterialize(input, deps = defaultDeps) {
1753
1636
  })
1754
1637
  };
1755
1638
  }
1756
- const indexPath = join13(input.vault, "index.md");
1757
- const logPath = join13(input.vault, "log.md");
1639
+ const indexPath = join12(input.vault, "index.md");
1640
+ const logPath = join12(input.vault, "log.md");
1758
1641
  const indexWrite = await deps.writeText(indexPath, indexProj.data.text);
1759
1642
  if (!indexWrite.ok) return { exitCode: ExitCode.WRITE_FAILED, result: indexWrite };
1760
1643
  const logWrite = await deps.writeText(logPath, logText);
@@ -1794,9 +1677,9 @@ async function runProjectionsMaterialize(input, deps = defaultDeps) {
1794
1677
  }
1795
1678
 
1796
1679
  // src/commands/claim.ts
1797
- import { mkdir as mkdir5, writeFile as writeFile3, readFile as readFile5 } from "fs/promises";
1680
+ import { mkdir as mkdir4, writeFile as writeFile3, readFile as readFile4 } from "fs/promises";
1798
1681
  import { existsSync as existsSync2, statSync } from "fs";
1799
- import { join as join14 } from "path";
1682
+ import { join as join13 } from "path";
1800
1683
  function extractDate(filename) {
1801
1684
  const m = filename.match(/^(\d{4}-\d{2}-\d{2})/);
1802
1685
  return m?.[1] ?? "";
@@ -1811,11 +1694,11 @@ async function runClaim(input) {
1811
1694
  if (!existsSync2(input.vault) || !statSync(input.vault).isDirectory()) {
1812
1695
  return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { path: input.vault }) };
1813
1696
  }
1814
- const absTranscript = join14(input.vault, input.transcript);
1697
+ const absTranscript = join13(input.vault, input.transcript);
1815
1698
  if (!existsSync2(absTranscript)) {
1816
1699
  return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: input.transcript }) };
1817
1700
  }
1818
- const content = await readFile5(absTranscript, "utf8");
1701
+ const content = await readFile4(absTranscript, "utf8");
1819
1702
  const fm = extractFrontmatter(content);
1820
1703
  let projectSlug = input.project;
1821
1704
  if (!projectSlug && fm.ok && typeof fm.data.project === "string") {
@@ -1827,7 +1710,7 @@ async function runClaim(input) {
1827
1710
  result: err("SCHEME_REJECTED", { message: "No project specified. Use --project or set project in transcript frontmatter." })
1828
1711
  };
1829
1712
  }
1830
- const projectDir = join14(input.vault, "projects", projectSlug);
1713
+ const projectDir = join13(input.vault, "projects", projectSlug);
1831
1714
  if (!existsSync2(projectDir) || !statSync(projectDir).isDirectory()) {
1832
1715
  return {
1833
1716
  exitCode: ExitCode.PROJECT_NOT_FOUND,
@@ -1844,7 +1727,7 @@ async function runClaim(input) {
1844
1727
  }
1845
1728
  const workSlug = input.slug || extractSlugFromFilename(filename);
1846
1729
  const dirName = `${date}-${workSlug}`;
1847
- const workDir = join14(projectDir, "work", dirName);
1730
+ const workDir = join13(projectDir, "work", dirName);
1848
1731
  const relWorkDir = `projects/${projectSlug}/work/${dirName}`;
1849
1732
  const relSpecPath = `${relWorkDir}/spec.md`;
1850
1733
  if (existsSync2(workDir)) {
@@ -1858,7 +1741,7 @@ async function runClaim(input) {
1858
1741
  })
1859
1742
  };
1860
1743
  }
1861
- await mkdir5(workDir, { recursive: true });
1744
+ await mkdir4(workDir, { recursive: true });
1862
1745
  const kind = fm.ok && typeof fm.data.kind === "string" ? fm.data.kind : "task";
1863
1746
  const specLines = [
1864
1747
  "---",
@@ -1873,7 +1756,7 @@ async function runClaim(input) {
1873
1756
  `Claimed from ${input.transcript}`,
1874
1757
  ""
1875
1758
  ];
1876
- await writeFile3(join14(workDir, "spec.md"), specLines.join("\n"), "utf8");
1759
+ await writeFile3(join13(workDir, "spec.md"), specLines.join("\n"), "utf8");
1877
1760
  appendLastOp(input.vault, {
1878
1761
  operation: "claim",
1879
1762
  summary: `claimed ${input.transcript} \u2192 ${relWorkDir}`,
@@ -1891,9 +1774,483 @@ async function runClaim(input) {
1891
1774
  };
1892
1775
  }
1893
1776
 
1777
+ // src/commands/work-complete.ts
1778
+ import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync10, renameSync, writeFileSync as writeFileSync3 } from "fs";
1779
+ import { join as join15 } from "path";
1780
+
1781
+ // src/commands/work-validate.ts
1782
+ import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync9 } from "fs";
1783
+ import { join as join14 } from "path";
1784
+ function normalizeWorkItemRel(workItem) {
1785
+ return workItem.replace(/\\/g, "/").replace(/^\.?\//, "");
1786
+ }
1787
+ function scanFileConflicts(relPath, text) {
1788
+ return scanConflictMarkerBlocksInText(relPath, text).length;
1789
+ }
1790
+ function countUnchecked(text) {
1791
+ return [...text.matchAll(/^- \[ \]/gm)].length;
1792
+ }
1793
+ function extractPrMetadata(fm) {
1794
+ const keys = [
1795
+ "pr_url",
1796
+ "pr_number",
1797
+ "merge_commit",
1798
+ "merged",
1799
+ "merge_method",
1800
+ "merge_auto_approved"
1801
+ ];
1802
+ const out = {};
1803
+ let any = false;
1804
+ for (const k of keys) {
1805
+ if (k in fm) {
1806
+ out[k] = fm[k];
1807
+ any = true;
1808
+ }
1809
+ }
1810
+ return any ? out : null;
1811
+ }
1812
+ function validatePrMetadata(meta, findings) {
1813
+ if (!meta) return;
1814
+ if ("pr_number" in meta && meta.pr_number !== null && meta.pr_number !== void 0) {
1815
+ if (typeof meta.pr_number !== "number" && !/^\d+$/.test(String(meta.pr_number))) {
1816
+ findings.push({
1817
+ code: "bad_pr_metadata",
1818
+ message: `pr_number must be numeric, got ${JSON.stringify(meta.pr_number)}`
1819
+ });
1820
+ }
1821
+ }
1822
+ if ("pr_url" in meta && meta.pr_url != null) {
1823
+ const url = String(meta.pr_url);
1824
+ if (url && !/^https?:\/\//.test(url)) {
1825
+ findings.push({
1826
+ code: "bad_pr_metadata",
1827
+ message: `pr_url must be an http(s) URL, got ${url}`
1828
+ });
1829
+ }
1830
+ }
1831
+ if (meta.merged === true && !meta.merge_commit && !meta.pr_number && !meta.pr_url) {
1832
+ findings.push({
1833
+ code: "bad_pr_metadata",
1834
+ message: "merged: true requires merge_commit or pr_number/pr_url"
1835
+ });
1836
+ }
1837
+ }
1838
+ async function runWorkValidate(input) {
1839
+ const rel = normalizeWorkItemRel(input.workItem);
1840
+ const workDir = join14(input.vault, rel);
1841
+ if (!existsSync3(workDir)) {
1842
+ return {
1843
+ exitCode: ExitCode.FILE_NOT_FOUND,
1844
+ result: err("FILE_NOT_FOUND", { path: rel })
1845
+ };
1846
+ }
1847
+ const findings = [];
1848
+ const status = {};
1849
+ let evidencePresent = false;
1850
+ let unchecked = 0;
1851
+ let conflicts = 0;
1852
+ let decisionsPresent = false;
1853
+ let prMetadata = null;
1854
+ const files = readdirSync(workDir).filter((f) => f.endsWith(".md"));
1855
+ if (!files.includes("spec.md")) {
1856
+ findings.push({ code: "missing_spec", path: `${rel}/spec.md`, message: "spec.md is required" });
1857
+ }
1858
+ for (const file of files) {
1859
+ const abs = join14(workDir, file);
1860
+ let text;
1861
+ try {
1862
+ text = readFileSync9(abs, "utf8");
1863
+ } catch {
1864
+ findings.push({ code: "unreadable", path: `${rel}/${file}`, message: "cannot read file" });
1865
+ continue;
1866
+ }
1867
+ const c = scanFileConflicts(`${rel}/${file}`, text);
1868
+ conflicts += c;
1869
+ if (c > 0) {
1870
+ findings.push({
1871
+ code: "conflict_markers",
1872
+ path: `${rel}/${file}`,
1873
+ message: `${c} conflict marker block(s)`
1874
+ });
1875
+ }
1876
+ unchecked += countUnchecked(text);
1877
+ const fm = extractFrontmatter(text);
1878
+ if (file === "spec.md" && fm.ok) {
1879
+ status.spec = typeof fm.data.status === "string" ? fm.data.status : void 0;
1880
+ prMetadata = extractPrMetadata(fm.data);
1881
+ validatePrMetadata(prMetadata, findings);
1882
+ }
1883
+ if (file === "plan.md" && fm.ok) {
1884
+ status.plan = typeof fm.data.status === "string" ? fm.data.status : void 0;
1885
+ }
1886
+ if (file === "evidence.md" || file === "retro.md") {
1887
+ evidencePresent = true;
1888
+ }
1889
+ if (file === "decisions.md" || /##\s+Decisions/i.test(text) || /structured.decisions/i.test(text)) {
1890
+ decisionsPresent = true;
1891
+ }
1892
+ if (/^- \*\*Decision\*\*:/m.test(text) || /^## Decision/m.test(text)) {
1893
+ decisionsPresent = true;
1894
+ }
1895
+ }
1896
+ if (input.requireComplete) {
1897
+ if (status.spec && status.spec !== "completed" && status.spec !== "complete" && status.spec !== "done") {
1898
+ findings.push({
1899
+ code: "spec_not_completed",
1900
+ path: `${rel}/spec.md`,
1901
+ message: `spec status is ${status.spec}, expected completed`
1902
+ });
1903
+ }
1904
+ if (!status.spec) {
1905
+ findings.push({
1906
+ code: "spec_status_missing",
1907
+ path: `${rel}/spec.md`,
1908
+ message: "spec frontmatter status missing for completion"
1909
+ });
1910
+ }
1911
+ if (!evidencePresent) {
1912
+ findings.push({
1913
+ code: "missing_evidence",
1914
+ path: rel,
1915
+ message: "completion requires evidence.md or retro.md"
1916
+ });
1917
+ }
1918
+ if (unchecked > 0) {
1919
+ findings.push({
1920
+ code: "unchecked_steps",
1921
+ path: rel,
1922
+ message: `${unchecked} unchecked completion step(s) remain`
1923
+ });
1924
+ }
1925
+ }
1926
+ if (status.plan === "completed" || status.plan === "complete") {
1927
+ if (!evidencePresent) {
1928
+ findings.push({
1929
+ code: "missing_evidence",
1930
+ path: rel,
1931
+ message: "plan is completed but evidence/retro is missing"
1932
+ });
1933
+ }
1934
+ }
1935
+ const valid = findings.length === 0;
1936
+ const hint = valid ? `work item valid: ${rel}` : `work item invalid (${findings.length}): ${findings.map((f) => f.code).join(", ")}`;
1937
+ return {
1938
+ exitCode: valid ? ExitCode.OK : ExitCode.PREFLIGHT_FAILED,
1939
+ result: ok({
1940
+ work_item: rel,
1941
+ valid,
1942
+ findings,
1943
+ status,
1944
+ evidence_present: evidencePresent,
1945
+ unchecked_steps: unchecked,
1946
+ conflict_markers: conflicts,
1947
+ decisions_present: decisionsPresent,
1948
+ pr_metadata: prMetadata,
1949
+ humanHint: hint
1950
+ })
1951
+ };
1952
+ }
1953
+
1954
+ // src/commands/work-complete.ts
1955
+ function workCompleteJournalPath(vault, opId) {
1956
+ return join15(vault, ".skillwiki", "work-complete", `${opId}.env`);
1957
+ }
1958
+ function readWorkJournal(vault, opId) {
1959
+ const path = workCompleteJournalPath(vault, opId);
1960
+ if (!existsSync4(path)) return null;
1961
+ try {
1962
+ return parseJournalEnv(readFileSync10(path, "utf8"));
1963
+ } catch {
1964
+ return null;
1965
+ }
1966
+ }
1967
+ function writeWorkJournal(vault, opId, fields) {
1968
+ const path = workCompleteJournalPath(vault, opId);
1969
+ const dir = join15(vault, ".skillwiki", "work-complete");
1970
+ mkdirSync(dir, { recursive: true });
1971
+ const tmp = `${path}.tmp.${process.pid}`;
1972
+ const body = serializeJournalEnv(fields, [
1973
+ "operation_id",
1974
+ "phase",
1975
+ "retry_count",
1976
+ "work_item",
1977
+ "committed",
1978
+ "reason"
1979
+ ]);
1980
+ writeFileSync3(tmp, body, "utf8");
1981
+ renameSync(tmp, path);
1982
+ }
1983
+ var PHASE_ORDER = ["validate", "evidence", "log", "projection", "commit", "done"];
1984
+ function resolveWorkDir(vault, workItem) {
1985
+ const rel = normalizeWorkItemRel(workItem);
1986
+ const abs = join15(vault, rel);
1987
+ if (!existsSync4(abs)) {
1988
+ return err("FILE_NOT_FOUND", { path: rel });
1989
+ }
1990
+ if (!existsSync4(join15(abs, "spec.md"))) {
1991
+ return err("PREFLIGHT_FAILED", { reason: "missing-spec", path: rel });
1992
+ }
1993
+ return ok(abs);
1994
+ }
1995
+ function deriveOpId(vault, workItem) {
1996
+ return operationId("skillwiki-work-complete-v1", [vault, workItem]);
1997
+ }
1998
+ function phaseIndex(phase) {
1999
+ const i = PHASE_ORDER.indexOf(phase);
2000
+ return i < 0 ? 0 : i;
2001
+ }
2002
+ async function patchFrontmatterStatus(filePath, extraBody) {
2003
+ let text;
2004
+ try {
2005
+ text = readFileSync10(filePath, "utf8");
2006
+ } catch {
2007
+ return;
2008
+ }
2009
+ const split = splitFrontmatter(text);
2010
+ if (!split.ok || !split.data.rawFrontmatter) {
2011
+ if (extraBody) {
2012
+ await atomicWriteText(filePath, extraBody(text));
2013
+ }
2014
+ return;
2015
+ }
2016
+ let fm = split.data.rawFrontmatter;
2017
+ if (/^status:\s*/m.test(fm)) {
2018
+ fm = fm.replace(/^status:\s*.*$/m, "status: completed");
2019
+ } else {
2020
+ fm = `status: completed
2021
+ ${fm}`;
2022
+ }
2023
+ if (!/^completed:\s*/m.test(fm)) {
2024
+ fm = `completed: ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}
2025
+ ${fm}`;
2026
+ }
2027
+ let body = split.data.body;
2028
+ if (extraBody) body = extraBody(body);
2029
+ await atomicWriteText(filePath, `---
2030
+ ${fm}
2031
+ ---
2032
+ ${body}`);
2033
+ }
2034
+ async function setStatusCompleted(filePath) {
2035
+ await patchFrontmatterStatus(filePath);
2036
+ }
2037
+ async function writeEvidence(workDir, opId, phases) {
2038
+ const path = join15(workDir, "evidence.md");
2039
+ const body = [
2040
+ "---",
2041
+ "title: work-complete evidence",
2042
+ "status: completed",
2043
+ `operation_id: ${opId}`,
2044
+ "---",
2045
+ "",
2046
+ "# Work-complete evidence",
2047
+ "",
2048
+ `- operation_id: \`${opId}\``,
2049
+ `- phases: ${phases.join(", ")}`,
2050
+ `- completed_at: ${(/* @__PURE__ */ new Date()).toISOString()}`,
2051
+ ""
2052
+ ].join("\n");
2053
+ await atomicWriteText(path, body);
2054
+ }
2055
+ async function markPlanComplete(workDir) {
2056
+ const planPath = join15(workDir, "plan.md");
2057
+ if (!existsSync4(planPath)) return;
2058
+ await patchFrontmatterStatus(planPath, (body) => body.replace(/- \[ \]/g, "- [x]"));
2059
+ }
2060
+ async function runWorkComplete(input) {
2061
+ const workDirResult = resolveWorkDir(input.vault, input.workItem);
2062
+ if (!workDirResult.ok) {
2063
+ return {
2064
+ exitCode: workDirResult.error === "FILE_NOT_FOUND" ? ExitCode.FILE_NOT_FOUND : ExitCode.PREFLIGHT_FAILED,
2065
+ result: workDirResult
2066
+ };
2067
+ }
2068
+ const workDir = workDirResult.data;
2069
+ const opId = input.operationId || deriveOpId(input.vault, input.workItem);
2070
+ if (!/^[0-9a-f]{64}$/.test(opId)) {
2071
+ return { exitCode: ExitCode.USAGE, result: err("USAGE", { message: "operationId must be SHA-256 hex" }) };
2072
+ }
2073
+ const journal = readWorkJournal(input.vault, opId) || {
2074
+ operation_id: opId,
2075
+ phase: "validate",
2076
+ retry_count: "0",
2077
+ work_item: input.workItem
2078
+ };
2079
+ const startPhase = journal.phase || "validate";
2080
+ if (startPhase === "done") {
2081
+ return {
2082
+ exitCode: ExitCode.OK,
2083
+ result: ok({
2084
+ operation_id: opId,
2085
+ work_item: input.workItem,
2086
+ phases: ["done"],
2087
+ completed: true,
2088
+ retried: true,
2089
+ committed: journal.committed === "true",
2090
+ humanHint: `work-complete already finished (${opId.slice(0, 12)})`
2091
+ })
2092
+ };
2093
+ }
2094
+ const completedPhases = [];
2095
+ let phase = PHASE_ORDER.includes(startPhase) ? startPhase : "validate";
2096
+ const retried = Number(journal.retry_count || "0") > 0;
2097
+ const advance = (next, extra = {}) => {
2098
+ phase = next;
2099
+ writeWorkJournal(input.vault, opId, {
2100
+ ...journal,
2101
+ operation_id: opId,
2102
+ phase: next,
2103
+ work_item: input.workItem,
2104
+ retry_count: journal.retry_count || "0",
2105
+ ...extra
2106
+ });
2107
+ Object.assign(journal, { phase: next, ...extra });
2108
+ completedPhases.push(next);
2109
+ };
2110
+ if (phaseIndex(startPhase) > 0) {
2111
+ journal.retry_count = String(Number(journal.retry_count || "0") + 1);
2112
+ }
2113
+ writeWorkJournal(input.vault, opId, {
2114
+ ...journal,
2115
+ operation_id: opId,
2116
+ phase: startPhase,
2117
+ work_item: input.workItem
2118
+ });
2119
+ try {
2120
+ if (phaseIndex(phase) <= phaseIndex("validate")) {
2121
+ const cross = await runWorkValidate({ vault: input.vault, workItem: input.workItem, requireComplete: false });
2122
+ if (!cross.result.ok || cross.exitCode !== ExitCode.OK) {
2123
+ return {
2124
+ exitCode: cross.exitCode || ExitCode.PREFLIGHT_FAILED,
2125
+ result: err("PREFLIGHT_FAILED", {
2126
+ reason: "work-validate-failed",
2127
+ detail: cross.result.ok ? cross.result.data : cross.result
2128
+ })
2129
+ };
2130
+ }
2131
+ if (input.failAfter === "validate") {
2132
+ throw new Error("simulated failure after validate");
2133
+ }
2134
+ advance("evidence");
2135
+ }
2136
+ if (phaseIndex(phase) <= phaseIndex("evidence")) {
2137
+ await setStatusCompleted(join15(workDir, "spec.md"));
2138
+ await markPlanComplete(workDir);
2139
+ await writeEvidence(workDir, opId, completedPhases);
2140
+ if (input.failAfter === "evidence") {
2141
+ throw new Error("simulated failure after evidence");
2142
+ }
2143
+ advance("log");
2144
+ }
2145
+ if (phaseIndex(phase) <= phaseIndex("log")) {
2146
+ const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2147
+ const log = await runLogAppend({
2148
+ vault: input.vault,
2149
+ content: `## [${date}] work-complete | ${input.workItem}
2150
+
2151
+ - operation_id: ${opId}`,
2152
+ operationId: opId,
2153
+ writeEvent: true,
2154
+ eventKind: "work-complete",
2155
+ eventTarget: input.workItem,
2156
+ eventNote: `work-complete ${input.workItem}`,
2157
+ recordLastOp: false
2158
+ });
2159
+ if (!log.result.ok) {
2160
+ return { exitCode: log.exitCode, result: log.result };
2161
+ }
2162
+ if (input.failAfter === "log") {
2163
+ throw new Error("simulated failure after log");
2164
+ }
2165
+ advance("projection");
2166
+ }
2167
+ if (phaseIndex(phase) <= phaseIndex("projection")) {
2168
+ if (!existsSync4(join15(workDir, "evidence.md"))) {
2169
+ await writeEvidence(workDir, opId, completedPhases);
2170
+ }
2171
+ const finalCheck = await runWorkValidate({
2172
+ vault: input.vault,
2173
+ workItem: input.workItem,
2174
+ requireComplete: true
2175
+ });
2176
+ if (!finalCheck.result.ok || finalCheck.result.ok && !finalCheck.result.data.valid) {
2177
+ return {
2178
+ exitCode: ExitCode.PREFLIGHT_FAILED,
2179
+ result: err("PREFLIGHT_FAILED", {
2180
+ reason: "completion-validation-failed",
2181
+ detail: finalCheck.result.ok ? finalCheck.result.data : finalCheck.result
2182
+ })
2183
+ };
2184
+ }
2185
+ if (input.failAfter === "projection") {
2186
+ throw new Error("simulated failure after projection");
2187
+ }
2188
+ advance("commit");
2189
+ }
2190
+ let committed = false;
2191
+ if (phaseIndex(phase) <= phaseIndex("commit")) {
2192
+ if (!input.noCommit && existsSync4(join15(input.vault, ".git"))) {
2193
+ try {
2194
+ appendLastOp(input.vault, {
2195
+ operation: "work-complete",
2196
+ summary: `completed ${input.workItem}`,
2197
+ files: [input.workItem],
2198
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2199
+ });
2200
+ git(input.vault, ["add", "--", input.workItem, "log.md", "meta/log-events"]);
2201
+ const porcelain = git(input.vault, ["status", "--porcelain"]);
2202
+ if (porcelain && porcelain.trim()) {
2203
+ gitStrict(input.vault, ["commit", "-m", `work-complete: ${input.workItem} (${opId.slice(0, 12)})`]);
2204
+ committed = true;
2205
+ }
2206
+ } catch (e) {
2207
+ return {
2208
+ exitCode: ExitCode.WRITE_FAILED,
2209
+ result: err("WRITE_FAILED", { stage: "commit", message: String(e) })
2210
+ };
2211
+ }
2212
+ }
2213
+ if (input.failAfter === "commit") {
2214
+ throw new Error("simulated failure after commit");
2215
+ }
2216
+ advance("done", { committed: committed ? "true" : "false" });
2217
+ }
2218
+ return {
2219
+ exitCode: ExitCode.OK,
2220
+ result: ok({
2221
+ operation_id: opId,
2222
+ work_item: input.workItem,
2223
+ phases: completedPhases,
2224
+ completed: true,
2225
+ retried,
2226
+ committed,
2227
+ humanHint: `work-complete finished ${input.workItem} (${opId.slice(0, 12)})`
2228
+ })
2229
+ };
2230
+ } catch (error) {
2231
+ writeWorkJournal(input.vault, opId, {
2232
+ ...journal,
2233
+ operation_id: opId,
2234
+ phase,
2235
+ work_item: input.workItem,
2236
+ retry_count: String(Number(journal.retry_count || "0") + 1),
2237
+ reason: String(error)
2238
+ });
2239
+ return {
2240
+ exitCode: ExitCode.WRITE_FAILED,
2241
+ result: err("WRITE_FAILED", {
2242
+ stage: phase,
2243
+ message: String(error),
2244
+ operation_id: opId,
2245
+ resumable: true
2246
+ })
2247
+ };
2248
+ }
2249
+ }
2250
+
1894
2251
  // src/commands/health.ts
1895
- import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync9, renameSync, writeFileSync as writeFileSync3 } from "fs";
1896
- import { dirname as dirname4, join as join15, resolve as resolve2 } from "path";
2252
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync11, renameSync as renameSync2, writeFileSync as writeFileSync4 } from "fs";
2253
+ import { dirname as dirname4, join as join16, resolve as resolve2 } from "path";
1897
2254
  import { platform } from "os";
1898
2255
  function statusFromCounts(counts) {
1899
2256
  if ((counts.error ?? 0) > 0) return "error";
@@ -2004,8 +2361,8 @@ function summarizeChecks(checks) {
2004
2361
  };
2005
2362
  }
2006
2363
  function classifyLog(path, id, label, okPattern) {
2007
- if (!existsSync3(path)) return { id, label, status: "warn", detail: `log file missing: ${path}` };
2008
- const lines = readFileSync9(path, "utf8").split(/\r?\n/).filter(Boolean);
2364
+ if (!existsSync5(path)) return { id, label, status: "warn", detail: `log file missing: ${path}` };
2365
+ const lines = readFileSync11(path, "utf8").split(/\r?\n/).filter(Boolean);
2009
2366
  if (lines.length === 0) return { id, label, status: "warn", detail: `log file empty: ${path}` };
2010
2367
  const statusLine = [...lines].reverse().find(
2011
2368
  (line) => /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z /.test(line) && (okPattern.test(line) || /\bFAIL\b|\bERROR\b/i.test(line))
@@ -2026,12 +2383,12 @@ function runVaultSyncHealth(home, syncMode) {
2026
2383
  };
2027
2384
  }
2028
2385
  const isMac = platform() === "darwin";
2029
- const shareDir = isMac ? join15(home, "Library", "Application Support", "vault-sync", "bin") : join15(home, ".local", "share", "vault-sync", "bin");
2030
- const logDir = isMac ? join15(home, "Library", "Logs") : join15(home, ".local", "state", "vault-sync", "log");
2031
- const filterPath = join15(home, ".config", "rclone", "wiki-push-filters.txt");
2386
+ const shareDir = isMac ? join16(home, "Library", "Application Support", "vault-sync", "bin") : join16(home, ".local", "share", "vault-sync", "bin");
2387
+ const logDir = isMac ? join16(home, "Library", "Logs") : join16(home, ".local", "state", "vault-sync", "log");
2388
+ const filterPath = join16(home, ".config", "rclone", "wiki-push-filters.txt");
2032
2389
  const checks = [];
2033
- const pushScript = join15(shareDir, "wiki-push.sh");
2034
- if (syncMode === "optional" && !existsSync3(pushScript)) {
2390
+ const pushScript = join16(shareDir, "wiki-push.sh");
2391
+ if (syncMode === "optional" && !existsSync5(pushScript)) {
2035
2392
  return {
2036
2393
  status: "pass",
2037
2394
  blocking: false,
@@ -2044,26 +2401,26 @@ function runVaultSyncHealth(home, syncMode) {
2044
2401
  }]
2045
2402
  };
2046
2403
  }
2047
- checks.push(existsSync3(pushScript) ? { id: "vault_sync_installed", label: "Vault sync installed", status: "pass", detail: `Found: ${pushScript}` } : { id: "vault_sync_installed", label: "Vault sync installed", status: "error", detail: `Script missing: ${pushScript}` });
2404
+ checks.push(existsSync5(pushScript) ? { id: "vault_sync_installed", label: "Vault sync installed", status: "pass", detail: `Found: ${pushScript}` } : { id: "vault_sync_installed", label: "Vault sync installed", status: "error", detail: `Script missing: ${pushScript}` });
2048
2405
  if (isMac) {
2049
- const pushPlist = join15(home, "Library", "LaunchAgents", "com.karlchow.wiki-push.plist");
2050
- const fetchPlist = join15(home, "Library", "LaunchAgents", "com.karlchow.wiki-fetch.plist");
2051
- checks.push(existsSync3(pushPlist) && existsSync3(fetchPlist) ? { id: "vault_sync_jobs_enabled", label: "Vault sync jobs enabled", status: "pass", detail: "launchd unit files present (read-only mode)" } : { id: "vault_sync_jobs_enabled", label: "Vault sync jobs enabled", status: "warn", detail: "launchd unit files missing (read-only mode)" });
2406
+ const pushPlist = join16(home, "Library", "LaunchAgents", "com.karlchow.wiki-push.plist");
2407
+ const fetchPlist = join16(home, "Library", "LaunchAgents", "com.karlchow.wiki-fetch.plist");
2408
+ checks.push(existsSync5(pushPlist) && existsSync5(fetchPlist) ? { id: "vault_sync_jobs_enabled", label: "Vault sync jobs enabled", status: "pass", detail: "launchd unit files present (read-only mode)" } : { id: "vault_sync_jobs_enabled", label: "Vault sync jobs enabled", status: "warn", detail: "launchd unit files missing (read-only mode)" });
2052
2409
  checks.push({ id: "vault_sync_fuse_refresh_job", label: "Vault sync fuse refresh job", status: "pass", detail: "macOS host \u2014 check skipped" });
2053
2410
  } else {
2054
- const pushTimer = join15(home, ".config", "systemd", "user", "wiki-push.timer");
2055
- const fetchTimer = join15(home, ".config", "systemd", "user", "wiki-fetch.timer");
2056
- const fuseTimer = join15(home, ".config", "systemd", "user", "wiki-fuse-refresh.timer");
2057
- const fuseService = join15(home, ".config", "systemd", "user", "wiki-fuse-refresh.service");
2058
- checks.push(existsSync3(pushTimer) && existsSync3(fetchTimer) ? { id: "vault_sync_jobs_enabled", label: "Vault sync jobs enabled", status: "pass", detail: "systemd timer unit files present (read-only mode)" } : { id: "vault_sync_jobs_enabled", label: "Vault sync jobs enabled", status: "warn", detail: "systemd timer unit files missing (read-only mode)" });
2059
- checks.push(existsSync3(fuseTimer) && existsSync3(fuseService) ? { id: "vault_sync_fuse_refresh_job", label: "Vault sync fuse refresh job", status: "pass", detail: "wiki-fuse-refresh unit files present (read-only mode)" } : { id: "vault_sync_fuse_refresh_job", label: "Vault sync fuse refresh job", status: "warn", detail: "wiki-fuse-refresh unit files missing (read-only mode)" });
2060
- }
2061
- checks.push(classifyLog(join15(logDir, "wiki-push.log"), "vault_sync_last_push_age", "Vault sync last push recency", /OK push/));
2062
- checks.push(classifyLog(join15(logDir, "wiki-fetch.log"), "vault_sync_last_fetch_status", "Vault sync last fetch status", /NOTIFY|OK behind|OK/));
2063
- if (!existsSync3(filterPath)) {
2411
+ const pushTimer = join16(home, ".config", "systemd", "user", "wiki-push.timer");
2412
+ const fetchTimer = join16(home, ".config", "systemd", "user", "wiki-fetch.timer");
2413
+ const fuseTimer = join16(home, ".config", "systemd", "user", "wiki-fuse-refresh.timer");
2414
+ const fuseService = join16(home, ".config", "systemd", "user", "wiki-fuse-refresh.service");
2415
+ checks.push(existsSync5(pushTimer) && existsSync5(fetchTimer) ? { id: "vault_sync_jobs_enabled", label: "Vault sync jobs enabled", status: "pass", detail: "systemd timer unit files present (read-only mode)" } : { id: "vault_sync_jobs_enabled", label: "Vault sync jobs enabled", status: "warn", detail: "systemd timer unit files missing (read-only mode)" });
2416
+ checks.push(existsSync5(fuseTimer) && existsSync5(fuseService) ? { id: "vault_sync_fuse_refresh_job", label: "Vault sync fuse refresh job", status: "pass", detail: "wiki-fuse-refresh unit files present (read-only mode)" } : { id: "vault_sync_fuse_refresh_job", label: "Vault sync fuse refresh job", status: "warn", detail: "wiki-fuse-refresh unit files missing (read-only mode)" });
2417
+ }
2418
+ checks.push(classifyLog(join16(logDir, "wiki-push.log"), "vault_sync_last_push_age", "Vault sync last push recency", /OK push/));
2419
+ checks.push(classifyLog(join16(logDir, "wiki-fetch.log"), "vault_sync_last_fetch_status", "Vault sync last fetch status", /NOTIFY|OK behind|OK/));
2420
+ if (!existsSync5(filterPath)) {
2064
2421
  checks.push({ id: "vault_sync_filter_present", label: "Vault sync filter file present", status: "error", detail: `Filter missing: ${filterPath}` });
2065
2422
  } else {
2066
- const content = readFileSync9(filterPath, "utf8");
2423
+ const content = readFileSync11(filterPath, "utf8");
2067
2424
  const missing = [
2068
2425
  "remotely-save/data.json",
2069
2426
  ".skillwiki/sync.lock",
@@ -2156,10 +2513,10 @@ function buildHumanHint(report) {
2156
2513
  return lines.join("\n");
2157
2514
  }
2158
2515
  function writeReport(out, report) {
2159
- mkdirSync(dirname4(out), { recursive: true });
2516
+ mkdirSync2(dirname4(out), { recursive: true });
2160
2517
  const tmp = `${out}.tmp-${process.pid}-${Date.now()}`;
2161
- writeFileSync3(tmp, JSON.stringify(ok(report), null, 2) + "\n", "utf8");
2162
- renameSync(tmp, out);
2518
+ writeFileSync4(tmp, JSON.stringify(ok(report), null, 2) + "\n", "utf8");
2519
+ renameSync2(tmp, out);
2163
2520
  }
2164
2521
  function buildIncompleteReport(input, syncMode) {
2165
2522
  const vaultSource = input.vaultSource ?? "resolved";
@@ -2382,12 +2739,12 @@ async function runHealth(input) {
2382
2739
  }
2383
2740
 
2384
2741
  // src/commands/archive.ts
2385
- import { rename as rename2, mkdir as mkdir7, readFile as readFile7, writeFile as writeFile5 } from "fs/promises";
2386
- import { join as join17, dirname as dirname5 } from "path";
2742
+ import { rename as rename2, mkdir as mkdir6, readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
2743
+ import { join as join18, dirname as dirname5 } from "path";
2387
2744
 
2388
2745
  // src/utils/delete-intent.ts
2389
- import { mkdir as mkdir6, writeFile as writeFile4, readdir as readdir4, readFile as readFile6 } from "fs/promises";
2390
- import { join as join16 } from "path";
2746
+ import { mkdir as mkdir5, writeFile as writeFile4, readdir as readdir3, readFile as readFile5 } from "fs/promises";
2747
+ import { join as join17 } from "path";
2391
2748
  var DELETE_INTENT_SCHEMA = "vault-delete-intent/v1";
2392
2749
  var DELETE_INTENT_DIR = "meta/delete-intents";
2393
2750
  function normalizeVaultRelPath(path) {
@@ -2418,10 +2775,10 @@ function buildDeleteIntent(input) {
2418
2775
  };
2419
2776
  }
2420
2777
  async function writeDeleteIntent(vault, intent) {
2421
- const dir = join16(vault, DELETE_INTENT_DIR);
2422
- await mkdir6(dir, { recursive: true });
2778
+ const dir = join17(vault, DELETE_INTENT_DIR);
2779
+ await mkdir5(dir, { recursive: true });
2423
2780
  const rel = `${DELETE_INTENT_DIR}/${pathToIntentFilename(intent.path)}`;
2424
- await writeFile4(join16(vault, rel), JSON.stringify(intent, null, 2) + "\n", "utf8");
2781
+ await writeFile4(join17(vault, rel), JSON.stringify(intent, null, 2) + "\n", "utf8");
2425
2782
  return rel;
2426
2783
  }
2427
2784
 
@@ -2459,7 +2816,7 @@ async function runArchive(input) {
2459
2816
  if (!relPath) return { exitCode: ExitCode.ARCHIVE_TARGET_NOT_FOUND, result: err("ARCHIVE_TARGET_NOT_FOUND", { page: input.page }) };
2460
2817
  if (relPath.startsWith("_archive/")) return { exitCode: ExitCode.ARCHIVE_ALREADY_ARCHIVED, result: err("ARCHIVE_ALREADY_ARCHIVED", { page: relPath }) };
2461
2818
  const slug = relPath.replace(/\.md$/, "").split("/").pop();
2462
- const archivePath = join17("_archive", relPath).replace(/\\/g, "/");
2819
+ const archivePath = join18("_archive", relPath).replace(/\\/g, "/");
2463
2820
  const remoteRoot = normalizeRemoteRoot(input.remote);
2464
2821
  const remoteObjectPath = buildRemoteObjectPath(remoteRoot, relPath);
2465
2822
  let cascade;
@@ -2485,7 +2842,7 @@ async function runArchive(input) {
2485
2842
  const indexRefs = [];
2486
2843
  if (!isRaw) {
2487
2844
  try {
2488
- const idx = await readFile7(join17(input.vault, "index.md"), "utf8");
2845
+ const idx = await readFile6(join18(input.vault, "index.md"), "utf8");
2489
2846
  idx.split("\n").forEach((line, i) => {
2490
2847
  if (line.includes(`[[${slug}]]`)) indexRefs.push({ line: i + 1, text: line });
2491
2848
  });
@@ -2512,8 +2869,8 @@ async function runArchive(input) {
2512
2869
  }
2513
2870
  if (input.cascade && input.apply && cascade) {
2514
2871
  for (const ref of cascade.source_array_refs) {
2515
- const absPath = join17(input.vault, ref.page);
2516
- const text = await readFile7(absPath, "utf8");
2872
+ const absPath = join18(input.vault, ref.page);
2873
+ const text = await readFile6(absPath, "utf8");
2517
2874
  const split = splitFrontmatter(text);
2518
2875
  if (!split.ok) continue;
2519
2876
  const before = split.data.rawFrontmatter;
@@ -2530,12 +2887,12 @@ ${fmRewritten}
2530
2887
  }
2531
2888
  }
2532
2889
  }
2533
- await mkdir7(dirname5(join17(input.vault, archivePath)), { recursive: true });
2534
- await rename2(join17(input.vault, relPath), join17(input.vault, archivePath));
2890
+ await mkdir6(dirname5(join18(input.vault, archivePath)), { recursive: true });
2891
+ await rename2(join18(input.vault, relPath), join18(input.vault, archivePath));
2535
2892
  let indexUpdated = false;
2536
2893
  if (!isRaw) {
2537
2894
  const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-VNJ3TLEK.js");
2538
- const before = await readFile7(join17(input.vault, "index.md"), "utf8").catch(() => "");
2895
+ const before = await readFile6(join18(input.vault, "index.md"), "utf8").catch(() => "");
2539
2896
  const fullTarget = relPath.replace(/\.md$/, "");
2540
2897
  const bare = fullTarget.split("/").pop() ?? fullTarget;
2541
2898
  const hadIndexEntry = before.includes(`[[${fullTarget}]]`) || before.includes(`[[${bare}]]`);
@@ -2586,7 +2943,7 @@ ${fmRewritten}
2586
2943
 
2587
2944
  // src/commands/remove.ts
2588
2945
  import { unlink as unlink2, access } from "fs/promises";
2589
- import { join as join18 } from "path";
2946
+ import { join as join19 } from "path";
2590
2947
  async function pathExists(abs) {
2591
2948
  try {
2592
2949
  await access(abs);
@@ -2612,7 +2969,7 @@ async function runRemove(input) {
2612
2969
  if (!relPath) {
2613
2970
  try {
2614
2971
  const candidate = normalizeVaultRelPath(input.page);
2615
- if (await pathExists(join18(input.vault, candidate))) {
2972
+ if (await pathExists(join19(input.vault, candidate))) {
2616
2973
  relPath = candidate;
2617
2974
  }
2618
2975
  } catch {
@@ -2641,12 +2998,12 @@ async function runRemove(input) {
2641
2998
  reason: input.reason
2642
2999
  });
2643
3000
  const tombstonePath = await writeDeleteIntent(input.vault, intent);
2644
- await unlink2(join18(input.vault, relPath));
3001
+ await unlink2(join19(input.vault, relPath));
2645
3002
  if (relPath.endsWith(".md") && !relPath.startsWith("raw/")) {
2646
- const { readFile: readFile17 } = await import("fs/promises");
3003
+ const { readFile: readFile16 } = await import("fs/promises");
2647
3004
  const { join: pathJoin } = await import("path");
2648
3005
  const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-VNJ3TLEK.js");
2649
- const before = await readFile17(pathJoin(input.vault, "index.md"), "utf8").catch(() => "");
3006
+ const before = await readFile16(pathJoin(input.vault, "index.md"), "utf8").catch(() => "");
2650
3007
  const fullTarget = relPath.replace(/\.md$/, "");
2651
3008
  const bare = fullTarget.split("/").pop() ?? fullTarget;
2652
3009
  const hadIndexEntry = before.includes(`[[${fullTarget}]]`) || before.includes(`[[${bare}]]`);
@@ -2994,7 +3351,7 @@ ${migratedBody}${newFooter}`;
2994
3351
 
2995
3352
  // src/commands/update.ts
2996
3353
  import { execSync } from "child_process";
2997
- import { join as join19 } from "path";
3354
+ import { join as join20 } from "path";
2998
3355
  function parseCoreSemver(version) {
2999
3356
  const m = version.match(/^(\d+)\.(\d+)\.(\d+)/);
3000
3357
  if (!m) return null;
@@ -3025,7 +3382,7 @@ function resolveGlobalSkillsRoot() {
3025
3382
  encoding: "utf8",
3026
3383
  timeout: 5e3
3027
3384
  }).trim();
3028
- return join19(globalRoot, "skillwiki", "skills");
3385
+ return join20(globalRoot, "skillwiki", "skills");
3029
3386
  } catch {
3030
3387
  return null;
3031
3388
  }
@@ -3053,7 +3410,7 @@ async function runUpdate(input) {
3053
3410
  const pkg2 = readCliPackageJson();
3054
3411
  const currentVersion = pkg2.version;
3055
3412
  const tag = normalizeDistTag(input.distTag);
3056
- const target = join19(input.home, ".claude", "skills");
3413
+ const target = join20(input.home, ".claude", "skills");
3057
3414
  let latest;
3058
3415
  try {
3059
3416
  latest = execSync(`npm view skillwiki@${tag} version`, {
@@ -3133,22 +3490,22 @@ async function runUpdate(input) {
3133
3490
 
3134
3491
  // src/commands/self-update.ts
3135
3492
  import { execSync as execSync2 } from "child_process";
3136
- import { existsSync as existsSync4, readFileSync as readFileSync10 } from "fs";
3137
- import { join as join20 } from "path";
3493
+ import { existsSync as existsSync6, readFileSync as readFileSync12 } from "fs";
3494
+ import { join as join21 } from "path";
3138
3495
  var DEFAULT_SOURCE_ROOT_SUFFIX = "/Desktop/code/llm-wiki";
3139
3496
  async function runSelfUpdate(input) {
3140
3497
  const currentVersion = readCliPackageJson().version;
3141
3498
  const sourceRoot = input.sourceRoot ?? `${input.home}${DEFAULT_SOURCE_ROOT_SUFFIX}`;
3142
3499
  const distTag = normalizeDistTag(input.distTag);
3143
- const localPkgPath = join20(sourceRoot, "packages", "cli", "package.json");
3144
- const hasLocalSource = existsSync4(localPkgPath);
3500
+ const localPkgPath = join21(sourceRoot, "packages", "cli", "package.json");
3501
+ const hasLocalSource = existsSync6(localPkgPath);
3145
3502
  if (input.check) {
3146
3503
  let availableVersion = null;
3147
3504
  let source;
3148
3505
  if (hasLocalSource) {
3149
3506
  source = "local";
3150
3507
  try {
3151
- availableVersion = JSON.parse(readFileSync10(localPkgPath, "utf8")).version ?? null;
3508
+ availableVersion = JSON.parse(readFileSync12(localPkgPath, "utf8")).version ?? null;
3152
3509
  } catch {
3153
3510
  availableVersion = null;
3154
3511
  }
@@ -3206,7 +3563,7 @@ async function runSelfUpdate(input) {
3206
3563
  }
3207
3564
  const newVersion = (() => {
3208
3565
  try {
3209
- return JSON.parse(readFileSync10(localPkgPath, "utf8")).version ?? "unknown";
3566
+ return JSON.parse(readFileSync12(localPkgPath, "utf8")).version ?? "unknown";
3210
3567
  } catch {
3211
3568
  return "unknown";
3212
3569
  }
@@ -3272,21 +3629,21 @@ async function runSelfUpdate(input) {
3272
3629
  }
3273
3630
 
3274
3631
  // src/commands/transcripts.ts
3275
- import { readdir as readdir5, stat as stat3, readFile as readFile9 } from "fs/promises";
3276
- import { join as join21 } from "path";
3632
+ import { readdir as readdir4, stat as stat3, readFile as readFile8 } from "fs/promises";
3633
+ import { join as join22 } from "path";
3277
3634
  async function runTranscripts(input) {
3278
- const dir = join21(input.vault, "raw", "transcripts");
3635
+ const dir = join22(input.vault, "raw", "transcripts");
3279
3636
  let entries;
3280
3637
  try {
3281
- entries = await readdir5(dir, { withFileTypes: true });
3638
+ entries = await readdir4(dir, { withFileTypes: true });
3282
3639
  } catch {
3283
3640
  return { exitCode: ExitCode.VAULT_PATH_INVALID, result: { ok: false, error: "VAULT_PATH_INVALID", detail: `raw/transcripts/ not found: ${dir}` } };
3284
3641
  }
3285
3642
  const transcripts = [];
3286
3643
  for (const entry of entries) {
3287
3644
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
3288
- const filePath = join21(dir, entry.name);
3289
- const content = await readFile9(filePath, "utf8");
3645
+ const filePath = join22(dir, entry.name);
3646
+ const content = await readFile8(filePath, "utf8");
3290
3647
  const fm = extractFrontmatter(content);
3291
3648
  if (!fm.ok) continue;
3292
3649
  const ingested = typeof fm.data.ingested === "string" ? fm.data.ingested : "";
@@ -3303,10 +3660,10 @@ async function runTranscripts(input) {
3303
3660
  }
3304
3661
 
3305
3662
  // src/commands/compound.ts
3306
- import { writeFile as writeFile7, mkdir as mkdir8, readdir as readdir6, unlink as unlink3 } from "fs/promises";
3307
- import { join as join22 } from "path";
3308
- import { existsSync as existsSync5 } from "fs";
3309
- import { readFile as readFile10 } from "fs/promises";
3663
+ import { writeFile as writeFile7, mkdir as mkdir7, readdir as readdir5, unlink as unlink3 } from "fs/promises";
3664
+ import { join as join23 } from "path";
3665
+ import { existsSync as existsSync7 } from "fs";
3666
+ import { readFile as readFile9 } from "fs/promises";
3310
3667
  var RETRO_HEADING_RE = /^## \[(\d{4}-\d{2}-\d{2})(?:\s+[^\]]+)?\] retro \| loop cycle(?: (\d+))?: (.+)$/;
3311
3668
  var FIELD_RE = {
3312
3669
  improve: /^-\s+\*?\*?Improve:?\*?\*?\s*(.+)$/m,
@@ -3404,17 +3761,17 @@ function extractRetroFields(date, cycleName, block) {
3404
3761
  };
3405
3762
  }
3406
3763
  async function runCompound(input) {
3407
- const logPath = join22(input.vault, "log.md");
3764
+ const logPath = join23(input.vault, "log.md");
3408
3765
  let logText;
3409
3766
  try {
3410
- logText = await readFile10(logPath, "utf8");
3767
+ logText = await readFile9(logPath, "utf8");
3411
3768
  } catch {
3412
3769
  return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: logPath }) };
3413
3770
  }
3414
3771
  const entries = parseRetroEntries(logText);
3415
3772
  const promoted = [];
3416
3773
  const skipped = [];
3417
- const compoundDir = join22(input.vault, "projects", input.project, "compound");
3774
+ const compoundDir = join23(input.vault, "projects", input.project, "compound");
3418
3775
  for (const entry of entries) {
3419
3776
  const generalizeValue = entry.generalize.trim();
3420
3777
  if (!/^yes/i.test(generalizeValue)) {
@@ -3422,8 +3779,8 @@ async function runCompound(input) {
3422
3779
  continue;
3423
3780
  }
3424
3781
  const slug = slugify(entry.cycleName);
3425
- const compoundPath = join22(compoundDir, `${slug}.md`);
3426
- if (existsSync5(compoundPath)) {
3782
+ const compoundPath = join23(compoundDir, `${slug}.md`);
3783
+ if (existsSync7(compoundPath)) {
3427
3784
  skipped.push(entry.date);
3428
3785
  continue;
3429
3786
  }
@@ -3461,8 +3818,8 @@ async function runCompound(input) {
3461
3818
  ].join("\n");
3462
3819
  const content = frontmatter + "\n" + body;
3463
3820
  if (!input.dryRun) {
3464
- if (!existsSync5(compoundDir)) {
3465
- await mkdir8(compoundDir, { recursive: true });
3821
+ if (!existsSync7(compoundDir)) {
3822
+ await mkdir7(compoundDir, { recursive: true });
3466
3823
  }
3467
3824
  await writeFile7(compoundPath, content, "utf8");
3468
3825
  }
@@ -3483,16 +3840,16 @@ async function runCompound(input) {
3483
3840
  };
3484
3841
  }
3485
3842
  async function runCompoundDelete(input) {
3486
- const projectDir = join22(input.vault, "projects", input.project);
3487
- if (!existsSync5(projectDir)) {
3843
+ const projectDir = join23(input.vault, "projects", input.project);
3844
+ if (!existsSync7(projectDir)) {
3488
3845
  return {
3489
3846
  exitCode: ExitCode.PROJECT_NOT_FOUND,
3490
3847
  result: err("PROJECT_NOT_FOUND", { slug: input.project, path: projectDir })
3491
3848
  };
3492
3849
  }
3493
3850
  const entryName = input.entry.replace(/\.md$/, "");
3494
- const compoundPath = join22(projectDir, "compound", `${entryName}.md`);
3495
- if (!existsSync5(compoundPath)) {
3851
+ const compoundPath = join23(projectDir, "compound", `${entryName}.md`);
3852
+ if (!existsSync7(compoundPath)) {
3496
3853
  return {
3497
3854
  exitCode: ExitCode.FILE_NOT_FOUND,
3498
3855
  result: err("FILE_NOT_FOUND", { path: compoundPath })
@@ -3525,8 +3882,8 @@ knowledge.md regenerated`
3525
3882
  };
3526
3883
  }
3527
3884
  async function runCompoundList(input) {
3528
- const compoundDir = join22(input.vault, "projects", input.project, "compound");
3529
- if (!existsSync5(compoundDir)) {
3885
+ const compoundDir = join23(input.vault, "projects", input.project, "compound");
3886
+ if (!existsSync7(compoundDir)) {
3530
3887
  return {
3531
3888
  exitCode: ExitCode.OK,
3532
3889
  result: ok({
@@ -3540,7 +3897,7 @@ no compound directory found`
3540
3897
  }
3541
3898
  let dirents;
3542
3899
  try {
3543
- dirents = await readdir6(compoundDir, { withFileTypes: true });
3900
+ dirents = await readdir5(compoundDir, { withFileTypes: true });
3544
3901
  } catch {
3545
3902
  return {
3546
3903
  exitCode: ExitCode.OK,
@@ -3556,10 +3913,10 @@ could not read compound directory`
3556
3913
  const entries = [];
3557
3914
  for (const dirent of dirents) {
3558
3915
  if (!dirent.isFile() || !dirent.name.endsWith(".md")) continue;
3559
- const filePath = join22(compoundDir, dirent.name);
3916
+ const filePath = join23(compoundDir, dirent.name);
3560
3917
  let text;
3561
3918
  try {
3562
- text = await readFile10(filePath, "utf8");
3919
+ text = await readFile9(filePath, "utf8");
3563
3920
  } catch {
3564
3921
  continue;
3565
3922
  }
@@ -3588,8 +3945,8 @@ no compound entries found`;
3588
3945
  }
3589
3946
 
3590
3947
  // src/commands/session-brief.ts
3591
- import { mkdir as mkdir9, readFile as readFile11, writeFile as writeFile8 } from "fs/promises";
3592
- import { join as join23, relative, sep } from "path";
3948
+ import { mkdir as mkdir8, readFile as readFile10, writeFile as writeFile8 } from "fs/promises";
3949
+ import { join as join24, relative, sep } from "path";
3593
3950
  var MAX_WORDS = 900;
3594
3951
  async function runSessionBrief(input) {
3595
3952
  const scan = await scanVault(input.vault);
@@ -3689,7 +4046,7 @@ async function resolveProject(input) {
3689
4046
  const envProject = input.env?.SKILLWIKI_PROJECT;
3690
4047
  if (envProject) return envProject;
3691
4048
  const cwd = input.cwd ?? process.cwd();
3692
- const projectDotenv = await readProjectSlug(join23(cwd, ".skillwiki", ".env"));
4049
+ const projectDotenv = await readProjectSlug(join24(cwd, ".skillwiki", ".env"));
3693
4050
  if (projectDotenv) return projectDotenv;
3694
4051
  const inferred = inferProjectFromPath(input.vault, cwd);
3695
4052
  if (inferred) return inferred;
@@ -3698,7 +4055,7 @@ async function resolveProject(input) {
3698
4055
  async function readProjectSlug(file) {
3699
4056
  let text;
3700
4057
  try {
3701
- text = await readFile11(file, "utf8");
4058
+ text = await readFile10(file, "utf8");
3702
4059
  } catch {
3703
4060
  return void 0;
3704
4061
  }
@@ -3775,7 +4132,7 @@ async function loadTrendDigests(typedPages) {
3775
4132
  return out;
3776
4133
  }
3777
4134
  async function loadSessionPins(vault, project) {
3778
- const text = await readIfExists(join23(vault, "meta", "session-pins.md"));
4135
+ const text = await readIfExists(join24(vault, "meta", "session-pins.md"));
3779
4136
  if (!text) return [];
3780
4137
  const fm = extractFrontmatter(text);
3781
4138
  if (!fm.ok) return [];
@@ -3892,7 +4249,7 @@ function satelliteHealthWarnings(warning) {
3892
4249
  return warning ? [warning] : [];
3893
4250
  }
3894
4251
  async function loadHealthWarnings(vault) {
3895
- const text = await readIfExists(join23(vault, ".skillwiki", "health.json"));
4252
+ const text = await readIfExists(join24(vault, ".skillwiki", "health.json"));
3896
4253
  if (!text) return [];
3897
4254
  try {
3898
4255
  const parsed = JSON.parse(text);
@@ -3905,7 +4262,7 @@ async function loadHealthWarnings(vault) {
3905
4262
  }
3906
4263
  }
3907
4264
  async function loadMemoryTopics(vault, project) {
3908
- const text = await readIfExists(join23(vault, ".skillwiki", "memory", project, "topics.json"));
4265
+ const text = await readIfExists(join24(vault, ".skillwiki", "memory", project, "topics.json"));
3909
4266
  if (!text) return [];
3910
4267
  try {
3911
4268
  const parsed = JSON.parse(text);
@@ -3930,11 +4287,11 @@ async function loadMemoryTopics(vault, project) {
3930
4287
  }
3931
4288
  }
3932
4289
  async function writeBriefArtifacts(vault, input) {
3933
- const metaPath = join23(vault, "meta", "latest-session-brief.md");
3934
- const cacheMdPath = join23(vault, ".skillwiki", "session-brief.md");
3935
- const cacheJsonPath = join23(vault, ".skillwiki", "session-brief.json");
3936
- await mkdir9(join23(vault, "meta"), { recursive: true });
3937
- await mkdir9(join23(vault, ".skillwiki"), { recursive: true });
4290
+ const metaPath = join24(vault, "meta", "latest-session-brief.md");
4291
+ const cacheMdPath = join24(vault, ".skillwiki", "session-brief.md");
4292
+ const cacheJsonPath = join24(vault, ".skillwiki", "session-brief.json");
4293
+ await mkdir8(join24(vault, "meta"), { recursive: true });
4294
+ await mkdir8(join24(vault, ".skillwiki"), { recursive: true });
3938
4295
  const committed = renderCommittedBrief(input);
3939
4296
  const previousComparable = comparableBrief(await readIfExists(metaPath));
3940
4297
  const nextComparable = comparableBrief(committed);
@@ -4004,7 +4361,7 @@ function renderCommittedBrief(input) {
4004
4361
  ].filter((line) => line !== "").join("\n");
4005
4362
  }
4006
4363
  async function rebuildRootIndexProjection(vault) {
4007
- const before = await readIfExists(join23(vault, "index.md"));
4364
+ const before = await readIfExists(join24(vault, "index.md"));
4008
4365
  if (!before) return false;
4009
4366
  const projection = await renderRootIndex({ vault, currentText: before });
4010
4367
  if (!projection.ok) return false;
@@ -4014,7 +4371,7 @@ async function rebuildRootIndexProjection(vault) {
4014
4371
  }
4015
4372
  async function readIfExists(path) {
4016
4373
  try {
4017
- return await readFile11(path, "utf8");
4374
+ return await readFile10(path, "utf8");
4018
4375
  } catch {
4019
4376
  return "";
4020
4377
  }
@@ -4056,14 +4413,14 @@ function dateFromPath(path) {
4056
4413
  }
4057
4414
 
4058
4415
  // src/commands/ingest.ts
4059
- import { readFile as readFile13, open as open2, unlink as unlink4, mkdir as mkdir10 } from "fs/promises";
4060
- import { join as join25 } from "path";
4416
+ import { readFile as readFile12, open, unlink as unlink4, mkdir as mkdir9 } from "fs/promises";
4417
+ import { join as join26 } from "path";
4061
4418
  import { createHash as createHash4 } from "crypto";
4062
4419
 
4063
4420
  // src/commands/page-publish.ts
4064
4421
  import { realpathSync } from "fs";
4065
- import { readFile as readFile12 } from "fs/promises";
4066
- import { join as join24, resolve as resolve3 } from "path";
4422
+ import { readFile as readFile11 } from "fs/promises";
4423
+ import { join as join25, resolve as resolve3 } from "path";
4067
4424
  var DEFAULT_DEPS = {
4068
4425
  afterStage: async () => void 0,
4069
4426
  preflight: (input) => runManagedWritePreflight(input)
@@ -4133,7 +4490,7 @@ function preparePagePublicationFromContent(input) {
4133
4490
  async function preparePagePublication(input) {
4134
4491
  let content;
4135
4492
  try {
4136
- content = await readFile12(input.draftPath, "utf8");
4493
+ content = await readFile11(input.draftPath, "utf8");
4137
4494
  } catch (error) {
4138
4495
  return err("FILE_NOT_FOUND", { path: input.draftPath, message: String(error) });
4139
4496
  }
@@ -4204,10 +4561,10 @@ async function runLockedPrimaryStages(input, vault, deps) {
4204
4561
  ExitCode.VAULT_PATH_INVALID
4205
4562
  );
4206
4563
  }
4207
- const schemaPath = join24(vault, "SCHEMA.md");
4564
+ const schemaPath = join25(vault, "SCHEMA.md");
4208
4565
  let schemaText;
4209
4566
  try {
4210
- schemaText = await readFile12(schemaPath, "utf8");
4567
+ schemaText = await readFile11(schemaPath, "utf8");
4211
4568
  } catch (error) {
4212
4569
  return lockedFailure("schema", state, err("WRITE_FAILED", { message: String(error) }));
4213
4570
  }
@@ -4237,8 +4594,8 @@ async function runLockedPrimaryStages(input, vault, deps) {
4237
4594
  let visibleSchema;
4238
4595
  try {
4239
4596
  [visible, visibleSchema] = await Promise.all([
4240
- readFile12(input.targetPath, "utf8"),
4241
- readFile12(schemaPath, "utf8")
4597
+ readFile11(input.targetPath, "utf8"),
4598
+ readFile11(schemaPath, "utf8")
4242
4599
  ]);
4243
4600
  } catch (error) {
4244
4601
  return lockedFailure("verify", state, err("WRITE_FAILED", { message: String(error) }));
@@ -4323,17 +4680,17 @@ function renderPublicationLog(input, added) {
4323
4680
  }
4324
4681
  async function readPageChanged(targetPath, content) {
4325
4682
  try {
4326
- return ok(await readFile12(targetPath, "utf8") !== content);
4683
+ return ok(await readFile11(targetPath, "utf8") !== content);
4327
4684
  } catch (error) {
4328
4685
  if (error.code === "ENOENT") return ok(true);
4329
4686
  return err("WRITE_FAILED", { path: targetPath, message: String(error) });
4330
4687
  }
4331
4688
  }
4332
4689
  async function previewPreparedPagePublication(input, vault) {
4333
- const schemaPath = join24(vault, "SCHEMA.md");
4690
+ const schemaPath = join25(vault, "SCHEMA.md");
4334
4691
  let schemaText;
4335
4692
  try {
4336
- schemaText = await readFile12(schemaPath, "utf8");
4693
+ schemaText = await readFile11(schemaPath, "utf8");
4337
4694
  } catch (error) {
4338
4695
  const result = err("FILE_NOT_FOUND", { path: schemaPath, message: String(error) });
4339
4696
  return { exitCode: ExitCode.FILE_NOT_FOUND, result };
@@ -4345,10 +4702,10 @@ async function previewPreparedPagePublication(input, vault) {
4345
4702
  if (!reconciled.ok) return { exitCode: errorExitCode(reconciled.error), result: reconciled };
4346
4703
  const pageChanged = await readPageChanged(input.targetPath, input.page.content);
4347
4704
  if (!pageChanged.ok) return { exitCode: errorExitCode(pageChanged.error), result: pageChanged };
4348
- const indexPath = join24(vault, "index.md");
4705
+ const indexPath = join25(vault, "index.md");
4349
4706
  let indexText;
4350
4707
  try {
4351
- indexText = await readFile12(indexPath, "utf8");
4708
+ indexText = await readFile11(indexPath, "utf8");
4352
4709
  } catch (error) {
4353
4710
  const result = err("FILE_NOT_FOUND", { path: indexPath, message: String(error) });
4354
4711
  return { exitCode: ExitCode.FILE_NOT_FOUND, result };
@@ -4359,10 +4716,10 @@ async function previewPreparedPagePublication(input, vault) {
4359
4716
  type: input.page.type
4360
4717
  });
4361
4718
  if (!index.ok) return { exitCode: errorExitCode(index.error), result: index };
4362
- const logPath = join24(vault, "log.md");
4719
+ const logPath = join25(vault, "log.md");
4363
4720
  let logText;
4364
4721
  try {
4365
- logText = await readFile12(logPath, "utf8");
4722
+ logText = await readFile11(logPath, "utf8");
4366
4723
  } catch (error) {
4367
4724
  const result = err("FILE_NOT_FOUND", { path: logPath, message: String(error) });
4368
4725
  return { exitCode: ExitCode.FILE_NOT_FOUND, result };
@@ -4624,7 +4981,7 @@ function buildTypedContent(title, ingested, type, tags, rawRelPath, provenance)
4624
4981
  }
4625
4982
  async function resolveRawCapture(input) {
4626
4983
  try {
4627
- const existing = await readFile13(input.path, "utf8");
4984
+ const existing = await readFile12(input.path, "utf8");
4628
4985
  const frontmatter = extractFrontmatter(existing);
4629
4986
  if (!frontmatter.ok) {
4630
4987
  return err("INGEST_VALIDATION_FAILED", {
@@ -4682,7 +5039,7 @@ async function acquireRawCaptureLock(path) {
4682
5039
  const lockPath = `${path}.ingest.lock`;
4683
5040
  for (let attempt = 0; attempt < 200; attempt++) {
4684
5041
  try {
4685
- const handle = await open2(lockPath, "wx");
5042
+ const handle = await open(lockPath, "wx");
4686
5043
  await handle.close();
4687
5044
  return ok(lockPath);
4688
5045
  } catch (error) {
@@ -4763,7 +5120,7 @@ async function runIngest(input) {
4763
5120
  sourceContent = fetchResult.data.body;
4764
5121
  } else {
4765
5122
  try {
4766
- sourceContent = await readFile13(input.source, "utf8");
5123
+ sourceContent = await readFile12(input.source, "utf8");
4767
5124
  } catch {
4768
5125
  return {
4769
5126
  exitCode: ExitCode.FILE_NOT_FOUND,
@@ -4788,7 +5145,7 @@ async function runIngest(input) {
4788
5145
  const rawRelPath = `raw/articles/${slug}.md`;
4789
5146
  const typedDir = TYPE_DIR[input.type] ?? `${input.type}s`;
4790
5147
  const typedRelPath = `${typedDir}/${slug}.md`;
4791
- const rawAbsPath = join25(input.vault, rawRelPath);
5148
+ const rawAbsPath = join26(input.vault, rawRelPath);
4792
5149
  const identity = assessSourceIdentity({
4793
5150
  rawPath: rawRelPath,
4794
5151
  sourceUrl: sourceUrl ?? void 0,
@@ -4832,11 +5189,11 @@ async function runIngest(input) {
4832
5189
  );
4833
5190
  if (!input.dryRun) {
4834
5191
  try {
4835
- await mkdir10(join25(input.vault, typedDir), { recursive: true });
5192
+ await mkdir9(join26(input.vault, typedDir), { recursive: true });
4836
5193
  } catch (error) {
4837
5194
  return {
4838
5195
  exitCode: ExitCode.WRITE_FAILED,
4839
- result: err("WRITE_FAILED", { path: join25(input.vault, typedDir), message: String(error) })
5196
+ result: err("WRITE_FAILED", { path: join26(input.vault, typedDir), message: String(error) })
4840
5197
  };
4841
5198
  }
4842
5199
  }
@@ -4883,11 +5240,11 @@ async function runIngest(input) {
4883
5240
  };
4884
5241
  }
4885
5242
  try {
4886
- await mkdir10(join25(input.vault, "raw", "articles"), { recursive: true });
5243
+ await mkdir9(join26(input.vault, "raw", "articles"), { recursive: true });
4887
5244
  } catch (error) {
4888
5245
  return {
4889
5246
  exitCode: ExitCode.WRITE_FAILED,
4890
- result: err("WRITE_FAILED", { path: join25(input.vault, "raw", "articles"), message: String(error) })
5247
+ result: err("WRITE_FAILED", { path: join26(input.vault, "raw", "articles"), message: String(error) })
4891
5248
  };
4892
5249
  }
4893
5250
  const rawWrite = await writeResolvedRaw({
@@ -5115,8 +5472,8 @@ ${body}`;
5115
5472
  }
5116
5473
 
5117
5474
  // src/commands/tag-reconcile.ts
5118
- import { readFile as readFile14 } from "fs/promises";
5119
- import { join as join26, posix } from "path";
5475
+ import { readFile as readFile13 } from "fs/promises";
5476
+ import { join as join27, posix } from "path";
5120
5477
  var TYPED_TARGET_RE = /^(entities|concepts|comparisons|queries|meta)\/[a-z0-9][a-z0-9./_-]*\.md$/;
5121
5478
  function errorExitCode2(error) {
5122
5479
  switch (error) {
@@ -5159,7 +5516,7 @@ function asTagArray(frontmatter, path) {
5159
5516
  async function readTagsFromFile(path) {
5160
5517
  let text;
5161
5518
  try {
5162
- text = await readFile14(path, "utf8");
5519
+ text = await readFile13(path, "utf8");
5163
5520
  } catch (error) {
5164
5521
  if (error.code === "ENOENT") {
5165
5522
  return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path }) };
@@ -5180,7 +5537,7 @@ async function resolveRequestedTags(input, page) {
5180
5537
  result: err("INVALID_FRONTMATTER", { message: "explicit tags must be an array of strings" })
5181
5538
  };
5182
5539
  }
5183
- const source = input.from ?? (explicit.length === 0 ? join26(input.vault, page) : void 0);
5540
+ const source = input.from ?? (explicit.length === 0 ? join27(input.vault, page) : void 0);
5184
5541
  if (!source) return { exitCode: ExitCode.OK, result: ok({ tags: [...new Set(explicit)].sort() }) };
5185
5542
  const sourced = await readTagsFromFile(source);
5186
5543
  if (!sourced.result.ok) return { exitCode: sourced.exitCode, result: sourced.result };
@@ -5191,7 +5548,7 @@ async function resolveRequestedTags(input, page) {
5191
5548
  }
5192
5549
  async function readSchema(schemaPath) {
5193
5550
  try {
5194
- return { exitCode: ExitCode.OK, result: ok(await readFile14(schemaPath, "utf8")) };
5551
+ return { exitCode: ExitCode.OK, result: ok(await readFile13(schemaPath, "utf8")) };
5195
5552
  } catch (error) {
5196
5553
  if (error.code === "ENOENT") {
5197
5554
  return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: schemaPath }) };
@@ -5219,7 +5576,7 @@ function previewResult(page, tags, reconciled, dryRun, filesChanged) {
5219
5576
  };
5220
5577
  }
5221
5578
  async function reconcileTagsWhileLocked(input, page, tags, comment) {
5222
- const schemaPath = join26(input.vault, "SCHEMA.md");
5579
+ const schemaPath = join27(input.vault, "SCHEMA.md");
5223
5580
  const current = await readSchema(schemaPath);
5224
5581
  if (!current.result.ok) return { exitCode: current.exitCode, result: current.result };
5225
5582
  const next = reconcileTaxonomyDocument(current.result.data, { tags, comment });
@@ -5230,7 +5587,7 @@ async function reconcileTagsWhileLocked(input, page, tags, comment) {
5230
5587
  }
5231
5588
  let verifiedText;
5232
5589
  try {
5233
- verifiedText = await readFile14(schemaPath, "utf8");
5590
+ verifiedText = await readFile13(schemaPath, "utf8");
5234
5591
  } catch (error) {
5235
5592
  return {
5236
5593
  exitCode: ExitCode.WRITE_FAILED,
@@ -5256,7 +5613,7 @@ async function runTagReconcile(input) {
5256
5613
  const comment = taxonomyCommentForPage(page.data, date, input.reason);
5257
5614
  if (!comment.ok) return { exitCode: ExitCode.SCHEME_REJECTED, result: comment };
5258
5615
  if (!input.write) {
5259
- const schema = await readSchema(join26(input.vault, "SCHEMA.md"));
5616
+ const schema = await readSchema(join27(input.vault, "SCHEMA.md"));
5260
5617
  if (!schema.result.ok) return { exitCode: schema.exitCode, result: schema.result };
5261
5618
  const preview = reconcileTaxonomyDocument(schema.result.data, { tags, comment: comment.data });
5262
5619
  return previewResult(page.data, tags, preview, true, []);
@@ -5308,8 +5665,8 @@ async function runTagReconcile(input) {
5308
5665
  }
5309
5666
 
5310
5667
  // src/commands/sync.ts
5311
- import { existsSync as existsSync6 } from "fs";
5312
- import { join as join27 } from "path";
5668
+ import { existsSync as existsSync8 } from "fs";
5669
+ import { join as join28 } from "path";
5313
5670
  import { execFileSync as execFileSync3 } from "child_process";
5314
5671
 
5315
5672
  // src/utils/vault-git-pathspec.ts
@@ -5363,7 +5720,7 @@ function refHasPath(vault, ref, path) {
5363
5720
  function runSyncStatus(input) {
5364
5721
  const vault = input.vault;
5365
5722
  const includeStashes = input.includeStashes ?? false;
5366
- if (!existsSync6(join27(vault, ".git"))) {
5723
+ if (!existsSync8(join28(vault, ".git"))) {
5367
5724
  return {
5368
5725
  exitCode: ExitCode.VAULT_PATH_INVALID,
5369
5726
  result: ok({
@@ -5470,7 +5827,7 @@ function runSyncStatus(input) {
5470
5827
  }
5471
5828
  async function runSyncPush(input) {
5472
5829
  const vault = input.vault;
5473
- if (!existsSync6(join27(vault, ".git"))) {
5830
+ if (!existsSync8(join28(vault, ".git"))) {
5474
5831
  return {
5475
5832
  exitCode: ExitCode.VAULT_PATH_INVALID,
5476
5833
  result: err("NOT_A_GIT_REPO", { path: vault })
@@ -5630,7 +5987,7 @@ function enableGitLongPathsOnWindows(vault) {
5630
5987
  }
5631
5988
  async function runSyncPull(input) {
5632
5989
  const vault = input.vault;
5633
- if (!existsSync6(join27(vault, ".git"))) {
5990
+ if (!existsSync8(join28(vault, ".git"))) {
5634
5991
  return {
5635
5992
  exitCode: ExitCode.VAULT_PATH_INVALID,
5636
5993
  result: err("NOT_A_GIT_REPO", { path: vault })
@@ -5745,7 +6102,7 @@ function runSyncPeers(input) {
5745
6102
  }
5746
6103
  function runSyncLock(input) {
5747
6104
  const vault = input.vault;
5748
- if (!existsSync6(vault)) {
6105
+ if (!existsSync8(vault)) {
5749
6106
  return {
5750
6107
  exitCode: ExitCode.VAULT_PATH_INVALID,
5751
6108
  result: err("VAULT_PATH_INVALID", { path: vault })
@@ -5780,7 +6137,7 @@ function runSyncLock(input) {
5780
6137
  }
5781
6138
  function runSyncUnlock(input) {
5782
6139
  const vault = input.vault;
5783
- if (!existsSync6(vault)) {
6140
+ if (!existsSync8(vault)) {
5784
6141
  return {
5785
6142
  exitCode: ExitCode.VAULT_PATH_INVALID,
5786
6143
  result: err("VAULT_PATH_INVALID", { path: vault })
@@ -5857,8 +6214,8 @@ function runSyncJournalClearStale(input) {
5857
6214
  }
5858
6215
 
5859
6216
  // src/commands/backup.ts
5860
- import { statSync as statSync2, readdirSync, readFileSync as readFileSync11, mkdirSync as mkdirSync2, writeFileSync as writeFileSync4 } from "fs";
5861
- import { join as join28, relative as relative2, dirname as dirname6 } from "path";
6217
+ import { statSync as statSync2, readdirSync as readdirSync2, readFileSync as readFileSync13, mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
6218
+ import { join as join29, relative as relative2, dirname as dirname6 } from "path";
5862
6219
  import { PutObjectCommand, HeadObjectCommand, ListObjectsV2Command, GetObjectCommand, DeleteObjectsCommand } from "@aws-sdk/client-s3";
5863
6220
 
5864
6221
  // src/utils/s3-client.ts
@@ -5880,9 +6237,9 @@ function createS3Client(config) {
5880
6237
  // src/commands/backup.ts
5881
6238
  var SKIP_DIRS = /* @__PURE__ */ new Set([".git", ".obsidian", "_archive", "node_modules", ".skillwiki"]);
5882
6239
  function* walkMarkdown(dir, base) {
5883
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
6240
+ for (const entry of readdirSync2(dir, { withFileTypes: true })) {
5884
6241
  if (SKIP_DIRS.has(entry.name)) continue;
5885
- const full = join28(dir, entry.name);
6242
+ const full = join29(dir, entry.name);
5886
6243
  if (entry.isDirectory()) {
5887
6244
  yield* walkMarkdown(full, base);
5888
6245
  } else if (entry.name.endsWith(".md")) {
@@ -5905,7 +6262,7 @@ async function runBackupSync(input) {
5905
6262
  let failed = 0;
5906
6263
  const files = [...walkMarkdown(input.vault, input.vault)];
5907
6264
  for (const relPath of files) {
5908
- const absPath = join28(input.vault, relPath);
6265
+ const absPath = join29(input.vault, relPath);
5909
6266
  const localStat = statSync2(absPath);
5910
6267
  let needsUpload = true;
5911
6268
  try {
@@ -5924,7 +6281,7 @@ async function runBackupSync(input) {
5924
6281
  continue;
5925
6282
  }
5926
6283
  try {
5927
- const body = readFileSync11(absPath);
6284
+ const body = readFileSync13(absPath);
5928
6285
  await client.send(new PutObjectCommand({ Bucket: input.bucket, Key: relPath, Body: body }));
5929
6286
  uploaded++;
5930
6287
  } catch {
@@ -5981,7 +6338,7 @@ async function runBackupRestore(input) {
5981
6338
  const objects = list.Contents ?? [];
5982
6339
  for (const obj of objects) {
5983
6340
  if (!obj.Key) continue;
5984
- const localPath = join28(target, obj.Key);
6341
+ const localPath = join29(target, obj.Key);
5985
6342
  try {
5986
6343
  const localStat = statSync2(localPath);
5987
6344
  if (obj.LastModified && localStat.mtime > obj.LastModified) {
@@ -5994,8 +6351,8 @@ async function runBackupRestore(input) {
5994
6351
  const resp = await client.send(new GetObjectCommand({ Bucket: input.bucket, Key: obj.Key }));
5995
6352
  const body = await resp.Body?.transformToByteArray();
5996
6353
  if (body) {
5997
- mkdirSync2(dirname6(localPath), { recursive: true });
5998
- writeFileSync4(localPath, Buffer.from(body));
6354
+ mkdirSync3(dirname6(localPath), { recursive: true });
6355
+ writeFileSync5(localPath, Buffer.from(body));
5999
6356
  downloaded++;
6000
6357
  }
6001
6358
  } catch {
@@ -6027,11 +6384,11 @@ async function runBackupRestore(input) {
6027
6384
  }
6028
6385
 
6029
6386
  // src/commands/status.ts
6030
- import { existsSync as existsSync7, statSync as statSync3 } from "fs";
6031
- import { readFile as readFile15 } from "fs/promises";
6032
- import { join as join29 } from "path";
6387
+ import { existsSync as existsSync9, statSync as statSync3 } from "fs";
6388
+ import { readFile as readFile14 } from "fs/promises";
6389
+ import { join as join30 } from "path";
6033
6390
  async function runStatus(input) {
6034
- if (!existsSync7(input.vault)) {
6391
+ if (!existsSync9(input.vault)) {
6035
6392
  return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { vault: input.vault }) };
6036
6393
  }
6037
6394
  const scan = await scanVault(input.vault);
@@ -6056,7 +6413,7 @@ async function runStatus(input) {
6056
6413
  const compound = scan.data.compound.length;
6057
6414
  let schemaVersion = "v1";
6058
6415
  try {
6059
- const schemaContent = await readFile15(join29(input.vault, "SCHEMA.md"), "utf8");
6416
+ const schemaContent = await readFile14(join30(input.vault, "SCHEMA.md"), "utf8");
6060
6417
  const versionMatch = schemaContent.match(/version:\s*["']?([^"'\s\n]+)/i);
6061
6418
  if (versionMatch) schemaVersion = versionMatch[1];
6062
6419
  } catch {
@@ -6116,8 +6473,8 @@ async function runStatus(input) {
6116
6473
  }
6117
6474
 
6118
6475
  // src/commands/seed.ts
6119
- import { mkdir as mkdir11, writeFile as writeFile9, stat as stat4 } from "fs/promises";
6120
- import { join as join30 } from "path";
6476
+ import { mkdir as mkdir10, writeFile as writeFile9, stat as stat4 } from "fs/promises";
6477
+ import { join as join31 } from "path";
6121
6478
  var TODAY = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
6122
6479
  var EXAMPLE_PAGES = {
6123
6480
  "entities/example-project.md": `---
@@ -6186,29 +6543,29 @@ Real sources are immutable after ingestion \u2014 never edit them.
6186
6543
  `;
6187
6544
  async function runSeed(input) {
6188
6545
  try {
6189
- await stat4(join30(input.vault, "SCHEMA.md"));
6546
+ await stat4(join31(input.vault, "SCHEMA.md"));
6190
6547
  } catch {
6191
6548
  return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { root: input.vault, reason: "SCHEMA.md missing \u2014 run `skillwiki init` first" }) };
6192
6549
  }
6193
6550
  const created = [];
6194
6551
  const skipped = [];
6195
6552
  for (const [relPath, content] of Object.entries(EXAMPLE_PAGES)) {
6196
- const absPath = join30(input.vault, relPath);
6553
+ const absPath = join31(input.vault, relPath);
6197
6554
  try {
6198
6555
  await stat4(absPath);
6199
6556
  skipped.push(relPath);
6200
6557
  } catch {
6201
- await mkdir11(join30(absPath, ".."), { recursive: true });
6558
+ await mkdir10(join31(absPath, ".."), { recursive: true });
6202
6559
  await writeFile9(absPath, content, "utf8");
6203
6560
  created.push(relPath);
6204
6561
  }
6205
6562
  }
6206
- const rawPath = join30(input.vault, "raw", "articles", "example-source.md");
6563
+ const rawPath = join31(input.vault, "raw", "articles", "example-source.md");
6207
6564
  try {
6208
6565
  await stat4(rawPath);
6209
6566
  skipped.push("raw/articles/example-source.md");
6210
6567
  } catch {
6211
- await mkdir11(join30(rawPath, ".."), { recursive: true });
6568
+ await mkdir10(join31(rawPath, ".."), { recursive: true });
6212
6569
  await writeFile9(rawPath, EXAMPLE_RAW, "utf8");
6213
6570
  created.push("raw/articles/example-source.md");
6214
6571
  }
@@ -6231,9 +6588,9 @@ async function runSeed(input) {
6231
6588
  }
6232
6589
 
6233
6590
  // src/commands/canvas.ts
6234
- import { readFile as readFile16, writeFile as writeFile10 } from "fs/promises";
6235
- import { existsSync as existsSync8 } from "fs";
6236
- import { join as join31 } from "path";
6591
+ import { readFile as readFile15, writeFile as writeFile10 } from "fs/promises";
6592
+ import { existsSync as existsSync10 } from "fs";
6593
+ import { join as join32 } from "path";
6237
6594
  var NODE_WIDTH = 240;
6238
6595
  var NODE_HEIGHT = 60;
6239
6596
  var COLUMN_SPACING = 400;
@@ -6311,8 +6668,8 @@ function buildCanvasEdges(adjacency) {
6311
6668
  return edges;
6312
6669
  }
6313
6670
  async function runCanvasGenerate(input) {
6314
- const graphPath = input.graphPath ?? join31(input.vault, ".skillwiki", "graph.json");
6315
- if (!existsSync8(graphPath)) {
6671
+ const graphPath = input.graphPath ?? join32(input.vault, ".skillwiki", "graph.json");
6672
+ if (!existsSync10(graphPath)) {
6316
6673
  return {
6317
6674
  exitCode: ExitCode.FILE_NOT_FOUND,
6318
6675
  result: err("FILE_NOT_FOUND", {
@@ -6323,7 +6680,7 @@ async function runCanvasGenerate(input) {
6323
6680
  }
6324
6681
  let raw;
6325
6682
  try {
6326
- raw = await readFile16(graphPath, "utf8");
6683
+ raw = await readFile15(graphPath, "utf8");
6327
6684
  } catch (e) {
6328
6685
  return {
6329
6686
  exitCode: ExitCode.FILE_NOT_FOUND,
@@ -6349,7 +6706,7 @@ async function runCanvasGenerate(input) {
6349
6706
  const nodes = buildCanvasNodes(paths);
6350
6707
  const edges = buildCanvasEdges(graph.adjacency);
6351
6708
  const canvas = { nodes, edges };
6352
- const outPath = join31(input.vault, "vault-graph.canvas");
6709
+ const outPath = join32(input.vault, "vault-graph.canvas");
6353
6710
  try {
6354
6711
  await writeFile10(outPath, JSON.stringify(canvas, null, 2));
6355
6712
  } catch (e) {
@@ -6371,10 +6728,10 @@ written: ${outPath}`
6371
6728
  }
6372
6729
 
6373
6730
  // src/commands/fleet-health.ts
6374
- import { existsSync as existsSync9, readFileSync as readFileSync12 } from "fs";
6731
+ import { existsSync as existsSync11, readFileSync as readFileSync14 } from "fs";
6375
6732
  import { execSync as nodeExecSync } from "child_process";
6376
6733
  import { hostname as nodeHostname, platform as nodePlatform } from "os";
6377
- import { join as join32 } from "path";
6734
+ import { join as join33 } from "path";
6378
6735
  var SSH_TIMEOUT_MS = 15e3;
6379
6736
  var TIMER_UNIT = "agent-memory-trends.timer";
6380
6737
  var SERVICE_UNIT = "agent-memory-trends.service";
@@ -6471,9 +6828,9 @@ function applyServiceFailedOverlay(run, serviceFailed) {
6471
6828
  function probeLocal(vaultPath, deps) {
6472
6829
  const latestPath = satelliteLatestRunPath(vaultPath);
6473
6830
  let parsed = null;
6474
- if (existsSync9(latestPath)) {
6831
+ if (existsSync11(latestPath)) {
6475
6832
  try {
6476
- const wire = readSatelliteLatestRunFromText(readFileSync12(latestPath, "utf8"));
6833
+ const wire = readSatelliteLatestRunFromText(readFileSync14(latestPath, "utf8"));
6477
6834
  if (wire) {
6478
6835
  parsed = {
6479
6836
  status: wire.status,
@@ -6604,7 +6961,7 @@ async function runFleetHealth(input) {
6604
6961
  const home = input.home ?? env.HOME ?? "";
6605
6962
  const osHostname = input.osHostname ?? env.HOSTNAME ?? nodeHostname();
6606
6963
  const vault = input.vault ?? env.WIKI_PATH;
6607
- const file = input.file ?? (vault ? join32(vault, FLEET_REL_PATH) : void 0);
6964
+ const file = input.file ?? (vault ? join33(vault, FLEET_REL_PATH) : void 0);
6608
6965
  if (!file) {
6609
6966
  return {
6610
6967
  exitCode: ExitCode.NO_VAULT_CONFIGURED,
@@ -6688,15 +7045,15 @@ async function runFleetHealth(input) {
6688
7045
  }
6689
7046
 
6690
7047
  // src/utils/auto-commit.ts
6691
- import { existsSync as existsSync10 } from "fs";
6692
- import { join as join33 } from "path";
7048
+ import { existsSync as existsSync12 } from "fs";
7049
+ import { join as join34 } from "path";
6693
7050
  async function postCommit(vault, exitCode) {
6694
7051
  if (exitCode !== 0) return;
6695
7052
  const home = process.env.HOME ?? "";
6696
7053
  const dotenv = await parseDotenvFile(configPath(home));
6697
7054
  const autoCommit = process.env.AUTO_COMMIT ?? dotenv["AUTO_COMMIT"];
6698
7055
  if (autoCommit === "false") return;
6699
- if (!existsSync10(join33(vault, ".git"))) return;
7056
+ if (!existsSync12(join34(vault, ".git"))) return;
6700
7057
  const lastOps = readLastOp(vault);
6701
7058
  if (lastOps.length === 0) return;
6702
7059
  const porcelain = git(vault, ["status", "--porcelain", "--", ...VAULT_COMMIT_PATHSPEC]);
@@ -6758,7 +7115,7 @@ async function emitManagedVaultWrite(vault, command, mutate, opts) {
6758
7115
  if (guard.blocked) {
6759
7116
  return emit({ exitCode: guard.exitCode, result: guard.result }, void 0, { postCommit: false });
6760
7117
  }
6761
- const { runManagedWriteTransaction: runManagedWriteTransaction2 } = await import("./managed-write-preflight-57DQZI2N.js");
7118
+ const { runManagedWriteTransaction: runManagedWriteTransaction2 } = await import("./managed-write-preflight-7TWSEZJZ.js");
6762
7119
  const run = await runManagedWriteTransaction2({
6763
7120
  vault,
6764
7121
  command,
@@ -6783,7 +7140,7 @@ program.command("validate <file>").description("validate vault page frontmatter
6783
7140
  emit(await runValidate({ file, apply: !!opts.apply, vault }), vault);
6784
7141
  });
6785
7142
  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) => {
6786
- const out = opts.out ?? join34(vault, ".skillwiki", "graph.json");
7143
+ const out = opts.out ?? join35(vault, ".skillwiki", "graph.json");
6787
7144
  return emitGuardedVaultWrite(vault, "graph build", () => runGraphBuild({ vault, out }));
6788
7145
  });
6789
7146
  var canvasCmd = program.command("canvas").description("manage Obsidian canvas files");
@@ -6814,18 +7171,25 @@ program.command("install").description("install skillwiki SKILL.md files into ~/
6814
7171
  const skillsRoot = opts.skillsRoot ?? new URL("../skills/", import.meta.url).pathname;
6815
7172
  emit(await runInstall({ skillsRoot, target: opts.target, dryRun: !!opts.dryRun, symlink: !!opts.symlink, home: process.env.HOME ?? "", force: !!opts.force }));
6816
7173
  });
6817
- program.command("path").description("show the resolved vault path").option("--vault <dir>", "explicit vault override (runtime)").option("--target <dir>", "explicit target override (init-time)").option("--wiki <name>", "wiki profile name").option("--init-time", "use init-time chain instead of runtime", false).option("--explain", "include resolution chain in output", false).action(async (opts) => {
7174
+ program.command("path").description("show the resolved vault path").option("--vault <dir>", "explicit vault override (runtime)").option("--target <dir>", "explicit target override (init-time)").option("--wiki <name>", "wiki profile name").option("--init-time", "use init-time chain instead of runtime", false).option("--explain", "include resolution chain in output", false).option("--plain", "print only the vault path (no JSON wrapper)", false).action(async (opts) => {
6818
7175
  const initTime = !!opts.initTime;
6819
7176
  const flag = initTime ? opts.target : opts.vault;
6820
- emit(await runPath({
7177
+ const r = await runPath({
6821
7178
  flag,
6822
7179
  envValue: process.env.WIKI_PATH,
6823
7180
  home: process.env.HOME ?? "",
6824
7181
  initTime,
6825
7182
  wiki: opts.wiki,
6826
7183
  cwd: process.cwd(),
6827
- explain: !!opts.explain
6828
- }));
7184
+ explain: !!opts.explain,
7185
+ plain: !!opts.plain
7186
+ });
7187
+ if (opts.plain && r.result.ok) {
7188
+ process.stdout.write(`${r.result.data.path}
7189
+ `);
7190
+ process.exit(r.exitCode);
7191
+ }
7192
+ emit(r);
6829
7193
  });
6830
7194
  program.command("lang").description("get or set the vault language").option("--lang <code>", "explicit language override").option("--explain", "include resolution chain in output", false).action(async (opts) => {
6831
7195
  emit(await runLang({
@@ -6971,15 +7335,44 @@ program.command("log-rotate [vault]").description("rotate or trim the vault log
6971
7335
  );
6972
7336
  else emit(await runLogRotate({ vault: v.vault, threshold: opts.threshold, apply: false }), v.vault);
6973
7337
  });
6974
- program.command("log-append [vault]").description("append a single entry to the vault log under a short advisory lock").requiredOption("--content <text>", "log entry text to append").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
7338
+ program.command("log-append [vault]").description("append a single entry to the vault log under a short advisory lock").requiredOption("--content <text>", "log entry text to append").option("--operation-id <id>", "stable SHA-256 operation id for retry-safe append + immutable event").option("--write-event", "also write meta/log-events (requires --operation-id)", false).option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
6975
7339
  const v = await resolveVaultArg(vault, opts.wiki);
6976
7340
  if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
6977
7341
  else return emitManagedVaultWrite(
6978
7342
  v.vault,
6979
7343
  "log-append",
6980
- () => runLogAppend({ vault: v.vault, content: opts.content })
7344
+ () => runLogAppend({
7345
+ vault: v.vault,
7346
+ content: opts.content,
7347
+ operationId: opts.operationId,
7348
+ writeEvent: !!opts.writeEvent || !!opts.operationId
7349
+ })
7350
+ );
7351
+ });
7352
+ program.command("work-complete [vault]").description("atomically complete a work item (validate, evidence, log, projection, commit)").requiredOption("--work-item <path>", "work item directory relative to vault (e.g. projects/x/work/yyyy-mm-dd-slug)").option("--operation-id <id>", "stable SHA-256 operation id (derived when omitted)").option("--no-commit", "skip git commit").option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
7353
+ const v = await resolveVaultArg(vault, opts.wiki);
7354
+ if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
7355
+ else return emitManagedVaultWrite(
7356
+ v.vault,
7357
+ "work-complete",
7358
+ () => runWorkComplete({
7359
+ vault: v.vault,
7360
+ workItem: opts.workItem,
7361
+ operationId: opts.operationId,
7362
+ noCommit: opts.commit === false
7363
+ }),
7364
+ { postCommit: false }
6981
7365
  );
6982
7366
  });
7367
+ program.command("work-validate [vault]").description("cross-check a work item for status, evidence, PR metadata, unchecked steps, and conflict markers").requiredOption("--work-item <path>", "work item directory relative to vault").option("--require-complete", "require completed status and evidence", false).option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
7368
+ const v = await resolveVaultArg(vault, opts.wiki);
7369
+ if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
7370
+ else emit(await runWorkValidate({
7371
+ vault: v.vault,
7372
+ workItem: opts.workItem,
7373
+ requireComplete: !!opts.requireComplete
7374
+ }), v.vault, { postCommit: false });
7375
+ });
6983
7376
  var logCmd = program.command("log").description("immutable log events and projections");
6984
7377
  logCmd.command("materialize [vault]").description("preview or write root log.md from immutable events").option("--write", "write projected log.md", false).option("--wiki <name>", "wiki profile name").action(async (vault, opts) => {
6985
7378
  const v = await resolveVaultArg(vault, opts.wiki);