skillwiki 0.10.7 → 0.10.9

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-45SMQAWC.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,493 @@ 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 (error) {
2007
+ return err("WRITE_FAILED", { path: filePath, stage: "read", message: String(error) });
2008
+ }
2009
+ const split = splitFrontmatter(text);
2010
+ if (!split.ok || !split.data.rawFrontmatter) {
2011
+ if (extraBody) {
2012
+ return atomicWriteText(filePath, extraBody(text));
2013
+ }
2014
+ return ok({ changed: false, existed: true });
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
+ return atomicWriteText(filePath, `---
2030
+ ${fm}
2031
+ ---
2032
+ ${body}`);
2033
+ }
2034
+ async function setStatusCompleted(filePath) {
2035
+ return 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
+ return atomicWriteText(path, body);
2054
+ }
2055
+ async function markPlanComplete(workDir) {
2056
+ const planPath = join15(workDir, "plan.md");
2057
+ if (!existsSync4(planPath)) return ok(null);
2058
+ return patchFrontmatterStatus(planPath, (body) => body.replace(/- \[ \]/g, "- [x]"));
2059
+ }
2060
+ function writeFailure(stage, result) {
2061
+ return {
2062
+ exitCode: ExitCode.WRITE_FAILED,
2063
+ result: result.ok ? err("WRITE_FAILED", { stage, message: "unexpected ok" }) : result
2064
+ };
2065
+ }
2066
+ async function runWorkComplete(input) {
2067
+ const workDirResult = resolveWorkDir(input.vault, input.workItem);
2068
+ if (!workDirResult.ok) {
2069
+ return {
2070
+ exitCode: workDirResult.error === "FILE_NOT_FOUND" ? ExitCode.FILE_NOT_FOUND : ExitCode.PREFLIGHT_FAILED,
2071
+ result: workDirResult
2072
+ };
2073
+ }
2074
+ const workDir = workDirResult.data;
2075
+ const opId = input.operationId || deriveOpId(input.vault, input.workItem);
2076
+ if (!/^[0-9a-f]{64}$/.test(opId)) {
2077
+ return { exitCode: ExitCode.USAGE, result: err("USAGE", { message: "operationId must be SHA-256 hex" }) };
2078
+ }
2079
+ const journal = readWorkJournal(input.vault, opId) || {
2080
+ operation_id: opId,
2081
+ phase: "validate",
2082
+ retry_count: "0",
2083
+ work_item: input.workItem
2084
+ };
2085
+ const startPhase = journal.phase || "validate";
2086
+ if (startPhase === "done") {
2087
+ return {
2088
+ exitCode: ExitCode.OK,
2089
+ result: ok({
2090
+ operation_id: opId,
2091
+ work_item: input.workItem,
2092
+ phases: ["done"],
2093
+ completed: true,
2094
+ retried: true,
2095
+ committed: journal.committed === "true",
2096
+ humanHint: `work-complete already finished (${opId.slice(0, 12)})`
2097
+ })
2098
+ };
2099
+ }
2100
+ const completedPhases = [];
2101
+ let phase = PHASE_ORDER.includes(startPhase) ? startPhase : "validate";
2102
+ const retried = Number(journal.retry_count || "0") > 0;
2103
+ const advance = (next, extra = {}) => {
2104
+ phase = next;
2105
+ writeWorkJournal(input.vault, opId, {
2106
+ ...journal,
2107
+ operation_id: opId,
2108
+ phase: next,
2109
+ work_item: input.workItem,
2110
+ retry_count: journal.retry_count || "0",
2111
+ ...extra
2112
+ });
2113
+ Object.assign(journal, { phase: next, ...extra });
2114
+ completedPhases.push(next);
2115
+ };
2116
+ if (phaseIndex(startPhase) > 0) {
2117
+ journal.retry_count = String(Number(journal.retry_count || "0") + 1);
2118
+ }
2119
+ writeWorkJournal(input.vault, opId, {
2120
+ ...journal,
2121
+ operation_id: opId,
2122
+ phase: startPhase,
2123
+ work_item: input.workItem
2124
+ });
2125
+ try {
2126
+ if (phaseIndex(phase) <= phaseIndex("validate")) {
2127
+ const cross = await runWorkValidate({ vault: input.vault, workItem: input.workItem, requireComplete: false });
2128
+ if (!cross.result.ok || cross.exitCode !== ExitCode.OK) {
2129
+ return {
2130
+ exitCode: cross.exitCode || ExitCode.PREFLIGHT_FAILED,
2131
+ result: err("PREFLIGHT_FAILED", {
2132
+ reason: "work-validate-failed",
2133
+ detail: cross.result.ok ? cross.result.data : cross.result
2134
+ })
2135
+ };
2136
+ }
2137
+ if (input.failAfter === "validate") {
2138
+ throw new Error("simulated failure after validate");
2139
+ }
2140
+ advance("evidence");
2141
+ }
2142
+ if (phaseIndex(phase) <= phaseIndex("evidence")) {
2143
+ const statusWrite = await setStatusCompleted(join15(workDir, "spec.md"));
2144
+ if (!statusWrite.ok) return writeFailure("evidence-spec", statusWrite);
2145
+ const planWrite = await markPlanComplete(workDir);
2146
+ if (!planWrite.ok) return writeFailure("evidence-plan", planWrite);
2147
+ const evidenceWrite = await writeEvidence(workDir, opId, completedPhases);
2148
+ if (!evidenceWrite.ok) return writeFailure("evidence", evidenceWrite);
2149
+ if (input.failAfter === "evidence") {
2150
+ throw new Error("simulated failure after evidence");
2151
+ }
2152
+ advance("log");
2153
+ }
2154
+ if (phaseIndex(phase) <= phaseIndex("log")) {
2155
+ const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2156
+ const log = await runLogAppend({
2157
+ vault: input.vault,
2158
+ content: `## [${date}] work-complete | ${input.workItem}
2159
+
2160
+ - operation_id: ${opId}`,
2161
+ operationId: opId,
2162
+ writeEvent: true,
2163
+ eventKind: "work-complete",
2164
+ eventTarget: input.workItem,
2165
+ eventNote: `work-complete ${input.workItem}`,
2166
+ recordLastOp: false
2167
+ });
2168
+ if (!log.result.ok) {
2169
+ return { exitCode: log.exitCode, result: log.result };
2170
+ }
2171
+ if (input.failAfter === "log") {
2172
+ throw new Error("simulated failure after log");
2173
+ }
2174
+ advance("projection");
2175
+ }
2176
+ if (phaseIndex(phase) <= phaseIndex("projection")) {
2177
+ if (!existsSync4(join15(workDir, "evidence.md"))) {
2178
+ const evidenceWrite = await writeEvidence(workDir, opId, completedPhases);
2179
+ if (!evidenceWrite.ok) return writeFailure("projection-evidence", evidenceWrite);
2180
+ }
2181
+ const finalCheck = await runWorkValidate({
2182
+ vault: input.vault,
2183
+ workItem: input.workItem,
2184
+ requireComplete: true
2185
+ });
2186
+ if (!finalCheck.result.ok || finalCheck.result.ok && !finalCheck.result.data.valid) {
2187
+ return {
2188
+ exitCode: ExitCode.PREFLIGHT_FAILED,
2189
+ result: err("PREFLIGHT_FAILED", {
2190
+ reason: "completion-validation-failed",
2191
+ detail: finalCheck.result.ok ? finalCheck.result.data : finalCheck.result
2192
+ })
2193
+ };
2194
+ }
2195
+ if (input.failAfter === "projection") {
2196
+ throw new Error("simulated failure after projection");
2197
+ }
2198
+ advance("commit");
2199
+ }
2200
+ let committed = false;
2201
+ if (phaseIndex(phase) <= phaseIndex("commit")) {
2202
+ if (!input.noCommit && existsSync4(join15(input.vault, ".git"))) {
2203
+ try {
2204
+ appendLastOp(input.vault, {
2205
+ operation: "work-complete",
2206
+ summary: `completed ${input.workItem}`,
2207
+ files: [input.workItem],
2208
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2209
+ });
2210
+ git(input.vault, ["add", "--", input.workItem, "log.md", "meta/log-events"]);
2211
+ const porcelain = git(input.vault, ["status", "--porcelain"]);
2212
+ if (porcelain && porcelain.trim()) {
2213
+ gitStrict(input.vault, ["commit", "-m", `work-complete: ${input.workItem} (${opId.slice(0, 12)})`]);
2214
+ committed = true;
2215
+ }
2216
+ } catch (e) {
2217
+ return {
2218
+ exitCode: ExitCode.WRITE_FAILED,
2219
+ result: err("WRITE_FAILED", { stage: "commit", message: String(e) })
2220
+ };
2221
+ }
2222
+ }
2223
+ if (input.failAfter === "commit") {
2224
+ throw new Error("simulated failure after commit");
2225
+ }
2226
+ advance("done", { committed: committed ? "true" : "false" });
2227
+ }
2228
+ return {
2229
+ exitCode: ExitCode.OK,
2230
+ result: ok({
2231
+ operation_id: opId,
2232
+ work_item: input.workItem,
2233
+ phases: completedPhases,
2234
+ completed: true,
2235
+ retried,
2236
+ committed,
2237
+ humanHint: `work-complete finished ${input.workItem} (${opId.slice(0, 12)})`
2238
+ })
2239
+ };
2240
+ } catch (error) {
2241
+ writeWorkJournal(input.vault, opId, {
2242
+ ...journal,
2243
+ operation_id: opId,
2244
+ phase,
2245
+ work_item: input.workItem,
2246
+ retry_count: String(Number(journal.retry_count || "0") + 1),
2247
+ reason: String(error)
2248
+ });
2249
+ return {
2250
+ exitCode: ExitCode.WRITE_FAILED,
2251
+ result: err("WRITE_FAILED", {
2252
+ stage: phase,
2253
+ message: String(error),
2254
+ operation_id: opId,
2255
+ resumable: true
2256
+ })
2257
+ };
2258
+ }
2259
+ }
2260
+
1894
2261
  // 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";
