diffwiki-core 0.12.0 → 0.14.0-rc.202608012047.5d78e11
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-MLUJY3HI.js → chunk-B67AZFRA.js} +9 -4
- package/dist/{events-UCJWB4WP.js → events-QZYAWK7I.js} +1 -1
- package/dist/index.cjs +257 -37
- package/dist/index.d.cts +66 -32
- package/dist/index.d.ts +66 -32
- package/dist/index.js +244 -36
- package/dist/plugin.d.cts +8 -0
- package/dist/plugin.d.ts +8 -0
- package/package.json +1 -1
|
@@ -116,7 +116,7 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
116
116
|
// src/collections.ts
|
|
117
117
|
async function fireHook(event, collection) {
|
|
118
118
|
try {
|
|
119
|
-
const { emitPluginEvent: emitPluginEvent2 } = await import("./events-
|
|
119
|
+
const { emitPluginEvent: emitPluginEvent2 } = await import("./events-QZYAWK7I.js");
|
|
120
120
|
await emitPluginEvent2(event, collection);
|
|
121
121
|
} catch {
|
|
122
122
|
}
|
|
@@ -130,7 +130,7 @@ async function findCollection(name) {
|
|
|
130
130
|
async function findCollectionById(id) {
|
|
131
131
|
return (await readRegistry()).collections.find((c) => c.id === id);
|
|
132
132
|
}
|
|
133
|
-
async function createCollection(name) {
|
|
133
|
+
async function createCollection(name, opts) {
|
|
134
134
|
if (await findCollection(name)) throw new CollectionExistsError(name);
|
|
135
135
|
const dir = collectionDir(name);
|
|
136
136
|
await fs2.mkdir(dir, { recursive: true });
|
|
@@ -138,7 +138,8 @@ async function createCollection(name) {
|
|
|
138
138
|
name,
|
|
139
139
|
type: "global",
|
|
140
140
|
path: dir,
|
|
141
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
141
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
142
|
+
...opts?.audience ? { audience: opts.audience } : {}
|
|
142
143
|
};
|
|
143
144
|
await registerCollection(entry);
|
|
144
145
|
void fireHook("collection-added", name);
|
|
@@ -350,7 +351,11 @@ function mapHits(result, collections) {
|
|
|
350
351
|
tags: Array.isArray(h.tags) ? h.tags.filter((t) => typeof t === "string") : [],
|
|
351
352
|
snippet: typeof h.snippet === "string" ? h.snippet : void 0,
|
|
352
353
|
score: typeof h.score === "number" ? h.score : void 0,
|
|
353
|
-
docid: typeof h.docid === "string" ? h.docid : void 0
|
|
354
|
+
docid: typeof h.docid === "string" ? h.docid : void 0,
|
|
355
|
+
rawChunk: typeof h.rawChunk === "string" ? h.rawChunk : void 0,
|
|
356
|
+
cleanedChunk: typeof h.cleanedChunk === "string" ? h.cleanedChunk : void 0,
|
|
357
|
+
line: typeof h.line === "number" ? h.line : void 0,
|
|
358
|
+
snippetFromBody: h.snippetFromBody === true ? true : void 0
|
|
354
359
|
});
|
|
355
360
|
}
|
|
356
361
|
return out;
|
package/dist/index.cjs
CHANGED
|
@@ -359,7 +359,11 @@ function mapHits(result, collections) {
|
|
|
359
359
|
tags: Array.isArray(h.tags) ? h.tags.filter((t) => typeof t === "string") : [],
|
|
360
360
|
snippet: typeof h.snippet === "string" ? h.snippet : void 0,
|
|
361
361
|
score: typeof h.score === "number" ? h.score : void 0,
|
|
362
|
-
docid: typeof h.docid === "string" ? h.docid : void 0
|
|
362
|
+
docid: typeof h.docid === "string" ? h.docid : void 0,
|
|
363
|
+
rawChunk: typeof h.rawChunk === "string" ? h.rawChunk : void 0,
|
|
364
|
+
cleanedChunk: typeof h.cleanedChunk === "string" ? h.cleanedChunk : void 0,
|
|
365
|
+
line: typeof h.line === "number" ? h.line : void 0,
|
|
366
|
+
snippetFromBody: h.snippetFromBody === true ? true : void 0
|
|
363
367
|
});
|
|
364
368
|
}
|
|
365
369
|
return out;
|
|
@@ -523,7 +527,7 @@ async function findCollection(name) {
|
|
|
523
527
|
async function findCollectionById(id) {
|
|
524
528
|
return (await readRegistry()).collections.find((c) => c.id === id);
|
|
525
529
|
}
|
|
526
|
-
async function createCollection(name) {
|
|
530
|
+
async function createCollection(name, opts) {
|
|
527
531
|
if (await findCollection(name)) throw new CollectionExistsError(name);
|
|
528
532
|
const dir = collectionDir(name);
|
|
529
533
|
await import_promises3.default.mkdir(dir, { recursive: true });
|
|
@@ -531,7 +535,8 @@ async function createCollection(name) {
|
|
|
531
535
|
name,
|
|
532
536
|
type: "global",
|
|
533
537
|
path: dir,
|
|
534
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
538
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
539
|
+
...opts?.audience ? { audience: opts.audience } : {}
|
|
535
540
|
};
|
|
536
541
|
await registerCollection(entry);
|
|
537
542
|
void fireHook("collection-added", name);
|
|
@@ -607,6 +612,8 @@ __export(index_exports, {
|
|
|
607
612
|
installPlugin: () => installPlugin,
|
|
608
613
|
invalidateLinkGraph: () => invalidateLinkGraph,
|
|
609
614
|
isLinkedWorktree: () => isLinkedWorktree,
|
|
615
|
+
lintArticle: () => lintArticle,
|
|
616
|
+
lintCollections: () => lintCollections,
|
|
610
617
|
listArticleTree: () => listArticleTree,
|
|
611
618
|
listAvailablePlugins: () => listAvailablePlugins,
|
|
612
619
|
listCollections: () => listCollections,
|
|
@@ -625,6 +632,7 @@ __export(index_exports, {
|
|
|
625
632
|
rankDocs: () => rankDocs,
|
|
626
633
|
readArticle: () => readArticle,
|
|
627
634
|
readConfig: () => readConfig,
|
|
635
|
+
readContextCollections: () => readContextCollections,
|
|
628
636
|
readPluginRegistry: () => readPluginRegistry,
|
|
629
637
|
readProjectConfig: () => readProjectConfig,
|
|
630
638
|
readRegistry: () => readRegistry,
|
|
@@ -655,7 +663,9 @@ __export(index_exports, {
|
|
|
655
663
|
setArticleMeta: () => setArticleMeta,
|
|
656
664
|
setConfigValue: () => setConfigValue,
|
|
657
665
|
setTags: () => setTags,
|
|
666
|
+
showSystemEnabled: () => showSystemEnabled,
|
|
658
667
|
slugify: () => slugify,
|
|
668
|
+
slugifyPath: () => slugifyPath,
|
|
659
669
|
toTreeNodes: () => toTreeNodes,
|
|
660
670
|
tokenize: () => tokenize,
|
|
661
671
|
unregisterCollection: () => unregisterCollection,
|
|
@@ -718,6 +728,10 @@ async function getConfigValue(key) {
|
|
|
718
728
|
const value = config[key];
|
|
719
729
|
return typeof value === "string" ? value : void 0;
|
|
720
730
|
}
|
|
731
|
+
async function showSystemEnabled() {
|
|
732
|
+
const config = await readConfig();
|
|
733
|
+
return config.showSystem === true || config.showSystem === "true";
|
|
734
|
+
}
|
|
721
735
|
|
|
722
736
|
// src/index.ts
|
|
723
737
|
init_registry();
|
|
@@ -727,6 +741,10 @@ function slugify(input) {
|
|
|
727
741
|
const slug = input.toLowerCase().replace(/^#+\s*/, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80).replace(/-+$/g, "");
|
|
728
742
|
return slug.length > 0 ? slug : "untitled";
|
|
729
743
|
}
|
|
744
|
+
function slugifyPath(input) {
|
|
745
|
+
const segs = input.split("/").map((s) => s.trim()).filter((s) => s.length > 0).map((s) => slugify(s));
|
|
746
|
+
return segs.length > 0 ? segs.join("/") : "untitled";
|
|
747
|
+
}
|
|
730
748
|
|
|
731
749
|
// src/frontmatter.ts
|
|
732
750
|
var import_gray_matter = __toESM(require("gray-matter"), 1);
|
|
@@ -740,10 +758,10 @@ var YAML_ENGINE = {
|
|
|
740
758
|
};
|
|
741
759
|
var MATTER_OPTS = { engines: { yaml: YAML_ENGINE } };
|
|
742
760
|
function coerceAudience(value) {
|
|
743
|
-
return value === "private"
|
|
761
|
+
return value === "private" || value === "system" ? value : "public";
|
|
744
762
|
}
|
|
745
763
|
function coerceStatus(value) {
|
|
746
|
-
return value === "draft"
|
|
764
|
+
return value === "draft" || value === "deprecated" ? value : "published";
|
|
747
765
|
}
|
|
748
766
|
function parseFallback(raw) {
|
|
749
767
|
const fenceRe = /^---[ \t]*\n([\s\S]*?)\n---[ \t]*\n/;
|
|
@@ -788,8 +806,8 @@ function parseArticle(raw) {
|
|
|
788
806
|
}
|
|
789
807
|
function serializeArticle(article) {
|
|
790
808
|
const front = { title: article.title, tags: article.tags };
|
|
791
|
-
if (article.audience === "private") front.audience =
|
|
792
|
-
if (article.status === "draft") front.status =
|
|
809
|
+
if (article.audience === "private" || article.audience === "system") front.audience = article.audience;
|
|
810
|
+
if (article.status === "draft" || article.status === "deprecated") front.status = article.status;
|
|
793
811
|
return import_gray_matter.default.stringify(article.body, front, MATTER_OPTS);
|
|
794
812
|
}
|
|
795
813
|
|
|
@@ -840,18 +858,15 @@ async function resolveCollectionDir(name) {
|
|
|
840
858
|
}
|
|
841
859
|
function ensureMdPath(file) {
|
|
842
860
|
if (/\.mdx?$/.test(file)) return file;
|
|
843
|
-
|
|
844
|
-
const base = slugify(import_node_path3.default.basename(file));
|
|
845
|
-
const rel = dir === "." ? `${base}.md` : import_node_path3.default.join(dir, `${base}.md`);
|
|
846
|
-
return rel;
|
|
861
|
+
return `${slugifyPath(file)}.md`;
|
|
847
862
|
}
|
|
848
863
|
async function createArticle(opts) {
|
|
849
864
|
const { collection, title } = parseAddTarget(opts.target);
|
|
850
865
|
const { name, dir } = await resolveCollectionDir(collection);
|
|
851
|
-
const filePath = import_node_path3.default.join(dir, `${
|
|
866
|
+
const filePath = import_node_path3.default.join(dir, `${slugifyPath(title)}.md`);
|
|
852
867
|
const body = opts.body ?? `# ${title}
|
|
853
868
|
`;
|
|
854
|
-
await import_promises5.default.mkdir(
|
|
869
|
+
await import_promises5.default.mkdir(import_node_path3.default.dirname(filePath), { recursive: true });
|
|
855
870
|
await import_promises5.default.writeFile(
|
|
856
871
|
filePath,
|
|
857
872
|
serializeArticle({ title, tags: opts.tags, body, audience: opts.audience, status: opts.status }),
|
|
@@ -1201,6 +1216,13 @@ function normaliseSite(raw) {
|
|
|
1201
1216
|
...home !== void 0 ? { home } : {}
|
|
1202
1217
|
};
|
|
1203
1218
|
}
|
|
1219
|
+
function normaliseContext(raw) {
|
|
1220
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
1221
|
+
const r = raw;
|
|
1222
|
+
if (!Array.isArray(r.collections)) return void 0;
|
|
1223
|
+
const collections = r.collections.filter((c) => typeof c === "string");
|
|
1224
|
+
return collections.length > 0 ? { collections } : void 0;
|
|
1225
|
+
}
|
|
1204
1226
|
async function readProjectConfig(dir) {
|
|
1205
1227
|
let raw;
|
|
1206
1228
|
try {
|
|
@@ -1229,7 +1251,11 @@ async function readProjectConfig(dir) {
|
|
|
1229
1251
|
collections.push({ name, path: parsed.path });
|
|
1230
1252
|
}
|
|
1231
1253
|
const site = normaliseSite(parsed.site);
|
|
1232
|
-
|
|
1254
|
+
const context = normaliseContext(parsed.context);
|
|
1255
|
+
return { ...site ? { site } : {}, ...context ? { context } : {}, collections };
|
|
1256
|
+
}
|
|
1257
|
+
async function readContextCollections(dir) {
|
|
1258
|
+
return (await readProjectConfig(dir))?.context?.collections ?? [];
|
|
1233
1259
|
}
|
|
1234
1260
|
async function resolveProjectCollections(dir) {
|
|
1235
1261
|
const config = await readProjectConfig(dir);
|
|
@@ -1727,8 +1753,12 @@ function localGraph(graph, focusId, include, siblingCap = 12) {
|
|
|
1727
1753
|
// src/site.ts
|
|
1728
1754
|
init_errors();
|
|
1729
1755
|
var isStatic = () => process.env.DIFFWIKI_STATIC === "1";
|
|
1730
|
-
function toTreeNodes(coll, nodes) {
|
|
1731
|
-
const visible =
|
|
1756
|
+
function toTreeNodes(coll, nodes, showSystem = false) {
|
|
1757
|
+
const visible = nodes.filter((n) => {
|
|
1758
|
+
if (!showSystem && n.audience === "system") return false;
|
|
1759
|
+
if (isStatic() && (n.audience === "private" || n.status === "draft")) return false;
|
|
1760
|
+
return true;
|
|
1761
|
+
});
|
|
1732
1762
|
return visible.map(
|
|
1733
1763
|
(n) => n.children ? {
|
|
1734
1764
|
name: n.name,
|
|
@@ -1736,7 +1766,7 @@ function toTreeNodes(coll, nodes) {
|
|
|
1736
1766
|
path: n.slug ? `/${coll}/${n.slug}` : void 0,
|
|
1737
1767
|
...n.audience ? { audience: n.audience } : {},
|
|
1738
1768
|
...n.status ? { status: n.status } : {},
|
|
1739
|
-
children: toTreeNodes(coll, n.children)
|
|
1769
|
+
children: toTreeNodes(coll, n.children, showSystem)
|
|
1740
1770
|
} : {
|
|
1741
1771
|
name: n.name,
|
|
1742
1772
|
title: n.title ?? n.name,
|
|
@@ -1754,44 +1784,50 @@ async function gitInfo(e) {
|
|
|
1754
1784
|
const s = await collectionGitStatus(e);
|
|
1755
1785
|
return s ? { branch: s.branch, behind: s.hasUpstream ? s.behind : void 0 } : {};
|
|
1756
1786
|
}
|
|
1757
|
-
async function buildCollections() {
|
|
1787
|
+
async function buildCollections(opts) {
|
|
1788
|
+
const showSystem = opts?.showSystem ?? false;
|
|
1758
1789
|
const entries = await listCollections();
|
|
1759
1790
|
const infos = await Promise.all(
|
|
1760
1791
|
entries.map(async (e) => {
|
|
1761
|
-
const
|
|
1792
|
+
const meta = await collectionMeta(e.path);
|
|
1793
|
+
const audience = meta.audience ?? e.audience;
|
|
1762
1794
|
return {
|
|
1763
1795
|
name: e.name,
|
|
1764
1796
|
type: e.type,
|
|
1765
1797
|
path: e.path,
|
|
1766
|
-
...title ? { title } : {},
|
|
1798
|
+
...meta.title ? { title: meta.title } : {},
|
|
1767
1799
|
...await gitInfo(e),
|
|
1768
1800
|
...audience ? { audience } : {},
|
|
1769
|
-
...status ? { status } : {}
|
|
1801
|
+
...meta.status ? { status: meta.status } : {}
|
|
1770
1802
|
};
|
|
1771
1803
|
})
|
|
1772
1804
|
);
|
|
1773
|
-
|
|
1805
|
+
const visible = showSystem ? infos : infos.filter((c) => c.audience !== "system");
|
|
1806
|
+
return isStatic() ? visible.filter((c) => c.audience !== "private" && c.status !== "draft") : visible;
|
|
1774
1807
|
}
|
|
1775
|
-
async function buildNavTree() {
|
|
1808
|
+
async function buildNavTree(opts) {
|
|
1809
|
+
const showSystem = opts?.showSystem ?? false;
|
|
1776
1810
|
const entries = await listCollections();
|
|
1777
1811
|
const trees = await Promise.all(
|
|
1778
1812
|
entries.map(async (e) => {
|
|
1779
|
-
const
|
|
1813
|
+
const meta = await collectionMeta(e.path);
|
|
1814
|
+
const audience = meta.audience ?? e.audience;
|
|
1780
1815
|
return {
|
|
1781
1816
|
collection: {
|
|
1782
1817
|
name: e.name,
|
|
1783
1818
|
type: e.type,
|
|
1784
1819
|
path: e.path,
|
|
1785
|
-
...title ? { title } : {},
|
|
1820
|
+
...meta.title ? { title: meta.title } : {},
|
|
1786
1821
|
...await gitInfo(e),
|
|
1787
1822
|
...audience ? { audience } : {},
|
|
1788
|
-
...status ? { status } : {}
|
|
1823
|
+
...meta.status ? { status: meta.status } : {}
|
|
1789
1824
|
},
|
|
1790
|
-
nodes: toTreeNodes(e.name, await listArticleTree(e.name))
|
|
1825
|
+
nodes: toTreeNodes(e.name, await listArticleTree(e.name), showSystem)
|
|
1791
1826
|
};
|
|
1792
1827
|
})
|
|
1793
1828
|
);
|
|
1794
|
-
|
|
1829
|
+
const visible = showSystem ? trees : trees.filter((t) => t.collection.audience !== "system");
|
|
1830
|
+
return isStatic() ? visible.filter((t) => t.collection.audience !== "private" && t.collection.status !== "draft") : visible;
|
|
1795
1831
|
}
|
|
1796
1832
|
function findNode(nodes, slug) {
|
|
1797
1833
|
for (const n of nodes) {
|
|
@@ -1978,12 +2014,20 @@ async function collectMarkdownFiles(dir) {
|
|
|
1978
2014
|
}
|
|
1979
2015
|
return files;
|
|
1980
2016
|
}
|
|
1981
|
-
|
|
2017
|
+
function normalizeCollections(collection) {
|
|
2018
|
+
if (collection === void 0) return [];
|
|
2019
|
+
const names = Array.isArray(collection) ? collection : [collection];
|
|
2020
|
+
return names.map((n) => n.trim()).filter(Boolean);
|
|
2021
|
+
}
|
|
2022
|
+
async function nativeSearch(term, collections, collection, hideSystem = false) {
|
|
1982
2023
|
let targets = collections;
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
2024
|
+
const names = normalizeCollections(collection);
|
|
2025
|
+
if (names.length > 0) {
|
|
2026
|
+
for (const n of names) {
|
|
2027
|
+
if (!collections.some((c) => c.name === n)) throw new CollectionNotFoundError(n);
|
|
2028
|
+
}
|
|
2029
|
+
const wanted = new Set(names);
|
|
2030
|
+
targets = collections.filter((c) => wanted.has(c.name));
|
|
1987
2031
|
}
|
|
1988
2032
|
const docs = [];
|
|
1989
2033
|
for (const entry of targets) {
|
|
@@ -1994,6 +2038,7 @@ async function nativeSearch(term, collections, collection) {
|
|
|
1994
2038
|
let body = "";
|
|
1995
2039
|
try {
|
|
1996
2040
|
const parsed = parseArticle(await import_promises10.default.readFile(full, "utf8"));
|
|
2041
|
+
if (hideSystem && parsed.audience === "system") continue;
|
|
1997
2042
|
if (parsed.title) title = parsed.title;
|
|
1998
2043
|
tags = parsed.tags;
|
|
1999
2044
|
body = parsed.body;
|
|
@@ -2046,28 +2091,49 @@ async function resolveDefaultSearchMode() {
|
|
|
2046
2091
|
}
|
|
2047
2092
|
return { engine, type: searchTypes[0] };
|
|
2048
2093
|
}
|
|
2094
|
+
async function dropSystemHits(hits) {
|
|
2095
|
+
const kept = [];
|
|
2096
|
+
for (const hit of hits) {
|
|
2097
|
+
try {
|
|
2098
|
+
if (parseArticle(await import_promises10.default.readFile(hit.path, "utf8")).audience === "system") continue;
|
|
2099
|
+
} catch {
|
|
2100
|
+
}
|
|
2101
|
+
kept.push(hit);
|
|
2102
|
+
}
|
|
2103
|
+
return kept;
|
|
2104
|
+
}
|
|
2049
2105
|
async function search(term, opts) {
|
|
2050
2106
|
const collections = await listCollections();
|
|
2107
|
+
const wanted = normalizeCollections(opts?.collection);
|
|
2108
|
+
for (const n of wanted) {
|
|
2109
|
+
if (!collections.some((c) => c.name === n)) throw new CollectionNotFoundError(n);
|
|
2110
|
+
}
|
|
2111
|
+
const showSystem = opts?.showSystem ?? await showSystemEnabled();
|
|
2112
|
+
const hideSystem = !showSystem && wanted.length === 0;
|
|
2113
|
+
const searchable = hideSystem ? collections.filter((c) => c.audience !== "system") : collections;
|
|
2114
|
+
const filterHits = (hits) => wanted.length > 0 ? hits.filter((h) => wanted.includes(h.collection)) : hits;
|
|
2051
2115
|
const engine = opts?.engine ?? await resolveDefaultEngine();
|
|
2052
2116
|
log2.debug(`resolved search engine "${engine}"`);
|
|
2053
2117
|
if (engine === NATIVE_ENGINE) {
|
|
2054
|
-
const hits = await nativeSearch(term,
|
|
2118
|
+
const hits = await nativeSearch(term, searchable, opts?.collection, hideSystem);
|
|
2055
2119
|
return { hits, engine, fellBackToNative: false };
|
|
2056
2120
|
}
|
|
2057
2121
|
const provider = await loadSearchProvider(engine);
|
|
2058
2122
|
if (!provider) {
|
|
2059
2123
|
const error = `no provider found for engine "${engine}"`;
|
|
2060
2124
|
log2.warn(`engine "${engine}" unavailable, falling back to native: ${error}`);
|
|
2061
|
-
const hits = await nativeSearch(term,
|
|
2125
|
+
const hits = await nativeSearch(term, searchable, opts?.collection, hideSystem);
|
|
2062
2126
|
return { hits, engine, fellBackToNative: true, error };
|
|
2063
2127
|
}
|
|
2064
2128
|
try {
|
|
2065
|
-
const
|
|
2129
|
+
const providerOpts = { ...opts, collection: wanted.length === 1 ? wanted[0] : void 0 };
|
|
2130
|
+
const raw = filterHits(await provider.handleSearch(term, searchable, providerOpts));
|
|
2131
|
+
const hits = hideSystem ? await dropSystemHits(raw) : raw;
|
|
2066
2132
|
return { hits, engine, fellBackToNative: false };
|
|
2067
2133
|
} catch (err) {
|
|
2068
2134
|
const message = err instanceof Error ? err.message : String(err);
|
|
2069
2135
|
log2.warn(`engine "${engine}" failed, falling back to native: ${message}`);
|
|
2070
|
-
const hits = await nativeSearch(term,
|
|
2136
|
+
const hits = await nativeSearch(term, searchable, opts?.collection, hideSystem);
|
|
2071
2137
|
return { hits, engine, fellBackToNative: true, error: message };
|
|
2072
2138
|
}
|
|
2073
2139
|
}
|
|
@@ -2092,6 +2158,7 @@ async function readArticleParts(collDir, slug) {
|
|
|
2092
2158
|
}
|
|
2093
2159
|
async function flatten(coll, collDir, nodes, out) {
|
|
2094
2160
|
for (const n of nodes) {
|
|
2161
|
+
if (n.audience === "system") continue;
|
|
2095
2162
|
if (isStatic2() && (n.audience === "private" || n.status === "draft")) continue;
|
|
2096
2163
|
if (n.slug && !n.children) {
|
|
2097
2164
|
const title = n.title ?? n.name;
|
|
@@ -2119,6 +2186,7 @@ async function buildSearchIndex(collection) {
|
|
|
2119
2186
|
try {
|
|
2120
2187
|
const collEntry = await findCollection(e.name);
|
|
2121
2188
|
const collDir = import_node_path10.default.resolve(collEntry?.path ?? e.path);
|
|
2189
|
+
if (collEntry?.audience === "system") continue;
|
|
2122
2190
|
if (isStatic2()) {
|
|
2123
2191
|
const cm = await collectionMeta(collDir);
|
|
2124
2192
|
if (cm.audience === "private" || cm.status === "draft") continue;
|
|
@@ -2480,6 +2548,153 @@ async function appendPluginDiagnostics(out, config) {
|
|
|
2480
2548
|
}
|
|
2481
2549
|
}
|
|
2482
2550
|
|
|
2551
|
+
// src/lint.ts
|
|
2552
|
+
var import_promises14 = __toESM(require("fs/promises"), 1);
|
|
2553
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
2554
|
+
init_collections();
|
|
2555
|
+
init_errors();
|
|
2556
|
+
var BUDGETS = {
|
|
2557
|
+
recipe: { min: 300, max: 1e3 },
|
|
2558
|
+
"recipe-steps": { min: 500, max: 1500 },
|
|
2559
|
+
guide: { min: 1500, max: 5e3 },
|
|
2560
|
+
"system-design": { min: 2e3, max: 6e3 },
|
|
2561
|
+
"component-design": { min: 1e3, max: 3e3 },
|
|
2562
|
+
// Fallback for a bare `architecture` tag when system vs component isn't declared.
|
|
2563
|
+
architecture: { min: 1e3, max: 6e3 }
|
|
2564
|
+
};
|
|
2565
|
+
function estimateTokens(body) {
|
|
2566
|
+
return Math.ceil(body.trim().length / 4);
|
|
2567
|
+
}
|
|
2568
|
+
function inferShape(tags, body) {
|
|
2569
|
+
const t = new Set(tags.map((x) => x.toLowerCase()));
|
|
2570
|
+
if (t.has("guide")) return "guide";
|
|
2571
|
+
if (t.has("component-design") || t.has("component")) return "component-design";
|
|
2572
|
+
if (t.has("system-design") || t.has("system")) return "system-design";
|
|
2573
|
+
if (t.has("architecture")) return "architecture";
|
|
2574
|
+
if (/^###\s+Step\b/im.test(body)) return "recipe-steps";
|
|
2575
|
+
return "recipe";
|
|
2576
|
+
}
|
|
2577
|
+
function lintArticle(article) {
|
|
2578
|
+
const findings = [];
|
|
2579
|
+
const { title, tags, status, body } = article;
|
|
2580
|
+
const shape = inferShape(tags, body);
|
|
2581
|
+
const tokens = estimateTokens(body);
|
|
2582
|
+
const budget = BUDGETS[shape];
|
|
2583
|
+
if (!title || !title.trim()) {
|
|
2584
|
+
findings.push({ level: "error", rule: "frontmatter-title", message: "missing frontmatter title" });
|
|
2585
|
+
}
|
|
2586
|
+
if (tags.map((x) => x.toLowerCase()).includes("diffwiki-template")) {
|
|
2587
|
+
findings.push({
|
|
2588
|
+
level: "error",
|
|
2589
|
+
rule: "template-tag-leak",
|
|
2590
|
+
message: "instance still tagged diffwiki-template"
|
|
2591
|
+
});
|
|
2592
|
+
}
|
|
2593
|
+
if (tags.length === 0) {
|
|
2594
|
+
findings.push({ level: "warn", rule: "no-tags", message: "no tags -- add topic tags for recall" });
|
|
2595
|
+
}
|
|
2596
|
+
const hasRefsSection = /^##\s+References\b/im.test(body);
|
|
2597
|
+
const hasInlineLink = /\]\([^)]+\)/.test(body);
|
|
2598
|
+
if (!hasRefsSection && !hasInlineLink) {
|
|
2599
|
+
findings.push({
|
|
2600
|
+
level: "error",
|
|
2601
|
+
rule: "no-references",
|
|
2602
|
+
message: "has no ## References section and no inline links"
|
|
2603
|
+
});
|
|
2604
|
+
}
|
|
2605
|
+
if ((shape === "guide" || shape === "architecture") && !hasRefsSection) {
|
|
2606
|
+
findings.push({
|
|
2607
|
+
level: "warn",
|
|
2608
|
+
rule: "missing-references-section",
|
|
2609
|
+
message: `${shape} should end with a ## References section`
|
|
2610
|
+
});
|
|
2611
|
+
}
|
|
2612
|
+
if (tokens > budget.max) {
|
|
2613
|
+
findings.push({
|
|
2614
|
+
level: "error",
|
|
2615
|
+
rule: "token-budget",
|
|
2616
|
+
message: `body ~${tokens} tokens exceeds ${shape} budget (${budget.max})`
|
|
2617
|
+
});
|
|
2618
|
+
} else if (tokens < budget.min) {
|
|
2619
|
+
findings.push({
|
|
2620
|
+
level: "warn",
|
|
2621
|
+
rule: "token-thin",
|
|
2622
|
+
message: `body ~${tokens} tokens is thin for ${shape} (min ${budget.min})`
|
|
2623
|
+
});
|
|
2624
|
+
}
|
|
2625
|
+
if (status === "draft") {
|
|
2626
|
+
findings.push({ level: "warn", rule: "status-draft", message: "status is still draft" });
|
|
2627
|
+
}
|
|
2628
|
+
if (/\*\*Load when:\*\*/i.test(body)) {
|
|
2629
|
+
findings.push({
|
|
2630
|
+
level: "warn",
|
|
2631
|
+
rule: "load-when-prefix",
|
|
2632
|
+
message: "remove the **Load when:** prefix (implicit trigger)"
|
|
2633
|
+
});
|
|
2634
|
+
}
|
|
2635
|
+
if (/^[A-Za-z0-9][\w .+-]*:\s/.test(title)) {
|
|
2636
|
+
findings.push({ level: "warn", rule: "title-prefix", message: `title "${title}" repeats a path-like prefix` });
|
|
2637
|
+
}
|
|
2638
|
+
return findings;
|
|
2639
|
+
}
|
|
2640
|
+
async function walkMarkdown(dir) {
|
|
2641
|
+
let entries;
|
|
2642
|
+
try {
|
|
2643
|
+
entries = await import_promises14.default.readdir(dir, { withFileTypes: true });
|
|
2644
|
+
} catch {
|
|
2645
|
+
return [];
|
|
2646
|
+
}
|
|
2647
|
+
const out = [];
|
|
2648
|
+
for (const e of entries) {
|
|
2649
|
+
const full = import_node_path13.default.join(dir, e.name);
|
|
2650
|
+
if (e.isDirectory()) out.push(...await walkMarkdown(full));
|
|
2651
|
+
else if (e.isFile() && /\.mdx?$/.test(e.name) && !/-template\.mdx?$/.test(e.name)) out.push(full);
|
|
2652
|
+
}
|
|
2653
|
+
return out;
|
|
2654
|
+
}
|
|
2655
|
+
async function resolveScope(scope) {
|
|
2656
|
+
const all = await listCollections();
|
|
2657
|
+
if (scope === "all") return all.filter((c) => c.audience !== "system");
|
|
2658
|
+
if (scope === "current") {
|
|
2659
|
+
const cur = await resolveCwdCollection(process.cwd());
|
|
2660
|
+
if (!cur) throw new Error("No collection for the current directory -- pass -c <name> or -c all.");
|
|
2661
|
+
return [cur];
|
|
2662
|
+
}
|
|
2663
|
+
const names = scope.split(",").map((s) => s.trim()).filter(Boolean);
|
|
2664
|
+
const chosen = [];
|
|
2665
|
+
for (const n of names) {
|
|
2666
|
+
const hit = all.find((c) => c.name === n);
|
|
2667
|
+
if (!hit) throw new CollectionNotFoundError(n);
|
|
2668
|
+
chosen.push(hit);
|
|
2669
|
+
}
|
|
2670
|
+
return chosen;
|
|
2671
|
+
}
|
|
2672
|
+
async function lintCollections(scope) {
|
|
2673
|
+
const collections = await resolveScope(scope);
|
|
2674
|
+
const files = [];
|
|
2675
|
+
let errorCount = 0;
|
|
2676
|
+
let warnCount = 0;
|
|
2677
|
+
for (const coll of collections) {
|
|
2678
|
+
for (const full of await walkMarkdown(coll.path)) {
|
|
2679
|
+
const rel = import_node_path13.default.relative(coll.path, full);
|
|
2680
|
+
const label = `${coll.name}/${rel}`;
|
|
2681
|
+
let findings = [];
|
|
2682
|
+
try {
|
|
2683
|
+
const parsed = parseArticle(await import_promises14.default.readFile(full, "utf8"));
|
|
2684
|
+
findings = lintArticle(parsed);
|
|
2685
|
+
} catch (err) {
|
|
2686
|
+
findings = [{ level: "error", rule: "parse", message: `could not parse: ${err.message}` }];
|
|
2687
|
+
}
|
|
2688
|
+
for (const f of findings) {
|
|
2689
|
+
if (f.level === "error") errorCount++;
|
|
2690
|
+
else warnCount++;
|
|
2691
|
+
}
|
|
2692
|
+
files.push({ path: label, findings });
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
return { files, fileCount: files.length, errorCount, warnCount };
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2483
2698
|
// src/index.ts
|
|
2484
2699
|
init_logger();
|
|
2485
2700
|
|
|
@@ -2584,6 +2799,8 @@ init_registry2();
|
|
|
2584
2799
|
installPlugin,
|
|
2585
2800
|
invalidateLinkGraph,
|
|
2586
2801
|
isLinkedWorktree,
|
|
2802
|
+
lintArticle,
|
|
2803
|
+
lintCollections,
|
|
2587
2804
|
listArticleTree,
|
|
2588
2805
|
listAvailablePlugins,
|
|
2589
2806
|
listCollections,
|
|
@@ -2602,6 +2819,7 @@ init_registry2();
|
|
|
2602
2819
|
rankDocs,
|
|
2603
2820
|
readArticle,
|
|
2604
2821
|
readConfig,
|
|
2822
|
+
readContextCollections,
|
|
2605
2823
|
readPluginRegistry,
|
|
2606
2824
|
readProjectConfig,
|
|
2607
2825
|
readRegistry,
|
|
@@ -2632,7 +2850,9 @@ init_registry2();
|
|
|
2632
2850
|
setArticleMeta,
|
|
2633
2851
|
setConfigValue,
|
|
2634
2852
|
setTags,
|
|
2853
|
+
showSystemEnabled,
|
|
2635
2854
|
slugify,
|
|
2855
|
+
slugifyPath,
|
|
2636
2856
|
toTreeNodes,
|
|
2637
2857
|
tokenize,
|
|
2638
2858
|
unregisterCollection,
|