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/{chunk-4KGCTQM3.js → chunk-3ZVZ2YEE.js} +1 -1
- package/dist/{chunk-45SMQAWC.js → chunk-AVCGIWSL.js} +456 -249
- package/dist/{chunk-R6BKJWVC.js → chunk-TYN2IHBY.js} +2 -0
- package/dist/cli.js +750 -347
- package/dist/{managed-write-preflight-57DQZI2N.js → managed-write-preflight-7TWSEZJZ.js} +2 -2
- package/dist/skillwiki-mcp.js +2 -2
- package/package.json +1 -1
- package/skills/.claude-plugin/plugin.json +1 -1
- package/skills/.codex-plugin/plugin.json +1 -1
- package/skills/package.json +1 -1
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
|
-
|
|
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-
|
|
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-
|
|
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
|
|
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 {
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
1210
|
+
let open2 = false;
|
|
1191
1211
|
let sep2 = false;
|
|
1192
1212
|
for (const line of lines) {
|
|
1193
1213
|
if (line.startsWith("<<<<<<< ")) {
|
|
1194
|
-
|
|
1214
|
+
open2 = true;
|
|
1195
1215
|
sep2 = false;
|
|
1196
1216
|
continue;
|
|
1197
1217
|
}
|
|
1198
|
-
if (line === "=======" &&
|
|
1218
|
+
if (line === "=======" && open2) {
|
|
1199
1219
|
sep2 = true;
|
|
1200
1220
|
continue;
|
|
1201
1221
|
}
|
|
1202
|
-
if (line.startsWith(">>>>>>> ") &&
|
|
1222
|
+
if (line.startsWith(">>>>>>> ") && open2 && sep2) return true;
|
|
1203
1223
|
if (line.startsWith(">>>>>>> ")) {
|
|
1204
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
1601
|
+
curIndex = readFileSync8(join12(input.vault, "index.md"), "utf8");
|
|
1719
1602
|
} catch {
|
|
1720
1603
|
}
|
|
1721
1604
|
try {
|
|
1722
|
-
curLog = readFileSync8(
|
|
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 =
|
|
1757
|
-
const logPath =
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
|
1896
|
-
import { dirname as dirname4, join as
|
|
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 (!
|
|
2008
|
-
const lines =
|
|
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 ?
|
|
2030
|
-
const logDir = isMac ?
|
|
2031
|
-
const filterPath =
|
|
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 =
|
|
2034
|
-
if (syncMode === "optional" && !
|
|
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(
|
|
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 =
|
|
2050
|
-
const fetchPlist =
|
|
2051
|
-
checks.push(
|
|
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 =
|
|
2055
|
-
const fetchTimer =
|
|
2056
|
-
const fuseTimer =
|
|
2057
|
-
const fuseService =
|
|
2058
|
-
checks.push(
|
|
2059
|
-
checks.push(
|
|
2060
|
-
}
|
|
2061
|
-
checks.push(classifyLog(
|
|
2062
|
-
checks.push(classifyLog(
|
|
2063
|
-
if (!
|
|
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 =
|
|
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
|
-
|
|
2526
|
+
mkdirSync2(dirname4(out), { recursive: true });
|
|
2160
2527
|
const tmp = `${out}.tmp-${process.pid}-${Date.now()}`;
|
|
2161
|
-
|
|
2162
|
-
|
|
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
|
|
2386
|
-
import { join as
|
|
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
|
|
2390
|
-
import { join as
|
|
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 =
|
|
2422
|
-
await
|
|
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(
|
|
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 =
|
|
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
|
|
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 =
|
|
2516
|
-
const text = await
|
|
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
|
|
2534
|
-
await rename2(
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
3011
|
+
await unlink2(join19(input.vault, relPath));
|
|
2645
3012
|
if (relPath.endsWith(".md") && !relPath.startsWith("raw/")) {
|
|
2646
|
-
const { readFile:
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
3137
|
-
import { join as
|
|
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 =
|
|
3144
|
-
const hasLocalSource =
|
|
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(
|
|
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(
|
|
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
|
|
3276
|
-
import { join as
|
|
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 =
|
|
3645
|
+
const dir = join22(input.vault, "raw", "transcripts");
|
|
3279
3646
|
let entries;
|
|
3280
3647
|
try {
|
|
3281
|
-
entries = await
|
|
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 =
|
|
3289
|
-
const content = await
|
|
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
|
|
3307
|
-
import { join as
|
|
3308
|
-
import { existsSync as
|
|
3309
|
-
import { readFile as
|
|
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 =
|
|
3774
|
+
const logPath = join23(input.vault, "log.md");
|
|
3408
3775
|
let logText;
|
|
3409
3776
|
try {
|
|
3410
|
-
logText = await
|
|
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 =
|
|
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 =
|
|
3426
|
-
if (
|
|
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 (!
|
|
3465
|
-
await
|
|
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 =
|
|
3487
|
-
if (!
|
|
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 =
|
|
3495
|
-
if (!
|
|
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 =
|
|
3529
|
-
if (!
|
|
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
|
|
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 =
|
|
3926
|
+
const filePath = join23(compoundDir, dirent.name);
|
|
3560
3927
|
let text;
|
|
3561
3928
|
try {
|
|
3562
|
-
text = await
|
|
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
|
|
3592
|
-
import { join as
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
3934
|
-
const cacheMdPath =
|
|
3935
|
-
const cacheJsonPath =
|
|
3936
|
-
await
|
|
3937
|
-
await
|
|
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(
|
|
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
|
|
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
|
|
4060
|
-
import { join as
|
|
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
|
|
4066
|
-
import { join as
|
|
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
|
|
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 =
|
|
4574
|
+
const schemaPath = join25(vault, "SCHEMA.md");
|
|
4208
4575
|
let schemaText;
|
|
4209
4576
|
try {
|
|
4210
|
-
schemaText = await
|
|
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
|
-
|
|
4241
|
-
|
|
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
|
|
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 =
|
|
4700
|
+
const schemaPath = join25(vault, "SCHEMA.md");
|
|
4334
4701
|
let schemaText;
|
|
4335
4702
|
try {
|
|
4336
|
-
schemaText = await
|
|
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 =
|
|
4715
|
+
const indexPath = join25(vault, "index.md");
|
|
4349
4716
|
let indexText;
|
|
4350
4717
|
try {
|
|
4351
|
-
indexText = await
|
|
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 =
|
|
4729
|
+
const logPath = join25(vault, "log.md");
|
|
4363
4730
|
let logText;
|
|
4364
4731
|
try {
|
|
4365
|
-
logText = await
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
5119
|
-
import { join as
|
|
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
|
|
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 ?
|
|
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
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
|
5312
|
-
import { join as
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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
|
|
5861
|
-
import { join as
|
|
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
|
|
6250
|
+
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
5884
6251
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
5885
|
-
const full =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
5998
|
-
|
|
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
|
|
6031
|
-
import { readFile as
|
|
6032
|
-
import { join as
|
|
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 (!
|
|
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
|
|
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
|
|
6120
|
-
import { join as
|
|
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(
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
6235
|
-
import { existsSync as
|
|
6236
|
-
import { join as
|
|
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 ??
|
|
6315
|
-
if (!
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 (
|
|
6841
|
+
if (existsSync11(latestPath)) {
|
|
6475
6842
|
try {
|
|
6476
|
-
const wire = readSatelliteLatestRunFromText(
|
|
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 ?
|
|
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
|
|
6692
|
-
import { join as
|
|
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 (!
|
|
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-
|
|
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 ??
|
|
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
|
-
|
|
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({
|
|
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);
|