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