2262
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync11, renameSync as renameSync2, writeFileSync as writeFileSync4 } from "fs";
2263
+ import { dirname as dirname4, join as join16, resolve as resolve2 } from "path";
1897
2264
  import { platform } from "os";
1898
2265
  function statusFromCounts(counts) {
1899
2266
  if ((counts.error ?? 0) > 0) return "error";
@@ -2004,8 +2371,8 @@ function summarizeChecks(checks) {
2004
2371
  };
2005
2372
  }
2006
2373
  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);
2374
+ if (!existsSync5(path)) return { id, label, status: "warn", detail: `log file missing: ${path}` };
2375
+ const lines = readFileSync11(path, "utf8").split(/\r?\n/).filter(Boolean);
2009
2376
  if (lines.length === 0) return { id, label, status: "warn", detail: `log file empty: ${path}` };
2010
2377
  const statusLine = [...lines].reverse().find(
2011
2378
  (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 +2393,12 @@ function runVaultSyncHealth(home, syncMode) {
2026
2393
  };
2027
2394
  }
2028
2395
  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");
2396
+ const shareDir = isMac ? join16(home, "Library", "Application Support", "vault-sync", "bin") : join16(home, ".local", "share", "vault-sync", "bin");
2397
+ const logDir = isMac ? join16(home, "Library", "Logs") : join16(home, ".local", "state", "vault-sync", "log");
2398
+ const filterPath = join16(home, ".config", "rclone", "wiki-push-filters.txt");
2032
2399
  const checks = [];
2033
- const pushScript = join15(shareDir, "wiki-push.sh");
2034
- if (syncMode === "optional" && !existsSync3(pushScript)) {
2400
+ const pushScript = join16(shareDir, "wiki-push.sh");
2401
+ if (syncMode === "optional" && !existsSync5(pushScript)) {
2035
2402
  return {
2036
2403
  status: "pass",
2037
2404
  blocking: false,
@@ -2044,26 +2411,26 @@ function runVaultSyncHealth(home, syncMode) {
2044
2411
  }]
2045
2412
  };
2046
2413
  }
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}` });
2414
+ 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
2415
  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)" });
2416
+ const pushPlist = join16(home, "Library", "LaunchAgents", "com.karlchow.wiki-push.plist");
2417
+ const fetchPlist = join16(home, "Library", "LaunchAgents", "com.karlchow.wiki-fetch.plist");
2418
+ 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
2419
  checks.push({ id: "vault_sync_fuse_refresh_job", label: "Vault sync fuse refresh job", status: "pass", detail: "macOS host \u2014 check skipped" });
2053
2420
  } 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)) {
2421
+ const pushTimer = join16(home, ".config", "systemd", "user", "wiki-push.timer");
2422
+ const fetchTimer = join16(home, ".config", "systemd", "user", "wiki-fetch.timer");
2423
+ const fuseTimer = join16(home, ".config", "systemd", "user", "wiki-fuse-refresh.timer");
2424
+ const fuseService = join16(home, ".config", "systemd", "user", "wiki-fuse-refresh.service");
2425
+ 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)" });
2426
+ 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)" });
2427
+ }
2428
+ checks.push(classifyLog(join16(logDir, "wiki-push.log"), "vault_sync_last_push_age", "Vault sync last push recency", /OK push/));
2429
+ checks.push(classifyLog(join16(logDir, "wiki-fetch.log"), "vault_sync_last_fetch_status", "Vault sync last fetch status", /NOTIFY|OK behind|OK/));
2430
+ if (!existsSync5(filterPath)) {
2064
2431
  checks.push({ id: "vault_sync_filter_present", label: "Vault sync filter file present", status: "error", detail: `Filter missing: ${filterPath}` });
2065
2432
  } else {
2066
- const content = readFileSync9(filterPath, "utf8");
2433
+ const content = readFileSync11(filterPath, "utf8");
2067
2434
  const missing = [
2068
2435
  "remotely-save/data.json",
2069
2436
  ".skillwiki/sync.lock",
@@ -2156,10 +2523,10 @@ function buildHumanHint(report) {
2156
2523
  return lines.join("\n");
2157
2524
  }
2158
2525
  function writeReport(out, report) {
2159
- mkdirSync(dirname4(out), { recursive: true });
2526
+ mkdirSync2(dirname4(out), { recursive: true });
2160
2527
  const tmp = `${out}.tmp-${process.pid}-${Date.now()}`;
2161
- writeFileSync3(tmp, JSON.stringify(ok(report), null, 2) + "\n", "utf8");
2162
- renameSync(tmp, out);
2528
+ writeFileSync4(tmp, JSON.stringify(ok(report), null, 2) + "\n", "utf8");
2529
+ renameSync2(tmp, out);
2163
2530
  }
2164
2531
  function buildIncompleteReport(input, syncMode) {
2165
2532
  const vaultSource = input.vaultSource ?? "resolved";
@@ -2382,12 +2749,12 @@ async function runHealth(input) {
2382
2749
  }
2383
2750
 
2384
2751
  // 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";
2752
+ import { rename as rename2, mkdir as mkdir6, readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
2753
+ import { join as join18, dirname as dirname5 } from "path";
2387
2754
 
2388
2755
  // 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";
2756
+ import { mkdir as mkdir5, writeFile as writeFile4, readdir as readdir3, readFile as readFile5 } from "fs/promises";
2757
+ import { join as join17 } from "path";
2391
2758
  var DELETE_INTENT_SCHEMA = "vault-delete-intent/v1";
2392
2759
  var DELETE_INTENT_DIR = "meta/delete-intents";
2393
2760
  function normalizeVaultRelPath(path) {
@@ -2418,10 +2785,10 @@ function buildDeleteIntent(input) {
2418
2785
  };
2419
2786
  }
2420
2787
  async function writeDeleteIntent(vault, intent) {
2421
- const dir = join16(vault, DELETE_INTENT_DIR);
2422
- await mkdir6(dir, { recursive: true });
2788
+ const dir = join17(vault, DELETE_INTENT_DIR);
2789
+ await mkdir5(dir, { recursive: true });
2423
2790
  const rel = `${DELETE_INTENT_DIR}/${pathToIntentFilename(intent.path)}`;
2424
- await writeFile4(join16(vault, rel), JSON.stringify(intent, null, 2) + "\n", "utf8");
2791
+ await writeFile4(join17(vault, rel), JSON.stringify(intent, null, 2) + "\n", "utf8");
2425
2792
  return rel;
2426
2793
  }
2427
2794
 
@@ -2459,7 +2826,7 @@ async function runArchive(input) {
2459
2826
  if (!relPath) return { exitCode: ExitCode.ARCHIVE_TARGET_NOT_FOUND, result: err("ARCHIVE_TARGET_NOT_FOUND", { page: input.page }) };
2460
2827
  if (relPath.startsWith("_archive/")) return { exitCode: ExitCode.ARCHIVE_ALREADY_ARCHIVED, result: err("ARCHIVE_ALREADY_ARCHIVED", { page: relPath }) };
2461
2828
  const slug = relPath.replace(/\.md$/, "").split("/").pop();
2462
- const archivePath = join17("_archive", relPath).replace(/\\/g, "/");
2829
+ const archivePath = join18("_archive", relPath).replace(/\\/g, "/");
2463
2830
  const remoteRoot = normalizeRemoteRoot(input.remote);
2464
2831
  const remoteObjectPath = buildRemoteObjectPath(remoteRoot, relPath);
2465
2832
  let cascade;
@@ -2485,7 +2852,7 @@ async function runArchive(input) {
2485
2852
  const indexRefs = [];
2486
2853
  if (!isRaw) {
2487
2854
  try {
2488
- const idx = await readFile7(join17(input.vault, "index.md"), "utf8");
2855
+ const idx = await readFile6(join18(input.vault, "index.md"), "utf8");
2489
2856
  idx.split("\n").forEach((line, i) => {
2490
2857
  if (line.includes(`[[${slug}]]`)) indexRefs.push({ line: i + 1, text: line });
2491
2858
  });
@@ -2512,8 +2879,8 @@ async function runArchive(input) {
2512
2879
  }
2513
2880
  if (input.cascade && input.apply && cascade) {
2514
2881
  for (const ref of cascade.source_array_refs) {
2515
- const absPath = join17(input.vault, ref.page);
2516
- const text = await readFile7(absPath, "utf8");
2882
+ const absPath = join18(input.vault, ref.page);
2883
+ const text = await readFile6(absPath, "utf8");
2517
2884
  const split = splitFrontmatter(text);
2518
2885
  if (!split.ok) continue;
2519
2886
  const before = split.data.rawFrontmatter;
@@ -2530,12 +2897,12 @@ ${fmRewritten}
2530
2897
  }
2531
2898
  }
2532
2899
  }
2533
- await mkdir7(dirname5(join17(input.vault, archivePath)), { recursive: true });
2534
- await rename2(join17(input.vault, relPath), join17(input.vault, archivePath));
2900
+ await mkdir6(dirname5(join18(input.vault, archivePath)), { recursive: true });
2901
+ await rename2(join18(input.vault, relPath), join18(input.vault, archivePath));
2535
2902
  let indexUpdated = false;
2536
2903
  if (!isRaw) {
2537
2904
  const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-VNJ3TLEK.js");
2538
- const before = await readFile7(join17(input.vault, "index.md"), "utf8").catch(() => "");
2905
+ const before = await readFile6(join18(input.vault, "index.md"), "utf8").catch(() => "");
2539
2906
  const fullTarget = relPath.replace(/\.md$/, "");
2540
2907
  const bare = fullTarget.split("/").pop() ?? fullTarget;
2541
2908
  const hadIndexEntry = before.includes(`[[${fullTarget}]]`) || before.includes(`[[${bare}]]`);
@@ -2586,7 +2953,7 @@ ${fmRewritten}
2586
2953
 
2587
2954
  // src/commands/remove.ts
2588
2955
  import { unlink as unlink2, access } from "fs/promises";
2589
- import { join as join18 } from "path";
2956
+ import { join as join19 } from "path";
2590
2957
  async function pathExists(abs) {
2591
2958
  try {
2592
2959
  await access(abs);
@@ -2612,7 +2979,7 @@ async function runRemove(input) {
2612
2979
  if (!relPath) {
2613
2980
  try {
2614
2981
  const candidate = normalizeVaultRelPath(input.page);
2615
- if (await pathExists(join18(input.vault, candidate))) {
2982
+ if (await pathExists(join19(input.vault, candidate))) {
2616
2983
  relPath = candidate;
2617
2984
  }
2618
2985
  } catch {
@@ -2641,12 +3008,12 @@ async function runRemove(input) {
2641
3008
  reason: input.reason
2642
3009
  });
2643
3010
  const tombstonePath = await writeDeleteIntent(input.vault, intent);
2644
- await unlink2(join18(input.vault, relPath));
3011
+ await unlink2(join19(input.vault, relPath));
2645
3012
  if (relPath.endsWith(".md") && !relPath.startsWith("raw/")) {
2646
- const { readFile: readFile17 } = await import("fs/promises");
3013
+ const { readFile: readFile16 } = await import("fs/promises");
2647
3014
  const { join: pathJoin } = await import("path");
2648
3015
  const { renderRootIndex: renderRootIndex2, writeRootIndexProjection: writeRootIndexProjection2 } = await import("./index-projection-VNJ3TLEK.js");
2649
- const before = await readFile17(pathJoin(input.vault, "index.md"), "utf8").catch(() => "");
3016
+ const before = await readFile16(pathJoin(input.vault, "index.md"), "utf8").catch(() => "");
2650
3017
  const fullTarget = relPath.replace(/\.md$/, "");
2651
3018
  const bare = fullTarget.split("/").pop() ?? fullTarget;
2652
3019
  const hadIndexEntry = before.includes(`[[${fullTarget}]]`) || before.includes(`[[${bare}]]`);
@@ -2994,7 +3361,7 @@ ${migratedBody}${newFooter}`;
2994
3361
 
