skillwiki 0.9.1 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1125 -683
- package/package.json +1 -1
- package/skills/.claude-plugin/plugin.json +1 -1
- package/skills/.codex-plugin/plugin.json +1 -1
- package/skills/agents/proj-decide.md +1 -0
- package/skills/agents/proj-distill.md +1 -0
- package/skills/agents/proj-init.md +1 -0
- package/skills/agents/proj-work.md +1 -0
- package/skills/agents/wiki-adapter-prd.md +8 -5
- package/skills/agents/wiki-add-task.md +8 -5
- package/skills/agents/wiki-archive.md +1 -0
- package/skills/agents/wiki-audit.md +1 -0
- package/skills/agents/wiki-canvas.md +1 -0
- package/skills/agents/wiki-crystallize.md +5 -2
- package/skills/agents/wiki-ingest.md +8 -5
- package/skills/agents/wiki-lint.md +5 -3
- package/skills/agents/wiki-query.md +4 -1
- package/skills/agents/wiki-reingest.md +6 -3
- package/skills/agents/wiki-sync.md +1 -0
- package/skills/package.json +1 -1
- package/skills/skills/using-skillwiki/SKILL.md +26 -1
- package/skills/skills/wiki-adapter-prd/SKILL.md +8 -5
- package/skills/skills/wiki-add-task/SKILL.md +9 -6
- package/skills/skills/wiki-crystallize/SKILL.md +5 -2
- package/skills/skills/wiki-ingest/SKILL.md +8 -6
- package/skills/skills/wiki-lint/SKILL.md +5 -3
- package/skills/skills/wiki-query/SKILL.md +4 -1
- package/skills/using-skillwiki/SKILL.md +26 -1
- package/skills/wiki-adapter-prd/SKILL.md +8 -5
- package/skills/wiki-add-task/SKILL.md +9 -6
- package/skills/wiki-crystallize/SKILL.md +5 -2
- package/skills/wiki-ingest/SKILL.md +8 -6
- package/skills/wiki-lint/SKILL.md +5 -3
- package/skills/wiki-query/SKILL.md +4 -1
package/dist/cli.js
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from "./chunk-TPS5XD2J.js";
|
|
15
15
|
|
|
16
16
|
// src/cli.ts
|
|
17
|
-
import { join as
|
|
17
|
+
import { join as join48 } from "path";
|
|
18
18
|
import { Command as Command2 } from "commander";
|
|
19
19
|
|
|
20
20
|
// ../shared/src/exit-codes.ts
|
|
@@ -69,7 +69,8 @@ var ExitCode = {
|
|
|
69
69
|
BODY_TRUNCATION_GUARD: 47,
|
|
70
70
|
SYNC_LOCK_HELD: 48,
|
|
71
71
|
LOG_APPEND_LOCK_HELD: 49,
|
|
72
|
-
FLEET_MANIFEST_INVALID: 50
|
|
72
|
+
FLEET_MANIFEST_INVALID: 50,
|
|
73
|
+
SENSITIVE_CONTENT_DETECTED: 51
|
|
73
74
|
};
|
|
74
75
|
|
|
75
76
|
// ../shared/src/json-output.ts
|
|
@@ -450,6 +451,148 @@ function sanitizeUrl(u) {
|
|
|
450
451
|
// src/commands/validate.ts
|
|
451
452
|
import { readFile as readFile2, writeFile } from "fs/promises";
|
|
452
453
|
import { join as join2, resolve, relative, sep } from "path";
|
|
454
|
+
|
|
455
|
+
// src/utils/sensitive-content.ts
|
|
456
|
+
import { createHash as createHash2 } from "crypto";
|
|
457
|
+
var REDACTED_RE = /\[REDACTED:[^\]]+\]/i;
|
|
458
|
+
var SYNTHETIC_RE = /^(?:<[^>]+>|\$\{[^}]+\}|REPLACE_WITH_[A-Z0-9_]+|YOUR_[A-Z0-9_]+|EXAMPLE_[A-Z0-9_]+)$/i;
|
|
459
|
+
var MATCHERS = [
|
|
460
|
+
{
|
|
461
|
+
kind: "private_key",
|
|
462
|
+
re: /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]*PRIVATE KEY-----/g
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
kind: "authorization_header",
|
|
466
|
+
re: /\bAuthorization["']?\s*:\s*["']?(Bearer\s+[A-Za-z0-9._~+/-]{20,})["']?/gi,
|
|
467
|
+
valueGroup: 1
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
kind: "cookie",
|
|
471
|
+
re: /\b(?:Cookie|Set-Cookie)\s*:\s*([^\n]{20,})/gi,
|
|
472
|
+
valueGroup: 1
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
kind: "jwt",
|
|
476
|
+
re: /\b([A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{12,}\.[A-Za-z0-9_-]{12,})\b/g,
|
|
477
|
+
valueGroup: 1
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
kind: "provider_key",
|
|
481
|
+
re: /\b(sk-[A-Za-z0-9_-]{20,}|xox[baprs]-[A-Za-z0-9-]{20,}|gh[pousr]_[A-Za-z0-9_=-]{20,})\b/g,
|
|
482
|
+
valueGroup: 1
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
kind: "access_key",
|
|
486
|
+
re: /\b((?:AKIA|ASIA)[A-Z0-9]{16})\b/g,
|
|
487
|
+
valueGroup: 1
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
kind: "access_key",
|
|
491
|
+
re: /\b(?:access[-_ ]?key|credential)["']?\s*[:=]\s*["']?([A-Za-z0-9._~+/-]{20,})["']?/gi,
|
|
492
|
+
valueGroup: 1
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
kind: "api_key",
|
|
496
|
+
re: /\b(?:api[-_ ]?key)["']?\s*[:=]\s*["']?([A-Za-z0-9._~+/-]{20,})["']?/gi,
|
|
497
|
+
valueGroup: 1
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
kind: "password",
|
|
501
|
+
re: /\b(?:pass(?:word|wd)?)["']?\s*[:=]\s*["']?([^\s`"']{8,})["']?/gi,
|
|
502
|
+
valueGroup: 1
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
kind: "secret",
|
|
506
|
+
re: /\b(?:secret|client[-_ ]?secret)["']?\s*[:=]\s*["']?([A-Za-z0-9._~+/-]{16,})["']?/gi,
|
|
507
|
+
valueGroup: 1
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
kind: "token",
|
|
511
|
+
re: /\b(?:token|session)["']?\s*[:=]\s*["']?([A-Za-z0-9._~+/-]{16,})["']?/gi,
|
|
512
|
+
valueGroup: 1
|
|
513
|
+
}
|
|
514
|
+
];
|
|
515
|
+
function fingerprint(value) {
|
|
516
|
+
return createHash2("sha256").update(value).digest("hex").slice(0, 12);
|
|
517
|
+
}
|
|
518
|
+
function lineFor(text, offset) {
|
|
519
|
+
return text.slice(0, offset).split(/\r?\n/).length;
|
|
520
|
+
}
|
|
521
|
+
function redactMarker(kind, value) {
|
|
522
|
+
return `[REDACTED:${kind}:${fingerprint(value)}]`;
|
|
523
|
+
}
|
|
524
|
+
function isSyntheticPlaceholder(value) {
|
|
525
|
+
return REDACTED_RE.test(value) || SYNTHETIC_RE.test(value.trim());
|
|
526
|
+
}
|
|
527
|
+
function collectMatches(text) {
|
|
528
|
+
const matches = [];
|
|
529
|
+
for (const matcher of MATCHERS) {
|
|
530
|
+
matcher.re.lastIndex = 0;
|
|
531
|
+
for (const m of text.matchAll(matcher.re)) {
|
|
532
|
+
const whole = m[0];
|
|
533
|
+
const start = m.index ?? 0;
|
|
534
|
+
if (REDACTED_RE.test(whole)) continue;
|
|
535
|
+
const value = matcher.valueGroup ? m[matcher.valueGroup] : whole;
|
|
536
|
+
if (isSyntheticPlaceholder(value)) continue;
|
|
537
|
+
const valueOffset = whole.lastIndexOf(value);
|
|
538
|
+
const valueStart = start + Math.max(0, valueOffset);
|
|
539
|
+
matches.push({
|
|
540
|
+
start,
|
|
541
|
+
end: start + whole.length,
|
|
542
|
+
valueStart,
|
|
543
|
+
valueEnd: valueStart + value.length,
|
|
544
|
+
kind: matcher.kind
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
return matches.sort((a, b) => {
|
|
549
|
+
if (a.valueStart !== b.valueStart) return a.valueStart - b.valueStart;
|
|
550
|
+
return b.valueEnd - b.valueStart - (a.valueEnd - a.valueStart);
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
function collapseOverlaps(matches) {
|
|
554
|
+
const kept = [];
|
|
555
|
+
for (const match of matches) {
|
|
556
|
+
const overlaps = kept.some((k) => match.valueStart < k.valueEnd && match.valueEnd > k.valueStart);
|
|
557
|
+
if (!overlaps) kept.push(match);
|
|
558
|
+
}
|
|
559
|
+
return kept;
|
|
560
|
+
}
|
|
561
|
+
function scanSensitiveContent(text, opts = {}) {
|
|
562
|
+
return collapseOverlaps(collectMatches(text)).map((match) => {
|
|
563
|
+
const value = text.slice(match.valueStart, match.valueEnd);
|
|
564
|
+
const marker = redactMarker(match.kind, value);
|
|
565
|
+
const rawPreview = text.slice(Math.max(0, match.start - 24), Math.min(text.length, match.end + 24));
|
|
566
|
+
const preview = rawPreview.replace(value, marker);
|
|
567
|
+
return {
|
|
568
|
+
file: opts.file,
|
|
569
|
+
line: lineFor(text, match.valueStart),
|
|
570
|
+
kind: match.kind,
|
|
571
|
+
preview,
|
|
572
|
+
fingerprint: fingerprint(value)
|
|
573
|
+
};
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
function redactSensitiveContent(text, opts = {}) {
|
|
577
|
+
const matches = collapseOverlaps(collectMatches(text));
|
|
578
|
+
if (matches.length === 0) return { text, changed: false, findings: [] };
|
|
579
|
+
let out = "";
|
|
580
|
+
let cursor = 0;
|
|
581
|
+
for (const match of matches) {
|
|
582
|
+
const value = text.slice(match.valueStart, match.valueEnd);
|
|
583
|
+
out += text.slice(cursor, match.valueStart);
|
|
584
|
+
out += redactMarker(match.kind, value);
|
|
585
|
+
cursor = match.valueEnd;
|
|
586
|
+
}
|
|
587
|
+
out += text.slice(cursor);
|
|
588
|
+
return {
|
|
589
|
+
text: out,
|
|
590
|
+
changed: out !== text,
|
|
591
|
+
findings: scanSensitiveContent(text, opts)
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// src/commands/validate.ts
|
|
453
596
|
var TYPE_TO_SECTION = {
|
|
454
597
|
entity: "Entities",
|
|
455
598
|
concept: "Concepts",
|
|
@@ -480,6 +623,26 @@ async function runValidate(input) {
|
|
|
480
623
|
return { exitCode: ExitCode.INVALID_FRONTMATTER, result: fm };
|
|
481
624
|
}
|
|
482
625
|
const det = detectSchema(fm.data);
|
|
626
|
+
const sensitiveFindings = scanSensitiveContent(text, { file: input.file });
|
|
627
|
+
if (sensitiveFindings.length > 0) {
|
|
628
|
+
const errors = sensitiveFindings.map((f) => ({
|
|
629
|
+
path: "sensitive_content",
|
|
630
|
+
message: `${f.kind} at line ${f.line}: ${f.preview}`
|
|
631
|
+
}));
|
|
632
|
+
return {
|
|
633
|
+
exitCode: ExitCode.SENSITIVE_CONTENT_DETECTED,
|
|
634
|
+
result: ok({
|
|
635
|
+
schema: det.schema,
|
|
636
|
+
valid: false,
|
|
637
|
+
errors,
|
|
638
|
+
index_updated: false,
|
|
639
|
+
log_updated: false,
|
|
640
|
+
humanHint: `SENSITIVE CONTENT (${sensitiveFindings.length})
|
|
641
|
+
${errors.map((e) => ` ${e.message}`).join("\n")}`,
|
|
642
|
+
sensitive_findings: sensitiveFindings
|
|
643
|
+
})
|
|
644
|
+
};
|
|
645
|
+
}
|
|
483
646
|
if (!det.schema) {
|
|
484
647
|
return { exitCode: ExitCode.SCHEMA_NOT_DETECTED, result: ok({ schema: null, valid: false, errors: [], index_updated: false, log_updated: false, humanHint: "schema not detected" }) };
|
|
485
648
|
}
|
|
@@ -1159,8 +1322,8 @@ function simulateRemoval(adj, removed) {
|
|
|
1159
1322
|
}
|
|
1160
1323
|
|
|
1161
1324
|
// src/commands/audit.ts
|
|
1162
|
-
import { readFile as readFile5, stat as
|
|
1163
|
-
import { dirname as dirname3, resolve as resolve2, join as
|
|
1325
|
+
import { readFile as readFile5, stat as stat3 } from "fs/promises";
|
|
1326
|
+
import { dirname as dirname3, resolve as resolve2, join as join6 } from "path";
|
|
1164
1327
|
|
|
1165
1328
|
// src/parsers/citations.ts
|
|
1166
1329
|
var FENCE2 = /```[\s\S]*?```/g;
|
|
@@ -1178,6 +1341,7 @@ function extractCitationMarkers(body) {
|
|
|
1178
1341
|
const out = [];
|
|
1179
1342
|
let m;
|
|
1180
1343
|
while ((m = MARKER_RE.exec(stripped)) !== null) {
|
|
1344
|
+
if (m[1] === "raw/...") continue;
|
|
1181
1345
|
out.push({ marker: m[0], target: m[1] });
|
|
1182
1346
|
}
|
|
1183
1347
|
return out;
|
|
@@ -1267,6 +1431,41 @@ function hasWikilinkCitations(body) {
|
|
|
1267
1431
|
return /\[\[raw\/[^\]]+\]\]/.test(stripped);
|
|
1268
1432
|
}
|
|
1269
1433
|
|
|
1434
|
+
// src/utils/raw-source.ts
|
|
1435
|
+
import { existsSync } from "fs";
|
|
1436
|
+
import { stat as stat2 } from "fs/promises";
|
|
1437
|
+
import { join as join5 } from "path";
|
|
1438
|
+
function normalizeRawSourceTarget(entry) {
|
|
1439
|
+
let target = entry.trim().replace(/^"/, "").replace(/"$/, "").replace(/^'/, "").replace(/'$/, "");
|
|
1440
|
+
target = target.replace(/^\^\[/, "").replace(/\]$/, "");
|
|
1441
|
+
if (!target.startsWith("raw/") && !target.startsWith("_archive/raw/")) return null;
|
|
1442
|
+
return target;
|
|
1443
|
+
}
|
|
1444
|
+
function rawSourceTargetCandidates(vault, target) {
|
|
1445
|
+
const normalized = normalizeRawSourceTarget(target);
|
|
1446
|
+
if (!normalized) return [];
|
|
1447
|
+
const candidates = [join5(vault, normalized)];
|
|
1448
|
+
if (!normalized.endsWith(".md")) candidates.push(join5(vault, `${normalized}.md`));
|
|
1449
|
+
if (normalized.startsWith("raw/")) {
|
|
1450
|
+
candidates.push(join5(vault, "_archive", normalized));
|
|
1451
|
+
if (!normalized.endsWith(".md")) candidates.push(join5(vault, "_archive", `${normalized}.md`));
|
|
1452
|
+
}
|
|
1453
|
+
return [...new Set(candidates)];
|
|
1454
|
+
}
|
|
1455
|
+
function rawSourceTargetExistsSync(vault, target) {
|
|
1456
|
+
return rawSourceTargetCandidates(vault, target).some((candidate) => existsSync(candidate));
|
|
1457
|
+
}
|
|
1458
|
+
async function rawSourceTargetExists(vault, target) {
|
|
1459
|
+
for (const candidate of rawSourceTargetCandidates(vault, target)) {
|
|
1460
|
+
try {
|
|
1461
|
+
await stat2(candidate);
|
|
1462
|
+
return true;
|
|
1463
|
+
} catch {
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
return false;
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1270
1469
|
// src/commands/audit.ts
|
|
1271
1470
|
async function runAudit(input) {
|
|
1272
1471
|
let text;
|
|
@@ -1283,12 +1482,7 @@ async function runAudit(input) {
|
|
|
1283
1482
|
if (!vault) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID") };
|
|
1284
1483
|
const markers = extractCitationMarkers(body);
|
|
1285
1484
|
const resolved = await Promise.all(markers.map(async (m) => {
|
|
1286
|
-
|
|
1287
|
-
await stat2(join5(vault, m.target));
|
|
1288
|
-
return { ...m, resolved: true };
|
|
1289
|
-
} catch {
|
|
1290
|
-
return { ...m, resolved: false };
|
|
1291
|
-
}
|
|
1485
|
+
return { ...m, resolved: await rawSourceTargetExists(vault, m.target) };
|
|
1292
1486
|
}));
|
|
1293
1487
|
const sources = (fm.data.sources ?? []).map((s) => s.replace(/^\^\[/, "").replace(/\]$/, ""));
|
|
1294
1488
|
const referenced = new Set(resolved.map((m) => m.target));
|
|
@@ -1329,7 +1523,7 @@ async function findVaultRoot(start) {
|
|
|
1329
1523
|
let cur = start;
|
|
1330
1524
|
for (let i = 0; i < 20; i++) {
|
|
1331
1525
|
try {
|
|
1332
|
-
await
|
|
1526
|
+
await stat3(join6(cur, "SCHEMA.md"));
|
|
1333
1527
|
return cur;
|
|
1334
1528
|
} catch {
|
|
1335
1529
|
}
|
|
@@ -1381,17 +1575,17 @@ async function validateCompoundReferences(vault) {
|
|
|
1381
1575
|
}
|
|
1382
1576
|
|
|
1383
1577
|
// src/commands/install.ts
|
|
1384
|
-
import { readdir as readdir2, stat as
|
|
1385
|
-
import { join as
|
|
1578
|
+
import { readdir as readdir2, stat as stat5, symlink, unlink, mkdir as mkdir4, readFile as readFile6 } from "fs/promises";
|
|
1579
|
+
import { join as join7, resolve as resolve3, dirname as dirname5 } from "path";
|
|
1386
1580
|
|
|
1387
1581
|
// src/utils/install-fs.ts
|
|
1388
|
-
import { copyFile, mkdir as mkdir3, rename, writeFile as writeFile4, stat as
|
|
1582
|
+
import { copyFile, mkdir as mkdir3, rename, writeFile as writeFile4, stat as stat4 } from "fs/promises";
|
|
1389
1583
|
import { dirname as dirname4 } from "path";
|
|
1390
1584
|
async function atomicCopyWithBackup(src, dst) {
|
|
1391
1585
|
await mkdir3(dirname4(dst), { recursive: true });
|
|
1392
1586
|
let backupPath = null;
|
|
1393
1587
|
try {
|
|
1394
|
-
await
|
|
1588
|
+
await stat4(dst);
|
|
1395
1589
|
backupPath = `${dst}.bak`;
|
|
1396
1590
|
await copyFile(dst, backupPath);
|
|
1397
1591
|
} catch {
|
|
@@ -1445,7 +1639,7 @@ async function runInstall(input) {
|
|
|
1445
1639
|
const withSkill = [];
|
|
1446
1640
|
for (const d of dirs) {
|
|
1447
1641
|
try {
|
|
1448
|
-
await
|
|
1642
|
+
await stat5(join7(input.skillsRoot, d.name, "SKILL.md"));
|
|
1449
1643
|
withSkill.push(d.name);
|
|
1450
1644
|
} catch {
|
|
1451
1645
|
}
|
|
@@ -1462,10 +1656,10 @@ async function runInstall(input) {
|
|
|
1462
1656
|
const version_warnings = [];
|
|
1463
1657
|
const skillMetas = {};
|
|
1464
1658
|
for (const name of entries) {
|
|
1465
|
-
const src =
|
|
1466
|
-
const dst =
|
|
1659
|
+
const src = join7(input.skillsRoot, name, "SKILL.md");
|
|
1660
|
+
const dst = join7(input.target, name, "SKILL.md");
|
|
1467
1661
|
try {
|
|
1468
|
-
await
|
|
1662
|
+
await stat5(src);
|
|
1469
1663
|
} catch {
|
|
1470
1664
|
return { exitCode: ExitCode.PREFLIGHT_FAILED, result: err("PREFLIGHT_FAILED", { missing: src }) };
|
|
1471
1665
|
}
|
|
@@ -1504,10 +1698,10 @@ async function runInstall(input) {
|
|
|
1504
1698
|
if (r.data.backupPath) backed_up.push(r.data.backupPath);
|
|
1505
1699
|
}
|
|
1506
1700
|
}
|
|
1507
|
-
const binSrc =
|
|
1701
|
+
const binSrc = join7(input.skillsRoot, "bin", "skillwiki");
|
|
1508
1702
|
try {
|
|
1509
|
-
await
|
|
1510
|
-
const binDst =
|
|
1703
|
+
await stat5(binSrc);
|
|
1704
|
+
const binDst = join7(input.target, "bin", "skillwiki");
|
|
1511
1705
|
if (!input.dryRun) {
|
|
1512
1706
|
if (input.symlink) {
|
|
1513
1707
|
const r = await createSymlink(binSrc, binDst);
|
|
@@ -1524,7 +1718,7 @@ async function runInstall(input) {
|
|
|
1524
1718
|
}
|
|
1525
1719
|
} catch {
|
|
1526
1720
|
}
|
|
1527
|
-
const manifest_path =
|
|
1721
|
+
const manifest_path = join7(input.target, "wiki-manifest.json");
|
|
1528
1722
|
if (!input.dryRun) await writeManifest(manifest_path, { installed, backed_up, symlink: input.symlink || void 0, skills: skillMetas });
|
|
1529
1723
|
const mode = input.symlink ? "symlink (dev mode)" : "copy";
|
|
1530
1724
|
const hintLines = [
|
|
@@ -1566,7 +1760,7 @@ async function runPath(input) {
|
|
|
1566
1760
|
}
|
|
1567
1761
|
|
|
1568
1762
|
// src/utils/lang.ts
|
|
1569
|
-
import { join as
|
|
1763
|
+
import { join as join8 } from "path";
|
|
1570
1764
|
var ALIASES = {
|
|
1571
1765
|
english: "en",
|
|
1572
1766
|
en: "en",
|
|
@@ -1589,7 +1783,7 @@ async function resolveLang(input) {
|
|
|
1589
1783
|
if (input.envValue !== void 0 && input.envValue.length > 0) {
|
|
1590
1784
|
return { value: input.envValue, source: "env", canonical: normalizeLang(input.envValue) };
|
|
1591
1785
|
}
|
|
1592
|
-
const dotenv = await parseDotenvFile(
|
|
1786
|
+
const dotenv = await parseDotenvFile(join8(input.home, ".skillwiki", ".env"));
|
|
1593
1787
|
if (dotenv.WIKI_LANG !== void 0) {
|
|
1594
1788
|
return { value: dotenv.WIKI_LANG, source: "skillwiki-dotenv", canonical: normalizeLang(dotenv.WIKI_LANG) };
|
|
1595
1789
|
}
|
|
@@ -1597,7 +1791,7 @@ async function resolveLang(input) {
|
|
|
1597
1791
|
}
|
|
1598
1792
|
|
|
1599
1793
|
// src/commands/lang.ts
|
|
1600
|
-
import { join as
|
|
1794
|
+
import { join as join9 } from "path";
|
|
1601
1795
|
async function runLang(input) {
|
|
1602
1796
|
const resolved = await resolveLang({ flag: input.flag, envValue: input.envValue, home: input.home });
|
|
1603
1797
|
let chain;
|
|
@@ -1606,7 +1800,7 @@ async function runLang(input) {
|
|
|
1606
1800
|
{ source: "flag", matched: input.flag !== void 0 && input.flag.length > 0, value: input.flag },
|
|
1607
1801
|
{ source: "env", matched: input.envValue !== void 0 && input.envValue.length > 0, value: input.envValue }
|
|
1608
1802
|
];
|
|
1609
|
-
const sw = await parseDotenvFile(
|
|
1803
|
+
const sw = await parseDotenvFile(join9(input.home, ".skillwiki", ".env"));
|
|
1610
1804
|
chain.push({ source: "skillwiki-dotenv", matched: sw.WIKI_LANG !== void 0, value: sw.WIKI_LANG });
|
|
1611
1805
|
chain.push({ source: "default", matched: resolved.source === "default", value: "en" });
|
|
1612
1806
|
}
|
|
@@ -1624,7 +1818,7 @@ async function runLang(input) {
|
|
|
1624
1818
|
|
|
1625
1819
|
// src/commands/init.ts
|
|
1626
1820
|
import { mkdir as mkdir5, readFile as readFile7, readdir as readdir3, writeFile as writeFile5 } from "fs/promises";
|
|
1627
|
-
import { join as
|
|
1821
|
+
import { join as join11 } from "path";
|
|
1628
1822
|
|
|
1629
1823
|
// src/parsers/taxonomy.ts
|
|
1630
1824
|
import yaml2 from "js-yaml";
|
|
@@ -1652,16 +1846,16 @@ function extractTaxonomy(schemaText) {
|
|
|
1652
1846
|
}
|
|
1653
1847
|
|
|
1654
1848
|
// src/utils/last-op.ts
|
|
1655
|
-
import { readFileSync as readFileSync2, writeFileSync, mkdirSync, unlinkSync, existsSync } from "fs";
|
|
1656
|
-
import { join as
|
|
1849
|
+
import { readFileSync as readFileSync2, writeFileSync, mkdirSync, unlinkSync, existsSync as existsSync2 } from "fs";
|
|
1850
|
+
import { join as join10 } from "path";
|
|
1657
1851
|
var LAST_OP_DIR = ".skillwiki";
|
|
1658
1852
|
var LAST_OP_FILE = "last-op.json";
|
|
1659
1853
|
function lastOpPath(vault) {
|
|
1660
|
-
return
|
|
1854
|
+
return join10(vault, LAST_OP_DIR, LAST_OP_FILE);
|
|
1661
1855
|
}
|
|
1662
1856
|
function readLastOp(vault) {
|
|
1663
1857
|
const p = lastOpPath(vault);
|
|
1664
|
-
if (!
|
|
1858
|
+
if (!existsSync2(p)) return [];
|
|
1665
1859
|
try {
|
|
1666
1860
|
const raw = readFileSync2(p, "utf8");
|
|
1667
1861
|
const parsed = JSON.parse(raw);
|
|
@@ -1681,8 +1875,8 @@ function readLastOp(vault) {
|
|
|
1681
1875
|
function appendLastOp(vault, entry) {
|
|
1682
1876
|
const existing = readLastOp(vault);
|
|
1683
1877
|
existing.push(entry);
|
|
1684
|
-
const dir =
|
|
1685
|
-
if (!
|
|
1878
|
+
const dir = join10(vault, LAST_OP_DIR);
|
|
1879
|
+
if (!existsSync2(dir)) mkdirSync(dir, { recursive: true });
|
|
1686
1880
|
writeFileSync(lastOpPath(vault), JSON.stringify(existing, null, 2), "utf8");
|
|
1687
1881
|
}
|
|
1688
1882
|
function clearLastOp(vault) {
|
|
@@ -1735,13 +1929,13 @@ async function discoverTagsFromPages(target, knownSlugs) {
|
|
|
1735
1929
|
for (const dir of ["entities", "concepts", "comparisons", "queries"]) {
|
|
1736
1930
|
let entries;
|
|
1737
1931
|
try {
|
|
1738
|
-
entries = (await readdir3(
|
|
1932
|
+
entries = (await readdir3(join11(target, dir), { withFileTypes: true })).filter((e) => e.isFile() && e.name.endsWith(".md")).map((e) => e.name);
|
|
1739
1933
|
} catch {
|
|
1740
1934
|
continue;
|
|
1741
1935
|
}
|
|
1742
1936
|
for (const file of entries) {
|
|
1743
1937
|
try {
|
|
1744
|
-
const text = await readFile7(
|
|
1938
|
+
const text = await readFile7(join11(target, dir, file), "utf8");
|
|
1745
1939
|
const fm = extractFrontmatter(text);
|
|
1746
1940
|
if (!fm.ok || !fm.data.tags || !Array.isArray(fm.data.tags)) continue;
|
|
1747
1941
|
for (const t of fm.data.tags) {
|
|
@@ -1760,7 +1954,7 @@ async function runInit(input) {
|
|
|
1760
1954
|
const canonicalLang = langRes.canonical;
|
|
1761
1955
|
let oldSchemaText;
|
|
1762
1956
|
try {
|
|
1763
|
-
oldSchemaText = await readFile7(
|
|
1957
|
+
oldSchemaText = await readFile7(join11(target, "SCHEMA.md"), "utf8");
|
|
1764
1958
|
} catch {
|
|
1765
1959
|
}
|
|
1766
1960
|
if (oldSchemaText && !input.force) {
|
|
@@ -1769,7 +1963,7 @@ async function runInit(input) {
|
|
|
1769
1963
|
result: err("INIT_TARGET_NOT_EMPTY", { target })
|
|
1770
1964
|
};
|
|
1771
1965
|
}
|
|
1772
|
-
const envPath =
|
|
1966
|
+
const envPath = join11(input.home, ".skillwiki", ".env");
|
|
1773
1967
|
let existingEnvRaw = "";
|
|
1774
1968
|
try {
|
|
1775
1969
|
existingEnvRaw = await readFile7(envPath, "utf8");
|
|
@@ -1795,7 +1989,7 @@ async function runInit(input) {
|
|
|
1795
1989
|
try {
|
|
1796
1990
|
await mkdir5(target, { recursive: true });
|
|
1797
1991
|
for (const d of VAULT_DIRS) {
|
|
1798
|
-
await mkdir5(
|
|
1992
|
+
await mkdir5(join11(target, d), { recursive: true });
|
|
1799
1993
|
created.push(d + "/");
|
|
1800
1994
|
}
|
|
1801
1995
|
} catch (e) {
|
|
@@ -1824,9 +2018,9 @@ async function runInit(input) {
|
|
|
1824
2018
|
const discovered_tags = discovered.length;
|
|
1825
2019
|
const fullTaxonomyYaml = discovered.length > 0 ? taxonomy.map((t) => ` - ${t}`).join("\n") + "\n # --- Discovered from existing pages ---\n" + discovered.map((t) => ` - ${t}`).join("\n") : taxonomy.map((t) => ` - ${t}`).join("\n");
|
|
1826
2020
|
try {
|
|
1827
|
-
const schemaTpl = await readFile7(
|
|
2021
|
+
const schemaTpl = await readFile7(join11(input.templates, "SCHEMA.md"), "utf8");
|
|
1828
2022
|
const schema = schemaTpl.replace("{{DOMAIN}}", domain).replace("{{WIKI_LANG}}", canonicalLang).replace("{{TAXONOMY_YAML}}", fullTaxonomyYaml);
|
|
1829
|
-
await writeFile5(
|
|
2023
|
+
await writeFile5(join11(target, "SCHEMA.md"), schema, "utf8");
|
|
1830
2024
|
created.push("SCHEMA.md");
|
|
1831
2025
|
} catch (e) {
|
|
1832
2026
|
return { exitCode: ExitCode.WRITE_FAILED, result: err("WRITE_FAILED", { file: "SCHEMA.md", message: String(e) }) };
|
|
@@ -1834,7 +2028,7 @@ async function runInit(input) {
|
|
|
1834
2028
|
const preserved = [];
|
|
1835
2029
|
async function writeOrPreserve(fileName, render) {
|
|
1836
2030
|
try {
|
|
1837
|
-
const existing = await readFile7(
|
|
2031
|
+
const existing = await readFile7(join11(target, fileName), "utf8");
|
|
1838
2032
|
if (existing.split("\n").length > 10) {
|
|
1839
2033
|
preserved.push(fileName);
|
|
1840
2034
|
return void 0;
|
|
@@ -1842,7 +2036,7 @@ async function runInit(input) {
|
|
|
1842
2036
|
} catch {
|
|
1843
2037
|
}
|
|
1844
2038
|
try {
|
|
1845
|
-
await writeFile5(
|
|
2039
|
+
await writeFile5(join11(target, fileName), await render(), "utf8");
|
|
1846
2040
|
created.push(fileName);
|
|
1847
2041
|
return void 0;
|
|
1848
2042
|
} catch (e) {
|
|
@@ -1850,7 +2044,7 @@ async function runInit(input) {
|
|
|
1850
2044
|
}
|
|
1851
2045
|
}
|
|
1852
2046
|
const err1 = await writeOrPreserve("index.md", async () => {
|
|
1853
|
-
const tpl = await readFile7(
|
|
2047
|
+
const tpl = await readFile7(join11(input.templates, "index.md"), "utf8");
|
|
1854
2048
|
return tpl.replace("{{INIT_DATE}}", today);
|
|
1855
2049
|
});
|
|
1856
2050
|
if (err1) return err1;
|
|
@@ -1877,7 +2071,7 @@ async function runInit(input) {
|
|
|
1877
2071
|
});
|
|
1878
2072
|
if (errTemplate) return errTemplate;
|
|
1879
2073
|
const err22 = await writeOrPreserve("log.md", async () => {
|
|
1880
|
-
const tpl = await readFile7(
|
|
2074
|
+
const tpl = await readFile7(join11(input.templates, "log.md"), "utf8");
|
|
1881
2075
|
return tpl.replace(/\{\{INIT_DATE\}\}/g, today).replace("{{DOMAIN}}", domain).replace("{{WIKI_LANG}}", canonicalLang);
|
|
1882
2076
|
});
|
|
1883
2077
|
if (err22) return err22;
|
|
@@ -1975,11 +2169,11 @@ ${broken.map((b) => ` ${b.page}:[[${b.slug}]] (line ${b.line})`).join("\n")}` }
|
|
|
1975
2169
|
|
|
1976
2170
|
// src/commands/tag-audit.ts
|
|
1977
2171
|
import { readFile as readFile8 } from "fs/promises";
|
|
1978
|
-
import { join as
|
|
2172
|
+
import { join as join12 } from "path";
|
|
1979
2173
|
async function runTagAudit(input) {
|
|
1980
2174
|
const scan = await scanVault(input.vault);
|
|
1981
2175
|
if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
|
|
1982
|
-
const schemaText = await readFile8(
|
|
2176
|
+
const schemaText = await readFile8(join12(input.vault, "SCHEMA.md"), "utf8");
|
|
1983
2177
|
const tax = extractTaxonomy(schemaText);
|
|
1984
2178
|
if (!tax.ok) return { exitCode: ExitCode.INVALID_FRONTMATTER, result: tax };
|
|
1985
2179
|
const allowed = new Set(tax.data);
|
|
@@ -2004,13 +2198,13 @@ async function runTagAudit(input) {
|
|
|
2004
2198
|
|
|
2005
2199
|
// src/commands/index-check.ts
|
|
2006
2200
|
import { readFile as readFile9 } from "fs/promises";
|
|
2007
|
-
import { join as
|
|
2201
|
+
import { join as join13 } from "path";
|
|
2008
2202
|
async function runIndexCheck(input) {
|
|
2009
2203
|
const scan = await scanVault(input.vault);
|
|
2010
2204
|
if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
|
|
2011
2205
|
let indexText = "";
|
|
2012
2206
|
try {
|
|
2013
|
-
indexText = await readFile9(
|
|
2207
|
+
indexText = await readFile9(join13(input.vault, "index.md"), "utf8");
|
|
2014
2208
|
} catch {
|
|
2015
2209
|
}
|
|
2016
2210
|
const indexSlugsLower = /* @__PURE__ */ new Map();
|
|
@@ -2050,7 +2244,7 @@ async function runIndexCheck(input) {
|
|
|
2050
2244
|
|
|
2051
2245
|
// src/commands/stale.ts
|
|
2052
2246
|
import { readdir as readdir4, rename as rename2, mkdir as mkdir6, readFile as readFile10 } from "fs/promises";
|
|
2053
|
-
import { join as
|
|
2247
|
+
import { join as join14 } from "path";
|
|
2054
2248
|
|
|
2055
2249
|
// src/parsers/expiry-annotations.ts
|
|
2056
2250
|
var HEADING_RE = /^#{1,6}\s+(.+)$/;
|
|
@@ -2098,7 +2292,7 @@ async function runStale(input) {
|
|
|
2098
2292
|
const archived = [];
|
|
2099
2293
|
const workDirs = /* @__PURE__ */ new Map();
|
|
2100
2294
|
const workDirsBySlug = /* @__PURE__ */ new Map();
|
|
2101
|
-
const projectsDir =
|
|
2295
|
+
const projectsDir = join14(input.vault, "projects");
|
|
2102
2296
|
let projectSlugs = [];
|
|
2103
2297
|
try {
|
|
2104
2298
|
projectSlugs = (await readdir4(projectsDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
@@ -2111,7 +2305,7 @@ async function runStale(input) {
|
|
|
2111
2305
|
projectSlugs = [input.project];
|
|
2112
2306
|
}
|
|
2113
2307
|
for (const slug of projectSlugs) {
|
|
2114
|
-
const workPath =
|
|
2308
|
+
const workPath = join14(projectsDir, slug, "work");
|
|
2115
2309
|
let entries;
|
|
2116
2310
|
try {
|
|
2117
2311
|
entries = await readdir4(workPath, { withFileTypes: true });
|
|
@@ -2122,7 +2316,7 @@ async function runStale(input) {
|
|
|
2122
2316
|
for (const e of entries) {
|
|
2123
2317
|
if (!e.isDirectory()) continue;
|
|
2124
2318
|
const relDir = `projects/${slug}/work/${e.name}`;
|
|
2125
|
-
const absDir =
|
|
2319
|
+
const absDir = join14(workPath, e.name);
|
|
2126
2320
|
let status = "";
|
|
2127
2321
|
let files;
|
|
2128
2322
|
try {
|
|
@@ -2135,7 +2329,7 @@ async function runStale(input) {
|
|
|
2135
2329
|
for (const f of files) {
|
|
2136
2330
|
if (!f.endsWith(".md")) continue;
|
|
2137
2331
|
try {
|
|
2138
|
-
const fm = extractFrontmatter(await readFile10(
|
|
2332
|
+
const fm = extractFrontmatter(await readFile10(join14(absDir, f), "utf8"));
|
|
2139
2333
|
if (fm.ok && typeof fm.data.status === "string") {
|
|
2140
2334
|
status = fm.data.status;
|
|
2141
2335
|
break;
|
|
@@ -2159,7 +2353,7 @@ async function runStale(input) {
|
|
|
2159
2353
|
const transcriptMeta = /* @__PURE__ */ new Map();
|
|
2160
2354
|
for (const t of transcripts) {
|
|
2161
2355
|
try {
|
|
2162
|
-
const content = await readFile10(
|
|
2356
|
+
const content = await readFile10(join14(input.vault, t.relPath), "utf8");
|
|
2163
2357
|
const fm = extractFrontmatter(content);
|
|
2164
2358
|
let kind = fm.ok && typeof fm.data.kind === "string" ? fm.data.kind : "";
|
|
2165
2359
|
let project = fm.ok && typeof fm.data.project === "string" ? fm.data.project : "";
|
|
@@ -2227,7 +2421,7 @@ async function runStale(input) {
|
|
|
2227
2421
|
}
|
|
2228
2422
|
}
|
|
2229
2423
|
for (const [relDir] of workDirs) {
|
|
2230
|
-
const specPath =
|
|
2424
|
+
const specPath = join14(input.vault, relDir, "spec.md");
|
|
2231
2425
|
try {
|
|
2232
2426
|
const specContent = await readFile10(specPath, "utf8");
|
|
2233
2427
|
const specFm = extractFrontmatter(specContent);
|
|
@@ -2258,7 +2452,7 @@ async function runStale(input) {
|
|
|
2258
2452
|
if (daysSince(dateStr) < input.days) continue;
|
|
2259
2453
|
let files;
|
|
2260
2454
|
try {
|
|
2261
|
-
files = await readdir4(
|
|
2455
|
+
files = await readdir4(join14(input.vault, relDir));
|
|
2262
2456
|
} catch {
|
|
2263
2457
|
continue;
|
|
2264
2458
|
}
|
|
@@ -2274,7 +2468,7 @@ async function runStale(input) {
|
|
|
2274
2468
|
const stale = [];
|
|
2275
2469
|
for (const page of scan.data.typedKnowledge) {
|
|
2276
2470
|
try {
|
|
2277
|
-
const text = await readFile10(
|
|
2471
|
+
const text = await readFile10(join14(input.vault, page.relPath), "utf8");
|
|
2278
2472
|
const fm = extractFrontmatter(text);
|
|
2279
2473
|
if (fm.ok && typeof fm.data.updated === "string") {
|
|
2280
2474
|
if (input.project) {
|
|
@@ -2294,7 +2488,7 @@ async function runStale(input) {
|
|
|
2294
2488
|
const staleSections = [];
|
|
2295
2489
|
for (const page of scan.data.typedKnowledge) {
|
|
2296
2490
|
try {
|
|
2297
|
-
const text = await readFile10(
|
|
2491
|
+
const text = await readFile10(join14(input.vault, page.relPath), "utf8");
|
|
2298
2492
|
const projectFilter = input.project;
|
|
2299
2493
|
if (projectFilter) {
|
|
2300
2494
|
const fm = extractFrontmatter(text);
|
|
@@ -2323,11 +2517,11 @@ async function runStale(input) {
|
|
|
2323
2517
|
}
|
|
2324
2518
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
2325
2519
|
if (input.archive) {
|
|
2326
|
-
const archiveDir =
|
|
2520
|
+
const archiveDir = join14(input.vault, "_archive", today);
|
|
2327
2521
|
await mkdir6(archiveDir, { recursive: true });
|
|
2328
2522
|
const citedRawPaths = /* @__PURE__ */ new Set();
|
|
2329
2523
|
for (const page of scan.data.typedKnowledge) {
|
|
2330
|
-
const text = await readFile10(
|
|
2524
|
+
const text = await readFile10(join14(input.vault, page.relPath), "utf8").catch(() => "");
|
|
2331
2525
|
for (const line of text.split("\n")) {
|
|
2332
2526
|
for (const m of line.matchAll(/\^\[(raw\/[^\]]+)\]/g)) {
|
|
2333
2527
|
citedRawPaths.add(m[1]);
|
|
@@ -2339,9 +2533,9 @@ async function runStale(input) {
|
|
|
2339
2533
|
}
|
|
2340
2534
|
for (const t of staleTranscripts) {
|
|
2341
2535
|
if (citedRawPaths.has(t.path) || citedRawPaths.has(t.path.replace(/\.md$/, ""))) continue;
|
|
2342
|
-
const dest =
|
|
2536
|
+
const dest = join14(archiveDir, t.path.split("/").pop());
|
|
2343
2537
|
try {
|
|
2344
|
-
await rename2(
|
|
2538
|
+
await rename2(join14(input.vault, t.path), dest);
|
|
2345
2539
|
archived.push(t.path);
|
|
2346
2540
|
} catch {
|
|
2347
2541
|
}
|
|
@@ -2351,18 +2545,18 @@ async function runStale(input) {
|
|
|
2351
2545
|
if (parts.length >= 4 && parts[0] === "projects") {
|
|
2352
2546
|
const slug = parts[1];
|
|
2353
2547
|
const itemName = parts[3];
|
|
2354
|
-
const histDir =
|
|
2548
|
+
const histDir = join14(input.vault, "projects", slug, "history", "archived-work");
|
|
2355
2549
|
await mkdir6(histDir, { recursive: true });
|
|
2356
|
-
const dest =
|
|
2550
|
+
const dest = join14(histDir, itemName);
|
|
2357
2551
|
try {
|
|
2358
|
-
await rename2(
|
|
2552
|
+
await rename2(join14(input.vault, w.path), dest);
|
|
2359
2553
|
archived.push(w.path);
|
|
2360
2554
|
} catch {
|
|
2361
2555
|
}
|
|
2362
2556
|
} else {
|
|
2363
|
-
const dest =
|
|
2557
|
+
const dest = join14(archiveDir, w.path.replace(/\//g, "_"));
|
|
2364
2558
|
try {
|
|
2365
|
-
await rename2(
|
|
2559
|
+
await rename2(join14(input.vault, w.path), dest);
|
|
2366
2560
|
archived.push(w.path);
|
|
2367
2561
|
} catch {
|
|
2368
2562
|
}
|
|
@@ -2402,8 +2596,8 @@ async function runStale(input) {
|
|
|
2402
2596
|
|
|
2403
2597
|
// src/commands/claim.ts
|
|
2404
2598
|
import { mkdir as mkdir7, writeFile as writeFile6, readFile as readFile11 } from "fs/promises";
|
|
2405
|
-
import { existsSync as
|
|
2406
|
-
import { join as
|
|
2599
|
+
import { existsSync as existsSync3, statSync } from "fs";
|
|
2600
|
+
import { join as join15 } from "path";
|
|
2407
2601
|
function extractDate(filename) {
|
|
2408
2602
|
const m = filename.match(/^(\d{4}-\d{2}-\d{2})/);
|
|
2409
2603
|
return m?.[1] ?? "";
|
|
@@ -2415,11 +2609,11 @@ function extractProjectSlug(projectField) {
|
|
|
2415
2609
|
return projectField.replace(/^\[\[/, "").replace(/\]\]$/, "").replace(/^"|"$/g, "");
|
|
2416
2610
|
}
|
|
2417
2611
|
async function runClaim(input) {
|
|
2418
|
-
if (!
|
|
2612
|
+
if (!existsSync3(input.vault) || !statSync(input.vault).isDirectory()) {
|
|
2419
2613
|
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { path: input.vault }) };
|
|
2420
2614
|
}
|
|
2421
|
-
const absTranscript =
|
|
2422
|
-
if (!
|
|
2615
|
+
const absTranscript = join15(input.vault, input.transcript);
|
|
2616
|
+
if (!existsSync3(absTranscript)) {
|
|
2423
2617
|
return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: input.transcript }) };
|
|
2424
2618
|
}
|
|
2425
2619
|
const content = await readFile11(absTranscript, "utf8");
|
|
@@ -2434,8 +2628,8 @@ async function runClaim(input) {
|
|
|
2434
2628
|
result: err("SCHEME_REJECTED", { message: "No project specified. Use --project or set project in transcript frontmatter." })
|
|
2435
2629
|
};
|
|
2436
2630
|
}
|
|
2437
|
-
const projectDir =
|
|
2438
|
-
if (!
|
|
2631
|
+
const projectDir = join15(input.vault, "projects", projectSlug);
|
|
2632
|
+
if (!existsSync3(projectDir) || !statSync(projectDir).isDirectory()) {
|
|
2439
2633
|
return {
|
|
2440
2634
|
exitCode: ExitCode.PROJECT_NOT_FOUND,
|
|
2441
2635
|
result: err("PROJECT_NOT_FOUND", { project: projectSlug })
|
|
@@ -2451,10 +2645,10 @@ async function runClaim(input) {
|
|
|
2451
2645
|
}
|
|
2452
2646
|
const workSlug = input.slug || extractSlugFromFilename(filename);
|
|
2453
2647
|
const dirName = `${date}-${workSlug}`;
|
|
2454
|
-
const workDir =
|
|
2648
|
+
const workDir = join15(projectDir, "work", dirName);
|
|
2455
2649
|
const relWorkDir = `projects/${projectSlug}/work/${dirName}`;
|
|
2456
2650
|
const relSpecPath = `${relWorkDir}/spec.md`;
|
|
2457
|
-
if (
|
|
2651
|
+
if (existsSync3(workDir)) {
|
|
2458
2652
|
return {
|
|
2459
2653
|
exitCode: ExitCode.OK,
|
|
2460
2654
|
result: ok({
|
|
@@ -2480,7 +2674,7 @@ async function runClaim(input) {
|
|
|
2480
2674
|
`Claimed from ${input.transcript}`,
|
|
2481
2675
|
""
|
|
2482
2676
|
];
|
|
2483
|
-
await writeFile6(
|
|
2677
|
+
await writeFile6(join15(workDir, "spec.md"), specLines.join("\n"), "utf8");
|
|
2484
2678
|
appendLastOp(input.vault, {
|
|
2485
2679
|
operation: "claim",
|
|
2486
2680
|
summary: `claimed ${input.transcript} \u2192 ${relWorkDir}`,
|
|
@@ -2515,16 +2709,16 @@ async function runPagesize(input) {
|
|
|
2515
2709
|
}
|
|
2516
2710
|
|
|
2517
2711
|
// src/commands/log-rotate.ts
|
|
2518
|
-
import { readFile as readFile12, rename as rename3, writeFile as writeFile7, stat as
|
|
2519
|
-
import { join as
|
|
2712
|
+
import { readFile as readFile12, rename as rename3, writeFile as writeFile7, stat as stat6 } from "fs/promises";
|
|
2713
|
+
import { join as join16 } from "path";
|
|
2520
2714
|
var ENTRY_RE = /^## \[(\d{4})-\d{2}-\d{2}\]/gm;
|
|
2521
2715
|
async function runLogRotate(input) {
|
|
2522
2716
|
try {
|
|
2523
|
-
await
|
|
2717
|
+
await stat6(join16(input.vault, "SCHEMA.md"));
|
|
2524
2718
|
} catch {
|
|
2525
2719
|
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { vault: input.vault }) };
|
|
2526
2720
|
}
|
|
2527
|
-
const logPath =
|
|
2721
|
+
const logPath = join16(input.vault, "log.md");
|
|
2528
2722
|
let logText;
|
|
2529
2723
|
try {
|
|
2530
2724
|
logText = await readFile12(logPath, "utf8");
|
|
@@ -2544,7 +2738,7 @@ async function runLogRotate(input) {
|
|
|
2544
2738
|
}
|
|
2545
2739
|
const newestYear = matches[matches.length - 1][1];
|
|
2546
2740
|
const rotatedName = `log-${newestYear}.md`;
|
|
2547
|
-
const rotatedPath =
|
|
2741
|
+
const rotatedPath = join16(input.vault, rotatedName);
|
|
2548
2742
|
try {
|
|
2549
2743
|
await rename3(logPath, rotatedPath);
|
|
2550
2744
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
@@ -2570,14 +2764,14 @@ Chronological action log. Newest entries last. Skill writes append entries; lint
|
|
|
2570
2764
|
}
|
|
2571
2765
|
|
|
2572
2766
|
// src/commands/log-append.ts
|
|
2573
|
-
import { readFile as readFile13, rename as rename4, writeFile as writeFile8, stat as
|
|
2574
|
-
import { join as
|
|
2767
|
+
import { readFile as readFile13, rename as rename4, writeFile as writeFile8, stat as stat7 } from "fs/promises";
|
|
2768
|
+
import { join as join18 } from "path";
|
|
2575
2769
|
|
|
2576
2770
|
// src/utils/log-lock.ts
|
|
2577
|
-
import { existsSync as
|
|
2578
|
-
import { join as
|
|
2771
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, statSync as statSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
2772
|
+
import { join as join17 } from "path";
|
|
2579
2773
|
function logLockPath(vault) {
|
|
2580
|
-
return
|
|
2774
|
+
return join17(vault, ".skillwiki", "log-append.lock");
|
|
2581
2775
|
}
|
|
2582
2776
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
2583
2777
|
async function acquireLogLock(vault, opts = {}) {
|
|
@@ -2585,8 +2779,8 @@ async function acquireLogLock(vault, opts = {}) {
|
|
|
2585
2779
|
const pollMs = opts.pollMs ?? 50;
|
|
2586
2780
|
const staleMs = opts.staleMs ?? 1e4;
|
|
2587
2781
|
const path = logLockPath(vault);
|
|
2588
|
-
const dir =
|
|
2589
|
-
if (!
|
|
2782
|
+
const dir = join17(vault, ".skillwiki");
|
|
2783
|
+
if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
|
|
2590
2784
|
const deadline = Date.now() + retryMs;
|
|
2591
2785
|
const content = JSON.stringify({ pid: process.pid, acquired: (/* @__PURE__ */ new Date()).toISOString() }) + "\n";
|
|
2592
2786
|
for (; ; ) {
|
|
@@ -2621,7 +2815,7 @@ function releaseLogLock(vault) {
|
|
|
2621
2815
|
var ENTRY_RE2 = /^## \[(\d{4})-\d{2}-\d{2}\]/gm;
|
|
2622
2816
|
async function runLogAppend(input) {
|
|
2623
2817
|
try {
|
|
2624
|
-
await
|
|
2818
|
+
await stat7(join18(input.vault, "SCHEMA.md"));
|
|
2625
2819
|
} catch {
|
|
2626
2820
|
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { vault: input.vault }) };
|
|
2627
2821
|
}
|
|
@@ -2633,7 +2827,7 @@ async function runLogAppend(input) {
|
|
|
2633
2827
|
if (!acquired.ok) {
|
|
2634
2828
|
return { exitCode: ExitCode.LOG_APPEND_LOCK_HELD, result: err("LOG_APPEND_LOCK_HELD", { vault: input.vault }) };
|
|
2635
2829
|
}
|
|
2636
|
-
const logPath =
|
|
2830
|
+
const logPath = join18(input.vault, "log.md");
|
|
2637
2831
|
try {
|
|
2638
2832
|
let logText;
|
|
2639
2833
|
try {
|
|
@@ -2671,9 +2865,10 @@ ${content}
|
|
|
2671
2865
|
}
|
|
2672
2866
|
|
|
2673
2867
|
// src/commands/lint.ts
|
|
2674
|
-
import { existsSync as
|
|
2868
|
+
import { existsSync as existsSync6 } from "fs";
|
|
2675
2869
|
import { readFile as readFile17 } from "fs/promises";
|
|
2676
|
-
import {
|
|
2870
|
+
import { createHash as createHash4 } from "crypto";
|
|
2871
|
+
import { join as join23 } from "path";
|
|
2677
2872
|
|
|
2678
2873
|
// src/commands/sparse-community.ts
|
|
2679
2874
|
async function runSparseCommunity(input) {
|
|
@@ -2709,12 +2904,12 @@ async function runTopicMapCheck(input) {
|
|
|
2709
2904
|
|
|
2710
2905
|
// src/commands/index-link-format.ts
|
|
2711
2906
|
import { readFile as readFile14 } from "fs/promises";
|
|
2712
|
-
import { join as
|
|
2907
|
+
import { join as join19 } from "path";
|
|
2713
2908
|
var MD_LINK_RE = /\[[^\[\]]+\]\([^)]+\.md\)/;
|
|
2714
2909
|
async function runIndexLinkFormat(input) {
|
|
2715
2910
|
let text = "";
|
|
2716
2911
|
try {
|
|
2717
|
-
text = await readFile14(
|
|
2912
|
+
text = await readFile14(join19(input.vault, "index.md"), "utf8");
|
|
2718
2913
|
} catch {
|
|
2719
2914
|
}
|
|
2720
2915
|
const markdown_links = [];
|
|
@@ -2728,7 +2923,7 @@ ${markdown_links.map((l) => ` line ${l.line}: ${l.text}`).join("\n")}`;
|
|
|
2728
2923
|
|
|
2729
2924
|
// src/commands/dedup.ts
|
|
2730
2925
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, unlinkSync as unlinkSync3 } from "fs";
|
|
2731
|
-
import { join as
|
|
2926
|
+
import { join as join20 } from "path";
|
|
2732
2927
|
async function runDedup(input) {
|
|
2733
2928
|
const scan = await scanVault(input.vault);
|
|
2734
2929
|
if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
|
|
@@ -2756,7 +2951,7 @@ async function runDedup(input) {
|
|
|
2756
2951
|
}
|
|
2757
2952
|
}
|
|
2758
2953
|
for (const page of scan.data.typedKnowledge) {
|
|
2759
|
-
const text = readFileSync3(
|
|
2954
|
+
const text = readFileSync3(join20(input.vault, page.relPath), "utf-8");
|
|
2760
2955
|
let updated = text;
|
|
2761
2956
|
let changed = false;
|
|
2762
2957
|
for (const [oldPath, newPath] of replacements) {
|
|
@@ -2774,12 +2969,12 @@ async function runDedup(input) {
|
|
|
2774
2969
|
}
|
|
2775
2970
|
}
|
|
2776
2971
|
if (changed) {
|
|
2777
|
-
writeFileSync3(
|
|
2972
|
+
writeFileSync3(join20(input.vault, page.relPath), updated);
|
|
2778
2973
|
rewired.push(page.relPath);
|
|
2779
2974
|
}
|
|
2780
2975
|
}
|
|
2781
2976
|
for (const [oldPath] of replacements) {
|
|
2782
|
-
const fullPath =
|
|
2977
|
+
const fullPath = join20(input.vault, oldPath);
|
|
2783
2978
|
try {
|
|
2784
2979
|
unlinkSync3(fullPath);
|
|
2785
2980
|
removed.push(oldPath);
|
|
@@ -2816,7 +3011,7 @@ async function runDedup(input) {
|
|
|
2816
3011
|
// src/utils/safe-write.ts
|
|
2817
3012
|
import { open, readFile as readFile15, rename as rename5, unlink as unlink2, writeFile as writeFile9 } from "fs/promises";
|
|
2818
3013
|
import { randomBytes } from "crypto";
|
|
2819
|
-
import { dirname as dirname7, basename, join as
|
|
3014
|
+
import { dirname as dirname7, basename, join as join21 } from "path";
|
|
2820
3015
|
var DEFAULT_MIN_BODY_RATIO = 0.5;
|
|
2821
3016
|
var DEFAULT_MIN_OLD_BODY_BYTES = 200;
|
|
2822
3017
|
function bodyBytes(text) {
|
|
@@ -2865,7 +3060,7 @@ async function safeWritePage(absPath, newContent, opts = {}) {
|
|
|
2865
3060
|
}
|
|
2866
3061
|
const dir = dirname7(absPath);
|
|
2867
3062
|
const tmpName = `.${basename(absPath)}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
|
|
2868
|
-
const tmpPath =
|
|
3063
|
+
const tmpPath = join21(dir, tmpName);
|
|
2869
3064
|
try {
|
|
2870
3065
|
const handle = await open(tmpPath, "w");
|
|
2871
3066
|
try {
|
|
@@ -2889,7 +3084,7 @@ async function safeWritePage(absPath, newContent, opts = {}) {
|
|
|
2889
3084
|
}
|
|
2890
3085
|
|
|
2891
3086
|
// src/commands/raw-body-dedup.ts
|
|
2892
|
-
import { createHash as
|
|
3087
|
+
import { createHash as createHash3 } from "crypto";
|
|
2893
3088
|
async function runRawBodyDedup(vault) {
|
|
2894
3089
|
const scan = await scanVault(vault);
|
|
2895
3090
|
if (!scan.ok) return { exitCode: ExitCode.VAULT_PATH_INVALID, result: scan };
|
|
@@ -2900,7 +3095,7 @@ async function runRawBodyDedup(vault) {
|
|
|
2900
3095
|
const split = splitFrontmatter(text);
|
|
2901
3096
|
if (!split.ok) continue;
|
|
2902
3097
|
totalFiles++;
|
|
2903
|
-
const bodyHash =
|
|
3098
|
+
const bodyHash = createHash3("sha256").update(split.data.body).digest("hex");
|
|
2904
3099
|
const fm = extractFrontmatter(text);
|
|
2905
3100
|
let fmSha256 = null;
|
|
2906
3101
|
if (fm.ok && typeof fm.data.sha256 === "string" && fm.data.sha256.length === 64) {
|
|
@@ -2929,9 +3124,9 @@ async function runRawBodyDedup(vault) {
|
|
|
2929
3124
|
}
|
|
2930
3125
|
|
|
2931
3126
|
// src/commands/path-too-long.ts
|
|
2932
|
-
import { existsSync as
|
|
3127
|
+
import { existsSync as existsSync5 } from "fs";
|
|
2933
3128
|
import { mkdir as mkdir8, readFile as readFile16, rename as rename6, unlink as unlink3 } from "fs/promises";
|
|
2934
|
-
import { dirname as dirname8, join as
|
|
3129
|
+
import { dirname as dirname8, join as join22, posix, resolve as resolve4 } from "path";
|
|
2935
3130
|
var MAX_PATH_LENGTH = 240;
|
|
2936
3131
|
var WINDOWS_ABSOLUTE_PATH_LIMIT = 259;
|
|
2937
3132
|
async function runPathTooLong(input) {
|
|
@@ -2964,10 +3159,10 @@ async function fixPathTooLong(input) {
|
|
|
2964
3159
|
}
|
|
2965
3160
|
try {
|
|
2966
3161
|
if (target.mode === "dedupe") {
|
|
2967
|
-
await unlink3(
|
|
3162
|
+
await unlink3(join22(input.vault, violation.relPath));
|
|
2968
3163
|
} else {
|
|
2969
|
-
await mkdir8(dirname8(
|
|
2970
|
-
await rename6(
|
|
3164
|
+
await mkdir8(dirname8(join22(input.vault, target.relPath)), { recursive: true });
|
|
3165
|
+
await rename6(join22(input.vault, violation.relPath), join22(input.vault, target.relPath));
|
|
2971
3166
|
}
|
|
2972
3167
|
fixed.push({ from: violation.relPath, to: target.relPath });
|
|
2973
3168
|
} catch {
|
|
@@ -3044,9 +3239,9 @@ function truncateFilename(relPath, maxLength = MAX_PATH_LENGTH) {
|
|
|
3044
3239
|
async function resolveFixTarget(vault, original, preferred, maxLength) {
|
|
3045
3240
|
for (const candidate of candidateRelPaths(preferred, maxLength)) {
|
|
3046
3241
|
if (candidate === original || candidate.length > maxLength) continue;
|
|
3047
|
-
const candidatePath =
|
|
3048
|
-
if (!
|
|
3049
|
-
if (await hasSameContent(
|
|
3242
|
+
const candidatePath = join22(vault, candidate);
|
|
3243
|
+
if (!existsSync5(candidatePath)) return { relPath: candidate, mode: "rename" };
|
|
3244
|
+
if (await hasSameContent(join22(vault, original), candidatePath)) {
|
|
3050
3245
|
return { relPath: candidate, mode: "dedupe" };
|
|
3051
3246
|
}
|
|
3052
3247
|
}
|
|
@@ -3356,7 +3551,7 @@ function extractSourceEntries(rawFm) {
|
|
|
3356
3551
|
}
|
|
3357
3552
|
return entries;
|
|
3358
3553
|
}
|
|
3359
|
-
var ERROR_ORDER = ["broken_wikilinks", "invalid_frontmatter", "raw_source_identity_conflict", "raw_dedup", "broken_sources", "tag_not_in_taxonomy", "path_too_long"];
|
|
3554
|
+
var ERROR_ORDER = ["sensitive_content", "broken_wikilinks", "invalid_frontmatter", "raw_source_identity_conflict", "raw_dedup", "broken_sources", "tag_not_in_taxonomy", "path_too_long"];
|
|
3360
3555
|
var WARNING_ORDER = ["raw_body_duplicate", "raw_subdirectory_duplicate", "file_source_url", "index_incomplete", "index_link_format", "stale_page", "page_too_large", "log_rotate_needed", "orphans", "compound_refs", "legacy_citation_style", "orphaned_citations", "duplicate_frontmatter", "work_item_health", "orphaned_project_pages", "missing_overview", "missing_diagram"];
|
|
3361
3556
|
var INFO_ORDER = ["bridges", "sparse_community", "page_structure", "topic_map_recommended", "frontmatter_wikilink", "wikilink_citation", "missing_tldr", "stale_sections", "cli_refs"];
|
|
3362
3557
|
var KNOWN_BUCKETS = [...ERROR_ORDER, ...WARNING_ORDER, ...INFO_ORDER];
|
|
@@ -3385,6 +3580,26 @@ function summarizeBucket(bucket, severity, vaultPath, examplesLimit) {
|
|
|
3385
3580
|
details_command: `skillwiki lint ${shellQuote(vaultPath)} --only ${bucket.kind}`
|
|
3386
3581
|
};
|
|
3387
3582
|
}
|
|
3583
|
+
function recomputeRawSha256IfPresent(content) {
|
|
3584
|
+
const split = splitFrontmatter(content);
|
|
3585
|
+
if (!split.ok) return content;
|
|
3586
|
+
if (!/^sha256:\s*[0-9a-f]{64}$/m.test(split.data.rawFrontmatter)) return content;
|
|
3587
|
+
const sha256 = createHash4("sha256").update(Buffer.from(split.data.body, "utf8")).digest("hex");
|
|
3588
|
+
const rawFrontmatter = split.data.rawFrontmatter.replace(/^sha256:\s*[0-9a-f]{64}$/m, `sha256: ${sha256}`);
|
|
3589
|
+
return `---
|
|
3590
|
+
${rawFrontmatter}
|
|
3591
|
+
---
|
|
3592
|
+
${split.data.body}`;
|
|
3593
|
+
}
|
|
3594
|
+
function appendLintFixLastOp(vault, fixed) {
|
|
3595
|
+
if (fixed.length === 0) return;
|
|
3596
|
+
appendLastOp(vault, {
|
|
3597
|
+
operation: "lint-fix",
|
|
3598
|
+
summary: `fixed ${fixed.length} page(s)`,
|
|
3599
|
+
files: fixed,
|
|
3600
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3601
|
+
});
|
|
3602
|
+
}
|
|
3388
3603
|
function summarizeLintOutput(output, examplesLimit = 3) {
|
|
3389
3604
|
const buckets = [
|
|
3390
3605
|
...output.by_severity.error.map((bucket) => summarizeBucket(bucket, "error", output.vault.path, examplesLimit)),
|
|
@@ -3486,6 +3701,16 @@ async function runLint(input) {
|
|
|
3486
3701
|
const allPages = scan.ok ? [...scan.data.typedKnowledge, ...scan.data.raw, ...scan.data.workItems, ...scan.data.compound] : [];
|
|
3487
3702
|
const slugs = scan.ok ? buildSlugMap(allPages) : /* @__PURE__ */ new Map();
|
|
3488
3703
|
if (scan.ok) {
|
|
3704
|
+
const sensitiveFlags = [];
|
|
3705
|
+
for (const page of scan.data.allMarkdown) {
|
|
3706
|
+
try {
|
|
3707
|
+
const text = await readPage(page);
|
|
3708
|
+
const findings = scanSensitiveContent(text, { file: page.relPath });
|
|
3709
|
+
sensitiveFlags.push(...findings);
|
|
3710
|
+
} catch {
|
|
3711
|
+
}
|
|
3712
|
+
}
|
|
3713
|
+
if (sensitiveFlags.length > 0) buckets.sensitive_content = sensitiveFlags;
|
|
3489
3714
|
const subDirDupes = [];
|
|
3490
3715
|
const flatStems = /* @__PURE__ */ new Map();
|
|
3491
3716
|
const deepFiles = [];
|
|
@@ -3543,7 +3768,7 @@ async function runLint(input) {
|
|
|
3543
3768
|
const noOverview = [];
|
|
3544
3769
|
const fmWikilinkFlags = [];
|
|
3545
3770
|
const wikilinkCitationFlags = [];
|
|
3546
|
-
const brokenSourceFlags =
|
|
3771
|
+
const brokenSourceFlags = /* @__PURE__ */ new Set();
|
|
3547
3772
|
const missingTldrFlags = [];
|
|
3548
3773
|
const missingDiagramFlags = [];
|
|
3549
3774
|
for (const page of scan.data.typedKnowledge) {
|
|
@@ -3558,11 +3783,15 @@ async function runLint(input) {
|
|
|
3558
3783
|
if (hasWikilinkCitations(body)) wikilinkCitationFlags.push(page.relPath);
|
|
3559
3784
|
const sourcesEntries = extractSourceEntries(rawFm);
|
|
3560
3785
|
for (const entry of sourcesEntries) {
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
if (!
|
|
3564
|
-
|
|
3565
|
-
|
|
3786
|
+
const rawPath = normalizeRawSourceTarget(entry);
|
|
3787
|
+
if (!rawPath) continue;
|
|
3788
|
+
if (!rawSourceTargetExistsSync(input.vault, rawPath)) {
|
|
3789
|
+
brokenSourceFlags.add(`${page.relPath}: ${rawPath}`);
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
3792
|
+
for (const marker of extractCitationMarkers(body)) {
|
|
3793
|
+
if (!rawSourceTargetExistsSync(input.vault, marker.target)) {
|
|
3794
|
+
brokenSourceFlags.add(`${page.relPath}: ${marker.target}`);
|
|
3566
3795
|
}
|
|
3567
3796
|
}
|
|
3568
3797
|
const fmLinks = rawFm.match(/\[\[([^\[\]|]+)(?:\|[^\[\]]*)?\]\]/g) ?? [];
|
|
@@ -3601,7 +3830,7 @@ async function runLint(input) {
|
|
|
3601
3830
|
if (noOverview.length > 0) buckets.missing_overview = noOverview;
|
|
3602
3831
|
if (fmWikilinkFlags.length > 0) buckets.frontmatter_wikilink = fmWikilinkFlags;
|
|
3603
3832
|
if (wikilinkCitationFlags.length > 0) buckets.wikilink_citation = wikilinkCitationFlags;
|
|
3604
|
-
if (brokenSourceFlags.
|
|
3833
|
+
if (brokenSourceFlags.size > 0) buckets.broken_sources = [...brokenSourceFlags];
|
|
3605
3834
|
if (missingTldrFlags.length > 0) buckets.missing_tldr = missingTldrFlags;
|
|
3606
3835
|
if (missingDiagramFlags.length > 0) buckets.missing_diagram = missingDiagramFlags;
|
|
3607
3836
|
const workItemHealth = [];
|
|
@@ -3652,8 +3881,8 @@ async function runLint(input) {
|
|
|
3652
3881
|
const slugMatch = String(entry).match(/\[\[([^\]]+)\]\]/);
|
|
3653
3882
|
if (!slugMatch) continue;
|
|
3654
3883
|
const slug = slugMatch[1];
|
|
3655
|
-
const knowledgePath =
|
|
3656
|
-
if (!
|
|
3884
|
+
const knowledgePath = join23(input.vault, "projects", slug, "knowledge.md");
|
|
3885
|
+
if (!existsSync6(knowledgePath)) continue;
|
|
3657
3886
|
const pageRef = page.relPath.replace(/\.md$/, "");
|
|
3658
3887
|
try {
|
|
3659
3888
|
const knowledgeContent = await readFile17(knowledgePath, "utf8");
|
|
@@ -3697,6 +3926,36 @@ async function runLint(input) {
|
|
|
3697
3926
|
}
|
|
3698
3927
|
}
|
|
3699
3928
|
if (staleSectionFlags.length > 0) buckets.stale_sections = staleSectionFlags;
|
|
3929
|
+
if (shouldFix("sensitive_content") && buckets.sensitive_content) {
|
|
3930
|
+
const sensitiveFixed = [];
|
|
3931
|
+
for (const page of scan.data.allMarkdown) {
|
|
3932
|
+
try {
|
|
3933
|
+
const raw = await readPage(page);
|
|
3934
|
+
const redacted = redactSensitiveContent(raw, { file: page.relPath });
|
|
3935
|
+
if (!redacted.changed) continue;
|
|
3936
|
+
const next = recomputeRawSha256IfPresent(redacted.text);
|
|
3937
|
+
const w = await safeWritePage(page.absPath, next, { minBodyRatio: null });
|
|
3938
|
+
if (!w.ok) {
|
|
3939
|
+
unresolved.push(page.relPath);
|
|
3940
|
+
continue;
|
|
3941
|
+
}
|
|
3942
|
+
sensitiveFixed.push(page.relPath);
|
|
3943
|
+
} catch {
|
|
3944
|
+
unresolved.push(page.relPath);
|
|
3945
|
+
}
|
|
3946
|
+
}
|
|
3947
|
+
fixed.push(...sensitiveFixed);
|
|
3948
|
+
const remainingSensitiveFlags = [];
|
|
3949
|
+
for (const page of scan.data.allMarkdown) {
|
|
3950
|
+
try {
|
|
3951
|
+
const text = await readPage(page);
|
|
3952
|
+
remainingSensitiveFlags.push(...scanSensitiveContent(text, { file: page.relPath }));
|
|
3953
|
+
} catch {
|
|
3954
|
+
}
|
|
3955
|
+
}
|
|
3956
|
+
if (remainingSensitiveFlags.length > 0) buckets.sensitive_content = remainingSensitiveFlags;
|
|
3957
|
+
else delete buckets.sensitive_content;
|
|
3958
|
+
}
|
|
3700
3959
|
if (shouldFix("legacy_citation_style") && legacyPages.length > 0) {
|
|
3701
3960
|
const FENCE_RE2 = /```[\s\S]*?```/g;
|
|
3702
3961
|
const INLINE_MARKER = /\^\[raw\/[^\]]+\]/g;
|
|
@@ -4053,6 +4312,7 @@ ${newBody}`;
|
|
|
4053
4312
|
humanHint: `--only ${input.only}
|
|
4054
4313
|
${match.length === 0 ? "0 violations" : match.map((b) => ` ${b.kind}: ${b.items.length}`).join("\n")}`
|
|
4055
4314
|
};
|
|
4315
|
+
if (input.fix) appendLintFixLastOp(input.vault, fixed);
|
|
4056
4316
|
return {
|
|
4057
4317
|
exitCode: fExit,
|
|
4058
4318
|
result: ok(input.summary ? summarizeLintOutput(output2, input.examplesLimit) : output2)
|
|
@@ -4075,14 +4335,7 @@ ${match.length === 0 ? "0 violations" : match.map((b) => ` ${b.kind}: ${b.items
|
|
|
4075
4335
|
hintLines.push(` ${b.kind}: ${b.items.length}`);
|
|
4076
4336
|
}
|
|
4077
4337
|
if (hintLines.length === 0) hintLines.push("0 errors, 0 warnings, 0 info");
|
|
4078
|
-
if (input.fix
|
|
4079
|
-
appendLastOp(input.vault, {
|
|
4080
|
-
operation: "lint-fix",
|
|
4081
|
-
summary: `fixed ${fixed.length} page(s)`,
|
|
4082
|
-
files: fixed,
|
|
4083
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4084
|
-
});
|
|
4085
|
-
}
|
|
4338
|
+
if (input.fix) appendLintFixLastOp(input.vault, fixed);
|
|
4086
4339
|
const output = {
|
|
4087
4340
|
vault: { path: input.vault, source: input.source ?? "resolved" },
|
|
4088
4341
|
summary,
|
|
@@ -4098,25 +4351,25 @@ ${match.length === 0 ? "0 violations" : match.map((b) => ` ${b.kind}: ${b.items
|
|
|
4098
4351
|
}
|
|
4099
4352
|
|
|
4100
4353
|
// src/commands/health.ts
|
|
4101
|
-
import { existsSync as
|
|
4102
|
-
import { dirname as dirname10, join as
|
|
4354
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync4, readFileSync as readFileSync8, renameSync, writeFileSync as writeFileSync6 } from "fs";
|
|
4355
|
+
import { dirname as dirname10, join as join30, resolve as resolve6 } from "path";
|
|
4103
4356
|
import { platform as platform3 } from "os";
|
|
4104
4357
|
|
|
4105
4358
|
// src/commands/doctor.ts
|
|
4106
|
-
import { existsSync as
|
|
4107
|
-
import { join as
|
|
4359
|
+
import { existsSync as existsSync11, lstatSync, readlinkSync, readdirSync as readdirSync2, statSync as statSync3, readFileSync as readFileSync7 } from "fs";
|
|
4360
|
+
import { join as join29, resolve as resolve5 } from "path";
|
|
4108
4361
|
import { execSync as execSync2 } from "child_process";
|
|
4109
4362
|
import { platform as platform2 } from "os";
|
|
4110
4363
|
|
|
4111
4364
|
// src/commands/config.ts
|
|
4112
4365
|
import { readFile as readFile18 } from "fs/promises";
|
|
4113
|
-
import { existsSync as
|
|
4114
|
-
import { join as
|
|
4366
|
+
import { existsSync as existsSync7 } from "fs";
|
|
4367
|
+
import { join as join24 } from "path";
|
|
4115
4368
|
function validateKey(key) {
|
|
4116
4369
|
return CONFIG_KEYS.includes(key) || isValidWikiProfileKey(key);
|
|
4117
4370
|
}
|
|
4118
4371
|
function configPath(home) {
|
|
4119
|
-
return
|
|
4372
|
+
return join24(home, ".skillwiki", ".env");
|
|
4120
4373
|
}
|
|
4121
4374
|
async function runConfigGet(input) {
|
|
4122
4375
|
if (!validateKey(input.key)) {
|
|
@@ -4166,15 +4419,15 @@ async function runConfigList(input) {
|
|
|
4166
4419
|
}
|
|
4167
4420
|
async function runConfigPath(input) {
|
|
4168
4421
|
const filePath = configPath(input.home);
|
|
4169
|
-
return { exitCode: ExitCode.OK, result: ok({ path: filePath, exists:
|
|
4422
|
+
return { exitCode: ExitCode.OK, result: ok({ path: filePath, exists: existsSync7(filePath), humanHint: filePath }) };
|
|
4170
4423
|
}
|
|
4171
4424
|
|
|
4172
4425
|
// src/utils/auto-update.ts
|
|
4173
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as
|
|
4174
|
-
import { join as
|
|
4426
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync8, mkdirSync as mkdirSync3 } from "fs";
|
|
4427
|
+
import { join as join25, dirname as dirname9 } from "path";
|
|
4175
4428
|
import { spawn } from "child_process";
|
|
4176
4429
|
function cachePath(home) {
|
|
4177
|
-
return
|
|
4430
|
+
return join25(home, ".skillwiki", CACHE_FILENAME);
|
|
4178
4431
|
}
|
|
4179
4432
|
function readCacheRaw(home) {
|
|
4180
4433
|
try {
|
|
@@ -4218,7 +4471,7 @@ function triggerAutoUpdate(home, currentVersion) {
|
|
|
4218
4471
|
if (!isStale2) return;
|
|
4219
4472
|
const distTag = distTagFromCache(home);
|
|
4220
4473
|
const bgScript = new URL("../auto-update-bg.js", import.meta.url).pathname;
|
|
4221
|
-
if (!
|
|
4474
|
+
if (!existsSync8(bgScript)) return;
|
|
4222
4475
|
const child = spawn(process.execPath, [bgScript, home, currentVersion, distTag], {
|
|
4223
4476
|
detached: true,
|
|
4224
4477
|
stdio: "ignore"
|
|
@@ -4229,14 +4482,14 @@ function triggerAutoUpdate(home, currentVersion) {
|
|
|
4229
4482
|
}
|
|
4230
4483
|
|
|
4231
4484
|
// src/utils/plugin-registry.ts
|
|
4232
|
-
import { existsSync as
|
|
4233
|
-
import { join as
|
|
4234
|
-
var REGISTRY_PATH =
|
|
4235
|
-
var CODEX_CONFIG_PATH =
|
|
4485
|
+
import { existsSync as existsSync9, readdirSync, readFileSync as readFileSync5 } from "fs";
|
|
4486
|
+
import { join as join26 } from "path";
|
|
4487
|
+
var REGISTRY_PATH = join26(".claude", "plugins", "installed_plugins.json");
|
|
4488
|
+
var CODEX_CONFIG_PATH = join26(".codex", "config.toml");
|
|
4236
4489
|
var PLUGIN_KEY = "skillwiki@llm-wiki";
|
|
4237
4490
|
function readInstalledPlugins(home) {
|
|
4238
4491
|
try {
|
|
4239
|
-
const raw = readFileSync5(
|
|
4492
|
+
const raw = readFileSync5(join26(home, REGISTRY_PATH), "utf8");
|
|
4240
4493
|
return JSON.parse(raw);
|
|
4241
4494
|
} catch {
|
|
4242
4495
|
return null;
|
|
@@ -4272,8 +4525,8 @@ function findPluginInstallations(home, key = PLUGIN_KEY) {
|
|
|
4272
4525
|
function findCodexPlugin(home, key, pluginName, marketplace) {
|
|
4273
4526
|
const config = readCodexPluginConfig(home, key, marketplace);
|
|
4274
4527
|
if (!config?.enabled) return null;
|
|
4275
|
-
const cacheRoot =
|
|
4276
|
-
if (!
|
|
4528
|
+
const cacheRoot = join26(home, ".codex", "plugins", "cache", marketplace, pluginName);
|
|
4529
|
+
if (!existsSync9(cacheRoot)) return null;
|
|
4277
4530
|
let versions;
|
|
4278
4531
|
try {
|
|
4279
4532
|
versions = readdirSync(cacheRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
@@ -4288,7 +4541,7 @@ function findCodexPlugin(home, key, pluginName, marketplace) {
|
|
|
4288
4541
|
key,
|
|
4289
4542
|
pluginName,
|
|
4290
4543
|
marketplace,
|
|
4291
|
-
installPath:
|
|
4544
|
+
installPath: join26(cacheRoot, version),
|
|
4292
4545
|
version,
|
|
4293
4546
|
sourceType: config.sourceType,
|
|
4294
4547
|
source: config.source
|
|
@@ -4305,7 +4558,7 @@ function parsePluginKey(key) {
|
|
|
4305
4558
|
function readCodexPluginConfig(home, key, marketplace) {
|
|
4306
4559
|
let raw;
|
|
4307
4560
|
try {
|
|
4308
|
-
raw = readFileSync5(
|
|
4561
|
+
raw = readFileSync5(join26(home, CODEX_CONFIG_PATH), "utf8");
|
|
4309
4562
|
} catch {
|
|
4310
4563
|
return null;
|
|
4311
4564
|
}
|
|
@@ -4346,90 +4599,490 @@ function parseTomlScalar(rawValue) {
|
|
|
4346
4599
|
return value;
|
|
4347
4600
|
}
|
|
4348
4601
|
|
|
4349
|
-
// src/
|
|
4350
|
-
import {
|
|
4351
|
-
import {
|
|
4352
|
-
import {
|
|
4353
|
-
import
|
|
4354
|
-
var
|
|
4355
|
-
function
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
4361
|
-
}).trim();
|
|
4362
|
-
const pids = out.split("\n").filter(Boolean);
|
|
4363
|
-
if (pids.length === 0) return null;
|
|
4364
|
-
return parseInt(pids[0], 10);
|
|
4365
|
-
} catch {
|
|
4366
|
-
try {
|
|
4367
|
-
const out = execSync("ps aux", { encoding: "utf8", timeout: 2e3, stdio: ["pipe", "pipe", "pipe"] });
|
|
4368
|
-
for (const line of out.split("\n")) {
|
|
4369
|
-
if (line.includes("rclone") && line.includes("mount") && !line.includes("grep")) {
|
|
4370
|
-
const parts = line.trim().split(/\s+/);
|
|
4371
|
-
if (parts.length >= 2) return parseInt(parts[1], 10);
|
|
4372
|
-
}
|
|
4373
|
-
}
|
|
4374
|
-
} catch {
|
|
4602
|
+
// src/commands/fleet.ts
|
|
4603
|
+
import { readFile as readFile19 } from "fs/promises";
|
|
4604
|
+
import { hostname as nodeHostname, userInfo } from "os";
|
|
4605
|
+
import { join as join27 } from "path";
|
|
4606
|
+
import yaml3 from "js-yaml";
|
|
4607
|
+
var FLEET_REL_PATH = join27("projects", "llm-wiki", "architecture", "fleet.yaml");
|
|
4608
|
+
async function runFleetValidate(input) {
|
|
4609
|
+
const loaded = await loadFleetManifest(input.file);
|
|
4610
|
+
if (!loaded.ok) {
|
|
4611
|
+
if (loaded.error === "FILE_NOT_FOUND") {
|
|
4612
|
+
return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: input.file }) };
|
|
4375
4613
|
}
|
|
4376
|
-
|
|
4614
|
+
const errors = fleetLoadErrors(loaded);
|
|
4615
|
+
return invalidFleet(errors);
|
|
4377
4616
|
}
|
|
4617
|
+
const warnings = fleetWarnings(loaded.manifest);
|
|
4618
|
+
const snapshotter = findSnapshotter(loaded.manifest);
|
|
4619
|
+
return {
|
|
4620
|
+
exitCode: ExitCode.OK,
|
|
4621
|
+
result: ok({
|
|
4622
|
+
valid: true,
|
|
4623
|
+
errors: [],
|
|
4624
|
+
warnings,
|
|
4625
|
+
host_count: Object.keys(loaded.manifest.hosts).length,
|
|
4626
|
+
snapshotter,
|
|
4627
|
+
humanHint: `VALID fleet manifest (${Object.keys(loaded.manifest.hosts).length} hosts; snapshotter: ${snapshotter ?? "none"})`
|
|
4628
|
+
})
|
|
4629
|
+
};
|
|
4378
4630
|
}
|
|
4379
|
-
function
|
|
4380
|
-
const
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4631
|
+
async function runFleetContext(input) {
|
|
4632
|
+
const env = input.env ?? process.env;
|
|
4633
|
+
const home = input.home ?? env.HOME ?? "";
|
|
4634
|
+
const cwd = input.cwd ?? process.cwd();
|
|
4635
|
+
const osHostname = input.osHostname ?? safeEnvValue(env.HOSTNAME) ?? nodeHostname();
|
|
4636
|
+
const user = input.user ?? safeEnvValue(env.USER) ?? safeUserName();
|
|
4637
|
+
const vault = input.vault ?? safeEnvValue(env.WIKI_PATH);
|
|
4638
|
+
const file = input.file ?? (vault ? join27(vault, FLEET_REL_PATH) : void 0);
|
|
4639
|
+
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4640
|
+
const loaded = file ? await loadFleetManifest(file) : { ok: false, error: "FILE_NOT_FOUND" };
|
|
4641
|
+
if (!loaded.ok) {
|
|
4642
|
+
const warnings = ["fleet manifest unavailable or invalid"];
|
|
4643
|
+
const markdown2 = formatUnknownContext({
|
|
4644
|
+
generatedAt,
|
|
4645
|
+
osHostname,
|
|
4646
|
+
user,
|
|
4647
|
+
cwd,
|
|
4648
|
+
vault,
|
|
4649
|
+
reason: warnings[0],
|
|
4650
|
+
trace: [],
|
|
4651
|
+
warnings
|
|
4652
|
+
});
|
|
4653
|
+
return {
|
|
4654
|
+
exitCode: ExitCode.OK,
|
|
4655
|
+
result: ok({
|
|
4656
|
+
manifest_loaded: false,
|
|
4657
|
+
generated_at: generatedAt,
|
|
4658
|
+
identity_status: "unknown",
|
|
4659
|
+
resolver_trace: [],
|
|
4660
|
+
warnings,
|
|
4661
|
+
markdown: markdown2,
|
|
4662
|
+
humanHint: markdown2
|
|
4663
|
+
})
|
|
4664
|
+
};
|
|
4399
4665
|
}
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4666
|
+
const resolved = await resolveHostId({
|
|
4667
|
+
manifest: loaded.manifest,
|
|
4668
|
+
hostId: input.hostId,
|
|
4669
|
+
env,
|
|
4670
|
+
home,
|
|
4671
|
+
osHostname
|
|
4672
|
+
});
|
|
4673
|
+
if (resolved.hostId && !loaded.manifest.hosts[resolved.hostId]) {
|
|
4674
|
+
const source = resolved.source ?? "unknown";
|
|
4675
|
+
const warnings = [`resolved host id \`${resolved.hostId}\` from ${source} is not in fleet.yaml`];
|
|
4676
|
+
const markdown2 = formatInvalidContext({
|
|
4677
|
+
generatedAt,
|
|
4678
|
+
hostId: resolved.hostId,
|
|
4679
|
+
source,
|
|
4680
|
+
osHostname,
|
|
4681
|
+
user,
|
|
4682
|
+
cwd,
|
|
4683
|
+
vault,
|
|
4684
|
+
trace: resolved.trace,
|
|
4685
|
+
warnings
|
|
4408
4686
|
});
|
|
4409
|
-
const match = out.match(/rclone\s+v(\d+)\.(\d+)\.(\d+)/i);
|
|
4410
|
-
if (!match) return null;
|
|
4411
4687
|
return {
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4688
|
+
exitCode: ExitCode.OK,
|
|
4689
|
+
result: ok({
|
|
4690
|
+
manifest_loaded: true,
|
|
4691
|
+
host_id: resolved.hostId,
|
|
4692
|
+
source: resolved.source,
|
|
4693
|
+
generated_at: generatedAt,
|
|
4694
|
+
identity_status: "invalid",
|
|
4695
|
+
resolver_trace: resolved.trace,
|
|
4696
|
+
warnings,
|
|
4697
|
+
markdown: markdown2,
|
|
4698
|
+
humanHint: markdown2
|
|
4699
|
+
})
|
|
4416
4700
|
};
|
|
4417
|
-
} catch {
|
|
4418
|
-
return null;
|
|
4419
4701
|
}
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4702
|
+
if (!resolved.hostId) {
|
|
4703
|
+
const warnings = ["host identity is unresolved"];
|
|
4704
|
+
const markdown2 = formatUnknownContext({
|
|
4705
|
+
generatedAt,
|
|
4706
|
+
osHostname,
|
|
4707
|
+
user,
|
|
4708
|
+
cwd,
|
|
4709
|
+
vault,
|
|
4710
|
+
reason: warnings[0],
|
|
4711
|
+
trace: resolved.trace,
|
|
4712
|
+
warnings
|
|
4713
|
+
});
|
|
4714
|
+
return {
|
|
4715
|
+
exitCode: ExitCode.OK,
|
|
4716
|
+
result: ok({
|
|
4717
|
+
manifest_loaded: true,
|
|
4718
|
+
generated_at: generatedAt,
|
|
4719
|
+
identity_status: "unknown",
|
|
4720
|
+
resolver_trace: resolved.trace,
|
|
4721
|
+
warnings,
|
|
4722
|
+
markdown: markdown2,
|
|
4723
|
+
humanHint: markdown2
|
|
4724
|
+
})
|
|
4725
|
+
};
|
|
4431
4726
|
}
|
|
4432
|
-
|
|
4727
|
+
const markdown = formatKnownContext({
|
|
4728
|
+
manifest: loaded.manifest,
|
|
4729
|
+
hostId: resolved.hostId,
|
|
4730
|
+
source: resolved.source,
|
|
4731
|
+
generatedAt,
|
|
4732
|
+
osHostname,
|
|
4733
|
+
user,
|
|
4734
|
+
cwd,
|
|
4735
|
+
vault,
|
|
4736
|
+
trace: resolved.trace
|
|
4737
|
+
});
|
|
4738
|
+
return {
|
|
4739
|
+
exitCode: ExitCode.OK,
|
|
4740
|
+
result: ok({
|
|
4741
|
+
manifest_loaded: true,
|
|
4742
|
+
host_id: resolved.hostId,
|
|
4743
|
+
source: resolved.source,
|
|
4744
|
+
generated_at: generatedAt,
|
|
4745
|
+
identity_status: "known",
|
|
4746
|
+
resolver_trace: resolved.trace,
|
|
4747
|
+
warnings: [],
|
|
4748
|
+
markdown,
|
|
4749
|
+
humanHint: markdown
|
|
4750
|
+
})
|
|
4751
|
+
};
|
|
4752
|
+
}
|
|
4753
|
+
async function loadFleetManifest(file) {
|
|
4754
|
+
let text;
|
|
4755
|
+
try {
|
|
4756
|
+
text = await readFile19(file, "utf8");
|
|
4757
|
+
} catch {
|
|
4758
|
+
return { ok: false, error: "FILE_NOT_FOUND" };
|
|
4759
|
+
}
|
|
4760
|
+
let parsed;
|
|
4761
|
+
try {
|
|
4762
|
+
parsed = yaml3.load(text, { schema: yaml3.JSON_SCHEMA });
|
|
4763
|
+
} catch (error) {
|
|
4764
|
+
return { ok: false, error: "INVALID_YAML", detail: error instanceof Error ? error.message : String(error) };
|
|
4765
|
+
}
|
|
4766
|
+
const result = FleetManifestSchema.safeParse(parsed);
|
|
4767
|
+
if (!result.success) {
|
|
4768
|
+
return { ok: false, error: "INVALID_FLEET_MANIFEST", detail: result.error.issues };
|
|
4769
|
+
}
|
|
4770
|
+
return { ok: true, manifest: result.data };
|
|
4771
|
+
}
|
|
4772
|
+
function invalidFleet(errors) {
|
|
4773
|
+
return {
|
|
4774
|
+
exitCode: ExitCode.FLEET_MANIFEST_INVALID,
|
|
4775
|
+
result: ok({
|
|
4776
|
+
valid: false,
|
|
4777
|
+
errors,
|
|
4778
|
+
warnings: [],
|
|
4779
|
+
host_count: 0,
|
|
4780
|
+
humanHint: `INVALID fleet manifest
|
|
4781
|
+
${errors.map((e) => ` ${e.path || "(root)"}: ${e.message}`).join("\n")}`
|
|
4782
|
+
})
|
|
4783
|
+
};
|
|
4784
|
+
}
|
|
4785
|
+
function fleetLoadErrors(loaded) {
|
|
4786
|
+
if (loaded.error === "INVALID_YAML") {
|
|
4787
|
+
return [{ path: "", message: `invalid YAML: ${String(loaded.detail ?? "parse failed")}` }];
|
|
4788
|
+
}
|
|
4789
|
+
if (loaded.error === "INVALID_FLEET_MANIFEST" && Array.isArray(loaded.detail)) {
|
|
4790
|
+
return loaded.detail.map((issue) => {
|
|
4791
|
+
const zodIssue = issue;
|
|
4792
|
+
return {
|
|
4793
|
+
path: (zodIssue.path ?? []).join("."),
|
|
4794
|
+
message: zodIssue.message ?? "invalid value"
|
|
4795
|
+
};
|
|
4796
|
+
});
|
|
4797
|
+
}
|
|
4798
|
+
return [{ path: "", message: loaded.error }];
|
|
4799
|
+
}
|
|
4800
|
+
function fleetWarnings(manifest) {
|
|
4801
|
+
const warnings = [];
|
|
4802
|
+
for (const [id, host] of Object.entries(manifest.hosts)) {
|
|
4803
|
+
if (host.role === "snapshotter" && host.protected !== true) {
|
|
4804
|
+
warnings.push(`snapshotter host '${id}' is not protected=true`);
|
|
4805
|
+
}
|
|
4806
|
+
}
|
|
4807
|
+
return warnings;
|
|
4808
|
+
}
|
|
4809
|
+
function findSnapshotter(manifest) {
|
|
4810
|
+
return Object.entries(manifest.hosts).find(([, host]) => host.role === "snapshotter")?.[0];
|
|
4811
|
+
}
|
|
4812
|
+
async function resolveHostId(input) {
|
|
4813
|
+
const trace = [];
|
|
4814
|
+
if (input.hostId) {
|
|
4815
|
+
trace.push({ source: "--host-id", status: "matched", value: input.hostId });
|
|
4816
|
+
return { hostId: input.hostId, source: "host-id", trace };
|
|
4817
|
+
}
|
|
4818
|
+
trace.push({ source: "--host-id", status: "unset" });
|
|
4819
|
+
if (input.env.SKILLWIKI_HOST_ID) {
|
|
4820
|
+
trace.push({ source: "SKILLWIKI_HOST_ID", status: "matched", value: input.env.SKILLWIKI_HOST_ID });
|
|
4821
|
+
return { hostId: input.env.SKILLWIKI_HOST_ID, source: "SKILLWIKI_HOST_ID", trace };
|
|
4822
|
+
}
|
|
4823
|
+
trace.push({ source: "SKILLWIKI_HOST_ID", status: "unset" });
|
|
4824
|
+
if (input.env.AGENT_HOST_ID) {
|
|
4825
|
+
trace.push({ source: "AGENT_HOST_ID", status: "matched", value: input.env.AGENT_HOST_ID });
|
|
4826
|
+
return { hostId: input.env.AGENT_HOST_ID, source: "AGENT_HOST_ID", trace };
|
|
4827
|
+
}
|
|
4828
|
+
trace.push({ source: "AGENT_HOST_ID", status: "unset" });
|
|
4829
|
+
if (input.home) {
|
|
4830
|
+
const dotenv = await parseDotenvFile(join27(input.home, ".skillwiki", ".env"));
|
|
4831
|
+
if (dotenv.SKILLWIKI_HOST_ID) {
|
|
4832
|
+
trace.push({ source: "~/.skillwiki/.env:SKILLWIKI_HOST_ID", status: "matched", value: dotenv.SKILLWIKI_HOST_ID });
|
|
4833
|
+
return { hostId: dotenv.SKILLWIKI_HOST_ID, source: "~/.skillwiki/.env:SKILLWIKI_HOST_ID", trace };
|
|
4834
|
+
}
|
|
4835
|
+
trace.push({ source: "~/.skillwiki/.env:SKILLWIKI_HOST_ID", status: "unset" });
|
|
4836
|
+
} else {
|
|
4837
|
+
trace.push({ source: "~/.skillwiki/.env:SKILLWIKI_HOST_ID", status: "skipped" });
|
|
4838
|
+
}
|
|
4839
|
+
if (input.env.VS_HOSTNAME) {
|
|
4840
|
+
trace.push({ source: "VS_HOSTNAME", status: "matched", value: input.env.VS_HOSTNAME });
|
|
4841
|
+
return { hostId: input.env.VS_HOSTNAME, source: "VS_HOSTNAME", trace };
|
|
4842
|
+
}
|
|
4843
|
+
trace.push({ source: "VS_HOSTNAME", status: "unset" });
|
|
4844
|
+
const hostname = input.osHostname.trim();
|
|
4845
|
+
if (hostname) {
|
|
4846
|
+
if (input.manifest.hosts[hostname]) {
|
|
4847
|
+
trace.push({ source: "hostname", status: "matched", value: hostname });
|
|
4848
|
+
return { hostId: hostname, source: "hostname", trace };
|
|
4849
|
+
}
|
|
4850
|
+
const byHostname = Object.entries(input.manifest.hosts).find(([, host]) => host.identity.hostnames.includes(hostname));
|
|
4851
|
+
if (byHostname) {
|
|
4852
|
+
trace.push({ source: "hostname", status: "matched", value: hostname });
|
|
4853
|
+
return { hostId: byHostname[0], source: "hostname", trace };
|
|
4854
|
+
}
|
|
4855
|
+
trace.push({ source: "hostname", status: "unmatched", value: hostname });
|
|
4856
|
+
} else {
|
|
4857
|
+
trace.push({ source: "hostname", status: "unset" });
|
|
4858
|
+
}
|
|
4859
|
+
return { trace };
|
|
4860
|
+
}
|
|
4861
|
+
function formatKnownContext(input) {
|
|
4862
|
+
const host = input.manifest.hosts[input.hostId];
|
|
4863
|
+
const protectedValue = host.protected === true ? "true" : "false";
|
|
4864
|
+
const writesTo = host.writes_to.join(", ");
|
|
4865
|
+
const selfAliases = collectSelfAliases(input.manifest, input.hostId);
|
|
4866
|
+
const outbound = collectOutboundAccess(input.manifest, input.hostId);
|
|
4867
|
+
const maintenanceLines = formatMaintenanceLines(host);
|
|
4868
|
+
const guidance = input.hostId === "macos-dev" ? "use declared SSH aliases for remote work when needed; do not assume undeclared hosts have reciprocal SSH access." : `this session is already on \`${input.hostId}\`; do not SSH to self aliases unless the user explicitly asks. Do not assume outbound SSH to other fleet hosts is configured.`;
|
|
4869
|
+
return [
|
|
4870
|
+
"## Runtime Host Context",
|
|
4871
|
+
"",
|
|
4872
|
+
`- Context generated: \`${input.generatedAt}\``,
|
|
4873
|
+
`- Current machine: \`${input.hostId}\`${input.source ? ` (source: \`${input.source}\`)` : ""}`,
|
|
4874
|
+
"- Identity status: `known`",
|
|
4875
|
+
`- Identity resolution: ${formatResolution(input.source, input.hostId)}`,
|
|
4876
|
+
`- Resolver trace: ${formatTrace(input.trace)}`,
|
|
4877
|
+
`- OS hostname: ${formatMaybe(input.osHostname)}`,
|
|
4878
|
+
`- User: ${formatMaybe(input.user)}`,
|
|
4879
|
+
`- Workspace: ${formatMaybe(input.cwd)}`,
|
|
4880
|
+
`- Vault: ${formatMaybe(input.vault)}`,
|
|
4881
|
+
"- Remote freshness: not checked by `fleet context`; run `sync status` or presync before host-sensitive work.",
|
|
4882
|
+
`- Fleet role: \`${host.role}\`; protected: \`${protectedValue}\`; writes_to: \`${writesTo}\``,
|
|
4883
|
+
...maintenanceLines,
|
|
4884
|
+
`- Self SSH aliases known in fleet: ${formatList(selfAliases)}`,
|
|
4885
|
+
`- Declared outbound SSH from this source: ${formatOutboundAccess(outbound)}`,
|
|
4886
|
+
`- Guidance: ${guidance}`
|
|
4887
|
+
].join("\n");
|
|
4888
|
+
}
|
|
4889
|
+
function formatUnknownContext(input) {
|
|
4890
|
+
return [
|
|
4891
|
+
"## Runtime Host Context",
|
|
4892
|
+
"",
|
|
4893
|
+
`- Context generated: \`${input.generatedAt}\``,
|
|
4894
|
+
"- Current machine: unknown",
|
|
4895
|
+
"- Identity status: `unknown`",
|
|
4896
|
+
`- Resolver trace: ${formatTrace(input.trace)}`,
|
|
4897
|
+
`- Warnings: ${formatWarnings(input.warnings)}`,
|
|
4898
|
+
`- OS hostname: ${formatMaybe(input.osHostname)}`,
|
|
4899
|
+
`- User: ${formatMaybe(input.user)}`,
|
|
4900
|
+
`- Workspace: ${formatMaybe(input.cwd)}`,
|
|
4901
|
+
`- Vault: ${formatMaybe(input.vault)}`,
|
|
4902
|
+
"- Remote freshness: not checked by `fleet context`; run `sync status` or presync before host-sensitive work.",
|
|
4903
|
+
"- Fleet role: unknown",
|
|
4904
|
+
"- Self SSH aliases known in fleet: unknown",
|
|
4905
|
+
"- Declared outbound SSH from this source: unknown",
|
|
4906
|
+
`- Guidance: ${input.reason}; do not assume local vs remote role. Inspect runtime or ask before SSH/deploy/sync work.`
|
|
4907
|
+
].join("\n");
|
|
4908
|
+
}
|
|
4909
|
+
function formatInvalidContext(input) {
|
|
4910
|
+
return [
|
|
4911
|
+
"## Runtime Host Context",
|
|
4912
|
+
"",
|
|
4913
|
+
`- Context generated: \`${input.generatedAt}\``,
|
|
4914
|
+
"- Current machine: unknown",
|
|
4915
|
+
"- Identity status: `invalid`",
|
|
4916
|
+
`- Identity resolution: ${formatResolution(input.source, input.hostId)}`,
|
|
4917
|
+
`- Resolver trace: ${formatTrace(input.trace)}`,
|
|
4918
|
+
`- Warnings: ${formatWarnings(input.warnings)}`,
|
|
4919
|
+
`- OS hostname: ${formatMaybe(input.osHostname)}`,
|
|
4920
|
+
`- User: ${formatMaybe(input.user)}`,
|
|
4921
|
+
`- Workspace: ${formatMaybe(input.cwd)}`,
|
|
4922
|
+
`- Vault: ${formatMaybe(input.vault)}`,
|
|
4923
|
+
"- Remote freshness: not checked by `fleet context`; run `sync status` or presync before host-sensitive work.",
|
|
4924
|
+
"- Fleet role: unknown",
|
|
4925
|
+
"- Self SSH aliases known in fleet: unknown",
|
|
4926
|
+
"- Declared outbound SSH from this source: unknown",
|
|
4927
|
+
`- Guidance: do not trust this identity; rerun with \`--host-id\` only if the user confirms \`${input.hostId}\` is the current fleet host id.`
|
|
4928
|
+
].join("\n");
|
|
4929
|
+
}
|
|
4930
|
+
function collectSelfAliases(manifest, hostId2) {
|
|
4931
|
+
const aliases = [];
|
|
4932
|
+
const host = manifest.hosts[hostId2];
|
|
4933
|
+
const access = host?.access?.from ?? {};
|
|
4934
|
+
for (const profile of Object.values(access)) {
|
|
4935
|
+
for (const alias of profile.ssh_aliases ?? []) aliases.push(alias);
|
|
4936
|
+
}
|
|
4937
|
+
return [...new Set(aliases)];
|
|
4938
|
+
}
|
|
4939
|
+
function collectOutboundAccess(manifest, sourceHostId) {
|
|
4940
|
+
const hosts = [];
|
|
4941
|
+
for (const [targetId, target] of Object.entries(manifest.hosts)) {
|
|
4942
|
+
if (targetId === sourceHostId) continue;
|
|
4943
|
+
const profile = target.access?.from?.[sourceHostId];
|
|
4944
|
+
if (profile && (profile.status === "configured" || profile.status === "local")) {
|
|
4945
|
+
hosts.push({
|
|
4946
|
+
hostId: targetId,
|
|
4947
|
+
sshAliases: [...new Set(profile.ssh_aliases ?? [])],
|
|
4948
|
+
users: [...new Set(profile.users ?? [])]
|
|
4949
|
+
});
|
|
4950
|
+
}
|
|
4951
|
+
}
|
|
4952
|
+
return hosts.sort((left, right) => left.hostId.localeCompare(right.hostId));
|
|
4953
|
+
}
|
|
4954
|
+
function formatMaintenanceLines(host) {
|
|
4955
|
+
const satellite = host.maintenance?.skillwiki_satellite;
|
|
4956
|
+
if (!satellite?.enabled) return [];
|
|
4957
|
+
return [
|
|
4958
|
+
`- Maintenance role: \`skillwiki satellite\`; user: \`${satellite.user}\`; ssh: \`${satellite.ssh_alias}\``,
|
|
4959
|
+
`- Maintenance paths: maintenance vault: \`${satellite.vault_path}\`; repo: \`${satellite.repo_path}\`; scheduler: \`${satellite.scheduler}\`; jobs: ${formatList(satellite.jobs)}`
|
|
4960
|
+
];
|
|
4961
|
+
}
|
|
4962
|
+
function formatOutboundAccess(values) {
|
|
4963
|
+
if (values.length === 0) return "none";
|
|
4964
|
+
return values.map((value) => {
|
|
4965
|
+
const aliasPart = value.sshAliases.length > 0 ? ` via ${formatList(value.sshAliases)}` : " (no SSH aliases)";
|
|
4966
|
+
const usersPart = value.users.length > 0 ? ` (users: ${formatList(value.users)})` : "";
|
|
4967
|
+
return `\`${value.hostId}\`${aliasPart}${usersPart}`;
|
|
4968
|
+
}).join("; ");
|
|
4969
|
+
}
|
|
4970
|
+
function formatResolution(source, hostId2) {
|
|
4971
|
+
return source ? `\`${source === "host-id" ? "--host-id" : source}\` -> \`${hostId2}\`` : `unknown -> \`${hostId2}\``;
|
|
4972
|
+
}
|
|
4973
|
+
function formatTrace(values) {
|
|
4974
|
+
if (values.length === 0) return "not available";
|
|
4975
|
+
return values.map((value) => {
|
|
4976
|
+
const source = `\`${value.source}\``;
|
|
4977
|
+
if (value.status === "matched") return `${source} matched \`${value.value ?? ""}\``;
|
|
4978
|
+
if (value.status === "unmatched") return `${source} unmatched \`${value.value ?? ""}\``;
|
|
4979
|
+
return `${source} ${value.status}`;
|
|
4980
|
+
}).join("; ");
|
|
4981
|
+
}
|
|
4982
|
+
function formatWarnings(values) {
|
|
4983
|
+
return values.length > 0 ? values.join("; ") : "none";
|
|
4984
|
+
}
|
|
4985
|
+
function formatList(values) {
|
|
4986
|
+
return values.length > 0 ? values.map((v) => `\`${v}\``).join(", ") : "none";
|
|
4987
|
+
}
|
|
4988
|
+
function formatMaybe(value) {
|
|
4989
|
+
return value && value.trim().length > 0 ? `\`${value}\`` : "unknown";
|
|
4990
|
+
}
|
|
4991
|
+
function safeEnvValue(value) {
|
|
4992
|
+
return value && value.trim().length > 0 ? value : void 0;
|
|
4993
|
+
}
|
|
4994
|
+
function safeUserName() {
|
|
4995
|
+
try {
|
|
4996
|
+
return userInfo().username;
|
|
4997
|
+
} catch {
|
|
4998
|
+
return "";
|
|
4999
|
+
}
|
|
5000
|
+
}
|
|
5001
|
+
|
|
5002
|
+
// src/utils/s3-mount-health.ts
|
|
5003
|
+
import { execSync } from "child_process";
|
|
5004
|
+
import { platform } from "os";
|
|
5005
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, unlinkSync as unlinkSync4, readFileSync as readFile20 } from "fs";
|
|
5006
|
+
import { join as join28 } from "path";
|
|
5007
|
+
var OS = platform();
|
|
5008
|
+
function findRcloneMountPid() {
|
|
5009
|
+
try {
|
|
5010
|
+
const out = execSync("pgrep -f 'rclone.*mount'", {
|
|
5011
|
+
encoding: "utf8",
|
|
5012
|
+
timeout: 2e3,
|
|
5013
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5014
|
+
}).trim();
|
|
5015
|
+
const pids = out.split("\n").filter(Boolean);
|
|
5016
|
+
if (pids.length === 0) return null;
|
|
5017
|
+
return parseInt(pids[0], 10);
|
|
5018
|
+
} catch {
|
|
5019
|
+
try {
|
|
5020
|
+
const out = execSync("ps aux", { encoding: "utf8", timeout: 2e3, stdio: ["pipe", "pipe", "pipe"] });
|
|
5021
|
+
for (const line of out.split("\n")) {
|
|
5022
|
+
if (line.includes("rclone") && line.includes("mount") && !line.includes("grep")) {
|
|
5023
|
+
const parts = line.trim().split(/\s+/);
|
|
5024
|
+
if (parts.length >= 2) return parseInt(parts[1], 10);
|
|
5025
|
+
}
|
|
5026
|
+
}
|
|
5027
|
+
} catch {
|
|
5028
|
+
}
|
|
5029
|
+
return null;
|
|
5030
|
+
}
|
|
5031
|
+
}
|
|
5032
|
+
function parseRcloneFlags(pid) {
|
|
5033
|
+
const flags = /* @__PURE__ */ new Map();
|
|
5034
|
+
try {
|
|
5035
|
+
const args = getRcloneArgs(pid);
|
|
5036
|
+
for (let i = 0; i < args.length; i++) {
|
|
5037
|
+
const arg = args[i];
|
|
5038
|
+
if (arg.startsWith("--") && arg.includes("=")) {
|
|
5039
|
+
const eq = arg.indexOf("=");
|
|
5040
|
+
flags.set(arg.slice(0, eq), arg.slice(eq + 1));
|
|
5041
|
+
} else if (arg.startsWith("--")) {
|
|
5042
|
+
const next = args[i + 1];
|
|
5043
|
+
if (next && !next.startsWith("-")) {
|
|
5044
|
+
flags.set(arg, next);
|
|
5045
|
+
i++;
|
|
5046
|
+
} else {
|
|
5047
|
+
flags.set(arg, "");
|
|
5048
|
+
}
|
|
5049
|
+
}
|
|
5050
|
+
}
|
|
5051
|
+
} catch {
|
|
5052
|
+
}
|
|
5053
|
+
return flags;
|
|
5054
|
+
}
|
|
5055
|
+
function getRcloneVersion() {
|
|
5056
|
+
try {
|
|
5057
|
+
const out = execSync("rclone version", {
|
|
5058
|
+
encoding: "utf8",
|
|
5059
|
+
timeout: 3e3,
|
|
5060
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5061
|
+
});
|
|
5062
|
+
const match = out.match(/rclone\s+v(\d+)\.(\d+)\.(\d+)/i);
|
|
5063
|
+
if (!match) return null;
|
|
5064
|
+
return {
|
|
5065
|
+
major: parseInt(match[1], 10),
|
|
5066
|
+
minor: parseInt(match[2], 10),
|
|
5067
|
+
patch: parseInt(match[3], 10),
|
|
5068
|
+
raw: out.split("\n")[0].trim()
|
|
5069
|
+
};
|
|
5070
|
+
} catch {
|
|
5071
|
+
return null;
|
|
5072
|
+
}
|
|
5073
|
+
}
|
|
5074
|
+
function extractRcloneFs(args) {
|
|
5075
|
+
let foundMount = false;
|
|
5076
|
+
for (const arg of args) {
|
|
5077
|
+
if (arg === "mount") {
|
|
5078
|
+
foundMount = true;
|
|
5079
|
+
continue;
|
|
5080
|
+
}
|
|
5081
|
+
if (foundMount && arg.includes(":") && !arg.startsWith("-") && !arg.startsWith("/")) {
|
|
5082
|
+
return arg;
|
|
5083
|
+
}
|
|
5084
|
+
}
|
|
5085
|
+
return null;
|
|
4433
5086
|
}
|
|
4434
5087
|
function getRcloneArgs(pid) {
|
|
4435
5088
|
try {
|
|
@@ -4508,7 +5161,7 @@ function detectFuseMount(vaultPath) {
|
|
|
4508
5161
|
return null;
|
|
4509
5162
|
}
|
|
4510
5163
|
function writeTest(dir) {
|
|
4511
|
-
const testFile =
|
|
5164
|
+
const testFile = join28(dir, `.doctor-write-test-${process.pid}.tmp`);
|
|
4512
5165
|
const payload = `skillwiki doctor write test \u2014 ${Date.now()} \u2014 ${Math.random().toString(36).slice(2)}`;
|
|
4513
5166
|
const start = Date.now();
|
|
4514
5167
|
try {
|
|
@@ -4519,7 +5172,7 @@ function writeTest(dir) {
|
|
|
4519
5172
|
const writeMs = Date.now() - start;
|
|
4520
5173
|
const readStart = Date.now();
|
|
4521
5174
|
try {
|
|
4522
|
-
const back =
|
|
5175
|
+
const back = readFile20(testFile, "utf8");
|
|
4523
5176
|
const readMs = Date.now() - readStart;
|
|
4524
5177
|
if (back !== payload) {
|
|
4525
5178
|
try {
|
|
@@ -4608,13 +5261,13 @@ function detectCliChannels(argv, home) {
|
|
|
4608
5261
|
}
|
|
4609
5262
|
const plugin = findPlugin(home);
|
|
4610
5263
|
if (plugin) {
|
|
4611
|
-
const pluginBin =
|
|
4612
|
-
if (
|
|
5264
|
+
const pluginBin = join29(plugin.installPath, "bin", "skillwiki");
|
|
5265
|
+
if (existsSync11(pluginBin)) {
|
|
4613
5266
|
channels.push({ name: "plugin", path: pluginBin, isDevLink: false });
|
|
4614
5267
|
}
|
|
4615
5268
|
}
|
|
4616
|
-
const installBin =
|
|
4617
|
-
if (
|
|
5269
|
+
const installBin = join29(home, ".claude", "skills", "bin", "skillwiki");
|
|
5270
|
+
if (existsSync11(installBin)) {
|
|
4618
5271
|
channels.push({ name: "install", path: installBin, isDevLink: false });
|
|
4619
5272
|
}
|
|
4620
5273
|
return channels;
|
|
@@ -4666,7 +5319,7 @@ function checkCliChannels(argv, home) {
|
|
|
4666
5319
|
}
|
|
4667
5320
|
async function checkConfigFile(home) {
|
|
4668
5321
|
const cfgPath = configPath(home);
|
|
4669
|
-
if (!
|
|
5322
|
+
if (!existsSync11(cfgPath)) {
|
|
4670
5323
|
return check("warn", "config_file", "Config file exists", `${cfgPath} not found`);
|
|
4671
5324
|
}
|
|
4672
5325
|
try {
|
|
@@ -4681,7 +5334,7 @@ function checkWikiPathExists(resolvedPath) {
|
|
|
4681
5334
|
if (resolvedPath === void 0) {
|
|
4682
5335
|
return check("error", "wiki_path_exists", "Vault directory exists", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
4683
5336
|
}
|
|
4684
|
-
if (
|
|
5337
|
+
if (existsSync11(resolvedPath) && statSync3(resolvedPath).isDirectory()) {
|
|
4685
5338
|
return check("pass", "wiki_path_exists", "Vault directory exists", resolvedPath);
|
|
4686
5339
|
}
|
|
4687
5340
|
return check("error", "wiki_path_exists", "Vault directory exists", `${resolvedPath} does not exist or is not a directory`);
|
|
@@ -4690,13 +5343,13 @@ function checkVaultStructure(resolvedPath) {
|
|
|
4690
5343
|
if (resolvedPath === void 0) {
|
|
4691
5344
|
return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
4692
5345
|
}
|
|
4693
|
-
if (!
|
|
5346
|
+
if (!existsSync11(resolvedPath)) {
|
|
4694
5347
|
return check("error", "vault_structure", "Vault structure valid", "Cannot check \u2014 vault directory does not exist");
|
|
4695
5348
|
}
|
|
4696
5349
|
const missing = [];
|
|
4697
|
-
if (!
|
|
5350
|
+
if (!existsSync11(join29(resolvedPath, "SCHEMA.md"))) missing.push("SCHEMA.md");
|
|
4698
5351
|
for (const dir of ["raw", "entities", "concepts", "meta"]) {
|
|
4699
|
-
if (!
|
|
5352
|
+
if (!existsSync11(join29(resolvedPath, dir))) missing.push(dir + "/");
|
|
4700
5353
|
}
|
|
4701
5354
|
if (missing.length === 0) {
|
|
4702
5355
|
return check("pass", "vault_structure", "Vault structure valid", "All required files and directories present");
|
|
@@ -4704,8 +5357,8 @@ function checkVaultStructure(resolvedPath) {
|
|
|
4704
5357
|
return check("warn", "vault_structure", "Vault structure valid", `Missing: ${missing.join(", ")} \u2014 run \`skillwiki init\` to add CodeWiki structure`);
|
|
4705
5358
|
}
|
|
4706
5359
|
function checkSkillsInstalled(home, cwd) {
|
|
4707
|
-
const srcDir = cwd ?
|
|
4708
|
-
if (srcDir &&
|
|
5360
|
+
const srcDir = cwd ? join29(cwd, "packages", "skills") : void 0;
|
|
5361
|
+
if (srcDir && existsSync11(srcDir)) {
|
|
4709
5362
|
const found = findInstalledSkillMd(srcDir);
|
|
4710
5363
|
if (found.length > 0) {
|
|
4711
5364
|
return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (source)`);
|
|
@@ -4718,8 +5371,8 @@ function checkSkillsInstalled(home, cwd) {
|
|
|
4718
5371
|
return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (plugin v${plugin.version})`);
|
|
4719
5372
|
}
|
|
4720
5373
|
}
|
|
4721
|
-
const skillsDir =
|
|
4722
|
-
if (
|
|
5374
|
+
const skillsDir = join29(home, ".claude", "skills");
|
|
5375
|
+
if (existsSync11(skillsDir)) {
|
|
4723
5376
|
const found = findInstalledSkillMd(skillsDir);
|
|
4724
5377
|
if (found.length > 0) {
|
|
4725
5378
|
return check("pass", "skills_installed", "Skills installed", `${found.length} SKILL.md file(s) found (CLI install)`);
|
|
@@ -4729,10 +5382,10 @@ function checkSkillsInstalled(home, cwd) {
|
|
|
4729
5382
|
}
|
|
4730
5383
|
function checkDuplicateSkills(home) {
|
|
4731
5384
|
const plugin = findPlugin(home);
|
|
4732
|
-
const skillsDir =
|
|
5385
|
+
const skillsDir = join29(home, ".claude", "skills");
|
|
4733
5386
|
const agentSkillDirs = [
|
|
4734
|
-
{ label: "~/.codex/skills/", path:
|
|
4735
|
-
{ label: "~/.agents/skills/", path:
|
|
5387
|
+
{ label: "~/.codex/skills/", path: join29(home, ".codex", "skills") },
|
|
5388
|
+
{ label: "~/.agents/skills/", path: join29(home, ".agents", "skills") }
|
|
4736
5389
|
];
|
|
4737
5390
|
if (!plugin) {
|
|
4738
5391
|
return check("pass", "skills_duplicate", "Skills not duplicated", "Single install channel");
|
|
@@ -4831,8 +5484,8 @@ async function checkProfiles(home) {
|
|
|
4831
5484
|
}
|
|
4832
5485
|
async function checkProjectLocalOverride(cwd) {
|
|
4833
5486
|
const dir = cwd ?? process.cwd();
|
|
4834
|
-
const envPath =
|
|
4835
|
-
if (
|
|
5487
|
+
const envPath = join29(dir, ".skillwiki", ".env");
|
|
5488
|
+
if (existsSync11(envPath)) {
|
|
4836
5489
|
return check("pass", "project_local", "Project-local config", `Found: ${envPath}`);
|
|
4837
5490
|
}
|
|
4838
5491
|
return check("pass", "project_local", "Project-local config", "None");
|
|
@@ -4841,7 +5494,7 @@ function checkVaultGitRemote(resolvedPath) {
|
|
|
4841
5494
|
if (resolvedPath === void 0) {
|
|
4842
5495
|
return check("error", "vault_git_remote", "Vault git remote", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
4843
5496
|
}
|
|
4844
|
-
if (!
|
|
5497
|
+
if (!existsSync11(join29(resolvedPath, ".git"))) {
|
|
4845
5498
|
return check("warn", "vault_git_remote", "Vault git remote", "Vault is not a git repository \u2014 sync features unavailable");
|
|
4846
5499
|
}
|
|
4847
5500
|
try {
|
|
@@ -4864,9 +5517,9 @@ function checkObsidianTemplates(resolvedPath) {
|
|
|
4864
5517
|
return check("error", "obsidian_templates", "Obsidian templates", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
4865
5518
|
}
|
|
4866
5519
|
const missing = [];
|
|
4867
|
-
if (!
|
|
4868
|
-
if (!
|
|
4869
|
-
if (!
|
|
5520
|
+
if (!existsSync11(join29(resolvedPath, "_Templates"))) missing.push("_Templates/");
|
|
5521
|
+
if (!existsSync11(join29(resolvedPath, ".obsidian", "templates.json"))) missing.push(".obsidian/templates.json");
|
|
5522
|
+
if (!existsSync11(join29(resolvedPath, ".obsidian", "app.json"))) missing.push(".obsidian/app.json");
|
|
4870
5523
|
if (missing.length === 0) {
|
|
4871
5524
|
return check("pass", "obsidian_templates", "Obsidian templates", "Template folder and config present");
|
|
4872
5525
|
}
|
|
@@ -4876,8 +5529,8 @@ function checkDotStoreClean(resolvedPath) {
|
|
|
4876
5529
|
if (resolvedPath === void 0) {
|
|
4877
5530
|
return check("error", "dsstore_clean", "No .DS_Store in raw/", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
4878
5531
|
}
|
|
4879
|
-
const rawDir =
|
|
4880
|
-
if (!
|
|
5532
|
+
const rawDir = join29(resolvedPath, "raw");
|
|
5533
|
+
if (!existsSync11(rawDir)) {
|
|
4881
5534
|
return check("pass", "dsstore_clean", "No .DS_Store in raw/", "raw/ directory not found \u2014 check skipped");
|
|
4882
5535
|
}
|
|
4883
5536
|
const found = [];
|
|
@@ -4892,7 +5545,7 @@ function checkDotStoreClean(resolvedPath) {
|
|
|
4892
5545
|
if (entry.name === ".DS_Store") {
|
|
4893
5546
|
found.push(rel ? `${rel}/.DS_Store` : ".DS_Store");
|
|
4894
5547
|
} else if (entry.isDirectory()) {
|
|
4895
|
-
walk2(
|
|
5548
|
+
walk2(join29(dir, entry.name), rel ? `${rel}/${entry.name}` : entry.name);
|
|
4896
5549
|
}
|
|
4897
5550
|
}
|
|
4898
5551
|
})(rawDir, "");
|
|
@@ -4905,7 +5558,7 @@ function checkSyncLastPush(resolvedPath) {
|
|
|
4905
5558
|
if (resolvedPath === void 0) {
|
|
4906
5559
|
return check("error", "sync_last_push", "Vault sync recency", "Cannot check \u2014 WIKI_PATH not resolved");
|
|
4907
5560
|
}
|
|
4908
|
-
if (!
|
|
5561
|
+
if (!existsSync11(join29(resolvedPath, ".git"))) {
|
|
4909
5562
|
return check("pass", "sync_last_push", "Vault sync recency", "No git repo \u2014 sync check skipped");
|
|
4910
5563
|
}
|
|
4911
5564
|
let timestamp;
|
|
@@ -4953,7 +5606,7 @@ function checkVaultGitDirty(resolvedPath) {
|
|
|
4953
5606
|
if (resolvedPath === void 0) {
|
|
4954
5607
|
return check("pass", "vault_git_dirty", "Vault git dirty state", "No vault path \u2014 check skipped");
|
|
4955
5608
|
}
|
|
4956
|
-
if (!
|
|
5609
|
+
if (!existsSync11(join29(resolvedPath, ".git"))) {
|
|
4957
5610
|
return check("pass", "vault_git_dirty", "Vault git dirty state", "No git repo \u2014 check skipped");
|
|
4958
5611
|
}
|
|
4959
5612
|
try {
|
|
@@ -4994,7 +5647,7 @@ function checkVaultGitComparison(resolvedPath, id, label, range, nonZeroSuffix,
|
|
|
4994
5647
|
if (resolvedPath === void 0) {
|
|
4995
5648
|
return check("pass", id, label, "No vault path \u2014 check skipped");
|
|
4996
5649
|
}
|
|
4997
|
-
if (!
|
|
5650
|
+
if (!existsSync11(join29(resolvedPath, ".git"))) {
|
|
4998
5651
|
return check("pass", id, label, "No git repo \u2014 check skipped");
|
|
4999
5652
|
}
|
|
5000
5653
|
if (!hasOriginMain(resolvedPath)) {
|
|
@@ -5014,13 +5667,38 @@ function checkVaultGitComparison(resolvedPath, id, label, range, nonZeroSuffix,
|
|
|
5014
5667
|
return check("warn", id, label, "Could not compare HEAD with origin/main");
|
|
5015
5668
|
}
|
|
5016
5669
|
}
|
|
5670
|
+
async function checkFleetIdentity(input) {
|
|
5671
|
+
if (!input.vaultPath) {
|
|
5672
|
+
return check("pass", "fleet_identity", "Fleet identity", "No vault path \u2014 check skipped");
|
|
5673
|
+
}
|
|
5674
|
+
const r = await runFleetContext({
|
|
5675
|
+
vault: input.vaultPath,
|
|
5676
|
+
env: { ...process.env, WIKI_PATH: input.envValue ?? input.vaultPath },
|
|
5677
|
+
home: input.home,
|
|
5678
|
+
cwd: input.cwd ?? process.cwd(),
|
|
5679
|
+
osHostname: process.env.HOSTNAME,
|
|
5680
|
+
user: process.env.USER
|
|
5681
|
+
});
|
|
5682
|
+
if (!r.result.ok) {
|
|
5683
|
+
return check("warn", "fleet_identity", "Fleet identity", "Could not evaluate fleet identity");
|
|
5684
|
+
}
|
|
5685
|
+
const data = r.result.data;
|
|
5686
|
+
if (!data.manifest_loaded) {
|
|
5687
|
+
return check("pass", "fleet_identity", "Fleet identity", "Fleet manifest unavailable \u2014 check skipped");
|
|
5688
|
+
}
|
|
5689
|
+
if (data.identity_status === "known") {
|
|
5690
|
+
return check("pass", "fleet_identity", "Fleet identity", `Resolved ${data.host_id ?? "unknown"} via ${data.source ?? "unknown"}`);
|
|
5691
|
+
}
|
|
5692
|
+
const detail = data.warnings.length > 0 ? data.warnings.join("; ") : "Fleet identity is unresolved";
|
|
5693
|
+
return check("warn", "fleet_identity", "Fleet identity", detail);
|
|
5694
|
+
}
|
|
5017
5695
|
function pullLogPaths(home) {
|
|
5018
5696
|
const paths = platform2() === "darwin" ? [
|
|
5019
|
-
|
|
5020
|
-
|
|
5697
|
+
join29(home, "Library", "Logs", "wiki-pull.log"),
|
|
5698
|
+
join29(home, ".local", "state", "vault-sync", "log", "wiki-pull.log")
|
|
5021
5699
|
] : [
|
|
5022
|
-
|
|
5023
|
-
|
|
5700
|
+
join29(home, ".local", "state", "vault-sync", "log", "wiki-pull.log"),
|
|
5701
|
+
join29(home, "Library", "Logs", "wiki-pull.log")
|
|
5024
5702
|
];
|
|
5025
5703
|
return [...new Set(paths)];
|
|
5026
5704
|
}
|
|
@@ -5032,7 +5710,7 @@ function isRecentLogLine(line, nowMs) {
|
|
|
5032
5710
|
return nowMs - ts <= 24 * 60 * 60 * 1e3;
|
|
5033
5711
|
}
|
|
5034
5712
|
function checkVaultGitPullFailures(home) {
|
|
5035
|
-
const path = pullLogPaths(home).find((p) =>
|
|
5713
|
+
const path = pullLogPaths(home).find((p) => existsSync11(p));
|
|
5036
5714
|
if (!path) {
|
|
5037
5715
|
return check("pass", "vault_git_pull_failures", "Vault pull failures", "No wiki-pull.log found \u2014 check skipped");
|
|
5038
5716
|
}
|
|
@@ -5060,8 +5738,8 @@ function checkS3MountPerf(resolvedPath) {
|
|
|
5060
5738
|
return check("pass", "s3_mount_perf", "S3 mount performance", "local disk");
|
|
5061
5739
|
}
|
|
5062
5740
|
const mountPoint = fuse.mountPoint;
|
|
5063
|
-
const conceptsDir =
|
|
5064
|
-
if (!
|
|
5741
|
+
const conceptsDir = join29(resolvedPath, "concepts");
|
|
5742
|
+
if (!existsSync11(conceptsDir)) {
|
|
5065
5743
|
return check("pass", "s3_mount_perf", "S3 mount performance", `S3 FUSE mount (${mountPoint}), no concepts/ to benchmark`);
|
|
5066
5744
|
}
|
|
5067
5745
|
const start = Date.now();
|
|
@@ -5243,8 +5921,8 @@ function checkWriteTest(resolvedPath) {
|
|
|
5243
5921
|
if (!fuse) {
|
|
5244
5922
|
return check("pass", "s3_write_test", "S3 write test", "local disk \u2014 check skipped");
|
|
5245
5923
|
}
|
|
5246
|
-
const conceptsDir =
|
|
5247
|
-
if (!
|
|
5924
|
+
const conceptsDir = join29(resolvedPath, "concepts");
|
|
5925
|
+
if (!existsSync11(conceptsDir)) {
|
|
5248
5926
|
return check("pass", "s3_write_test", "S3 write test", "no concepts/ dir to test \u2014 check skipped");
|
|
5249
5927
|
}
|
|
5250
5928
|
const result = writeTest(conceptsDir);
|
|
@@ -5330,7 +6008,7 @@ function checkVfsCacheHealth(resolvedPath) {
|
|
|
5330
6008
|
}
|
|
5331
6009
|
function readVaultSyncConfig(home) {
|
|
5332
6010
|
try {
|
|
5333
|
-
const content = readFileSync7(
|
|
6011
|
+
const content = readFileSync7(join29(home, ".skillwiki", ".env"), "utf8");
|
|
5334
6012
|
let installed = false;
|
|
5335
6013
|
let role;
|
|
5336
6014
|
for (const line of content.split(/\r?\n/)) {
|
|
@@ -5364,12 +6042,12 @@ function vaultSyncChecks(input) {
|
|
|
5364
6042
|
];
|
|
5365
6043
|
}
|
|
5366
6044
|
const isMac = os === "darwin";
|
|
5367
|
-
const logDir = input.logDir ?? (isMac ?
|
|
5368
|
-
const shareDir = input.shareDir ?? (isMac ?
|
|
5369
|
-
const filterPath = input.filterPath ??
|
|
6045
|
+
const logDir = input.logDir ?? (isMac ? join29(home, "Library", "Logs") : join29(home, ".local", "state", "vault-sync", "log"));
|
|
6046
|
+
const shareDir = input.shareDir ?? (isMac ? join29(home, "Library", "Application Support", "vault-sync", "bin") : join29(home, ".local", "share", "vault-sync", "bin"));
|
|
6047
|
+
const filterPath = input.filterPath ?? join29(home, ".config", "rclone", "wiki-push-filters.txt");
|
|
5370
6048
|
const snapshotPath = input.snapshotScriptPath ?? "/root/.hermes/scripts/wiki-snapshot-v3.sh";
|
|
5371
|
-
const pushScriptPath =
|
|
5372
|
-
const c1 =
|
|
6049
|
+
const pushScriptPath = join29(shareDir, "wiki-push.sh");
|
|
6050
|
+
const c1 = existsSync11(pushScriptPath) ? check("pass", "vault_sync_installed", "Vault sync installed", `Found: ${pushScriptPath}`) : check("error", "vault_sync_installed", "Vault sync installed", `Script not found at ${pushScriptPath} \u2014 run vault-sync-install`);
|
|
5373
6051
|
let c2;
|
|
5374
6052
|
try {
|
|
5375
6053
|
if (isMac) {
|
|
@@ -5420,7 +6098,7 @@ function vaultSyncChecks(input) {
|
|
|
5420
6098
|
"Scheduler check failed \u2014 run vault-sync-install"
|
|
5421
6099
|
);
|
|
5422
6100
|
}
|
|
5423
|
-
const logFile =
|
|
6101
|
+
const logFile = join29(logDir, "wiki-push.log");
|
|
5424
6102
|
let c3;
|
|
5425
6103
|
try {
|
|
5426
6104
|
const logContent = readFileSync7(logFile, "utf8");
|
|
@@ -5479,7 +6157,7 @@ function vaultSyncChecks(input) {
|
|
|
5479
6157
|
}
|
|
5480
6158
|
}
|
|
5481
6159
|
} catch {
|
|
5482
|
-
c3 =
|
|
6160
|
+
c3 = existsSync11(logDir) ? check(
|
|
5483
6161
|
"warn",
|
|
5484
6162
|
"vault_sync_last_push_age",
|
|
5485
6163
|
"Vault sync last push recency",
|
|
@@ -5491,7 +6169,7 @@ function vaultSyncChecks(input) {
|
|
|
5491
6169
|
`Log directory not found at ${logDir}`
|
|
5492
6170
|
);
|
|
5493
6171
|
}
|
|
5494
|
-
const fetchLogFile =
|
|
6172
|
+
const fetchLogFile = join29(logDir, "wiki-fetch.log");
|
|
5495
6173
|
let cFetch;
|
|
5496
6174
|
try {
|
|
5497
6175
|
const logContent = readFileSync7(fetchLogFile, "utf8");
|
|
@@ -5538,7 +6216,7 @@ function vaultSyncChecks(input) {
|
|
|
5538
6216
|
}
|
|
5539
6217
|
let c4;
|
|
5540
6218
|
try {
|
|
5541
|
-
if (!
|
|
6219
|
+
if (!existsSync11(filterPath)) {
|
|
5542
6220
|
c4 = check(
|
|
5543
6221
|
"error",
|
|
5544
6222
|
"vault_sync_filter_present",
|
|
@@ -5587,7 +6265,7 @@ function vaultSyncChecks(input) {
|
|
|
5587
6265
|
);
|
|
5588
6266
|
} else {
|
|
5589
6267
|
try {
|
|
5590
|
-
if (!
|
|
6268
|
+
if (!existsSync11(snapshotPath)) {
|
|
5591
6269
|
c5 = check(
|
|
5592
6270
|
"error",
|
|
5593
6271
|
"vault_sync_snapshot_guard",
|
|
@@ -5633,15 +6311,15 @@ function findSkillMd(dir) {
|
|
|
5633
6311
|
}
|
|
5634
6312
|
for (const entry of entries) {
|
|
5635
6313
|
if (entry.isFile() && entry.name === "SKILL.md") {
|
|
5636
|
-
results.push(
|
|
6314
|
+
results.push(join29(dir, entry.name));
|
|
5637
6315
|
} else if (entry.isDirectory()) {
|
|
5638
|
-
results.push(...findSkillMd(
|
|
6316
|
+
results.push(...findSkillMd(join29(dir, entry.name)));
|
|
5639
6317
|
}
|
|
5640
6318
|
}
|
|
5641
6319
|
return results;
|
|
5642
6320
|
}
|
|
5643
6321
|
function findInstalledSkillMd(dir) {
|
|
5644
|
-
const directSkills = findSkillNames(dir).map((name) =>
|
|
6322
|
+
const directSkills = findSkillNames(dir).map((name) => join29(dir, name, "SKILL.md"));
|
|
5645
6323
|
return directSkills.length > 0 ? directSkills : findSkillMd(dir);
|
|
5646
6324
|
}
|
|
5647
6325
|
function findSkillNames(dir) {
|
|
@@ -5653,7 +6331,7 @@ function findSkillNames(dir) {
|
|
|
5653
6331
|
return results;
|
|
5654
6332
|
}
|
|
5655
6333
|
for (const entry of entries) {
|
|
5656
|
-
if (entry.isDirectory() &&
|
|
6334
|
+
if (entry.isDirectory() && existsSync11(join29(dir, entry.name, "SKILL.md"))) {
|
|
5657
6335
|
results.push(entry.name);
|
|
5658
6336
|
}
|
|
5659
6337
|
}
|
|
@@ -5697,7 +6375,7 @@ async function vaultMetrics(resolvedPath) {
|
|
|
5697
6375
|
}
|
|
5698
6376
|
let logLines = 0;
|
|
5699
6377
|
try {
|
|
5700
|
-
logLines = readFileSync7(
|
|
6378
|
+
logLines = readFileSync7(join29(resolvedPath, "log.md"), "utf8").split("\n").length;
|
|
5701
6379
|
} catch {
|
|
5702
6380
|
}
|
|
5703
6381
|
return [
|
|
@@ -5727,6 +6405,12 @@ async function runDoctor(input) {
|
|
|
5727
6405
|
checks.push(checkVaultStructure(resolvedPath));
|
|
5728
6406
|
checks.push(checkObsidianTemplates(resolvedPath));
|
|
5729
6407
|
checks.push(checkVaultGitRemote(resolvedPath));
|
|
6408
|
+
checks.push(await checkFleetIdentity({
|
|
6409
|
+
vaultPath: resolvedPath,
|
|
6410
|
+
home: input.home,
|
|
6411
|
+
cwd: input.cwd,
|
|
6412
|
+
envValue: input.envValue
|
|
6413
|
+
}));
|
|
5730
6414
|
checks.push(checkSyncLastPush(resolvedPath));
|
|
5731
6415
|
checks.push(checkVaultGitDirty(resolvedPath));
|
|
5732
6416
|
checks.push(checkVaultGitAhead(resolvedPath));
|
|
@@ -5881,7 +6565,7 @@ function summarizeChecks(checks) {
|
|
|
5881
6565
|
};
|
|
5882
6566
|
}
|
|
5883
6567
|
function classifyLog(path, id, label, okPattern) {
|
|
5884
|
-
if (!
|
|
6568
|
+
if (!existsSync12(path)) return { id, label, status: "warn", detail: `log file missing: ${path}` };
|
|
5885
6569
|
const lines = readFileSync8(path, "utf8").split(/\r?\n/).filter(Boolean);
|
|
5886
6570
|
const last = lines[lines.length - 1] ?? "";
|
|
5887
6571
|
if (!last) return { id, label, status: "warn", detail: `log file empty: ${path}` };
|
|
@@ -5900,28 +6584,28 @@ function runVaultSyncHealth(home, syncMode) {
|
|
|
5900
6584
|
};
|
|
5901
6585
|
}
|
|
5902
6586
|
const isMac = platform3() === "darwin";
|
|
5903
|
-
const shareDir = isMac ?
|
|
5904
|
-
const logDir = isMac ?
|
|
5905
|
-
const filterPath =
|
|
6587
|
+
const shareDir = isMac ? join30(home, "Library", "Application Support", "vault-sync", "bin") : join30(home, ".local", "share", "vault-sync", "bin");
|
|
6588
|
+
const logDir = isMac ? join30(home, "Library", "Logs") : join30(home, ".local", "state", "vault-sync", "log");
|
|
6589
|
+
const filterPath = join30(home, ".config", "rclone", "wiki-push-filters.txt");
|
|
5906
6590
|
const checks = [];
|
|
5907
|
-
const pushScript =
|
|
5908
|
-
checks.push(
|
|
6591
|
+
const pushScript = join30(shareDir, "wiki-push.sh");
|
|
6592
|
+
checks.push(existsSync12(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}` });
|
|
5909
6593
|
if (isMac) {
|
|
5910
|
-
const pushPlist =
|
|
5911
|
-
const fetchPlist =
|
|
5912
|
-
checks.push(
|
|
6594
|
+
const pushPlist = join30(home, "Library", "LaunchAgents", "com.karlchow.wiki-push.plist");
|
|
6595
|
+
const fetchPlist = join30(home, "Library", "LaunchAgents", "com.karlchow.wiki-fetch.plist");
|
|
6596
|
+
checks.push(existsSync12(pushPlist) && existsSync12(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)" });
|
|
5913
6597
|
checks.push({ id: "vault_sync_fuse_refresh_job", label: "Vault sync fuse refresh job", status: "pass", detail: "macOS host \u2014 check skipped" });
|
|
5914
6598
|
} else {
|
|
5915
|
-
const pushTimer =
|
|
5916
|
-
const fetchTimer =
|
|
5917
|
-
const fuseTimer =
|
|
5918
|
-
const fuseService =
|
|
5919
|
-
checks.push(
|
|
5920
|
-
checks.push(
|
|
5921
|
-
}
|
|
5922
|
-
checks.push(classifyLog(
|
|
5923
|
-
checks.push(classifyLog(
|
|
5924
|
-
if (!
|
|
6599
|
+
const pushTimer = join30(home, ".config", "systemd", "user", "wiki-push.timer");
|
|
6600
|
+
const fetchTimer = join30(home, ".config", "systemd", "user", "wiki-fetch.timer");
|
|
6601
|
+
const fuseTimer = join30(home, ".config", "systemd", "user", "wiki-fuse-refresh.timer");
|
|
6602
|
+
const fuseService = join30(home, ".config", "systemd", "user", "wiki-fuse-refresh.service");
|
|
6603
|
+
checks.push(existsSync12(pushTimer) && existsSync12(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)" });
|
|
6604
|
+
checks.push(existsSync12(fuseTimer) && existsSync12(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)" });
|
|
6605
|
+
}
|
|
6606
|
+
checks.push(classifyLog(join30(logDir, "wiki-push.log"), "vault_sync_last_push_age", "Vault sync last push recency", /OK push/));
|
|
6607
|
+
checks.push(classifyLog(join30(logDir, "wiki-fetch.log"), "vault_sync_last_fetch_status", "Vault sync last fetch status", /NOTIFY|OK behind|OK/));
|
|
6608
|
+
if (!existsSync12(filterPath)) {
|
|
5925
6609
|
checks.push({ id: "vault_sync_filter_present", label: "Vault sync filter file present", status: "error", detail: `Filter missing: ${filterPath}` });
|
|
5926
6610
|
} else {
|
|
5927
6611
|
const content = readFileSync8(filterPath, "utf8");
|
|
@@ -6146,8 +6830,8 @@ async function runHealth(input) {
|
|
|
6146
6830
|
}
|
|
6147
6831
|
|
|
6148
6832
|
// src/commands/archive.ts
|
|
6149
|
-
import { rename as rename7, mkdir as mkdir9, readFile as
|
|
6150
|
-
import { join as
|
|
6833
|
+
import { rename as rename7, mkdir as mkdir9, readFile as readFile21, writeFile as writeFile10 } from "fs/promises";
|
|
6834
|
+
import { join as join31, dirname as dirname11 } from "path";
|
|
6151
6835
|
function countWikilinks(body, slug) {
|
|
6152
6836
|
const escaped = slug.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6153
6837
|
const re = new RegExp(`\\[\\[${escaped}(?:[|#][^\\]]*)?\\]\\]`, "g");
|
|
@@ -6175,7 +6859,7 @@ async function runArchive(input) {
|
|
|
6175
6859
|
if (!relPath) return { exitCode: ExitCode.ARCHIVE_TARGET_NOT_FOUND, result: err("ARCHIVE_TARGET_NOT_FOUND", { page: input.page }) };
|
|
6176
6860
|
if (relPath.startsWith("_archive/")) return { exitCode: ExitCode.ARCHIVE_ALREADY_ARCHIVED, result: err("ARCHIVE_ALREADY_ARCHIVED", { page: relPath }) };
|
|
6177
6861
|
const slug = relPath.replace(/\.md$/, "").split("/").pop();
|
|
6178
|
-
const archivePath =
|
|
6862
|
+
const archivePath = join31("_archive", relPath).replace(/\\/g, "/");
|
|
6179
6863
|
let cascade;
|
|
6180
6864
|
if (input.cascade) {
|
|
6181
6865
|
const wikilinkRefs = [];
|
|
@@ -6199,7 +6883,7 @@ async function runArchive(input) {
|
|
|
6199
6883
|
const indexRefs = [];
|
|
6200
6884
|
if (!isRaw) {
|
|
6201
6885
|
try {
|
|
6202
|
-
const idx = await
|
|
6886
|
+
const idx = await readFile21(join31(input.vault, "index.md"), "utf8");
|
|
6203
6887
|
idx.split("\n").forEach((line, i) => {
|
|
6204
6888
|
if (line.includes(`[[${slug}]]`)) indexRefs.push({ line: i + 1, text: line });
|
|
6205
6889
|
});
|
|
@@ -6225,8 +6909,8 @@ async function runArchive(input) {
|
|
|
6225
6909
|
}
|
|
6226
6910
|
if (input.cascade && input.apply && cascade) {
|
|
6227
6911
|
for (const ref of cascade.source_array_refs) {
|
|
6228
|
-
const absPath =
|
|
6229
|
-
const text = await
|
|
6912
|
+
const absPath = join31(input.vault, ref.page);
|
|
6913
|
+
const text = await readFile21(absPath, "utf8");
|
|
6230
6914
|
const split = splitFrontmatter(text);
|
|
6231
6915
|
if (!split.ok) continue;
|
|
6232
6916
|
const before = split.data.rawFrontmatter;
|
|
@@ -6243,12 +6927,12 @@ ${fmRewritten}
|
|
|
6243
6927
|
}
|
|
6244
6928
|
}
|
|
6245
6929
|
}
|
|
6246
|
-
await mkdir9(dirname11(
|
|
6930
|
+
await mkdir9(dirname11(join31(input.vault, archivePath)), { recursive: true });
|
|
6247
6931
|
let indexUpdated = false;
|
|
6248
6932
|
if (!isRaw) {
|
|
6249
|
-
const indexPath =
|
|
6933
|
+
const indexPath = join31(input.vault, "index.md");
|
|
6250
6934
|
try {
|
|
6251
|
-
const idx = await
|
|
6935
|
+
const idx = await readFile21(indexPath, "utf8");
|
|
6252
6936
|
const originalLines = idx.split("\n");
|
|
6253
6937
|
const filtered = originalLines.filter((l) => !l.includes(`[[${slug}]]`));
|
|
6254
6938
|
if (filtered.length !== originalLines.length) {
|
|
@@ -6259,7 +6943,7 @@ ${fmRewritten}
|
|
|
6259
6943
|
if (e instanceof Error && "code" in e && e.code !== "ENOENT") throw e;
|
|
6260
6944
|
}
|
|
6261
6945
|
}
|
|
6262
|
-
await rename7(
|
|
6946
|
+
await rename7(join31(input.vault, relPath), join31(input.vault, archivePath));
|
|
6263
6947
|
appendLastOp(input.vault, {
|
|
6264
6948
|
operation: input.cascade ? "archive-cascade" : "archive",
|
|
6265
6949
|
summary: `moved ${relPath} to ${archivePath}${input.cascade ? ` (cascade: ${cascade?.source_array_refs.length ?? 0} source arrays updated)` : ""}`,
|
|
@@ -6282,7 +6966,7 @@ ${fmRewritten}
|
|
|
6282
6966
|
}
|
|
6283
6967
|
|
|
6284
6968
|
// src/commands/drift.ts
|
|
6285
|
-
import { createHash as
|
|
6969
|
+
import { createHash as createHash5 } from "crypto";
|
|
6286
6970
|
|
|
6287
6971
|
// src/utils/fetch.ts
|
|
6288
6972
|
async function controlledFetch(url, opts) {
|
|
@@ -6361,7 +7045,7 @@ async function runDrift(input) {
|
|
|
6361
7045
|
});
|
|
6362
7046
|
continue;
|
|
6363
7047
|
}
|
|
6364
|
-
const currentHash =
|
|
7048
|
+
const currentHash = createHash5("sha256").update(Buffer.from(resp.data.body, "utf8")).digest("hex");
|
|
6365
7049
|
const identity = assessSourceIdentity({
|
|
6366
7050
|
rawPath: raw.relPath,
|
|
6367
7051
|
sourceUrl,
|
|
@@ -6668,7 +7352,7 @@ ${newBody}`;
|
|
|
6668
7352
|
|
|
6669
7353
|
// src/commands/update.ts
|
|
6670
7354
|
import { execSync as execSync3 } from "child_process";
|
|
6671
|
-
import { join as
|
|
7355
|
+
import { join as join32 } from "path";
|
|
6672
7356
|
|
|
6673
7357
|
// src/utils/package-info.ts
|
|
6674
7358
|
import { readFileSync as readFileSync9 } from "fs";
|
|
@@ -6698,7 +7382,7 @@ function resolveGlobalSkillsRoot() {
|
|
|
6698
7382
|
encoding: "utf8",
|
|
6699
7383
|
timeout: 5e3
|
|
6700
7384
|
}).trim();
|
|
6701
|
-
return
|
|
7385
|
+
return join32(globalRoot, "skillwiki", "skills");
|
|
6702
7386
|
} catch {
|
|
6703
7387
|
return null;
|
|
6704
7388
|
}
|
|
@@ -6722,7 +7406,7 @@ async function runUpdate(input) {
|
|
|
6722
7406
|
const pkg2 = readCliPackageJson();
|
|
6723
7407
|
const currentVersion = pkg2.version;
|
|
6724
7408
|
const tag = normalizeDistTag(input.distTag);
|
|
6725
|
-
const target =
|
|
7409
|
+
const target = join32(input.home, ".claude", "skills");
|
|
6726
7410
|
let latest;
|
|
6727
7411
|
try {
|
|
6728
7412
|
latest = execSync3(`npm view skillwiki@${tag} version`, {
|
|
@@ -6793,15 +7477,15 @@ async function runUpdate(input) {
|
|
|
6793
7477
|
|
|
6794
7478
|
// src/commands/self-update.ts
|
|
6795
7479
|
import { execSync as execSync4 } from "child_process";
|
|
6796
|
-
import { existsSync as
|
|
6797
|
-
import { join as
|
|
7480
|
+
import { existsSync as existsSync13, readFileSync as readFileSync10 } from "fs";
|
|
7481
|
+
import { join as join33 } from "path";
|
|
6798
7482
|
var DEFAULT_SOURCE_ROOT_SUFFIX = "/Desktop/code/llm-wiki";
|
|
6799
7483
|
async function runSelfUpdate(input) {
|
|
6800
7484
|
const currentVersion = readCliPackageJson().version;
|
|
6801
7485
|
const sourceRoot = input.sourceRoot ?? `${input.home}${DEFAULT_SOURCE_ROOT_SUFFIX}`;
|
|
6802
7486
|
const distTag = normalizeDistTag(input.distTag);
|
|
6803
|
-
const localPkgPath =
|
|
6804
|
-
const hasLocalSource =
|
|
7487
|
+
const localPkgPath = join33(sourceRoot, "packages", "cli", "package.json");
|
|
7488
|
+
const hasLocalSource = existsSync13(localPkgPath);
|
|
6805
7489
|
if (input.check) {
|
|
6806
7490
|
let availableVersion = null;
|
|
6807
7491
|
let source;
|
|
@@ -6932,10 +7616,10 @@ async function runSelfUpdate(input) {
|
|
|
6932
7616
|
}
|
|
6933
7617
|
|
|
6934
7618
|
// src/commands/transcripts.ts
|
|
6935
|
-
import { readdir as readdir5, stat as
|
|
6936
|
-
import { join as
|
|
7619
|
+
import { readdir as readdir5, stat as stat8, readFile as readFile22 } from "fs/promises";
|
|
7620
|
+
import { join as join34 } from "path";
|
|
6937
7621
|
async function runTranscripts(input) {
|
|
6938
|
-
const dir =
|
|
7622
|
+
const dir = join34(input.vault, "raw", "transcripts");
|
|
6939
7623
|
let entries;
|
|
6940
7624
|
try {
|
|
6941
7625
|
entries = await readdir5(dir, { withFileTypes: true });
|
|
@@ -6945,13 +7629,13 @@ async function runTranscripts(input) {
|
|
|
6945
7629
|
const transcripts = [];
|
|
6946
7630
|
for (const entry of entries) {
|
|
6947
7631
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
6948
|
-
const filePath =
|
|
6949
|
-
const content = await
|
|
7632
|
+
const filePath = join34(dir, entry.name);
|
|
7633
|
+
const content = await readFile22(filePath, "utf8");
|
|
6950
7634
|
const fm = extractFrontmatter(content);
|
|
6951
7635
|
if (!fm.ok) continue;
|
|
6952
7636
|
const ingested = typeof fm.data.ingested === "string" ? fm.data.ingested : "";
|
|
6953
7637
|
if (input.since && ingested && ingested < input.since) continue;
|
|
6954
|
-
const s = await
|
|
7638
|
+
const s = await stat8(filePath);
|
|
6955
7639
|
transcripts.push({
|
|
6956
7640
|
file: `raw/transcripts/${entry.name}`,
|
|
6957
7641
|
ingested,
|
|
@@ -6963,12 +7647,12 @@ async function runTranscripts(input) {
|
|
|
6963
7647
|
}
|
|
6964
7648
|
|
|
6965
7649
|
// src/commands/project-index.ts
|
|
6966
|
-
import { readdir as readdir6, readFile as
|
|
6967
|
-
import { join as
|
|
7650
|
+
import { readdir as readdir6, readFile as readFile23, writeFile as writeFile11, mkdir as mkdir10 } from "fs/promises";
|
|
7651
|
+
import { join as join35, dirname as dirname12 } from "path";
|
|
6968
7652
|
var LAYER2_DIRS = ["entities", "concepts", "comparisons", "queries", "meta"];
|
|
6969
7653
|
async function runProjectIndex(input) {
|
|
6970
7654
|
const slug = input.slug;
|
|
6971
|
-
const projectDir =
|
|
7655
|
+
const projectDir = join35(input.vault, "projects", slug);
|
|
6972
7656
|
try {
|
|
6973
7657
|
await readdir6(projectDir);
|
|
6974
7658
|
} catch {
|
|
@@ -6979,15 +7663,15 @@ async function runProjectIndex(input) {
|
|
|
6979
7663
|
}
|
|
6980
7664
|
const wikilinkPattern = `[[${slug}]]`;
|
|
6981
7665
|
const entries = [];
|
|
6982
|
-
const compoundDir =
|
|
7666
|
+
const compoundDir = join35(input.vault, "projects", slug, "compound");
|
|
6983
7667
|
try {
|
|
6984
7668
|
const compoundFiles = await readdir6(compoundDir, { withFileTypes: true });
|
|
6985
7669
|
for (const entry of compoundFiles) {
|
|
6986
7670
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
6987
|
-
const filePath =
|
|
7671
|
+
const filePath = join35(compoundDir, entry.name);
|
|
6988
7672
|
let text;
|
|
6989
7673
|
try {
|
|
6990
|
-
text = await
|
|
7674
|
+
text = await readFile23(filePath, "utf8");
|
|
6991
7675
|
} catch {
|
|
6992
7676
|
continue;
|
|
6993
7677
|
}
|
|
@@ -7004,16 +7688,16 @@ async function runProjectIndex(input) {
|
|
|
7004
7688
|
for (const dir of LAYER2_DIRS) {
|
|
7005
7689
|
let files;
|
|
7006
7690
|
try {
|
|
7007
|
-
files = await readdir6(
|
|
7691
|
+
files = await readdir6(join35(input.vault, dir), { withFileTypes: true });
|
|
7008
7692
|
} catch {
|
|
7009
7693
|
continue;
|
|
7010
7694
|
}
|
|
7011
7695
|
for (const entry of files) {
|
|
7012
7696
|
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
|
|
7013
|
-
const filePath =
|
|
7697
|
+
const filePath = join35(input.vault, dir, entry.name);
|
|
7014
7698
|
let text;
|
|
7015
7699
|
try {
|
|
7016
|
-
text = await
|
|
7700
|
+
text = await readFile23(filePath, "utf8");
|
|
7017
7701
|
} catch {
|
|
7018
7702
|
continue;
|
|
7019
7703
|
}
|
|
@@ -7034,11 +7718,11 @@ async function runProjectIndex(input) {
|
|
|
7034
7718
|
const tb = typeOrder[b.type] ?? 99;
|
|
7035
7719
|
return ta !== tb ? ta - tb : a.title.localeCompare(b.title);
|
|
7036
7720
|
});
|
|
7037
|
-
const indexPath =
|
|
7721
|
+
const indexPath = join35(projectDir, "knowledge.md");
|
|
7038
7722
|
let existing = false;
|
|
7039
7723
|
let stale = false;
|
|
7040
7724
|
try {
|
|
7041
|
-
const existingText = await
|
|
7725
|
+
const existingText = await readFile23(indexPath, "utf8");
|
|
7042
7726
|
existing = true;
|
|
7043
7727
|
const existingEntries = existingText.split("\n").filter((l) => l.startsWith("- [["));
|
|
7044
7728
|
const existingPages = new Set(existingEntries.map((l) => {
|
|
@@ -7108,9 +7792,9 @@ ${entries.map((e) => ` ${e.type}: [[${e.page.replace(/\.md$/, "")}]] \u2014 ${e
|
|
|
7108
7792
|
|
|
7109
7793
|
// src/commands/compound.ts
|
|
7110
7794
|
import { writeFile as writeFile12, mkdir as mkdir11, readdir as readdir7, unlink as unlink4 } from "fs/promises";
|
|
7111
|
-
import { join as
|
|
7112
|
-
import { existsSync as
|
|
7113
|
-
import { readFile as
|
|
7795
|
+
import { join as join36 } from "path";
|
|
7796
|
+
import { existsSync as existsSync14 } from "fs";
|
|
7797
|
+
import { readFile as readFile24 } from "fs/promises";
|
|
7114
7798
|
var RETRO_HEADING_RE = /^## \[(\d{4}-\d{2}-\d{2})(?:\s+[^\]]+)?\] retro \| loop cycle(?: (\d+))?: (.+)$/;
|
|
7115
7799
|
var FIELD_RE = {
|
|
7116
7800
|
improve: /^-\s+\*?\*?Improve:?\*?\*?\s*(.+)$/m,
|
|
@@ -7208,17 +7892,17 @@ function extractRetroFields(date, cycleName, block) {
|
|
|
7208
7892
|
};
|
|
7209
7893
|
}
|
|
7210
7894
|
async function runCompound(input) {
|
|
7211
|
-
const logPath =
|
|
7895
|
+
const logPath = join36(input.vault, "log.md");
|
|
7212
7896
|
let logText;
|
|
7213
7897
|
try {
|
|
7214
|
-
logText = await
|
|
7898
|
+
logText = await readFile24(logPath, "utf8");
|
|
7215
7899
|
} catch {
|
|
7216
7900
|
return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: logPath }) };
|
|
7217
7901
|
}
|
|
7218
7902
|
const entries = parseRetroEntries(logText);
|
|
7219
7903
|
const promoted = [];
|
|
7220
7904
|
const skipped = [];
|
|
7221
|
-
const compoundDir =
|
|
7905
|
+
const compoundDir = join36(input.vault, "projects", input.project, "compound");
|
|
7222
7906
|
for (const entry of entries) {
|
|
7223
7907
|
const generalizeValue = entry.generalize.trim();
|
|
7224
7908
|
if (!/^yes/i.test(generalizeValue)) {
|
|
@@ -7226,8 +7910,8 @@ async function runCompound(input) {
|
|
|
7226
7910
|
continue;
|
|
7227
7911
|
}
|
|
7228
7912
|
const slug = slugify(entry.cycleName);
|
|
7229
|
-
const compoundPath =
|
|
7230
|
-
if (
|
|
7913
|
+
const compoundPath = join36(compoundDir, `${slug}.md`);
|
|
7914
|
+
if (existsSync14(compoundPath)) {
|
|
7231
7915
|
skipped.push(entry.date);
|
|
7232
7916
|
continue;
|
|
7233
7917
|
}
|
|
@@ -7265,7 +7949,7 @@ async function runCompound(input) {
|
|
|
7265
7949
|
].join("\n");
|
|
7266
7950
|
const content = frontmatter + "\n" + body;
|
|
7267
7951
|
if (!input.dryRun) {
|
|
7268
|
-
if (!
|
|
7952
|
+
if (!existsSync14(compoundDir)) {
|
|
7269
7953
|
await mkdir11(compoundDir, { recursive: true });
|
|
7270
7954
|
}
|
|
7271
7955
|
await writeFile12(compoundPath, content, "utf8");
|
|
@@ -7287,16 +7971,16 @@ async function runCompound(input) {
|
|
|
7287
7971
|
};
|
|
7288
7972
|
}
|
|
7289
7973
|
async function runCompoundDelete(input) {
|
|
7290
|
-
const projectDir =
|
|
7291
|
-
if (!
|
|
7974
|
+
const projectDir = join36(input.vault, "projects", input.project);
|
|
7975
|
+
if (!existsSync14(projectDir)) {
|
|
7292
7976
|
return {
|
|
7293
7977
|
exitCode: ExitCode.PROJECT_NOT_FOUND,
|
|
7294
7978
|
result: err("PROJECT_NOT_FOUND", { slug: input.project, path: projectDir })
|
|
7295
7979
|
};
|
|
7296
7980
|
}
|
|
7297
7981
|
const entryName = input.entry.replace(/\.md$/, "");
|
|
7298
|
-
const compoundPath =
|
|
7299
|
-
if (!
|
|
7982
|
+
const compoundPath = join36(projectDir, "compound", `${entryName}.md`);
|
|
7983
|
+
if (!existsSync14(compoundPath)) {
|
|
7300
7984
|
return {
|
|
7301
7985
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
7302
7986
|
result: err("FILE_NOT_FOUND", { path: compoundPath })
|
|
@@ -7329,8 +8013,8 @@ knowledge.md regenerated`
|
|
|
7329
8013
|
};
|
|
7330
8014
|
}
|
|
7331
8015
|
async function runCompoundList(input) {
|
|
7332
|
-
const compoundDir =
|
|
7333
|
-
if (!
|
|
8016
|
+
const compoundDir = join36(input.vault, "projects", input.project, "compound");
|
|
8017
|
+
if (!existsSync14(compoundDir)) {
|
|
7334
8018
|
return {
|
|
7335
8019
|
exitCode: ExitCode.OK,
|
|
7336
8020
|
result: ok({
|
|
@@ -7360,10 +8044,10 @@ could not read compound directory`
|
|
|
7360
8044
|
const entries = [];
|
|
7361
8045
|
for (const dirent of dirents) {
|
|
7362
8046
|
if (!dirent.isFile() || !dirent.name.endsWith(".md")) continue;
|
|
7363
|
-
const filePath =
|
|
8047
|
+
const filePath = join36(compoundDir, dirent.name);
|
|
7364
8048
|
let text;
|
|
7365
8049
|
try {
|
|
7366
|
-
text = await
|
|
8050
|
+
text = await readFile24(filePath, "utf8");
|
|
7367
8051
|
} catch {
|
|
7368
8052
|
continue;
|
|
7369
8053
|
}
|
|
@@ -7393,9 +8077,9 @@ no compound entries found`;
|
|
|
7393
8077
|
|
|
7394
8078
|
// src/commands/observe.ts
|
|
7395
8079
|
import { mkdir as mkdir12, writeFile as writeFile13 } from "fs/promises";
|
|
7396
|
-
import { existsSync as
|
|
7397
|
-
import { join as
|
|
7398
|
-
import { createHash as
|
|
8080
|
+
import { existsSync as existsSync15, statSync as statSync4 } from "fs";
|
|
8081
|
+
import { join as join37 } from "path";
|
|
8082
|
+
import { createHash as createHash6 } from "crypto";
|
|
7399
8083
|
var ALLOWED_KINDS = /* @__PURE__ */ new Set(["note", "bug", "task", "idea", "session-log"]);
|
|
7400
8084
|
function slugify2(text) {
|
|
7401
8085
|
const words = text.trim().split(/\s+/).slice(0, 6).join("-").toLowerCase().replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
@@ -7417,13 +8101,13 @@ async function runObserve(input) {
|
|
|
7417
8101
|
result: err("SCHEME_REJECTED", { message: "Text must not be empty" })
|
|
7418
8102
|
};
|
|
7419
8103
|
}
|
|
7420
|
-
if (!
|
|
8104
|
+
if (!existsSync15(input.vault) || !statSync4(input.vault).isDirectory()) {
|
|
7421
8105
|
return {
|
|
7422
8106
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
7423
8107
|
result: err("VAULT_PATH_INVALID", { path: input.vault })
|
|
7424
8108
|
};
|
|
7425
8109
|
}
|
|
7426
|
-
const transcriptsDir =
|
|
8110
|
+
const transcriptsDir = join37(input.vault, "raw", "transcripts");
|
|
7427
8111
|
try {
|
|
7428
8112
|
await mkdir12(transcriptsDir, { recursive: true });
|
|
7429
8113
|
} catch {
|
|
@@ -7435,11 +8119,11 @@ async function runObserve(input) {
|
|
|
7435
8119
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
7436
8120
|
const slug = slugify2(input.text);
|
|
7437
8121
|
const fileName = `${today}-observation-${slug}.md`;
|
|
7438
|
-
const filePath =
|
|
8122
|
+
const filePath = join37(transcriptsDir, fileName);
|
|
7439
8123
|
const body = `
|
|
7440
8124
|
${input.text.trim()}
|
|
7441
8125
|
`;
|
|
7442
|
-
const sha256 =
|
|
8126
|
+
const sha256 = createHash6("sha256").update(Buffer.from(body, "utf8")).digest("hex");
|
|
7443
8127
|
const frontmatterLines = [
|
|
7444
8128
|
"---",
|
|
7445
8129
|
"source_url:",
|
|
@@ -7475,8 +8159,8 @@ ${input.text.trim()}
|
|
|
7475
8159
|
}
|
|
7476
8160
|
|
|
7477
8161
|
// src/commands/session-brief.ts
|
|
7478
|
-
import { mkdir as mkdir13, readFile as
|
|
7479
|
-
import { join as
|
|
8162
|
+
import { mkdir as mkdir13, readFile as readFile25, writeFile as writeFile14 } from "fs/promises";
|
|
8163
|
+
import { join as join38, relative as relative3, sep as sep3 } from "path";
|
|
7480
8164
|
var MAX_WORDS = 900;
|
|
7481
8165
|
async function runSessionBrief(input) {
|
|
7482
8166
|
const scan = await scanVault(input.vault);
|
|
@@ -7554,7 +8238,7 @@ async function resolveProject(input) {
|
|
|
7554
8238
|
const envProject = input.env?.SKILLWIKI_PROJECT;
|
|
7555
8239
|
if (envProject) return envProject;
|
|
7556
8240
|
const cwd = input.cwd ?? process.cwd();
|
|
7557
|
-
const projectDotenv = await readProjectSlug(
|
|
8241
|
+
const projectDotenv = await readProjectSlug(join38(cwd, ".skillwiki", ".env"));
|
|
7558
8242
|
if (projectDotenv) return projectDotenv;
|
|
7559
8243
|
const inferred = inferProjectFromPath(input.vault, cwd);
|
|
7560
8244
|
if (inferred) return inferred;
|
|
@@ -7563,7 +8247,7 @@ async function resolveProject(input) {
|
|
|
7563
8247
|
async function readProjectSlug(file) {
|
|
7564
8248
|
let text;
|
|
7565
8249
|
try {
|
|
7566
|
-
text = await
|
|
8250
|
+
text = await readFile25(file, "utf8");
|
|
7567
8251
|
} catch {
|
|
7568
8252
|
return void 0;
|
|
7569
8253
|
}
|
|
@@ -7688,7 +8372,7 @@ function appendTextSection(lines, title, items, empty) {
|
|
|
7688
8372
|
lines.push("");
|
|
7689
8373
|
}
|
|
7690
8374
|
async function loadHealthWarnings(vault) {
|
|
7691
|
-
const text = await readIfExists2(
|
|
8375
|
+
const text = await readIfExists2(join38(vault, ".skillwiki", "health.json"));
|
|
7692
8376
|
if (!text) return [];
|
|
7693
8377
|
try {
|
|
7694
8378
|
const parsed = JSON.parse(text);
|
|
@@ -7701,11 +8385,11 @@ async function loadHealthWarnings(vault) {
|
|
|
7701
8385
|
}
|
|
7702
8386
|
}
|
|
7703
8387
|
async function writeBriefArtifacts(vault, input) {
|
|
7704
|
-
const metaPath =
|
|
7705
|
-
const cacheMdPath =
|
|
7706
|
-
const cacheJsonPath =
|
|
7707
|
-
await mkdir13(
|
|
7708
|
-
await mkdir13(
|
|
8388
|
+
const metaPath = join38(vault, "meta", "latest-session-brief.md");
|
|
8389
|
+
const cacheMdPath = join38(vault, ".skillwiki", "session-brief.md");
|
|
8390
|
+
const cacheJsonPath = join38(vault, ".skillwiki", "session-brief.json");
|
|
8391
|
+
await mkdir13(join38(vault, "meta"), { recursive: true });
|
|
8392
|
+
await mkdir13(join38(vault, ".skillwiki"), { recursive: true });
|
|
7709
8393
|
const committed = renderCommittedBrief(input);
|
|
7710
8394
|
const previousComparable = comparableBrief(await readIfExists2(metaPath));
|
|
7711
8395
|
const nextComparable = comparableBrief(committed);
|
|
@@ -7765,7 +8449,7 @@ function renderCommittedBrief(input) {
|
|
|
7765
8449
|
].filter((line) => line !== "").join("\n");
|
|
7766
8450
|
}
|
|
7767
8451
|
async function ensureIndexEntry(vault) {
|
|
7768
|
-
const indexPath =
|
|
8452
|
+
const indexPath = join38(vault, "index.md");
|
|
7769
8453
|
let text = await readIfExists2(indexPath);
|
|
7770
8454
|
if (!text) return false;
|
|
7771
8455
|
if (text.includes("[[meta/latest-session-brief]]")) return false;
|
|
@@ -7784,7 +8468,7 @@ async function ensureIndexEntry(vault) {
|
|
|
7784
8468
|
return true;
|
|
7785
8469
|
}
|
|
7786
8470
|
async function appendMaterialLog(vault, today) {
|
|
7787
|
-
const logPath =
|
|
8471
|
+
const logPath = join38(vault, "log.md");
|
|
7788
8472
|
const text = await readIfExists2(logPath);
|
|
7789
8473
|
if (!text) return false;
|
|
7790
8474
|
const entry = `
|
|
@@ -7794,7 +8478,7 @@ async function appendMaterialLog(vault, today) {
|
|
|
7794
8478
|
}
|
|
7795
8479
|
async function readIfExists2(path) {
|
|
7796
8480
|
try {
|
|
7797
|
-
return await
|
|
8481
|
+
return await readFile25(path, "utf8");
|
|
7798
8482
|
} catch {
|
|
7799
8483
|
return "";
|
|
7800
8484
|
}
|
|
@@ -7836,9 +8520,9 @@ function dateFromPath(path) {
|
|
|
7836
8520
|
}
|
|
7837
8521
|
|
|
7838
8522
|
// src/commands/ingest.ts
|
|
7839
|
-
import { readFile as
|
|
7840
|
-
import { join as
|
|
7841
|
-
import { createHash as
|
|
8523
|
+
import { readFile as readFile26, writeFile as writeFile15, mkdir as mkdir14 } from "fs/promises";
|
|
8524
|
+
import { join as join39 } from "path";
|
|
8525
|
+
import { createHash as createHash7 } from "crypto";
|
|
7842
8526
|
var ALLOWED_TYPES = /* @__PURE__ */ new Set(["entity", "concept", "comparison", "query"]);
|
|
7843
8527
|
var TYPE_DIR = {
|
|
7844
8528
|
entity: "entities",
|
|
@@ -7996,7 +8680,7 @@ async function runIngest(input) {
|
|
|
7996
8680
|
sourceContent = fetchResult.data.body;
|
|
7997
8681
|
} else {
|
|
7998
8682
|
try {
|
|
7999
|
-
sourceContent = await
|
|
8683
|
+
sourceContent = await readFile26(input.source, "utf8");
|
|
8000
8684
|
} catch {
|
|
8001
8685
|
return {
|
|
8002
8686
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
@@ -8004,15 +8688,25 @@ async function runIngest(input) {
|
|
|
8004
8688
|
};
|
|
8005
8689
|
}
|
|
8006
8690
|
}
|
|
8007
|
-
const
|
|
8691
|
+
const sensitiveFindings = scanSensitiveContent(sourceContent, { file: input.source });
|
|
8692
|
+
if (sensitiveFindings.length > 0) {
|
|
8693
|
+
return {
|
|
8694
|
+
exitCode: ExitCode.SENSITIVE_CONTENT_DETECTED,
|
|
8695
|
+
result: err("SENSITIVE_CONTENT_DETECTED", {
|
|
8696
|
+
message: "source content contains sensitive authentication material; provide a redacted source before ingesting",
|
|
8697
|
+
findings: sensitiveFindings
|
|
8698
|
+
})
|
|
8699
|
+
};
|
|
8700
|
+
}
|
|
8701
|
+
const sha256 = createHash7("sha256").update(Buffer.from(sourceContent, "utf8")).digest("hex");
|
|
8008
8702
|
const today = todayIso();
|
|
8009
8703
|
const slug = slugify3(input.title);
|
|
8010
8704
|
const tags = input.tags && input.tags.length > 0 ? input.tags : [];
|
|
8011
8705
|
const rawRelPath = `raw/articles/${slug}.md`;
|
|
8012
8706
|
const typedDir = TYPE_DIR[input.type] ?? `${input.type}s`;
|
|
8013
8707
|
const typedRelPath = `${typedDir}/${slug}.md`;
|
|
8014
|
-
const rawAbsPath =
|
|
8015
|
-
const typedAbsPath =
|
|
8708
|
+
const rawAbsPath = join39(input.vault, rawRelPath);
|
|
8709
|
+
const typedAbsPath = join39(input.vault, typedRelPath);
|
|
8016
8710
|
const identity = assessSourceIdentity({
|
|
8017
8711
|
rawPath: rawRelPath,
|
|
8018
8712
|
sourceUrl: sourceUrl ?? void 0,
|
|
@@ -8094,7 +8788,7 @@ async function runIngest(input) {
|
|
|
8094
8788
|
};
|
|
8095
8789
|
}
|
|
8096
8790
|
try {
|
|
8097
|
-
await mkdir14(
|
|
8791
|
+
await mkdir14(join39(input.vault, "raw", "articles"), { recursive: true });
|
|
8098
8792
|
await writeFile15(rawAbsPath, rawContent, "utf8");
|
|
8099
8793
|
} catch (e) {
|
|
8100
8794
|
return {
|
|
@@ -8103,7 +8797,7 @@ async function runIngest(input) {
|
|
|
8103
8797
|
};
|
|
8104
8798
|
}
|
|
8105
8799
|
try {
|
|
8106
|
-
await mkdir14(
|
|
8800
|
+
await mkdir14(join39(input.vault, typedDir), { recursive: true });
|
|
8107
8801
|
await writeFile15(typedAbsPath, typedContent, "utf8");
|
|
8108
8802
|
} catch (e) {
|
|
8109
8803
|
return {
|
|
@@ -8282,24 +8976,24 @@ ${body}`;
|
|
|
8282
8976
|
}
|
|
8283
8977
|
|
|
8284
8978
|
// src/commands/sync.ts
|
|
8285
|
-
import { existsSync as
|
|
8286
|
-
import { join as
|
|
8979
|
+
import { existsSync as existsSync17 } from "fs";
|
|
8980
|
+
import { join as join41 } from "path";
|
|
8287
8981
|
|
|
8288
8982
|
// src/utils/sync-lock.ts
|
|
8289
|
-
import { existsSync as
|
|
8290
|
-
import { join as
|
|
8291
|
-
import { createHash as
|
|
8983
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync5, readFileSync as readFileSync11, renameSync as renameSync2, unlinkSync as unlinkSync5, writeFileSync as writeFileSync7 } from "fs";
|
|
8984
|
+
import { join as join40 } from "path";
|
|
8985
|
+
import { createHash as createHash8 } from "crypto";
|
|
8292
8986
|
function getSessionId() {
|
|
8293
8987
|
if (process.env.CLAUDE_SESSION_ID) return process.env.CLAUDE_SESSION_ID;
|
|
8294
8988
|
if (process.env.SKILLWIKI_SESSION_ID) return process.env.SKILLWIKI_SESSION_ID;
|
|
8295
8989
|
return process.pid.toString();
|
|
8296
8990
|
}
|
|
8297
8991
|
function lockPath(vault) {
|
|
8298
|
-
return
|
|
8992
|
+
return join40(vault, ".skillwiki", "sync.lock");
|
|
8299
8993
|
}
|
|
8300
8994
|
function readLock(vault) {
|
|
8301
8995
|
const path = lockPath(vault);
|
|
8302
|
-
if (!
|
|
8996
|
+
if (!existsSync16(path)) return null;
|
|
8303
8997
|
try {
|
|
8304
8998
|
const raw = readFileSync11(path, "utf8");
|
|
8305
8999
|
return JSON.parse(raw);
|
|
@@ -8314,8 +9008,8 @@ function isStale(lock, now) {
|
|
|
8314
9008
|
}
|
|
8315
9009
|
function acquireLock(vault, opts = {}) {
|
|
8316
9010
|
const path = lockPath(vault);
|
|
8317
|
-
const dir =
|
|
8318
|
-
if (!
|
|
9011
|
+
const dir = join40(vault, ".skillwiki");
|
|
9012
|
+
if (!existsSync16(dir)) {
|
|
8319
9013
|
mkdirSync5(dir, { recursive: true });
|
|
8320
9014
|
}
|
|
8321
9015
|
const sessionId = opts.sessionId ?? getSessionId();
|
|
@@ -8360,7 +9054,7 @@ function writeLockedFile(path, lock) {
|
|
|
8360
9054
|
}
|
|
8361
9055
|
function releaseLock(vault, opts = {}) {
|
|
8362
9056
|
const path = lockPath(vault);
|
|
8363
|
-
if (!
|
|
9057
|
+
if (!existsSync16(path)) {
|
|
8364
9058
|
return { released: false };
|
|
8365
9059
|
}
|
|
8366
9060
|
const sessionId = opts.sessionId ?? getSessionId();
|
|
@@ -8389,7 +9083,7 @@ function releaseLock(vault, opts = {}) {
|
|
|
8389
9083
|
function runSyncStatus(input) {
|
|
8390
9084
|
const vault = input.vault;
|
|
8391
9085
|
const includeStashes = input.includeStashes ?? false;
|
|
8392
|
-
if (!
|
|
9086
|
+
if (!existsSync17(join41(vault, ".git"))) {
|
|
8393
9087
|
return {
|
|
8394
9088
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
8395
9089
|
result: ok({
|
|
@@ -8467,7 +9161,7 @@ function runSyncStatus(input) {
|
|
|
8467
9161
|
}
|
|
8468
9162
|
async function runSyncPush(input) {
|
|
8469
9163
|
const vault = input.vault;
|
|
8470
|
-
if (!
|
|
9164
|
+
if (!existsSync17(join41(vault, ".git"))) {
|
|
8471
9165
|
return {
|
|
8472
9166
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
8473
9167
|
result: err("NOT_A_GIT_REPO", { path: vault })
|
|
@@ -8590,7 +9284,7 @@ function enableGitLongPathsOnWindows(vault) {
|
|
|
8590
9284
|
}
|
|
8591
9285
|
async function runSyncPull(input) {
|
|
8592
9286
|
const vault = input.vault;
|
|
8593
|
-
if (!
|
|
9287
|
+
if (!existsSync17(join41(vault, ".git"))) {
|
|
8594
9288
|
return {
|
|
8595
9289
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
8596
9290
|
result: err("NOT_A_GIT_REPO", { path: vault })
|
|
@@ -8762,7 +9456,7 @@ function runSyncPeers(input) {
|
|
|
8762
9456
|
}
|
|
8763
9457
|
function runSyncLock(input) {
|
|
8764
9458
|
const vault = input.vault;
|
|
8765
|
-
if (!
|
|
9459
|
+
if (!existsSync17(vault)) {
|
|
8766
9460
|
return {
|
|
8767
9461
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
8768
9462
|
result: err("VAULT_PATH_INVALID", { path: vault })
|
|
@@ -8797,7 +9491,7 @@ function runSyncLock(input) {
|
|
|
8797
9491
|
}
|
|
8798
9492
|
function runSyncUnlock(input) {
|
|
8799
9493
|
const vault = input.vault;
|
|
8800
|
-
if (!
|
|
9494
|
+
if (!existsSync17(vault)) {
|
|
8801
9495
|
return {
|
|
8802
9496
|
exitCode: ExitCode.VAULT_PATH_INVALID,
|
|
8803
9497
|
result: err("VAULT_PATH_INVALID", { path: vault })
|
|
@@ -8831,7 +9525,7 @@ function runSyncUnlock(input) {
|
|
|
8831
9525
|
|
|
8832
9526
|
// src/commands/backup.ts
|
|
8833
9527
|
import { statSync as statSync5, readdirSync as readdirSync3, readFileSync as readFileSync12, mkdirSync as mkdirSync6, writeFileSync as writeFileSync8 } from "fs";
|
|
8834
|
-
import { join as
|
|
9528
|
+
import { join as join42, relative as relative4, dirname as dirname13 } from "path";
|
|
8835
9529
|
import { PutObjectCommand, HeadObjectCommand, ListObjectsV2Command, GetObjectCommand, DeleteObjectsCommand } from "@aws-sdk/client-s3";
|
|
8836
9530
|
|
|
8837
9531
|
// src/utils/s3-client.ts
|
|
@@ -8855,7 +9549,7 @@ var SKIP_DIRS2 = /* @__PURE__ */ new Set([".git", ".obsidian", "_archive", "node
|
|
|
8855
9549
|
function* walkMarkdown(dir, base) {
|
|
8856
9550
|
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
8857
9551
|
if (SKIP_DIRS2.has(entry.name)) continue;
|
|
8858
|
-
const full =
|
|
9552
|
+
const full = join42(dir, entry.name);
|
|
8859
9553
|
if (entry.isDirectory()) {
|
|
8860
9554
|
yield* walkMarkdown(full, base);
|
|
8861
9555
|
} else if (entry.name.endsWith(".md")) {
|
|
@@ -8878,7 +9572,7 @@ async function runBackupSync(input) {
|
|
|
8878
9572
|
let failed = 0;
|
|
8879
9573
|
const files = [...walkMarkdown(input.vault, input.vault)];
|
|
8880
9574
|
for (const relPath of files) {
|
|
8881
|
-
const absPath =
|
|
9575
|
+
const absPath = join42(input.vault, relPath);
|
|
8882
9576
|
const localStat = statSync5(absPath);
|
|
8883
9577
|
let needsUpload = true;
|
|
8884
9578
|
try {
|
|
@@ -8954,7 +9648,7 @@ async function runBackupRestore(input) {
|
|
|
8954
9648
|
const objects = list.Contents ?? [];
|
|
8955
9649
|
for (const obj of objects) {
|
|
8956
9650
|
if (!obj.Key) continue;
|
|
8957
|
-
const localPath =
|
|
9651
|
+
const localPath = join42(target, obj.Key);
|
|
8958
9652
|
try {
|
|
8959
9653
|
const localStat = statSync5(localPath);
|
|
8960
9654
|
if (obj.LastModified && localStat.mtime > obj.LastModified) {
|
|
@@ -9000,11 +9694,11 @@ async function runBackupRestore(input) {
|
|
|
9000
9694
|
}
|
|
9001
9695
|
|
|
9002
9696
|
// src/commands/status.ts
|
|
9003
|
-
import { existsSync as
|
|
9004
|
-
import { readFile as
|
|
9005
|
-
import { join as
|
|
9697
|
+
import { existsSync as existsSync18, statSync as statSync6 } from "fs";
|
|
9698
|
+
import { readFile as readFile27 } from "fs/promises";
|
|
9699
|
+
import { join as join43 } from "path";
|
|
9006
9700
|
async function runStatus(input) {
|
|
9007
|
-
if (!
|
|
9701
|
+
if (!existsSync18(input.vault)) {
|
|
9008
9702
|
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { vault: input.vault }) };
|
|
9009
9703
|
}
|
|
9010
9704
|
const scan = await scanVault(input.vault);
|
|
@@ -9029,7 +9723,7 @@ async function runStatus(input) {
|
|
|
9029
9723
|
const compound = scan.data.compound.length;
|
|
9030
9724
|
let schemaVersion = "v1";
|
|
9031
9725
|
try {
|
|
9032
|
-
const schemaContent = await
|
|
9726
|
+
const schemaContent = await readFile27(join43(input.vault, "SCHEMA.md"), "utf8");
|
|
9033
9727
|
const versionMatch = schemaContent.match(/version:\s*["']?([^"'\s\n]+)/i);
|
|
9034
9728
|
if (versionMatch) schemaVersion = versionMatch[1];
|
|
9035
9729
|
} catch {
|
|
@@ -9089,8 +9783,8 @@ async function runStatus(input) {
|
|
|
9089
9783
|
}
|
|
9090
9784
|
|
|
9091
9785
|
// src/commands/seed.ts
|
|
9092
|
-
import { mkdir as mkdir15, writeFile as writeFile16, stat as
|
|
9093
|
-
import { join as
|
|
9786
|
+
import { mkdir as mkdir15, writeFile as writeFile16, stat as stat9 } from "fs/promises";
|
|
9787
|
+
import { join as join44 } from "path";
|
|
9094
9788
|
var TODAY = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
9095
9789
|
var EXAMPLE_PAGES = {
|
|
9096
9790
|
"entities/example-project.md": `---
|
|
@@ -9159,29 +9853,29 @@ Real sources are immutable after ingestion \u2014 never edit them.
|
|
|
9159
9853
|
`;
|
|
9160
9854
|
async function runSeed(input) {
|
|
9161
9855
|
try {
|
|
9162
|
-
await
|
|
9856
|
+
await stat9(join44(input.vault, "SCHEMA.md"));
|
|
9163
9857
|
} catch {
|
|
9164
9858
|
return { exitCode: ExitCode.VAULT_PATH_INVALID, result: err("VAULT_PATH_INVALID", { root: input.vault, reason: "SCHEMA.md missing \u2014 run `skillwiki init` first" }) };
|
|
9165
9859
|
}
|
|
9166
9860
|
const created = [];
|
|
9167
9861
|
const skipped = [];
|
|
9168
9862
|
for (const [relPath, content] of Object.entries(EXAMPLE_PAGES)) {
|
|
9169
|
-
const absPath =
|
|
9863
|
+
const absPath = join44(input.vault, relPath);
|
|
9170
9864
|
try {
|
|
9171
|
-
await
|
|
9865
|
+
await stat9(absPath);
|
|
9172
9866
|
skipped.push(relPath);
|
|
9173
9867
|
} catch {
|
|
9174
|
-
await mkdir15(
|
|
9868
|
+
await mkdir15(join44(absPath, ".."), { recursive: true });
|
|
9175
9869
|
await writeFile16(absPath, content, "utf8");
|
|
9176
9870
|
created.push(relPath);
|
|
9177
9871
|
}
|
|
9178
9872
|
}
|
|
9179
|
-
const rawPath =
|
|
9873
|
+
const rawPath = join44(input.vault, "raw", "articles", "example-source.md");
|
|
9180
9874
|
try {
|
|
9181
|
-
await
|
|
9875
|
+
await stat9(rawPath);
|
|
9182
9876
|
skipped.push("raw/articles/example-source.md");
|
|
9183
9877
|
} catch {
|
|
9184
|
-
await mkdir15(
|
|
9878
|
+
await mkdir15(join44(rawPath, ".."), { recursive: true });
|
|
9185
9879
|
await writeFile16(rawPath, EXAMPLE_RAW, "utf8");
|
|
9186
9880
|
created.push("raw/articles/example-source.md");
|
|
9187
9881
|
}
|
|
@@ -9204,9 +9898,9 @@ async function runSeed(input) {
|
|
|
9204
9898
|
}
|
|
9205
9899
|
|
|
9206
9900
|
// src/commands/canvas.ts
|
|
9207
|
-
import { readFile as
|
|
9208
|
-
import { existsSync as
|
|
9209
|
-
import { join as
|
|
9901
|
+
import { readFile as readFile28, writeFile as writeFile17 } from "fs/promises";
|
|
9902
|
+
import { existsSync as existsSync19 } from "fs";
|
|
9903
|
+
import { join as join45 } from "path";
|
|
9210
9904
|
var NODE_WIDTH = 240;
|
|
9211
9905
|
var NODE_HEIGHT = 60;
|
|
9212
9906
|
var COLUMN_SPACING = 400;
|
|
@@ -9284,8 +9978,8 @@ function buildCanvasEdges(adjacency) {
|
|
|
9284
9978
|
return edges;
|
|
9285
9979
|
}
|
|
9286
9980
|
async function runCanvasGenerate(input) {
|
|
9287
|
-
const graphPath = input.graphPath ??
|
|
9288
|
-
if (!
|
|
9981
|
+
const graphPath = input.graphPath ?? join45(input.vault, ".skillwiki", "graph.json");
|
|
9982
|
+
if (!existsSync19(graphPath)) {
|
|
9289
9983
|
return {
|
|
9290
9984
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
9291
9985
|
result: err("FILE_NOT_FOUND", {
|
|
@@ -9296,7 +9990,7 @@ async function runCanvasGenerate(input) {
|
|
|
9296
9990
|
}
|
|
9297
9991
|
let raw;
|
|
9298
9992
|
try {
|
|
9299
|
-
raw = await
|
|
9993
|
+
raw = await readFile28(graphPath, "utf8");
|
|
9300
9994
|
} catch (e) {
|
|
9301
9995
|
return {
|
|
9302
9996
|
exitCode: ExitCode.FILE_NOT_FOUND,
|
|
@@ -9322,7 +10016,7 @@ async function runCanvasGenerate(input) {
|
|
|
9322
10016
|
const nodes = buildCanvasNodes(paths);
|
|
9323
10017
|
const edges = buildCanvasEdges(graph.adjacency);
|
|
9324
10018
|
const canvas = { nodes, edges };
|
|
9325
|
-
const outPath =
|
|
10019
|
+
const outPath = join45(input.vault, "vault-graph.canvas");
|
|
9326
10020
|
try {
|
|
9327
10021
|
await writeFile17(outPath, JSON.stringify(canvas, null, 2));
|
|
9328
10022
|
} catch (e) {
|
|
@@ -9344,8 +10038,8 @@ written: ${outPath}`
|
|
|
9344
10038
|
}
|
|
9345
10039
|
|
|
9346
10040
|
// src/commands/query.ts
|
|
9347
|
-
import { readFile as
|
|
9348
|
-
import { join as
|
|
10041
|
+
import { readFile as readFile29, stat as stat10 } from "fs/promises";
|
|
10042
|
+
import { join as join46 } from "path";
|
|
9349
10043
|
var W_KEYWORD = 2;
|
|
9350
10044
|
var W_SOURCE_OVERLAP = 4;
|
|
9351
10045
|
var W_WIKILINK = 3;
|
|
@@ -9466,10 +10160,10 @@ function computeKeywordScore(terms, title, tags, body) {
|
|
|
9466
10160
|
return score;
|
|
9467
10161
|
}
|
|
9468
10162
|
async function loadOrBuildGraph(vault) {
|
|
9469
|
-
const graphPath =
|
|
10163
|
+
const graphPath = join46(vault, ".skillwiki", "graph.json");
|
|
9470
10164
|
let needsBuild = false;
|
|
9471
10165
|
try {
|
|
9472
|
-
const fileStat = await
|
|
10166
|
+
const fileStat = await stat10(graphPath);
|
|
9473
10167
|
const ageHours = (Date.now() - fileStat.mtimeMs) / (1e3 * 60 * 60);
|
|
9474
10168
|
if (ageHours > 24) needsBuild = true;
|
|
9475
10169
|
} catch {
|
|
@@ -9480,274 +10174,22 @@ async function loadOrBuildGraph(vault) {
|
|
|
9480
10174
|
if (buildResult.exitCode !== 0) return null;
|
|
9481
10175
|
}
|
|
9482
10176
|
try {
|
|
9483
|
-
const raw = await
|
|
10177
|
+
const raw = await readFile29(graphPath, "utf8");
|
|
9484
10178
|
return JSON.parse(raw);
|
|
9485
10179
|
} catch {
|
|
9486
10180
|
return null;
|
|
9487
10181
|
}
|
|
9488
10182
|
}
|
|
9489
10183
|
|
|
9490
|
-
// src/commands/fleet.ts
|
|
9491
|
-
import { readFile as readFile29 } from "fs/promises";
|
|
9492
|
-
import { hostname as nodeHostname, userInfo } from "os";
|
|
9493
|
-
import { join as join45 } from "path";
|
|
9494
|
-
import yaml3 from "js-yaml";
|
|
9495
|
-
var FLEET_REL_PATH = join45("projects", "llm-wiki", "architecture", "fleet.yaml");
|
|
9496
|
-
async function runFleetValidate(input) {
|
|
9497
|
-
const loaded = await loadFleetManifest(input.file);
|
|
9498
|
-
if (!loaded.ok) {
|
|
9499
|
-
if (loaded.error === "FILE_NOT_FOUND") {
|
|
9500
|
-
return { exitCode: ExitCode.FILE_NOT_FOUND, result: err("FILE_NOT_FOUND", { path: input.file }) };
|
|
9501
|
-
}
|
|
9502
|
-
const errors = fleetLoadErrors(loaded);
|
|
9503
|
-
return invalidFleet(errors);
|
|
9504
|
-
}
|
|
9505
|
-
const warnings = fleetWarnings(loaded.manifest);
|
|
9506
|
-
const snapshotter = findSnapshotter(loaded.manifest);
|
|
9507
|
-
return {
|
|
9508
|
-
exitCode: ExitCode.OK,
|
|
9509
|
-
result: ok({
|
|
9510
|
-
valid: true,
|
|
9511
|
-
errors: [],
|
|
9512
|
-
warnings,
|
|
9513
|
-
host_count: Object.keys(loaded.manifest.hosts).length,
|
|
9514
|
-
snapshotter,
|
|
9515
|
-
humanHint: `VALID fleet manifest (${Object.keys(loaded.manifest.hosts).length} hosts; snapshotter: ${snapshotter ?? "none"})`
|
|
9516
|
-
})
|
|
9517
|
-
};
|
|
9518
|
-
}
|
|
9519
|
-
async function runFleetContext(input) {
|
|
9520
|
-
const env = input.env ?? process.env;
|
|
9521
|
-
const home = input.home ?? env.HOME ?? "";
|
|
9522
|
-
const cwd = input.cwd ?? process.cwd();
|
|
9523
|
-
const osHostname = input.osHostname ?? safeEnvValue(env.HOSTNAME) ?? nodeHostname();
|
|
9524
|
-
const user = input.user ?? safeEnvValue(env.USER) ?? safeUserName();
|
|
9525
|
-
const vault = input.vault ?? safeEnvValue(env.WIKI_PATH);
|
|
9526
|
-
const file = input.file ?? (vault ? join45(vault, FLEET_REL_PATH) : void 0);
|
|
9527
|
-
const loaded = file ? await loadFleetManifest(file) : { ok: false, error: "FILE_NOT_FOUND" };
|
|
9528
|
-
if (!loaded.ok) {
|
|
9529
|
-
const markdown2 = formatUnknownContext({ osHostname, user, cwd, vault, reason: "fleet manifest unavailable or invalid" });
|
|
9530
|
-
return {
|
|
9531
|
-
exitCode: ExitCode.OK,
|
|
9532
|
-
result: ok({ manifest_loaded: false, markdown: markdown2, humanHint: markdown2 })
|
|
9533
|
-
};
|
|
9534
|
-
}
|
|
9535
|
-
const resolved = await resolveHostId({
|
|
9536
|
-
manifest: loaded.manifest,
|
|
9537
|
-
hostId: input.hostId,
|
|
9538
|
-
env,
|
|
9539
|
-
home,
|
|
9540
|
-
osHostname
|
|
9541
|
-
});
|
|
9542
|
-
if (!resolved.hostId || !loaded.manifest.hosts[resolved.hostId]) {
|
|
9543
|
-
const markdown2 = formatUnknownContext({ osHostname, user, cwd, vault, reason: "host identity is unresolved" });
|
|
9544
|
-
return {
|
|
9545
|
-
exitCode: ExitCode.OK,
|
|
9546
|
-
result: ok({ manifest_loaded: true, markdown: markdown2, humanHint: markdown2 })
|
|
9547
|
-
};
|
|
9548
|
-
}
|
|
9549
|
-
const markdown = formatKnownContext({
|
|
9550
|
-
manifest: loaded.manifest,
|
|
9551
|
-
hostId: resolved.hostId,
|
|
9552
|
-
source: resolved.source,
|
|
9553
|
-
osHostname,
|
|
9554
|
-
user,
|
|
9555
|
-
cwd,
|
|
9556
|
-
vault
|
|
9557
|
-
});
|
|
9558
|
-
return {
|
|
9559
|
-
exitCode: ExitCode.OK,
|
|
9560
|
-
result: ok({
|
|
9561
|
-
manifest_loaded: true,
|
|
9562
|
-
host_id: resolved.hostId,
|
|
9563
|
-
source: resolved.source,
|
|
9564
|
-
markdown,
|
|
9565
|
-
humanHint: markdown
|
|
9566
|
-
})
|
|
9567
|
-
};
|
|
9568
|
-
}
|
|
9569
|
-
async function loadFleetManifest(file) {
|
|
9570
|
-
let text;
|
|
9571
|
-
try {
|
|
9572
|
-
text = await readFile29(file, "utf8");
|
|
9573
|
-
} catch {
|
|
9574
|
-
return { ok: false, error: "FILE_NOT_FOUND" };
|
|
9575
|
-
}
|
|
9576
|
-
let parsed;
|
|
9577
|
-
try {
|
|
9578
|
-
parsed = yaml3.load(text, { schema: yaml3.JSON_SCHEMA });
|
|
9579
|
-
} catch (error) {
|
|
9580
|
-
return { ok: false, error: "INVALID_YAML", detail: error instanceof Error ? error.message : String(error) };
|
|
9581
|
-
}
|
|
9582
|
-
const result = FleetManifestSchema.safeParse(parsed);
|
|
9583
|
-
if (!result.success) {
|
|
9584
|
-
return { ok: false, error: "INVALID_FLEET_MANIFEST", detail: result.error.issues };
|
|
9585
|
-
}
|
|
9586
|
-
return { ok: true, manifest: result.data };
|
|
9587
|
-
}
|
|
9588
|
-
function invalidFleet(errors) {
|
|
9589
|
-
return {
|
|
9590
|
-
exitCode: ExitCode.FLEET_MANIFEST_INVALID,
|
|
9591
|
-
result: ok({
|
|
9592
|
-
valid: false,
|
|
9593
|
-
errors,
|
|
9594
|
-
warnings: [],
|
|
9595
|
-
host_count: 0,
|
|
9596
|
-
humanHint: `INVALID fleet manifest
|
|
9597
|
-
${errors.map((e) => ` ${e.path || "(root)"}: ${e.message}`).join("\n")}`
|
|
9598
|
-
})
|
|
9599
|
-
};
|
|
9600
|
-
}
|
|
9601
|
-
function fleetLoadErrors(loaded) {
|
|
9602
|
-
if (loaded.error === "INVALID_YAML") {
|
|
9603
|
-
return [{ path: "", message: `invalid YAML: ${String(loaded.detail ?? "parse failed")}` }];
|
|
9604
|
-
}
|
|
9605
|
-
if (loaded.error === "INVALID_FLEET_MANIFEST" && Array.isArray(loaded.detail)) {
|
|
9606
|
-
return loaded.detail.map((issue) => {
|
|
9607
|
-
const zodIssue = issue;
|
|
9608
|
-
return {
|
|
9609
|
-
path: (zodIssue.path ?? []).join("."),
|
|
9610
|
-
message: zodIssue.message ?? "invalid value"
|
|
9611
|
-
};
|
|
9612
|
-
});
|
|
9613
|
-
}
|
|
9614
|
-
return [{ path: "", message: loaded.error }];
|
|
9615
|
-
}
|
|
9616
|
-
function fleetWarnings(manifest) {
|
|
9617
|
-
const warnings = [];
|
|
9618
|
-
for (const [id, host] of Object.entries(manifest.hosts)) {
|
|
9619
|
-
if (host.role === "snapshotter" && host.protected !== true) {
|
|
9620
|
-
warnings.push(`snapshotter host '${id}' is not protected=true`);
|
|
9621
|
-
}
|
|
9622
|
-
}
|
|
9623
|
-
return warnings;
|
|
9624
|
-
}
|
|
9625
|
-
function findSnapshotter(manifest) {
|
|
9626
|
-
return Object.entries(manifest.hosts).find(([, host]) => host.role === "snapshotter")?.[0];
|
|
9627
|
-
}
|
|
9628
|
-
async function resolveHostId(input) {
|
|
9629
|
-
if (input.hostId) return { hostId: input.hostId, source: "host-id" };
|
|
9630
|
-
if (input.env.SKILLWIKI_HOST_ID) return { hostId: input.env.SKILLWIKI_HOST_ID, source: "SKILLWIKI_HOST_ID" };
|
|
9631
|
-
if (input.env.AGENT_HOST_ID) return { hostId: input.env.AGENT_HOST_ID, source: "AGENT_HOST_ID" };
|
|
9632
|
-
if (input.home) {
|
|
9633
|
-
const dotenv = await parseDotenvFile(join45(input.home, ".skillwiki", ".env"));
|
|
9634
|
-
if (dotenv.SKILLWIKI_HOST_ID) {
|
|
9635
|
-
return { hostId: dotenv.SKILLWIKI_HOST_ID, source: "~/.skillwiki/.env:SKILLWIKI_HOST_ID" };
|
|
9636
|
-
}
|
|
9637
|
-
}
|
|
9638
|
-
if (input.env.VS_HOSTNAME) return { hostId: input.env.VS_HOSTNAME, source: "VS_HOSTNAME" };
|
|
9639
|
-
const hostname = input.osHostname.trim();
|
|
9640
|
-
if (hostname) {
|
|
9641
|
-
if (input.manifest.hosts[hostname]) return { hostId: hostname, source: "hostname" };
|
|
9642
|
-
const byHostname = Object.entries(input.manifest.hosts).find(([, host]) => host.identity.hostnames.includes(hostname));
|
|
9643
|
-
if (byHostname) return { hostId: byHostname[0], source: "hostname" };
|
|
9644
|
-
}
|
|
9645
|
-
return {};
|
|
9646
|
-
}
|
|
9647
|
-
function formatKnownContext(input) {
|
|
9648
|
-
const host = input.manifest.hosts[input.hostId];
|
|
9649
|
-
const protectedValue = host.protected === true ? "true" : "false";
|
|
9650
|
-
const writesTo = host.writes_to.join(", ");
|
|
9651
|
-
const selfAliases = collectSelfAliases(input.manifest, input.hostId);
|
|
9652
|
-
const outbound = collectOutboundAccess(input.manifest, input.hostId);
|
|
9653
|
-
const maintenanceLines = formatMaintenanceLines(host);
|
|
9654
|
-
const guidance = input.hostId === "macos-dev" ? "use declared SSH aliases for remote work when needed; do not assume undeclared hosts have reciprocal SSH access." : `this session is already on \`${input.hostId}\`; do not SSH to self aliases unless the user explicitly asks. Do not assume outbound SSH to other fleet hosts is configured.`;
|
|
9655
|
-
return [
|
|
9656
|
-
"## Runtime Host Context",
|
|
9657
|
-
"",
|
|
9658
|
-
`- Current machine: \`${input.hostId}\`${input.source ? ` (source: \`${input.source}\`)` : ""}`,
|
|
9659
|
-
`- OS hostname: ${formatMaybe(input.osHostname)}`,
|
|
9660
|
-
`- User: ${formatMaybe(input.user)}`,
|
|
9661
|
-
`- Workspace: ${formatMaybe(input.cwd)}`,
|
|
9662
|
-
`- Vault: ${formatMaybe(input.vault)}`,
|
|
9663
|
-
`- Fleet role: \`${host.role}\`; protected: \`${protectedValue}\`; writes_to: \`${writesTo}\``,
|
|
9664
|
-
...maintenanceLines,
|
|
9665
|
-
`- Self SSH aliases known in fleet: ${formatList(selfAliases)}`,
|
|
9666
|
-
`- Declared outbound SSH from this source: ${formatOutboundAccess(outbound)}`,
|
|
9667
|
-
`- Guidance: ${guidance}`
|
|
9668
|
-
].join("\n");
|
|
9669
|
-
}
|
|
9670
|
-
function formatUnknownContext(input) {
|
|
9671
|
-
return [
|
|
9672
|
-
"## Runtime Host Context",
|
|
9673
|
-
"",
|
|
9674
|
-
"- Current machine: unknown",
|
|
9675
|
-
`- OS hostname: ${formatMaybe(input.osHostname)}`,
|
|
9676
|
-
`- User: ${formatMaybe(input.user)}`,
|
|
9677
|
-
`- Workspace: ${formatMaybe(input.cwd)}`,
|
|
9678
|
-
`- Vault: ${formatMaybe(input.vault)}`,
|
|
9679
|
-
"- Fleet role: unknown",
|
|
9680
|
-
"- Self SSH aliases known in fleet: unknown",
|
|
9681
|
-
"- Declared outbound SSH from this source: unknown",
|
|
9682
|
-
`- Guidance: ${input.reason}; do not assume local vs remote role. Inspect runtime or ask before SSH/deploy/sync work.`
|
|
9683
|
-
].join("\n");
|
|
9684
|
-
}
|
|
9685
|
-
function collectSelfAliases(manifest, hostId2) {
|
|
9686
|
-
const aliases = [];
|
|
9687
|
-
const host = manifest.hosts[hostId2];
|
|
9688
|
-
const access = host?.access?.from ?? {};
|
|
9689
|
-
for (const profile of Object.values(access)) {
|
|
9690
|
-
for (const alias of profile.ssh_aliases ?? []) aliases.push(alias);
|
|
9691
|
-
}
|
|
9692
|
-
return [...new Set(aliases)];
|
|
9693
|
-
}
|
|
9694
|
-
function collectOutboundAccess(manifest, sourceHostId) {
|
|
9695
|
-
const hosts = [];
|
|
9696
|
-
for (const [targetId, target] of Object.entries(manifest.hosts)) {
|
|
9697
|
-
if (targetId === sourceHostId) continue;
|
|
9698
|
-
const profile = target.access?.from?.[sourceHostId];
|
|
9699
|
-
if (profile && (profile.status === "configured" || profile.status === "local")) {
|
|
9700
|
-
hosts.push({
|
|
9701
|
-
hostId: targetId,
|
|
9702
|
-
sshAliases: [...new Set(profile.ssh_aliases ?? [])],
|
|
9703
|
-
users: [...new Set(profile.users ?? [])]
|
|
9704
|
-
});
|
|
9705
|
-
}
|
|
9706
|
-
}
|
|
9707
|
-
return hosts.sort((left, right) => left.hostId.localeCompare(right.hostId));
|
|
9708
|
-
}
|
|
9709
|
-
function formatMaintenanceLines(host) {
|
|
9710
|
-
const satellite = host.maintenance?.skillwiki_satellite;
|
|
9711
|
-
if (!satellite?.enabled) return [];
|
|
9712
|
-
return [
|
|
9713
|
-
`- Maintenance role: \`skillwiki satellite\`; user: \`${satellite.user}\`; ssh: \`${satellite.ssh_alias}\``,
|
|
9714
|
-
`- Maintenance paths: maintenance vault: \`${satellite.vault_path}\`; repo: \`${satellite.repo_path}\`; scheduler: \`${satellite.scheduler}\`; jobs: ${formatList(satellite.jobs)}`
|
|
9715
|
-
];
|
|
9716
|
-
}
|
|
9717
|
-
function formatOutboundAccess(values) {
|
|
9718
|
-
if (values.length === 0) return "none";
|
|
9719
|
-
return values.map((value) => {
|
|
9720
|
-
const aliasPart = value.sshAliases.length > 0 ? ` via ${formatList(value.sshAliases)}` : " (no SSH aliases)";
|
|
9721
|
-
const usersPart = value.users.length > 0 ? ` (users: ${formatList(value.users)})` : "";
|
|
9722
|
-
return `\`${value.hostId}\`${aliasPart}${usersPart}`;
|
|
9723
|
-
}).join("; ");
|
|
9724
|
-
}
|
|
9725
|
-
function formatList(values) {
|
|
9726
|
-
return values.length > 0 ? values.map((v) => `\`${v}\``).join(", ") : "none";
|
|
9727
|
-
}
|
|
9728
|
-
function formatMaybe(value) {
|
|
9729
|
-
return value && value.trim().length > 0 ? `\`${value}\`` : "unknown";
|
|
9730
|
-
}
|
|
9731
|
-
function safeEnvValue(value) {
|
|
9732
|
-
return value && value.trim().length > 0 ? value : void 0;
|
|
9733
|
-
}
|
|
9734
|
-
function safeUserName() {
|
|
9735
|
-
try {
|
|
9736
|
-
return userInfo().username;
|
|
9737
|
-
} catch {
|
|
9738
|
-
return "";
|
|
9739
|
-
}
|
|
9740
|
-
}
|
|
9741
|
-
|
|
9742
10184
|
// src/utils/auto-commit.ts
|
|
9743
|
-
import { existsSync as
|
|
9744
|
-
import { join as
|
|
10185
|
+
import { existsSync as existsSync20 } from "fs";
|
|
10186
|
+
import { join as join47 } from "path";
|
|
9745
10187
|
async function postCommit(vault, exitCode) {
|
|
9746
10188
|
if (exitCode !== 0) return;
|
|
9747
10189
|
const home = process.env.HOME ?? "";
|
|
9748
10190
|
const dotenv = await parseDotenvFile(configPath(home));
|
|
9749
10191
|
if (dotenv["AUTO_COMMIT"] === "false") return;
|
|
9750
|
-
if (!
|
|
10192
|
+
if (!existsSync20(join47(vault, ".git"))) return;
|
|
9751
10193
|
const lastOps = readLastOp(vault);
|
|
9752
10194
|
if (lastOps.length === 0) return;
|
|
9753
10195
|
const porcelain = git(vault, ["status", "--porcelain"]);
|
|
@@ -9798,7 +10240,7 @@ program.command("validate <file>").description("validate vault page frontmatter
|
|
|
9798
10240
|
emit(await runValidate({ file, apply: !!opts.apply, vault }), vault);
|
|
9799
10241
|
});
|
|
9800
10242
|
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) => {
|
|
9801
|
-
const out = opts.out ??
|
|
10243
|
+
const out = opts.out ?? join48(vault, ".skillwiki", "graph.json");
|
|
9802
10244
|
emit(await runGraphBuild({ vault, out }), vault);
|
|
9803
10245
|
});
|
|
9804
10246
|
var canvasCmd = program.command("canvas").description("manage Obsidian canvas files");
|