skillwiki 0.10.26 → 0.10.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-EQPU2BPM.js → chunk-23CR4YPL.js} +6 -8
- package/dist/chunk-5XQWYC5K.js +5255 -0
- package/dist/chunk-NOXDRIHM.js +1194 -0
- package/dist/chunk-OJVIND5D.js +126 -0
- package/dist/{chunk-C2DKFJFA.js → chunk-RQARJ6HB.js} +286 -1
- package/dist/{chunk-QNTBNNEL.js → chunk-SGZPIGGX.js} +12 -14
- package/dist/{chunk-QBZYEEBD.js → chunk-UI4SOWI2.js} +7 -123
- package/dist/{chunk-VXMHHXVP.js → chunk-W7NPTDIU.js} +463 -4745
- package/dist/cli.js +107 -703
- package/dist/{index-projection-FRWTZB5Y.js → index-projection-64WW36N3.js} +3 -3
- package/dist/{managed-write-preflight-4LPVMP42.js → managed-write-preflight-2FU5F56H.js} +5 -3
- package/dist/skillwiki-mcp.js +6 -6
- package/dist/sources-7UC6PJEV.js +9 -0
- package/dist/vault-sync/scripts/lib/platform.sh +264 -0
- package/package.json +1 -1
- package/skills/.claude-plugin/plugin.json +1 -1
- package/skills/.codex-plugin/plugin.json +1 -1
- package/skills/package.json +1 -1
- package/dist/chunk-6ZDKTNLA.js +0 -294
- package/dist/chunk-BASWDOQB.js +0 -455
- package/dist/chunk-PQG26AGJ.js +0 -941
- package/dist/sources-L2SQV63E.js +0 -10
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ROOT_INDEX_SECTION_ORDER,
|
|
4
|
+
atomicWriteText,
|
|
5
|
+
buildRootIndexUniverse
|
|
6
|
+
} from "./chunk-UI4SOWI2.js";
|
|
7
|
+
import {
|
|
8
|
+
err,
|
|
9
|
+
ok
|
|
10
|
+
} from "./chunk-RQARJ6HB.js";
|
|
11
|
+
|
|
12
|
+
// src/utils/index-projection.ts
|
|
13
|
+
import { readFile } from "fs/promises";
|
|
14
|
+
import { join } from "path";
|
|
15
|
+
var UNMANAGED_START = "<!-- skillwiki:index-unmanaged:start -->";
|
|
16
|
+
var UNMANAGED_END = "<!-- skillwiki:index-unmanaged:end -->";
|
|
17
|
+
function extractUnmanaged(currentText) {
|
|
18
|
+
const startCount = currentText.split(UNMANAGED_START).length - 1;
|
|
19
|
+
const endCount = currentText.split(UNMANAGED_END).length - 1;
|
|
20
|
+
if (startCount === 0 && endCount === 0) return ok("");
|
|
21
|
+
if (startCount !== 1 || endCount !== 1) {
|
|
22
|
+
return err("SCHEME_REJECTED", {
|
|
23
|
+
message: "index.md must contain exactly one complete unmanaged marker pair"
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
const start = currentText.indexOf(UNMANAGED_START);
|
|
27
|
+
const end = currentText.indexOf(UNMANAGED_END);
|
|
28
|
+
if (start < 0 || end < 0 || end < start) {
|
|
29
|
+
return err("SCHEME_REJECTED", {
|
|
30
|
+
message: "unmanaged marker pair is reversed or incomplete"
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
const body = currentText.slice(start + UNMANAGED_START.length, end).replace(/^\n/, "").replace(/\n$/, "");
|
|
34
|
+
return ok(body);
|
|
35
|
+
}
|
|
36
|
+
function priorWikilinkTargets(text) {
|
|
37
|
+
const out = [];
|
|
38
|
+
const re = /\[\[([^\]|#]+)(?:[|#][^\]]*)?\]\]/g;
|
|
39
|
+
let m;
|
|
40
|
+
while ((m = re.exec(text)) !== null) {
|
|
41
|
+
out.push(m[1].trim().replace(/\.md$/, ""));
|
|
42
|
+
}
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
async function renderRootIndex(input) {
|
|
46
|
+
const vault = input.vault;
|
|
47
|
+
let currentText = input.currentText;
|
|
48
|
+
if (currentText === void 0) {
|
|
49
|
+
try {
|
|
50
|
+
currentText = await readFile(join(vault, "index.md"), "utf8");
|
|
51
|
+
} catch {
|
|
52
|
+
currentText = "";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const unmanaged = extractUnmanaged(currentText);
|
|
56
|
+
if (!unmanaged.ok) return unmanaged;
|
|
57
|
+
const universe = await buildRootIndexUniverse({ vault });
|
|
58
|
+
if (!universe.ok) return universe;
|
|
59
|
+
const entries = universe.data.required;
|
|
60
|
+
let duplicatesRemoved = universe.data.duplicatesRemoved;
|
|
61
|
+
const prior = priorWikilinkTargets(currentText);
|
|
62
|
+
const knownTargets = universe.data.knownTargets;
|
|
63
|
+
const knownBasenames = new Set(
|
|
64
|
+
[...knownTargets].map((target) => target.split("/").pop().toLowerCase())
|
|
65
|
+
);
|
|
66
|
+
const priorLower = /* @__PURE__ */ new Map();
|
|
67
|
+
for (const t of prior) {
|
|
68
|
+
const k = t.toLowerCase();
|
|
69
|
+
priorLower.set(k, (priorLower.get(k) ?? 0) + 1);
|
|
70
|
+
}
|
|
71
|
+
for (const count of priorLower.values()) {
|
|
72
|
+
if (count > 1) duplicatesRemoved += count - 1;
|
|
73
|
+
}
|
|
74
|
+
const headingDupes = (currentText.match(/^##\s+(entities|concepts|comparisons|queries|meta|projects)\s*$/gim) ?? []).map((h) => h.replace(/^##\s+/i, "").trim().toLowerCase());
|
|
75
|
+
const headingCounts = /* @__PURE__ */ new Map();
|
|
76
|
+
for (const h of headingDupes) headingCounts.set(h, (headingCounts.get(h) ?? 0) + 1);
|
|
77
|
+
for (const c of headingCounts.values()) {
|
|
78
|
+
if (c > 1) duplicatesRemoved += c - 1;
|
|
79
|
+
}
|
|
80
|
+
const ghostsRemoved = [
|
|
81
|
+
...new Set(
|
|
82
|
+
prior.filter((t) => {
|
|
83
|
+
if (!t.includes("/")) {
|
|
84
|
+
return !knownBasenames.has(t.toLowerCase());
|
|
85
|
+
}
|
|
86
|
+
return !knownTargets.has(t);
|
|
87
|
+
})
|
|
88
|
+
)
|
|
89
|
+
];
|
|
90
|
+
const lines = [
|
|
91
|
+
"# Vault Index",
|
|
92
|
+
"",
|
|
93
|
+
"Generated by `skillwiki index rebuild`. Generated sections are derived from typed-page frontmatter and project manifests.",
|
|
94
|
+
""
|
|
95
|
+
];
|
|
96
|
+
for (const section of ROOT_INDEX_SECTION_ORDER) {
|
|
97
|
+
lines.push(`## ${section}`, "");
|
|
98
|
+
const sectionEntries = entries.filter((e) => e.section === section);
|
|
99
|
+
for (const e of sectionEntries) {
|
|
100
|
+
lines.push(`- [[${e.target}]] \u2014 ${e.title}`);
|
|
101
|
+
}
|
|
102
|
+
if (sectionEntries.length > 0) lines.push("");
|
|
103
|
+
}
|
|
104
|
+
lines.push(UNMANAGED_START);
|
|
105
|
+
if (unmanaged.data) {
|
|
106
|
+
lines.push(unmanaged.data);
|
|
107
|
+
}
|
|
108
|
+
lines.push(UNMANAGED_END);
|
|
109
|
+
lines.push("");
|
|
110
|
+
return ok({
|
|
111
|
+
text: lines.join("\n"),
|
|
112
|
+
entries,
|
|
113
|
+
duplicates_removed: duplicatesRemoved,
|
|
114
|
+
ghosts_removed: ghostsRemoved
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
async function writeRootIndexProjection(vault, projection) {
|
|
118
|
+
return atomicWriteText(join(vault, "index.md"), projection.text);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export {
|
|
122
|
+
UNMANAGED_START,
|
|
123
|
+
UNMANAGED_END,
|
|
124
|
+
renderRootIndex,
|
|
125
|
+
writeRootIndexProjection
|
|
126
|
+
};
|
|
@@ -411,6 +411,281 @@ function systemdPropertyFor(semantic) {
|
|
|
411
411
|
return SYSTEMD_PROPERTY_CATALOG[semantic];
|
|
412
412
|
}
|
|
413
413
|
|
|
414
|
+
// src/parsers/frontmatter.ts
|
|
415
|
+
import yaml from "js-yaml";
|
|
416
|
+
var FM_OPEN = /^---\r?\n/;
|
|
417
|
+
function splitFrontmatter(text) {
|
|
418
|
+
if (!FM_OPEN.test(text)) return ok({ rawFrontmatter: "", body: text, bodyStart: 0 });
|
|
419
|
+
const afterOpen = text.replace(FM_OPEN, "");
|
|
420
|
+
const closeIdx = afterOpen.search(/\r?\n---\r?\n/);
|
|
421
|
+
if (closeIdx === -1) return err("MISSING_CLOSING_DELIMITER");
|
|
422
|
+
const rawFrontmatter = afterOpen.slice(0, closeIdx);
|
|
423
|
+
const closeMatch = afterOpen.slice(closeIdx).match(/\r?\n---\r?\n/);
|
|
424
|
+
const bodyStart = text.length - (afterOpen.length - closeIdx - closeMatch[0].length);
|
|
425
|
+
const body = text.slice(bodyStart);
|
|
426
|
+
return ok({ rawFrontmatter, body, bodyStart });
|
|
427
|
+
}
|
|
428
|
+
function extractFrontmatter(text) {
|
|
429
|
+
const split = splitFrontmatter(text);
|
|
430
|
+
if (!split.ok) return split;
|
|
431
|
+
if (!split.data.rawFrontmatter) return ok({});
|
|
432
|
+
try {
|
|
433
|
+
const parsed = yaml.load(split.data.rawFrontmatter, { schema: yaml.JSON_SCHEMA });
|
|
434
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return ok({});
|
|
435
|
+
return ok(parsed);
|
|
436
|
+
} catch (e) {
|
|
437
|
+
return err("INVALID_FRONTMATTER", { message: getErrorMessage(e) });
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// src/utils/sensitive-content.ts
|
|
442
|
+
import { createHash } from "crypto";
|
|
443
|
+
var REDACTED_RE = /\[REDACTED:[^\]]+\]/i;
|
|
444
|
+
var SYNTHETIC_RE = /^(?:<[^>]+>|\$\{[^}]+\}|REPLACE_WITH_[A-Z0-9_]+|YOUR_[A-Z0-9_]+|EXAMPLE_[A-Z0-9_]+)$/i;
|
|
445
|
+
var MATCHERS = [
|
|
446
|
+
{
|
|
447
|
+
kind: "private_key",
|
|
448
|
+
re: /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]*PRIVATE KEY-----/g
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
kind: "authorization_header",
|
|
452
|
+
re: /\bAuthorization["']?\s*:\s*["']?(Bearer\s+[A-Za-z0-9._~+/-]{20,})["']?/gi,
|
|
453
|
+
valueGroup: 1
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
kind: "cookie",
|
|
457
|
+
re: /\b(?:Cookie|Set-Cookie)\s*:\s*([^\n]{20,})/gi,
|
|
458
|
+
valueGroup: 1
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
kind: "jwt",
|
|
462
|
+
re: /\b([A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{12,}\.[A-Za-z0-9_-]{12,})\b/g,
|
|
463
|
+
valueGroup: 1
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
kind: "provider_key",
|
|
467
|
+
re: /\b(sk-[A-Za-z0-9_-]{20,}|xox[baprs]-[A-Za-z0-9-]{20,}|gh[pousr]_[A-Za-z0-9_=-]{20,})\b/g,
|
|
468
|
+
valueGroup: 1
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
kind: "access_key",
|
|
472
|
+
re: /\b((?:AKIA|ASIA)[A-Z0-9]{16})\b/g,
|
|
473
|
+
valueGroup: 1
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
kind: "access_key",
|
|
477
|
+
re: /\b(?:access[-_ ]?key|credential)["']?\s*[:=]\s*["']?([A-Za-z0-9._~+/-]{20,})["']?/gi,
|
|
478
|
+
valueGroup: 1
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
kind: "api_key",
|
|
482
|
+
re: /\b(?:api[-_ ]?key)["']?\s*[:=]\s*["']?([A-Za-z0-9._~+/-]{20,})["']?/gi,
|
|
483
|
+
valueGroup: 1
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
kind: "password",
|
|
487
|
+
re: /\b(?:pass(?:word|wd)?)["']?\s*[:=]\s*["']?([^\s`"']{8,})["']?/gi,
|
|
488
|
+
valueGroup: 1
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
kind: "secret",
|
|
492
|
+
re: /\b(?:secret|client[-_ ]?secret)["']?\s*[:=]\s*["']?([A-Za-z0-9._~+/-]{16,})["']?/gi,
|
|
493
|
+
valueGroup: 1
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
kind: "token",
|
|
497
|
+
re: /\b(?:token|session)["']?\s*[:=]\s*["']?([A-Za-z0-9._~+/-]{16,})["']?/gi,
|
|
498
|
+
valueGroup: 1
|
|
499
|
+
}
|
|
500
|
+
];
|
|
501
|
+
function fingerprint(value) {
|
|
502
|
+
return createHash("sha256").update(value).digest("hex").slice(0, 12);
|
|
503
|
+
}
|
|
504
|
+
function lineFor(text, offset) {
|
|
505
|
+
return text.slice(0, offset).split(/\r?\n/).length;
|
|
506
|
+
}
|
|
507
|
+
function redactMarker(kind, value) {
|
|
508
|
+
return `[REDACTED:${kind}:${fingerprint(value)}]`;
|
|
509
|
+
}
|
|
510
|
+
function isSyntheticPlaceholder(value) {
|
|
511
|
+
return REDACTED_RE.test(value) || SYNTHETIC_RE.test(value.trim());
|
|
512
|
+
}
|
|
513
|
+
function isNonSecretTokenCapture(value) {
|
|
514
|
+
if (value.startsWith("//")) return true;
|
|
515
|
+
if (/^[a-z]{2,}(?:-[a-z]{2,})+$/.test(value)) return true;
|
|
516
|
+
return false;
|
|
517
|
+
}
|
|
518
|
+
function collectMatches(text) {
|
|
519
|
+
const matches = [];
|
|
520
|
+
for (const matcher of MATCHERS) {
|
|
521
|
+
matcher.re.lastIndex = 0;
|
|
522
|
+
for (const m of text.matchAll(matcher.re)) {
|
|
523
|
+
const whole = m[0];
|
|
524
|
+
const start = m.index ?? 0;
|
|
525
|
+
if (REDACTED_RE.test(whole)) continue;
|
|
526
|
+
const value = matcher.valueGroup ? m[matcher.valueGroup] : whole;
|
|
527
|
+
if (isSyntheticPlaceholder(value)) continue;
|
|
528
|
+
if (matcher.kind === "token" && isNonSecretTokenCapture(value)) continue;
|
|
529
|
+
const valueOffset = whole.lastIndexOf(value);
|
|
530
|
+
const valueStart = start + Math.max(0, valueOffset);
|
|
531
|
+
matches.push({
|
|
532
|
+
start,
|
|
533
|
+
end: start + whole.length,
|
|
534
|
+
valueStart,
|
|
535
|
+
valueEnd: valueStart + value.length,
|
|
536
|
+
kind: matcher.kind
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return matches.sort((a, b) => {
|
|
541
|
+
if (a.valueStart !== b.valueStart) return a.valueStart - b.valueStart;
|
|
542
|
+
return b.valueEnd - b.valueStart - (a.valueEnd - a.valueStart);
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
function collapseOverlaps(matches) {
|
|
546
|
+
const kept = [];
|
|
547
|
+
for (const match of matches) {
|
|
548
|
+
const overlaps = kept.some((k) => match.valueStart < k.valueEnd && match.valueEnd > k.valueStart);
|
|
549
|
+
if (!overlaps) kept.push(match);
|
|
550
|
+
}
|
|
551
|
+
return kept;
|
|
552
|
+
}
|
|
553
|
+
function scanSensitiveContent(text, opts = {}) {
|
|
554
|
+
return collapseOverlaps(collectMatches(text)).map((match) => {
|
|
555
|
+
const value = text.slice(match.valueStart, match.valueEnd);
|
|
556
|
+
const marker = redactMarker(match.kind, value);
|
|
557
|
+
const rawPreview = text.slice(Math.max(0, match.start - 24), Math.min(text.length, match.end + 24));
|
|
558
|
+
const preview = rawPreview.replace(value, marker);
|
|
559
|
+
return {
|
|
560
|
+
file: opts.file,
|
|
561
|
+
line: lineFor(text, match.valueStart),
|
|
562
|
+
kind: match.kind,
|
|
563
|
+
preview,
|
|
564
|
+
fingerprint: fingerprint(value)
|
|
565
|
+
};
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
function redactSensitiveContent(text, opts = {}) {
|
|
569
|
+
const matches = collapseOverlaps(collectMatches(text));
|
|
570
|
+
if (matches.length === 0) return { text, changed: false, findings: [] };
|
|
571
|
+
let out = "";
|
|
572
|
+
let cursor = 0;
|
|
573
|
+
for (const match of matches) {
|
|
574
|
+
const value = text.slice(match.valueStart, match.valueEnd);
|
|
575
|
+
out += text.slice(cursor, match.valueStart);
|
|
576
|
+
out += redactMarker(match.kind, value);
|
|
577
|
+
cursor = match.valueEnd;
|
|
578
|
+
}
|
|
579
|
+
out += text.slice(cursor);
|
|
580
|
+
return {
|
|
581
|
+
text: out,
|
|
582
|
+
changed: out !== text,
|
|
583
|
+
findings: scanSensitiveContent(text, opts)
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// src/utils/vault.ts
|
|
588
|
+
import { existsSync, readFileSync } from "fs";
|
|
589
|
+
import { readFile, readdir, stat } from "fs/promises";
|
|
590
|
+
import { join, relative, sep } from "path";
|
|
591
|
+
var TYPED_DIRS = ["entities", "concepts", "comparisons", "queries", "meta"];
|
|
592
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
593
|
+
var DEFAULT_IO_CONCURRENCY = 1;
|
|
594
|
+
function vaultIoConcurrency() {
|
|
595
|
+
const raw = Number.parseInt(process.env.SKILLWIKI_VAULT_IO_CONCURRENCY ?? "", 10);
|
|
596
|
+
return Number.isFinite(raw) && raw > 0 ? Math.min(raw, 64) : DEFAULT_IO_CONCURRENCY;
|
|
597
|
+
}
|
|
598
|
+
function decodeProcMountPath(value) {
|
|
599
|
+
return value.replace(/\\040/g, " ");
|
|
600
|
+
}
|
|
601
|
+
function isRcloneFuseVaultFromMounts(root, mounts) {
|
|
602
|
+
return mounts.split(/\r?\n/).some((line) => {
|
|
603
|
+
const parts = line.split(" ");
|
|
604
|
+
if (parts.length < 3) return false;
|
|
605
|
+
const mountPoint = decodeProcMountPath(parts[1]);
|
|
606
|
+
const fsType = parts[2];
|
|
607
|
+
return fsType === "fuse.rclone" && (root === mountPoint || root.startsWith(`${mountPoint}/`));
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
function resolveReadOnlyVaultRootWithMounts(root, mounts) {
|
|
611
|
+
if (/^(1|true|yes)$/i.test(process.env.SKILLWIKI_DISABLE_VAULT_READ_MIRROR ?? "")) {
|
|
612
|
+
return { root, mirrored: false };
|
|
613
|
+
}
|
|
614
|
+
const explicitMirror = process.env.SKILLWIKI_VAULT_READ_MIRROR;
|
|
615
|
+
if (explicitMirror && existsSync(join(explicitMirror, "SCHEMA.md"))) {
|
|
616
|
+
return { root: explicitMirror, mirrored: explicitMirror !== root };
|
|
617
|
+
}
|
|
618
|
+
const siblingMirror = `${root}-git`;
|
|
619
|
+
if (isRcloneFuseVaultFromMounts(root, mounts) && existsSync(join(siblingMirror, "SCHEMA.md"))) {
|
|
620
|
+
return { root: siblingMirror, mirrored: true };
|
|
621
|
+
}
|
|
622
|
+
return { root, mirrored: false };
|
|
623
|
+
}
|
|
624
|
+
function resolveReadOnlyVaultRoot(root) {
|
|
625
|
+
let mounts = "";
|
|
626
|
+
try {
|
|
627
|
+
mounts = readFileSync("/proc/mounts", "utf8");
|
|
628
|
+
} catch {
|
|
629
|
+
}
|
|
630
|
+
return resolveReadOnlyVaultRootWithMounts(root, mounts);
|
|
631
|
+
}
|
|
632
|
+
async function mapWithConcurrency(items, limit, mapper) {
|
|
633
|
+
const out = new Array(items.length);
|
|
634
|
+
let next = 0;
|
|
635
|
+
const workers = Array.from({ length: Math.min(Math.max(1, limit), items.length) }, async () => {
|
|
636
|
+
for (; ; ) {
|
|
637
|
+
const index = next++;
|
|
638
|
+
if (index >= items.length) return;
|
|
639
|
+
out[index] = await mapper(items[index], index);
|
|
640
|
+
}
|
|
641
|
+
});
|
|
642
|
+
await Promise.all(workers);
|
|
643
|
+
return out;
|
|
644
|
+
}
|
|
645
|
+
async function scanVault(root) {
|
|
646
|
+
try {
|
|
647
|
+
await stat(join(root, "SCHEMA.md"));
|
|
648
|
+
} catch {
|
|
649
|
+
return err("VAULT_PATH_INVALID", { root, reason: "SCHEMA.md missing" });
|
|
650
|
+
}
|
|
651
|
+
const all = await walk(root);
|
|
652
|
+
const rels = all.map((p) => ({ absPath: p, relPath: relative(root, p).split(sep).join("/") }));
|
|
653
|
+
return ok({
|
|
654
|
+
root,
|
|
655
|
+
allMarkdown: rels,
|
|
656
|
+
typedKnowledge: rels.filter((p) => TYPED_DIRS.some((d) => p.relPath.startsWith(d + "/"))),
|
|
657
|
+
raw: rels.filter((p) => p.relPath.startsWith("raw/")),
|
|
658
|
+
workItems: rels.filter((p) => /^projects\/[^/]+\/work\/[^/]+\/(spec|plan|log)\.md$/.test(p.relPath)),
|
|
659
|
+
compound: rels.filter((p) => /^projects\/[^/]+\/compound\//.test(p.relPath))
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
async function walk(dir) {
|
|
663
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
664
|
+
const out = [];
|
|
665
|
+
const subdirs = [];
|
|
666
|
+
for (const e of entries) {
|
|
667
|
+
const p = join(dir, e.name);
|
|
668
|
+
if (e.isDirectory()) {
|
|
669
|
+
if (SKIP_DIRS.has(e.name)) continue;
|
|
670
|
+
subdirs.push(p);
|
|
671
|
+
} else if (e.isFile() && e.name.endsWith(".md")) out.push(p);
|
|
672
|
+
}
|
|
673
|
+
const nested = await mapWithConcurrency(subdirs, Math.min(8, vaultIoConcurrency()), walk);
|
|
674
|
+
for (const files of nested) out.push(...files);
|
|
675
|
+
return out;
|
|
676
|
+
}
|
|
677
|
+
async function readPage(p) {
|
|
678
|
+
return readFile(p.absPath, "utf8");
|
|
679
|
+
}
|
|
680
|
+
async function readPageCached(p, cache) {
|
|
681
|
+
if (!cache) return readPage(p);
|
|
682
|
+
const existing = cache.get(p.absPath);
|
|
683
|
+
if (existing) return existing;
|
|
684
|
+
const pending = readPage(p);
|
|
685
|
+
cache.set(p.absPath, pending);
|
|
686
|
+
return pending;
|
|
687
|
+
}
|
|
688
|
+
|
|
414
689
|
export {
|
|
415
690
|
ExitCode,
|
|
416
691
|
ok,
|
|
@@ -426,5 +701,15 @@ export {
|
|
|
426
701
|
detectSchema,
|
|
427
702
|
isBlockedHost,
|
|
428
703
|
getErrorMessage,
|
|
429
|
-
systemdPropertyFor
|
|
704
|
+
systemdPropertyFor,
|
|
705
|
+
splitFrontmatter,
|
|
706
|
+
extractFrontmatter,
|
|
707
|
+
scanSensitiveContent,
|
|
708
|
+
redactSensitiveContent,
|
|
709
|
+
vaultIoConcurrency,
|
|
710
|
+
resolveReadOnlyVaultRoot,
|
|
711
|
+
mapWithConcurrency,
|
|
712
|
+
scanVault,
|
|
713
|
+
readPage,
|
|
714
|
+
readPageCached
|
|
430
715
|
};
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
err,
|
|
3
4
|
extractFrontmatter,
|
|
5
|
+
ok,
|
|
4
6
|
readPage,
|
|
5
7
|
scanSensitiveContent,
|
|
6
8
|
splitFrontmatter
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import {
|
|
9
|
-
err,
|
|
10
|
-
ok
|
|
11
|
-
} from "./chunk-C2DKFJFA.js";
|
|
9
|
+
} from "./chunk-RQARJ6HB.js";
|
|
12
10
|
|
|
13
11
|
// src/utils/log-events.ts
|
|
14
12
|
import { mkdir, open, readFile, readdir } from "fs/promises";
|
|
@@ -459,14 +457,6 @@ async function rawSourceTargetExists(vault, target) {
|
|
|
459
457
|
return false;
|
|
460
458
|
}
|
|
461
459
|
|
|
462
|
-
// src/utils/operation-id.ts
|
|
463
|
-
import { createHash } from "crypto";
|
|
464
|
-
function operationId(namespace, parts) {
|
|
465
|
-
const hash = createHash("sha256").update(namespace).update("\0");
|
|
466
|
-
for (const part of parts) hash.update(part).update("\0");
|
|
467
|
-
return hash.digest("hex");
|
|
468
|
-
}
|
|
469
|
-
|
|
470
460
|
// src/utils/raw-operation-policy.ts
|
|
471
461
|
import { posix as posix2 } from "path";
|
|
472
462
|
var SOURCE_CATEGORIES = /* @__PURE__ */ new Set(["articles", "papers", "transcripts"]);
|
|
@@ -527,6 +517,14 @@ function authorizeRawOperation(input) {
|
|
|
527
517
|
return ok(true);
|
|
528
518
|
}
|
|
529
519
|
|
|
520
|
+
// src/utils/operation-id.ts
|
|
521
|
+
import { createHash } from "crypto";
|
|
522
|
+
function operationId(namespace, parts) {
|
|
523
|
+
const hash = createHash("sha256").update(namespace).update("\0");
|
|
524
|
+
for (const part of parts) hash.update(part).update("\0");
|
|
525
|
+
return hash.digest("hex");
|
|
526
|
+
}
|
|
527
|
+
|
|
530
528
|
// src/utils/source-reference-index.ts
|
|
531
529
|
function canonicalTarget(value) {
|
|
532
530
|
const normalized = normalizeRawSourceTarget(value);
|
|
@@ -608,9 +606,9 @@ export {
|
|
|
608
606
|
normalizeRawSourceTarget,
|
|
609
607
|
rawSourceTargetExistsSync,
|
|
610
608
|
rawSourceTargetExists,
|
|
611
|
-
operationId,
|
|
612
609
|
classifyRawPath,
|
|
613
610
|
lifecycleDestination,
|
|
614
611
|
authorizeRawOperation,
|
|
612
|
+
operationId,
|
|
615
613
|
buildSourceReferenceIndex
|
|
616
614
|
};
|
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
extractFrontmatter,
|
|
4
|
-
scanSensitiveContent,
|
|
5
|
-
scanVault
|
|
6
|
-
} from "./chunk-6ZDKTNLA.js";
|
|
7
2
|
import {
|
|
8
3
|
MetaSchema,
|
|
9
4
|
TypedKnowledgeSchema,
|
|
10
5
|
detectSchema,
|
|
11
6
|
err,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
import { join as join2 } from "path";
|
|
7
|
+
extractFrontmatter,
|
|
8
|
+
ok,
|
|
9
|
+
scanSensitiveContent,
|
|
10
|
+
scanVault
|
|
11
|
+
} from "./chunk-RQARJ6HB.js";
|
|
18
12
|
|
|
19
13
|
// src/utils/atomic-write.ts
|
|
20
14
|
import { randomBytes } from "crypto";
|
|
@@ -291,120 +285,10 @@ async function buildRootIndexUniverse(input) {
|
|
|
291
285
|
});
|
|
292
286
|
}
|
|
293
287
|
|
|
294
|
-
// src/utils/index-projection.ts
|
|
295
|
-
var UNMANAGED_START = "<!-- skillwiki:index-unmanaged:start -->";
|
|
296
|
-
var UNMANAGED_END = "<!-- skillwiki:index-unmanaged:end -->";
|
|
297
|
-
function extractUnmanaged(currentText) {
|
|
298
|
-
const startCount = currentText.split(UNMANAGED_START).length - 1;
|
|
299
|
-
const endCount = currentText.split(UNMANAGED_END).length - 1;
|
|
300
|
-
if (startCount === 0 && endCount === 0) return ok("");
|
|
301
|
-
if (startCount !== 1 || endCount !== 1) {
|
|
302
|
-
return err("SCHEME_REJECTED", {
|
|
303
|
-
message: "index.md must contain exactly one complete unmanaged marker pair"
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
const start = currentText.indexOf(UNMANAGED_START);
|
|
307
|
-
const end = currentText.indexOf(UNMANAGED_END);
|
|
308
|
-
if (start < 0 || end < 0 || end < start) {
|
|
309
|
-
return err("SCHEME_REJECTED", {
|
|
310
|
-
message: "unmanaged marker pair is reversed or incomplete"
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
const body = currentText.slice(start + UNMANAGED_START.length, end).replace(/^\n/, "").replace(/\n$/, "");
|
|
314
|
-
return ok(body);
|
|
315
|
-
}
|
|
316
|
-
function priorWikilinkTargets(text) {
|
|
317
|
-
const out = [];
|
|
318
|
-
const re = /\[\[([^\]|#]+)(?:[|#][^\]]*)?\]\]/g;
|
|
319
|
-
let m;
|
|
320
|
-
while ((m = re.exec(text)) !== null) {
|
|
321
|
-
out.push(m[1].trim().replace(/\.md$/, ""));
|
|
322
|
-
}
|
|
323
|
-
return out;
|
|
324
|
-
}
|
|
325
|
-
async function renderRootIndex(input) {
|
|
326
|
-
const vault = input.vault;
|
|
327
|
-
let currentText = input.currentText;
|
|
328
|
-
if (currentText === void 0) {
|
|
329
|
-
try {
|
|
330
|
-
currentText = await readFile3(join2(vault, "index.md"), "utf8");
|
|
331
|
-
} catch {
|
|
332
|
-
currentText = "";
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
const unmanaged = extractUnmanaged(currentText);
|
|
336
|
-
if (!unmanaged.ok) return unmanaged;
|
|
337
|
-
const universe = await buildRootIndexUniverse({ vault });
|
|
338
|
-
if (!universe.ok) return universe;
|
|
339
|
-
const entries = universe.data.required;
|
|
340
|
-
let duplicatesRemoved = universe.data.duplicatesRemoved;
|
|
341
|
-
const prior = priorWikilinkTargets(currentText);
|
|
342
|
-
const knownTargets = universe.data.knownTargets;
|
|
343
|
-
const knownBasenames = new Set(
|
|
344
|
-
[...knownTargets].map((target) => target.split("/").pop().toLowerCase())
|
|
345
|
-
);
|
|
346
|
-
const priorLower = /* @__PURE__ */ new Map();
|
|
347
|
-
for (const t of prior) {
|
|
348
|
-
const k = t.toLowerCase();
|
|
349
|
-
priorLower.set(k, (priorLower.get(k) ?? 0) + 1);
|
|
350
|
-
}
|
|
351
|
-
for (const count of priorLower.values()) {
|
|
352
|
-
if (count > 1) duplicatesRemoved += count - 1;
|
|
353
|
-
}
|
|
354
|
-
const headingDupes = (currentText.match(/^##\s+(entities|concepts|comparisons|queries|meta|projects)\s*$/gim) ?? []).map((h) => h.replace(/^##\s+/i, "").trim().toLowerCase());
|
|
355
|
-
const headingCounts = /* @__PURE__ */ new Map();
|
|
356
|
-
for (const h of headingDupes) headingCounts.set(h, (headingCounts.get(h) ?? 0) + 1);
|
|
357
|
-
for (const c of headingCounts.values()) {
|
|
358
|
-
if (c > 1) duplicatesRemoved += c - 1;
|
|
359
|
-
}
|
|
360
|
-
const ghostsRemoved = [
|
|
361
|
-
...new Set(
|
|
362
|
-
prior.filter((t) => {
|
|
363
|
-
if (!t.includes("/")) {
|
|
364
|
-
return !knownBasenames.has(t.toLowerCase());
|
|
365
|
-
}
|
|
366
|
-
return !knownTargets.has(t);
|
|
367
|
-
})
|
|
368
|
-
)
|
|
369
|
-
];
|
|
370
|
-
const lines = [
|
|
371
|
-
"# Vault Index",
|
|
372
|
-
"",
|
|
373
|
-
"Generated by `skillwiki index rebuild`. Generated sections are derived from typed-page frontmatter and project manifests.",
|
|
374
|
-
""
|
|
375
|
-
];
|
|
376
|
-
for (const section of ROOT_INDEX_SECTION_ORDER) {
|
|
377
|
-
lines.push(`## ${section}`, "");
|
|
378
|
-
const sectionEntries = entries.filter((e) => e.section === section);
|
|
379
|
-
for (const e of sectionEntries) {
|
|
380
|
-
lines.push(`- [[${e.target}]] \u2014 ${e.title}`);
|
|
381
|
-
}
|
|
382
|
-
if (sectionEntries.length > 0) lines.push("");
|
|
383
|
-
}
|
|
384
|
-
lines.push(UNMANAGED_START);
|
|
385
|
-
if (unmanaged.data) {
|
|
386
|
-
lines.push(unmanaged.data);
|
|
387
|
-
}
|
|
388
|
-
lines.push(UNMANAGED_END);
|
|
389
|
-
lines.push("");
|
|
390
|
-
return ok({
|
|
391
|
-
text: lines.join("\n"),
|
|
392
|
-
entries,
|
|
393
|
-
duplicates_removed: duplicatesRemoved,
|
|
394
|
-
ghosts_removed: ghostsRemoved
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
async function writeRootIndexProjection(vault, projection) {
|
|
398
|
-
return atomicWriteText(join2(vault, "index.md"), projection.text);
|
|
399
|
-
}
|
|
400
|
-
|
|
401
288
|
export {
|
|
402
289
|
atomicWriteText,
|
|
403
290
|
assertTargetInsideVault,
|
|
404
291
|
prepareTypedPage,
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
UNMANAGED_END,
|
|
408
|
-
renderRootIndex,
|
|
409
|
-
writeRootIndexProjection
|
|
292
|
+
ROOT_INDEX_SECTION_ORDER,
|
|
293
|
+
buildRootIndexUniverse
|
|
410
294
|
};
|