2995
3362
  // src/commands/update.ts
2996
3363
  import { execSync } from "child_process";
2997
- import { join as join19 } from "path";
3364
+ import { join as join20 } from "path";
2998
3365
  function parseCoreSemver(version) {
2999
3366
  const m = version.match(/^(\d+)\.(\d+)\.(\d+)/);
3000
3367
  if (!m) return null;
@@ -3025,7 +3392,7 @@ function resolveGlobalSkillsRoot() {
3025
3392
  encoding: "utf8",
3026
3393
  timeout: 5e3
3027
3394
  }).trim();
3028
- return join19(globalRoot, "skillwiki", "skills");
3395
+ return join20(globalRoot, "skillwiki", "skills");
3029
3396
  } catch {
3030
3397
  return null;
3031
3398
  }
@@ -3053,7 +3420,7 @@ async function runUpdate(input) {
3053
3420
  const pkg2 = readCliPackageJson();
3054
3421
  const currentVersion = pkg2.version;
3055
3422
  const tag = normalizeDistTag(input.distTag);
3056
- const target = join19(input.home, ".claude", "skills");
3423
+ const target = join20(input.home, ".claude", "skills");
3057
3424
  let latest;
3058
3425
  try {
3059
3426
  latest = execSync(`npm view skillwiki@${tag} version`, {
@@ -3133,22 +3500,22 @@ async function runUpdate(input) {
3133
3500
 
3134
3501
  // src/commands/self-update.ts
3135
3502
  import { execSync as execSync2 } from "child_process";
3136
- import { existsSync as existsSync4, readFileSync as readFileSync10 } from "fs";
3137
- import { join as join20 } from "path";
3503
+ import { existsSync as existsSync6, readFileSync as readFileSync12 } from "fs";
3504
+ import { join as join21 } from "path";
3138
3505
  var DEFAULT_SOURCE_ROOT_SUFFIX = "/Desktop/code/llm-wiki";
3139
3506
  async function runSelfUpdate(input) {
3140
3507
  const currentVersion = readCliPackageJson().version;
3141
3508
  const sourceRoot = input.sourceRoot ?? `${input.home}${DEFAULT_SOURCE_ROOT_SUFFIX}`;
3142
3509
  const distTag = normalizeDistTag(input.distTag);
3143
- const localPkgPath = join20(sourceRoot, "packages", "cli", "package.json");
3144
- const hasLocalSource = existsSync4(localPkgPath);
3510
+ const localPkgPath = join21(sourceRoot, "packages", "cli", "package.json");
3511
+ const hasLocalSource = existsSync6(localPkgPath);
3145
3512
  if (input.check) {
3146
3513
  let availableVersion = null;
3147
3514
  let source;
3148
3515
  if (hasLocalSource) {
3149
3516
  source = "local";
3150
3517
  try {
3151
- availableVersion = JSON.parse(readFileSync10(localPkgPath, "utf8")).version ?? null;
3518
+ availableVersion = JSON.parse(readFileSync12(localPkgPath, "utf8")).version ?? null;
3152
3519
  } catch {
3153
3520
  availableVersion = null;
3154
3521
  }
@@ -3206,7 +3573,7 @@ async function runSelfUpdate(input) {
3206
3573
  }
3207
3574
  const newVersion = (() => {
3208
3575
  try {
3209
- return JSON.parse(readFileSync10(localPkgPath, "utf8")).version ?? "unknown";
3576
+ return JSON.parse(readFileSync12(localPkgPath, "utf8")).version ?? "unknown";
3210
3577
  } catch {
3211
3578
  return "unknown";
3212
3579
  }
@@ -3272,21 +3639,21 @@ async function runSelfUpdate(input) {
3272
3639
  }
3273
3640
 
3274
3641
  // 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";
3642
+ import { readdir as readdir4, stat as stat3, readFile as readFile8 } from "fs/promises";
3643
+ import { join as join22 } from "path";
3277
3644
  async function runTranscripts(input) {
3278
- const dir = join21(input.vault, "raw", "transcripts");
3645
+ const dir = join22(input.vault, "raw", "transcripts");
3279
3646
  let entries;
3280
3647
  try {
3281
- entries = await readdir5(dir, { withFileTypes: true });
3648
+ entries = await readdir4(dir, { withFileTypes: true });
3282
3649
  } catch {
3283
3650
  return { exitCode: ExitCode.VAULT_PATH_INVALID, result: { ok: false, error: "VAULT_PATH_INVALID", detail: `raw/transcripts/ not found: ${dir}` } };
3284
3651
  }
3285
3652
  const transcripts = [];
3286
3653
  for (const entry of entries) {
3287
3654
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
3288
- const filePath = join21(dir, entry.name);
3289
- const content = await readFile9(filePath, "utf8");
3655
+ const filePath = join22(dir, entry.name);
3656
+ const content = await readFile8(filePath, "utf8");
3290
3657
  const fm = extractFrontmatter(content);
3291
3658
  if (!fm.ok) continue;
3292
3659
  const ingested = typeof fm.data.ingested === "string" ? fm.data.ingested : "";
@@ -3303,10 +3670,10 @@ async function runTranscripts(input) {
3303
3670
  }
3304
3671
 
3305
3672
  // 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";
3673
+ import { writeFile as writeFile7, mkdir as mkdir7, readdir as readdir5, unlink as unlink3 } from "fs/promises";
3674
+ import { join as join23 } from "path";
3675
+ import { existsSync as existsSync7 } from "fs";
3676
+ import { readFile as readFile9 } from "fs/promises";
3310
3677
  var RETRO_HEADING_RE = /^## \[(\d{4}-\d{2}-\d{2})(?:\s+[^\]]+)?\] retro \| loop cycle(?: (\d+))?: (.+)$/;
3311
3678
  var FIELD_RE = {
3312
3679
  improve: /^-\s+\*?\*?Improve:?\*?\*?\s*(.+)$/m,
@@ -3404,17 +3771,17 @@ function extractRetroFields(date, cycleName, block) {
3404
3771
  };
3405
3772
  }
3406
3773
  async function runCompound(input) {
3407
- const logPath = join22(input.vault, "log.md");
3774
+ const logPath = join23(input.vault, "log.md");
3408
3775
  let logText;
3409
3776
  try {
3410
- logText = await readFile10(logPath, "utf8");
3777
+ logText = await readFile9(logPath, "utf8");
3411
3778
  } catch {
3412
3779
  return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: logPath }) };
3413
3780
  }
3414
3781
  const entries = parseRetroEntries(logText);
3415
3782
  const promoted = [];
3416
3783
  const skipped = [];
3417
- const compoundDir = join22(input.vault, "projects", input.project, "compound");
3784
+ const compoundDir = join23(input.vault, "projects", input.project, "compound");
3418
3785
  for (const entry of entries) {
3419
3786
  const generalizeValue = entry.generalize.trim();
3420
3787
  if (!/^yes/i.test(generalizeValue)) {
@@ -3422,8 +3789,8 @@ async function runCompound(input) {
3422
3789
  continue;
3423
3790
  }
3424
3791
  const slug = slugify(entry.cycleName);
3425
- const compoundPath = join22(compoundDir, `${slug}.md`);
3426
- if (existsSync5(compoundPath)) {
3792
+ const compoundPath = join23(compoundDir, `${slug}.md`);
3793
+ if (existsSync7(compoundPath)) {
3427
3794
  skipped.push(entry.date);
3428
3795
  continue;
3429
3796
  }
@@ -3461,8 +3828,8 @@ async function runCompound(input) {
3461
3828
  ].join("\n");
3462
3829
  const content = frontmatter + "\n" + body;
3463
3830
  if (!input.dryRun) {
3464
- if (!existsSync5(compoundDir)) {
3465
- await mkdir8(compoundDir, { recursive: true });
3831
+ if (!existsSync7(compoundDir)) {
3832
+ await mkdir7(compoundDir, { recursive: true });
3466
3833
  }
3467
3834
  await writeFile7(compoundPath, content, "utf8");
3468
3835
  }
@@ -3483,16 +3850,16 @@ async function runCompound(input) {
3483
3850
  };
3484
3851
  }
3485
3852
  async function runCompoundDelete(input) {
3486
- const projectDir = join22(input.vault, "projects", input.project);
3487
- if (!existsSync5(projectDir)) {
3853
+ const projectDir = join23(input.vault, "projects", input.project);
3854
+ if (!existsSync7(projectDir)) {
3488
3855
  return {
3489
3856
  exitCode: ExitCode.PROJECT_NOT_FOUND,
3490
3857
  result: err("PROJECT_NOT_FOUND", { slug: input.project, path: projectDir })
3491
3858
  };
3492
3859
  }
3493
3860
  const entryName = input.entry.replace(/\.md$/, "");
3494
- const compoundPath = join22(projectDir, "compound", `${entryName}.md`);
3495
- if (!existsSync5(compoundPath)) {
3861
+ const compoundPath = join23(projectDir, "compound", `${entryName}.md`);
3862
+ if (!existsSync7(compoundPath)) {
3496
3863
  return {
3497
3864
  exitCode: ExitCode.FILE_NOT_FOUND,
3498
3865
  result: err("FILE_NOT_FOUND", { path: compoundPath })
@@ -3525,8 +3892,8 @@ knowledge.md regenerated`
3525
3892
  };
3526
3893
  }
3527
3894
  async function runCompoundList(input) {
3528
- const compoundDir = join22(input.vault, "projects", input.project, "compound");
3529
- if (!existsSync5(compoundDir)) {
3895
+ const compoundDir = join23(input.vault, "projects", input.project, "compound");
3896
+ if (!existsSync7(compoundDir)) {
3530
3897
  return {
3531
3898
  exitCode: ExitCode.OK,
3532
3899
  result: ok({
@@ -3540,7 +3907,7 @@ no compound directory found`
3540
3907
  }
3541
3908
  let dirents;
3542
3909
  try {
3543
- dirents = await readdir6(compoundDir, { withFileTypes: true });
3910
+ dirents = await readdir5(compoundDir, { withFileTypes: true });
3544
3911
  } catch {
3545
3912
  return {
3546
3913
  exitCode: ExitCode.OK,
@@ -3556,10 +3923,10 @@ could not read compound directory`
3556
3923
  const entries = [];
3557
3924
  for (const dirent of dirents) {
3558
3925
  if (!dirent.isFile() || !dirent.name.endsWith(".md")) continue;
3559
- const filePath = join22(compoundDir, dirent.name);
3926
+ const filePath = join23(compoundDir, dirent.name);
3560
3927
  let text;
3561
3928
  try {
3562
- text = await readFile10(filePath, "utf8");
3929
+ text = await readFile9(filePath, "utf8");
3563
3930
  } catch {
3564
3931
  continue;
3565
3932
  }
@@ -3588,8 +3955,8 @@ no compound entries found`;
3588
3955
  }
3589
3956
 
3590
3957
  // 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";
3958
+ import { mkdir as mkdir8, readFile as readFile10, writeFile as writeFile8 } from "fs/promises";
3959
+ import { join as join24, relative, sep } from "path";
3593
3960
  var MAX_WORDS = 900;
3594
3961
  async function runSessionBrief(input) {
3595
3962
  const scan = await scanVault(input.vault);
@@ -3689,7 +4056,7 @@ async function resolveProject(input) {
3689
4056
  const envProject = input.env?.SKILLWIKI_PROJECT;
3690
4057
  if (envProject) return envProject;
3691
4058
  const cwd = input.cwd ?? process.cwd();
3692
- const projectDotenv = await readProjectSlug(join23(cwd, ".skillwiki", ".env"));
4059
+ const projectDotenv = await readProjectSlug(join24(cwd, ".skillwiki", ".env"));
3693
4060
  if (projectDotenv) return projectDotenv;
3694
4061
  const inferred = inferProjectFromPath(input.vault, cwd);
3695
4062
  if (inferred) return inferred;
@@ -3698,7 +4065,7 @@ async function resolveProject(input) {
3698
4065
  async function readProjectSlug(file) {
3699
4066
  let text;
3700
4067
  try {
3701
- text = await readFile11(file, "utf8");
4068
+ text = await readFile10(file, "utf8");
3702
4069
  } catch {
3703
4070
  return void 0;
3704
4071
  }
@@ -3775,7 +4142,7 @@ async function loadTrendDigests(typedPages) {
3775
4142
  return out;
3776
4143
  }
3777
4144
  async function loadSessionPins(vault, project) {
3778
- const text = await readIfExists(join23(vault, "meta", "session-pins.md"));
4145
+ const text = await readIfExists(join24(vault, "meta", "session-pins.md"));
3779
4146
  if (!text) return [];
3780
4147
  const fm = extractFrontmatter(text);
3781
4148
  if (!fm.ok) return [];
@@ -3892,7 +4259,7 @@ function satelliteHealthWarnings(warning) {
3892
4259
  return warning ? [warning] : [];
3893
4260
  }
3894
4261
  async function loadHealthWarnings(vault) {
3895
- const text = await readIfExists(join23(vault, ".skillwiki", "health.json"));
4262
+ const text = await readIfExists(join24(vault, ".skillwiki", "health.json"));
3896
4263
  if (!text) return [];
3897
4264
  try {
3898
4265
  const parsed = JSON.parse(text);
@@ -3905,7 +4272,7 @@ async function loadHealthWarnings(vault) {
3905
4272
  }
3906
4273
  }
3907
4274
  async function loadMemoryTopics(vault, project) {
3908
- const text = await readIfExists(join23(vault, ".skillwiki", "memory", project, "topics.json"));
4275
+ const text = await readIfExists(join24(vault, ".skillwiki", "memory", project, "topics.json"));
3909
4276
  if (!text) return [];
3910
4277
  try {
3911
4278
  const parsed = JSON.parse(text);
@@ -3930,11 +4297,11 @@ async function loadMemoryTopics(vault, project) {
3930
4297
  }
3931
4298
  }
3932
4299
  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 });
4300
+ const metaPath = join24(vault, "meta", "latest-session-brief.md");
4301
+ const cacheMdPath = join24(vault, ".skillwiki", "session-brief.md");
4302
+ const cacheJsonPath = join24(vault, ".skillwiki", "session-brief.json");
4303
+ await mkdir8(join24(vault, "meta"), { recursive: true });
4304
+ await mkdir8(join24(vault, ".skillwiki"), { recursive: true });
3938
4305
  const committed = renderCommittedBrief(input);
3939
4306
  const previousComparable = comparableBrief(await readIfExists(metaPath));
3940
4307
  const nextComparable = comparableBrief(committed);
@@ -4004,7 +4371,7 @@ function renderCommittedBrief(input) {
4004
4371
  ].filter((line) => line !== "").join("\n");
4005
4372
  }
4006
4373
  async function rebuildRootIndexProjection(vault) {
4007
- const before = await readIfExists(join23(vault, "index.md"));
4374
+ const before = await readIfExists(join24(vault, "index.md"));
4008
4375
  if (!before) return false;
4009
4376
  const projection = await renderRootIndex({ vault, currentText: before });
4010
4377
  if (!projection.ok) return false;
@@ -4014,7 +4381,7 @@ async function rebuildRootIndexProjection(vault) {
4014
4381
  }
4015
4382
  async function readIfExists(path) {
4016
4383
  try {
4017
- return await readFile11(path, "utf8");
4384
+ return await readFile10(path, "utf8");
4018
4385
  } catch {
4019
4386
  return "";
4020
4387
  }
@@ -4056,14 +4423,14 @@ function dateFromPath(path) {
4056
4423
  }
4057
4424
 
4058
4425
  // 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";
4426
+ import { readFile as readFile12, open, unlink as unlink4, mkdir as mkdir9 } from "fs/promises";
4427
+ import { join as join26 } from "path";
4061
4428
  import { createHash as createHash4 } from "crypto";
4062
4429
 
4063
4430
  // src/commands/page-publish.ts
4064
4431
  import { realpathSync } from "fs";
4065
- import { readFile as readFile12 } from "fs/promises";
4066
- import { join as join24, resolve as resolve3 } from "path";
4432
+ import { readFile as readFile11 } from "fs/promises";
4433
+ import { join as join25, resolve as resolve3 } from "path";
4067
4434
  var DEFAULT_DEPS = {
4068
4435
  afterStage: async () => void 0,
4069
4436
  preflight: (input) => runManagedWritePreflight(input)
@@ -4133,7 +4500,7 @@ function preparePagePublicationFromContent(input) {
4133
4500
  async function preparePagePublication(input) {
4134
4501
  let content;
4135
4502
  try {
4136
- content = await readFile12(input.draftPath, "utf8");
4503
+ content = await readFile11(input.draftPath, "utf8");
4137
4504
  } catch (error) {
4138
4505
  return err("FILE_NOT_FOUND", { path: input.draftPath, message: String(error) });
4139
4506
  }
@@ -4204,10 +4571,10 @@ async function runLockedPrimaryStages(input, vault, deps) {
4204
4571
  ExitCode.VAULT_PATH_INVALID
4205
4572
  );
4206
4573
  }
4207
- const schemaPath = join24(vault, "SCHEMA.md");
4574
+ const schemaPath = join25(vault, "SCHEMA.md");
4208
4575
  let schemaText;
4209
4576
  try {
4210
- schemaText = await readFile12(schemaPath, "utf8");
4577
+ schemaText = await readFile11(schemaPath, "utf8");
4211
4578
  } catch (error) {
4212
4579
  return lockedFailure("schema", state, err("WRITE_FAILED", { message: String(error) }));
4213
4580
  }
@@ -4237,8 +4604,8 @@ async function runLockedPrimaryStages(input, vault, deps) {
4237
4604
  let visibleSchema;
4238
4605
  try {
4239
4606
  [visible, visibleSchema] = await Promise.all([
4240
- readFile12(input.targetPath, "utf8"),
4241
- readFile12(schemaPath, "utf8")
4607
+ readFile11(input.targetPath, "utf8"),
4608
+ readFile11(schemaPath, "utf8")
4242
4609
  ]);
4243
4610
  } catch (error) {
4244
4611
  return lockedFailure("verify", state, err("WRITE_FAILED", { message: String(error) }));
@@ -4323,17 +4690,17 @@ function renderPublicationLog(input, added) {
4323
4690
  }
4324
4691
  async function readPageChanged(targetPath, content) {
4325
4692
  try {
4326
- return ok(await readFile12(targetPath, "utf8") !== content);
4693
+ return ok(await readFile11(targetPath, "utf8") !== content);
4327
4694
  } catch (error) {
4328
4695
  if (error.code === "ENOENT") return ok(true);
4329
4696
  return err("WRITE_FAILED", { path: targetPath, message: String(error) });
4330
4697
  }
4331
4698
  }
4332
4699
  async function previewPreparedPagePublication(input, vault) {
4333
- const schemaPath = join24(vault, "SCHEMA.md");
4700
+ const schemaPath = join25(vault, "SCHEMA.md");
4334
4701
  let schemaText;
4335
4702
  try {
4336
- schemaText = await readFile12(schemaPath, "utf8");
4703
+ schemaText = await readFile11(schemaPath, "utf8");
4337
4704
  } catch (error) {
4338
4705
  const result = err("FILE_NOT_FOUND", { path: schemaPath, message: String(error) });
4339
4706
  return { exitCode: ExitCode.FILE_NOT_FOUND, result };
@@ -4345,10 +4712,10 @@ async function previewPreparedPagePublication(input, vault) {
4345
4712
  if (!reconciled.ok) return { exitCode: errorExitCode(reconciled.error), result: reconciled };
4346
4713
  const pageChanged = await readPageChanged(input.targetPath, input.page.content);
4347
4714
  if (!pageChanged.ok) return { exitCode: errorExitCode(pageChanged.error), result: pageChanged };
4348
- const indexPath = join24(vault, "index.md");
4715
+ const indexPath = join25(vault, "index.md");
4349
4716
  let indexText;
4350
4717
  try {
4351
- indexText = await readFile12(indexPath, "utf8");
4718
+ indexText = await readFile11(indexPath, "utf8");
4352
4719
  } catch (error) {
4353
4720
  const result = err("FILE_NOT_FOUND", { path: indexPath, message: String(error) });
4354
4721
  return { exitCode: ExitCode.FILE_NOT_FOUND, result };
@@ -4359,10 +4726,10 @@ async function previewPreparedPagePublication(input, vault) {
4359
4726
  type: input.page.type
4360
4727
  });
4361
4728
  if (!index.ok) return { exitCode: errorExitCode(index.error), result: index };
4362
- const logPath = join24(vault, "log.md");
4729
+ const logPath = join25(vault, "log.md");
4363
4730
  let logText;
4364
4731
  try {
4365
- logText = await readFile12(logPath, "utf8");
4732
+ logText = await readFile11(logPath, "utf8");
4366
4733
  } catch (error) {
4367
4734
  const result = err("FILE_NOT_FOUND", { path: logPath, message: String(error) });
4368
4735
  return { exitCode: ExitCode.FILE_NOT_FOUND, result };
@@ -4624,7 +4991,7 @@ function buildTypedContent(title, ingested, type, tags, rawRelPath, provenance)
4624
4991
  }
4625
4992
  async function resolveRawCapture(input) {
4626
4993
  try {
4627
- const existing = await readFile13(input.path, "utf8");
4994
+ const existing = await readFile12(input.path, "utf8");
4628
4995
  const frontmatter = extractFrontmatter(existing);
4629
4996
  if (!frontmatter.ok) {
4630
4997
  return err("INGEST_VALIDATION_FAILED", {
@@ -4682,7 +5049,7 @@ async function acquireRawCaptureLock(path) {
4682
5049
  const lockPath = `${path}.ingest.lock`;
4683
5050
  for (let attempt = 0; attempt < 200; attempt++) {
4684
5051
  try {
4685
- const handle = await open2(lockPath, "wx");
5052
+ const handle = await open(lockPath, "wx");
4686
5053
  await handle.close();
4687
5054
  return ok(lockPath);
4688
5055
  } catch (error) {
@@ -4763,7 +5130,7 @@ async function runIngest(input) {
4763
5130
  sourceContent = fetchResult.data.body;
4764
5131
  } else {
4765
5132
  try {
4766
- sourceContent = await readFile13(input.source, "utf8");
5133
+ sourceContent = await readFile12(input.source, "utf8");
4767
5134
  } catch {
4768
5135
  return {
4769
5136
  exitCode: ExitCode.FILE_NOT_FOUND,
@@ -4788,7 +5155,7 @@ async function runIngest(input) {
4788
5155
  const rawRelPath = `raw/articles/${slug}.md`;
4789
5156
  const typedDir = TYPE_DIR[input.type] ?? `${input.type}s`;
4790
5157
  const typedRelPath = `${typedDir}/${slug}.md`;
4791
- const rawAbsPath = join25(input.vault, rawRelPath);
5158
+ const rawAbsPath = join26(input.vault, rawRelPath);
4792
5159
  const identity = assessSourceIdentity({
4793
5160
  rawPath: rawRelPath,
4794
5161
  sourceUrl: sourceUrl ?? void 0,
@@ -4832,11 +5199,11 @@ async function runIngest(input) {
4832
5199
  );
4833
5200
  if (!input.dryRun) {
4834
5201
  try {
4835
- await mkdir10(join25(input.vault, typedDir), { recursive: true });
5202
+ await mkdir9(join26(input.vault, typedDir), { recursive: true });
4836
5203
  } catch (error) {
4837
5204
  return {
4838
5205
  exitCode: ExitCode.WRITE_FAILED,
4839
- result: err("WRITE_FAILED", { path: join25(input.vault, typedDir), message: String(error) })
5206
+ result: err("WRITE_FAILED", { path: join26(input.vault, typedDir), message: String(error) })
4840
5207
  };
4841
5208
  }
4842
5209
  }
@@ -4883,11 +5250,11 @@ async function runIngest(input) {
4883
5250
  };
4884
5251
  }
4885
5252
  try {
4886
- await mkdir10(join25(input.vault, "raw", "articles"), { recursive: true });
5253
+ await mkdir9(join26(input.vault, "raw", "articles"), { recursive: true });
4887
5254
  } catch (error) {
4888
5255
  return {
4889
5256
  exitCode: ExitCode.WRITE_FAILED,
4890
- result: err("WRITE_FAILED", { path: join25(input.vault, "raw", "articles"), message: String(error) })
5257
+ result: err("WRITE_FAILED", { path: join26(input.vault, "raw", "articles"), message: String(error) })
4891
5258
  };
4892
5259
  }
4893
5260
  const rawWrite = await writeResolvedRaw({
@@ -5115,8 +5482,8 @@ ${body}`;
5115
5482
  }
5116
5483
 
5117
5484
  // src/commands/tag-reconcile.ts
5118
- import { readFile as readFile14 } from "fs/promises";
5119
- import { join as join26, posix } from "path";
5485
+ import { readFile as readFile13 } from "fs/promises";
5486
+ import { join as join27, posix } from "path";
5120
5487
  var TYPED_TARGET_RE = /^(entities|concepts|comparisons|queries|meta)\/[a-z0-9][a-z0-9./_-]*\.md$/;
5121
5488
  function errorExitCode2(error) {
5122
5489
  switch (error) {
@@ -5159,7 +5526,7 @@ function asTagArray(frontmatter, path) {
5159
5526
  async function readTagsFromFile(path) {
5160
5527
  let text;
5161
5528
  try {
5162
- text = await readFile14(path, "utf8");
5529
+ text = await readFile13(path, "utf8");
5163
5530
  } catch (error) {
5164
5531
  if (error.code === "ENOENT") {
5165
5532
  return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path }) };
@@ -5180,7 +5547,7 @@ async function resolveRequestedTags(input, page) {
5180
5547
  result: err("INVALID_FRONTMATTER", { message: "explicit tags must be an array of strings" })
5181
5548
  };
5182
5549
  }
5183
- const source = input.from ?? (explicit.length === 0 ? join26(input.vault, page) : void 0);
5550
+ const source = input.from ?? (explicit.length === 0 ? join27(input.vault, page) : void 0);
5184
5551
  if (!source) return { exitCode: ExitCode.OK, result: ok({ tags: [...new Set(explicit)].sort() }) };
5185
5552
  const sourced = await readTagsFromFile(source);
5186
5553
  if (!sourced.result.ok) return { exitCode: sourced.exitCode, result: sourced.result };
@@ -5191,7 +5558,7 @@ async function resolveRequestedTags(input, page) {
5191
5558
  }
5192
5559
  async function readSchema(schemaPath) {
5193
5560
  try {
5194
- return { exitCode: ExitCode.OK, result: ok(await readFile14(schemaPath, "utf8")) };
5561
+ return { exitCode: ExitCode.OK, result: ok(await readFile13(schemaPath, "utf8")) };
5195
5562
  } catch (error) {
5196
5563
  if (error.code === "ENOENT") {
5197
5564
  return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: schemaPath }) };
@@ -5219,7 +5586,7 @@ function previewResult(page, tags, reconciled, dryRun, filesChanged) {
5219
5586
  };
5220
5587
  }
5221
5588
  async function reconcileTagsWhileLocked(input, page, tags, comment) {
5222
- const schemaPath = join26(input.vault, "SCHEMA.md");
5589
+ const schemaPath = join27(input.vault, "SCHEMA.md");
5223
5590
  const current = await readSchema(schemaPath);
5224
5591
  if (!current.result.ok) return { exitCode: current.exitCode, result: current.result };
5225
5592
  const next = reconcileTaxonomyDocument(current.result.data, { tags, comment });
@@ -5230,7 +5597,7 @@ async function reconcileTagsWhileLocked(input, page, tags, comment) {
5230
5597
  }
5231
5598
  let verifiedText;
5232
5599
  try {
5233
- verifiedText = await readFile14(schemaPath, "utf8");
5600
+ verifiedText = await readFile13(schemaPath, "utf8");
5234
5601
  } catch (error) {
5235
5602
  return {
5236
5603
  exitCode: ExitCode.WRITE_FAILED,
@@ -5256,7 +5623,7 @@ async function runTagReconcile(input) {
5256
5623
  const comment = taxonomyCommentForPage(page.data, date, input.reason);
5257
5624
  if (!comment.ok) return { exitCode: ExitCode.SCHEME_REJECTED, result: comment };
5258
5625
  if (!input.write) {
5259
- const schema = await readSchema(join26(input.vault, "SCHEMA.md"));
5626
+ const schema = await readSchema(join27(input.vault, "SCHEMA.md"));
5260
5627
  if (!schema.result.ok) return { exitCode: schema.exitCode, result: schema.result };
5261
5628
  const preview = reconcileTaxonomyDocument(schema.result.data, { tags, comment: comment.data });
5262
5629
  return previewResult(page.data, tags, preview, true, []);
@@ -5308,8 +5675,8 @@ async function runTagReconcile(input) {
5308
5675
  }
5309
5676
 
5310
5677
  // src/commands/sync.ts
5311
- import { existsSync as existsSync6 } from "fs";
5312
- import { join as join27 } from "path";
5678
+ import { existsSync as existsSync8 } from "fs";
5679
+ import { join as join28 } from "path";
5313
5680
  import { execFileSync as execFileSync3 } from "child_process";
5314
5681
 
5315
5682
  // src/utils/vault-git-pathspec.ts
@@ -5363,7 +5730,7 @@ function refHasPath(vault, ref, path) {
5363
5730
  function runSyncStatus(input) {
5364
5731
  const vault = input.vault;
5365
5732
  const includeStashes = input.includeStashes ?? false;
5366
- if (!existsSync6(join27(vault, ".git"))) {
5733
+ if (!existsSync8(join28(vault, ".git"))) {
5367
5734
  return {
5368
5735
  exitCode: ExitCode.VAULT_PATH_INVALID,
5369
5736
  result: ok({
@@ -5470,7 +5837,7 @@ function runSyncStatus(input) {
5470
5837
  }
5471
5838
  async function runSyncPush(input) {
5472
5839
  const vault = input.vault;
5473
- if (!existsSync6(join27(vault, ".git"))) {
5840
+ if (!existsSync8(join28(vault, ".git"))) {
5474
5841
  return {
5475
5842
  exitCode: ExitCode.VAULT_PATH_INVALID,
5476
5843
  result: err("NOT_A_GIT_REPO", { path: vault })
@@ -5630,7 +5997,7 @@ function enableGitLongPathsOnWindows(vault) {
5630
5997
  }
5631
5998
  async function runSyncPull(input) {
5632
5999
  const vault = input.vault;
5633
- if (!existsSync6(join27(vault, ".git"))) {
6000
+ if (!existsSync8(join28(vault, ".git"))) {
5634
6001
  return {
5635
6002
  exitCode: ExitCode.VAULT_PATH_INVALID,
5636
6003
  result: err("NOT_A_GIT_REPO", { path: vault })
@@ -5745,7 +6112,7 @@ function runSyncPeers(input) {
5745
6112
  }
5746
6113
  function runSyncLock(input) {
5747
6114
  const vault = input.vault;
5748
- if (!existsSync6(vault)) {
6115
+ if (!existsSync8(vault)) {
5749
6116
  return {
5750
6117
  exitCode: ExitCode.VAULT_PATH_INVALID,
5751
6118
  result: err("VAULT_PATH_INVALID", { path: vault })
@@ -5780,7 +6147,7 @@ function runSyncLock(input) {
5780
6147
  }
5781
6148
  function runSyncUnlock(input) {
5782
6149
  const vault = input.vault;
5783
- if (!existsSync6(vault)) {
6150
+ if (!existsSync8(vault)) {
5784
6151
  return {
5785
6152
  exitCode: ExitCode.VAULT_PATH_INVALID,
5786
6153
  result: err("VAULT_PATH_INVALID", { path: vault })
@@ -5857,8 +6224,8 @@ function runSyncJournalClearStale(input) {
5857
6224
  }
5858
6225
 
5859
6226
  // 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";
6227
+ import { statSync as statSync2, readdirSync as readdirSync2, readFileSync as readFileSync13, mkdirSync as mkdirSync3, writeFileSync as writeFileSync5 } from "fs";
6228
+ import { join as join29, relative as relative2, dirname as dirname6 } from "path";
5862
6229
  import { PutObjectCommand, HeadObjectCommand, ListObjectsV2Command, GetObjectCommand, DeleteObjectsCommand } from "@aws-sdk/client-s3";
5863
6230
 
5864
6231
  // src/utils/s3-client.ts
@@ -5880,9 +6247,9 @@ function createS3Client(config) {
5880
6247
  // src/commands/backup.ts
5881
6248
  var SKIP_DIRS = /* @__PURE__ */ new Set([".git", ".obsidian", "_archive", "node_modules", ".skillwiki"]);
5882
6249
  function* walkMarkdown(dir, base) {
5883
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
6250
+ for (const entry of readdirSync2(dir, { withFileTypes: true })) {
5884
6251
  if (SKIP_DIRS.has(entry.name)) continue;
5885
- const full = join28(dir, entry.name);
6252
+ const full = join29(dir, entry.name);
5886
6253
  if (entry.isDirectory()) {
5887
6254
  yield* walkMarkdown(full, base);
5888
6255
  } else if (entry.name.endsWith(".md")) {
@@ -5905,7 +6272,7 @@ async function runBackupSync(input) {
5905
6272
  let failed = 0;
5906
6273
  const files = [...walkMarkdown(input.vault, input.vault)];
5907
6274
  for (const relPath of files) {
5908
- const absPath = join28(input.vault, relPath);
6275
+ const absPath = join29(input.vault, relPath);
5909
6276
  const localStat = statSync2(absPath);
5910
6277
  let needsUpload = true;
5911
6278
  try {
@@ -5924,7 +6291,7 @@ async function runBackupSync(input) {
5924
6291
  continue;
5925
6292
  }
5926
6293
  try {
5927
- const body = readFileSync11(absPath);
6294
+ const body = readFileSync13(absPath);
5928
6295
  await client.send(new PutObjectCommand({ Bucket: input.bucket, Key: relPath, Body: body }));
5929
6296
  uploaded++;
5930
6297
  } catch {
@@ -5981,7 +6348,7 @@ async function runBackupRestore(input) {
5981
6348
  const objects = list.Contents ?? [];
5982
6349
  for (const obj of objects) {
5983
6350
  if (!obj.Key) continue;
5984
- const localPath = join28(target, obj.Key);
6351
+ const localPath = join29(target, obj.Key);
5985
6352
  try {
5986
6353
  const localStat = statSync2(localPath);
5987
6354
  if (obj.LastModified && localStat.mtime > obj.LastModified) {
@@ -5994,8 +6361,8 @@ async function runBackupRestore(input) {
5994
6361
  const resp = await client.send(new GetObjectCommand({ Bucket: input.bucket, Key: obj.Key }));
5995
6362
  const body = await resp.Body?.transformToByteArray();
5996
6363
  if (body) {
5997
- mkdirSync2(dirname6(localPath), { recursive: true });
5998
- writeFileSync4(localPath, Buffer.from(body));
6364
+ mkdirSync3(dirname6(localPath), { recursive: true });
6365
+ writeFileSync5(localPath, Buffer.from(body));
5999
6366
  downloaded++;
6000
6367
  }
6001
6368
  } catch {
@@ -6027,11 +6394,11 @@ async function runBackupRestore(input) {
6027
6394
  }
6028
6395
 
6029
6396
  // 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";
6397
+ import { existsSync as existsSync9, statSync as statSync3 } from "fs";
6398
+ import { readFile as readFile14 } from "fs/promises";
6399
+ import { join as join30 } from "path";
6033
6400
  async function runStatus(input) {
6034
- if (!existsSync7(input.vault)) {
6401
+ if (!existsSync9(input.vault)) {
6035
6402
  return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { vault: input.vault }) };
6036
6403
  }
6037
6404
  const scan = await scanVault(input.vault);
@@ -6056,7 +6423,7 @@ async function runStatus(input) {
6056
6423
  const compound = scan.data.compound.length;
6057
6424
  let schemaVersion = "v1";
6058
6425
  try {
6059
- const schemaContent = await readFile15(join29(input.vault, "SCHEMA.md"), "utf8");
6426
+ const schemaContent = await readFile14(join30(input.vault, "SCHEMA.md"), "utf8");
6060
6427
  const versionMatch = schemaContent.match(/version:\s*["']?([^"'\s\n]+)/i);
6061
6428
  if (versionMatch) schemaVersion = versionMatch[1];
6062
6429
  } catch {
@@ -6116,8 +6483,8 @@ async function runStatus(input) {
6116
6483
  }
6117
6484
 
6118
6485
  // 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";
6486
+ import { mkdir as mkdir10, writeFile as writeFile9, stat as stat4 } from "fs/promises";
6487
+ import { join as join31 } from "path";
6121
6488
  var TODAY = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
6122
6489
  var EXAMPLE_PAGES = {
6123
6490
  "entities/example-project.md": `---
@@ -6186,29 +6553,29 @@ Real sources are immutable after ingestion \u2014 never edit them.
6186
6553
  `;
6187
6554
  async function runSeed(input) {
6188
6555
  try {
6189
- await stat4(join30(input.vault, "SCHEMA.md"));
6556
+ await stat4(join31(input.vault, "SCHEMA.md"));
6190
6557
  } catch {
6191
6558
  return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { root: input.vault, reason: "SCHEMA.md missing \u2014 run `skillwiki init` first" }) };
6192
6559
  }
6193
6560
  const created = [];
6194
6561
  const skipped = [];
6195
6562
  for (const [relPath, content] of Object.entries(EXAMPLE_PAGES)) {
6196
- const absPath = join30(input.vault, relPath);
6563
+ const absPath = join31(input.vault, relPath);
6197
6564
  try {
6198
6565
  await stat4(absPath);
6199
6566
  skipped.push(relPath);
6200
6567
  } catch {
6201
- await mkdir11(join30(absPath, ".."), { recursive: true });
6568
+ await mkdir10(join31(absPath, ".."), { recursive: true });
6202
6569
  await writeFile9(absPath, content, "utf8");
6203
6570
  created.push(relPath);
6204
6571
  }
6205
6572
  }
6206
- const rawPath = join30(input.vault, "raw", "articles", "example-source.md");
6573
+ const rawPath = join31(input.vault, "raw", "articles", "example-source.md");
6207
6574
  try {
6208
6575
  await stat4(rawPath);
6209
6576
  skipped.push("raw/articles/example-source.md");
6210
6577
  } catch {
6211
- await mkdir11(join30(rawPath, ".."), { recursive: true });
6578
+ await mkdir10(join31(rawPath, ".."), { recursive: true });
6212
6579
  await writeFile9(rawPath, EXAMPLE_RAW, "utf8");
6213
6580
  created.push("raw/articles/example-source.md");
6214
6581
  }
@@ -6231,9 +6598,9 @@ async function runSeed(input) {
6231
6598
  }
6232
6599
 
6233
6600
  // 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";
6601
+ import { readFile as readFile15, writeFile as writeFile10 } from "fs/promises";
6602
+ import { existsSync as existsSync10 } from "fs";
6603
+ import { join as join32 } from "path";
6237
6604
  var NODE_WIDTH = 240;
6238
6605
  var NODE_HEIGHT = 60;
6239
6606
  var COLUMN_SPACING = 400;
@@ -6311,8 +6678,8 @@ function buildCanvasEdges(adjacency) {
6311
6678
  return edges;
6312
6679
  }
6313
6680
  async function runCanvasGenerate(input) {
6314
- const graphPath = input.graphPath ?? join31(input.vault, ".skillwiki", "graph.json");
6315
- if (!existsSync8(graphPath)) {
6681
+ const graphPath = input.graphPath ?? join32(input.vault, ".skillwiki", "graph.json");
6682
+ if (!existsSync10(graphPath)) {
6316
6683
  return {
6317
6684
  exitCode: ExitCode.FILE_NOT_FOUND,
6318
6685
  result: err("FILE_NOT_FOUND", {
@@ -6323,7 +6690,7 @@ async function runCanvasGenerate(input) {
6323
6690
  }
6324
6691
  let raw;
6325
6692
  try {
6326
- raw = await readFile16(graphPath, "utf8");
6693
+ raw = await readFile15(graphPath, "utf8");
6327
6694
  } catch (e) {
6328
6695
  return {
6329
6696
  exitCode: ExitCode.FILE_NOT_FOUND,
@@ -6349,7 +6716,7 @@ async function runCanvasGenerate(input) {
6349
6716
  const nodes = buildCanvasNodes(paths);
6350
6717
  const edges = buildCanvasEdges(graph.adjacency);
6351
6718
  const canvas = { nodes, edges };
6352
- const outPath = join31(input.vault, "vault-graph.canvas");
6719
+ const outPath = join32(input.vault, "vault-graph.canvas");
6353
6720
  try {
6354
6721
  await writeFile10(outPath, JSON.stringify(canvas, null, 2));
6355
6722
  } catch (e) {
@@ -6371,10 +6738,10 @@ written: ${outPath}`
6371
6738
  }
6372
6739
 
6373
6740
  // src/commands/fleet-health.ts
6374
- import { existsSync as existsSync9, readFileSync as readFileSync12 } from "fs";
6741
+ import { existsSync as existsSync11, readFileSync as readFileSync14 } from "fs";
6375
6742
  import { execSync as nodeExecSync } from "child_process";
6376
6743
  import { hostname as nodeHostname, platform as nodePlatform } from "os";
6377
- import { join as join32 } from "path";
6744
+ import { join as join33 } from "path";
6378
6745
  var SSH_TIMEOUT_MS = 15e3;
6379
6746
  var TIMER_UNIT = "agent-memory-trends.timer";
6380
6747
  var SERVICE_UNIT = "agent-memory-trends.service";
@@ -6471,9 +6838,9 @@ function applyServiceFailedOverlay(run, serviceFailed) {
6471
6838
  function probeLocal(vaultPath, deps) {
6472
6839
  const latestPath = satelliteLatestRunPath(vaultPath);
6473
6840
  let parsed = null;
6474
- if (existsSync9(latestPath)) {
6841
+ if (existsSync11(latestPath)) {
6475
6842
  try {
6476
- const wire = readSatelliteLatestRunFromText(readFileSync12(latestPath, "utf8"));
6843
+ const wire = readSatelliteLatestRunFromText(readFileSync14(latestPath, "utf8"));
6477
6844
  if (wire) {
6478
6845
  parsed = {
6479
6846
  status: wire.status,
@@ -6604,7 +6971,7 @@ async function runFleetHealth(input) {
6604
6971
  const home = input.home ?? env.HOME ?? "";
6605
6972
  const osHostname = input.osHostname ?? env.HOSTNAME ?? nodeHostname();
6606
6973
  const vault = input.vault ?? env.WIKI_PATH;
6607
- const file = input.file ?? (vault ? join32(vault, FLEET_REL_PATH) : void 0);
6974
+ const file = input.file ?? (vault ? join33(vault, FLEET_REL_PATH) : void 0);
6608
6975
  if (!file) {
6609
6976
  return {
6610
6977
  exitCode: ExitCode.NO_VAULT_CONFIGURED,
@@ -6688,15 +7055,15 @@ async function runFleetHealth(input) {
6688
7055
  }
6689
7056
 
6690
7057
  // src/utils/auto-commit.ts
6691
- import { existsSync as existsSync10 } from "fs";
6692
- import { join as join33 } from "path";
7058
+ import { existsSync as existsSync12 } from "fs";
7059
+ import { join as join34 } from "path";
6693
7060
  async function postCommit(vault, exitCode) {
6694
7061
  if (exitCode !== 0) return;
6695
7062
  const home = process.env.HOME ?? "";
6696
7063
  const dotenv = await parseDotenvFile(configPath(home));
6697
7064
  const autoCommit = process.env.AUTO_COMMIT ?? dotenv["AUTO_COMMIT"];
6698
7065
  if (autoCommit === "false") return;
6699
- if (!existsSync10(join33(vault, ".git"))) return;
7066
+ if (!existsSync12(join34(vault, ".git"))) return;
6700
7067
  const lastOps = readLastOp(vault);
6701
7068
  if (lastOps.length === 0) return;
6702
7069
  const porcelain = git(vault, ["status", "--porcelain", "--", ...VAULT_COMMIT_PATHSPEC]);
@@ -6758,7 +7125,7 @@ async function emitManagedVaultWrite(vault, command, mutate, opts) {
6758
7125
  if (guard.blocked) {
6759
7126
  return emit({ exitCode: guard.exitCode, result: guard.result }, void 0, { postCommit: false });
6760
7127
  }
6761
- const { runManagedWriteTransaction: runManagedWriteTransaction2 } = await import("./managed-write-preflight-57DQZI2N.js");
7128
+ const { runManagedWriteTransaction: runManagedWriteTransaction2 } = await import("./managed-write-preflight-7TWSEZJZ.js");
6762
7129
  const run = await runManagedWriteTransaction2({
6763
7130
  vault,
6764
7131
  command,
@@ -6783,7 +7150,7 @@ program.command("validate <file>").description("validate vault page frontmatter
6783
7150
  emit(await runValidate({ file, apply: !!opts.apply, vault }), vault);
6784
7151
  });
6785
7152
  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");
7153
+ const out = opts.out ?? join35(vault, ".skillwiki", "graph.json");
6787
7154
  return emitGuardedVaultWrite(vault, "graph build", () => runGraphBuild({ vault, out }));
6788
7155
  });
6789
7156
  var canvasCmd = program.command("canvas").description("manage Obsidian canvas files");
@@ -6814,18 +7181,25 @@ program.command("install").description("install skillwiki SKILL.md files into ~/
6814
7181
  const skillsRoot = opts.skillsRoot ?? new URL("../skills/", import.meta.url).pathname;
6815
7182
  emit(await runInstall({ skillsRoot, target: opts.target, dryRun: !!opts.dryRun, symlink: !!opts.symlink, home: process.env.HOME ?? "", force: !!opts.force }));
6816
7183
  });
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) => {
7184
+ 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
7185
  const initTime = !!opts.initTime;
6819
7186
  const flag = initTime ? opts.target : opts.vault;
6820
- emit(await runPath({
7187
+ const r = await runPath({
6821
7188
  flag,
6822
7189
  envValue: process.env.WIKI_PATH,
6823
7190
  home: process.env.HOME ?? "",
6824
7191
  initTime,
6825
7192
  wiki: opts.wiki,
6826
7193
  cwd: process.cwd(),
6827
- explain: !!opts.explain
6828
- }));
7194
+ explain: !!opts.explain,
7195
+ plain: !!opts.plain
7196
+ });
7197
+ if (opts.plain && r.result.ok) {
7198
+ process.stdout.write(`${r.result.data.path}
7199
+ `);
7200
+ process.exit(r.exitCode);
7201
+ }
7202
+ emit(r);
6829
7203
  });
6830
7204
  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
7205
  emit(await runLang({
@@ -6971,15 +7345,44 @@ program.command("log-rotate [vault]").description("rotate or trim the vault log
6971
7345
  );
6972
7346
  else emit(await runLogRotate({ vault: v.vault, threshold: opts.threshold, apply: false }), v.vault);
6973
7347
  });
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) => {
7348
+ 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
7349
  const v = await resolveVaultArg(vault, opts.wiki);
6976
7350
  if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
6977
7351
  else return emitManagedVaultWrite(
6978
7352
  v.vault,
6979
7353
  "log-append",
6980
- () => runLogAppend({ vault: v.vault, content: opts.content })
7354
+ () => runLogAppend({
7355
+ vault: v.vault,
7356
+ content: opts.content,
7357
+ operationId: opts.operationId,
7358
+ writeEvent: !!opts.writeEvent || !!opts.operationId
7359
+ })
7360
+ );
7361
+ });
7362
+ 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) => {
7363
+ const v = await resolveVaultArg(vault, opts.wiki);
7364
+ if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
7365
+ else return emitManagedVaultWrite(
7366
+ v.vault,
7367
+ "work-complete",
7368
+ () => runWorkComplete({
7369
+ vault: v.vault,
7370
+ workItem: opts.workItem,
7371
+ operationId: opts.operationId,
7372
+ noCommit: opts.commit === false
7373
+ }),
7374
+ { postCommit: false }
6981
7375
  );
6982
7376
  });
7377
+ 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) => {
7378
+ const v = await resolveVaultArg(vault, opts.wiki);
7379
+ if (!v.ok) emit({ exitCode: v.exitCode, result: v.payload });
7380
+ else emit(await runWorkValidate({
7381
+ vault: v.vault,
7382
+ workItem: opts.workItem,
7383
+ requireComplete: !!opts.requireComplete
7384
+ }), v.vault, { postCommit: false });
7385
+ });
6983
7386
  var logCmd = program.command("log").description("immutable log events and projections");
6984
7387
  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
7388
  const v = await resolveVaultArg(vault, opts.wiki);