docspack 0.2.0 → 0.3.0
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/README.md +9 -3
- package/dist/build.d.ts +21 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +138 -24
- package/dist/build.js.map +1 -1
- package/dist/cli.js +74 -131
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +7 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +20 -1
- package/dist/config.js.map +1 -1
- package/dist/db.d.ts +8 -4
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +13 -7
- package/dist/db.js.map +1 -1
- package/dist/doctor.js +1 -1
- package/dist/doctor.js.map +1 -1
- package/dist/eval.d.ts +52 -0
- package/dist/eval.d.ts.map +1 -0
- package/dist/eval.js +101 -0
- package/dist/eval.js.map +1 -0
- package/dist/help.d.ts +31 -0
- package/dist/help.d.ts.map +1 -0
- package/dist/help.js +342 -0
- package/dist/help.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/preview.d.ts.map +1 -1
- package/dist/preview.js +4 -2
- package/dist/preview.js.map +1 -1
- package/dist/search.d.ts +6 -0
- package/dist/search.d.ts.map +1 -1
- package/dist/search.js +25 -3
- package/dist/search.js.map +1 -1
- package/dist/spec.d.ts +21 -1
- package/dist/spec.d.ts.map +1 -1
- package/dist/spec.js +44 -5
- package/dist/spec.js.map +1 -1
- package/dist/stopwords.d.ts +14 -0
- package/dist/stopwords.d.ts.map +1 -0
- package/dist/stopwords.js +121 -0
- package/dist/stopwords.js.map +1 -0
- package/dist/sync.js +1 -1
- package/dist/sync.js.map +1 -1
- package/dist/verify.d.ts +8 -2
- package/dist/verify.d.ts.map +1 -1
- package/dist/verify.js +48 -15
- package/dist/verify.js.map +1 -1
- package/package.json +1 -1
- package/src/build.ts +178 -24
- package/src/cli.ts +91 -138
- package/src/config.ts +35 -5
- package/src/db.ts +13 -7
- package/src/doctor.ts +1 -1
- package/src/eval.ts +149 -0
- package/src/help.ts +382 -0
- package/src/index.ts +20 -0
- package/src/preview.ts +4 -1
- package/src/search.ts +33 -3
- package/src/spec.ts +66 -6
- package/src/stopwords.ts +120 -0
- package/src/sync.ts +1 -1
- package/src/verify.ts +69 -19
package/dist/verify.js
CHANGED
|
@@ -4,7 +4,7 @@ import { readBuildConfig } from "./config.js";
|
|
|
4
4
|
import { discoverPackages, resolvePackageDir } from "./discovery.js";
|
|
5
5
|
import { DocspackError } from "./errors.js";
|
|
6
6
|
import { readExportSurface } from "./exports.js";
|
|
7
|
-
import { chunkId, LLMS_DIR, MANIFEST_FILE, parseManifest } from "./spec.js";
|
|
7
|
+
import { chunkId, formatDocumentedLibrary, LLMS_DIR, MANIFEST_FILE, parseDocumentedLibrary, parseManifest, } from "./spec.js";
|
|
8
8
|
/** Below this length a name is too generic to reason about. */
|
|
9
9
|
const MIN_SYMBOL = 4;
|
|
10
10
|
/**
|
|
@@ -125,28 +125,39 @@ async function verifyOne(cwd, packageDir) {
|
|
|
125
125
|
}
|
|
126
126
|
async function verifyPackage(cwd, id, packageDir, manifest) {
|
|
127
127
|
const empty = { id, checked: 0, matched: 0, unrecognised: 0, findings: [] };
|
|
128
|
-
const
|
|
129
|
-
if (
|
|
128
|
+
const declaredLibraries = await documentedLibraries(packageDir, manifest);
|
|
129
|
+
if (declaredLibraries.length === 0) {
|
|
130
130
|
return {
|
|
131
131
|
...empty,
|
|
132
132
|
status: "skipped",
|
|
133
|
-
reason: `declares no "
|
|
133
|
+
reason: `declares no "documents", so there is nothing to check against`,
|
|
134
134
|
};
|
|
135
135
|
}
|
|
136
|
-
const
|
|
137
|
-
if
|
|
138
|
-
|
|
136
|
+
const documents = declaredLibraries.map(formatDocumentedLibrary);
|
|
137
|
+
// An entity is fine if any documented library declares it: a monorepo documents eighteen
|
|
138
|
+
// packages from one surface, and a name belongs to whichever of them exports it.
|
|
139
|
+
const names = new Set();
|
|
140
|
+
const missing = [];
|
|
141
|
+
for (const library of declaredLibraries) {
|
|
142
|
+
const libraryDir = (await resolvePackageDir(library.name, packageDir)) ??
|
|
143
|
+
(await resolvePackageDir(library.name, cwd));
|
|
144
|
+
const surface = libraryDir === undefined ? undefined : await readExportSurface(libraryDir);
|
|
145
|
+
if (surface === undefined || surface.names.size === 0) {
|
|
146
|
+
missing.push(library.name);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
for (const name of surface.names)
|
|
150
|
+
names.add(name);
|
|
139
151
|
}
|
|
140
|
-
|
|
141
|
-
if (surface === undefined || surface.names.size === 0) {
|
|
152
|
+
if (names.size === 0) {
|
|
142
153
|
return {
|
|
143
154
|
...empty,
|
|
144
155
|
status: "skipped",
|
|
145
156
|
documents,
|
|
146
|
-
reason: `${
|
|
157
|
+
reason: `${missing.join(", ")} ${missing.length === 1 ? "is" : "are"} not installed, or ship no type declarations`,
|
|
147
158
|
};
|
|
148
159
|
}
|
|
149
|
-
const declared = new Set([...
|
|
160
|
+
const declared = new Set([...names].map(normalize));
|
|
150
161
|
const findings = [];
|
|
151
162
|
let checked = 0;
|
|
152
163
|
let matched = 0;
|
|
@@ -163,7 +174,7 @@ async function verifyPackage(cwd, id, packageDir, manifest) {
|
|
|
163
174
|
matched += 1;
|
|
164
175
|
continue;
|
|
165
176
|
}
|
|
166
|
-
const suggestion = nearestName(symbol,
|
|
177
|
+
const suggestion = nearestName(symbol, names);
|
|
167
178
|
if (suggestion === undefined) {
|
|
168
179
|
unrecognised += 1;
|
|
169
180
|
continue;
|
|
@@ -177,7 +188,29 @@ async function verifyPackage(cwd, id, packageDir, manifest) {
|
|
|
177
188
|
});
|
|
178
189
|
}
|
|
179
190
|
}
|
|
180
|
-
return {
|
|
191
|
+
return {
|
|
192
|
+
id,
|
|
193
|
+
status: "verified",
|
|
194
|
+
documents,
|
|
195
|
+
...(missing.length === 0 ? {} : { unchecked: missing }),
|
|
196
|
+
checked,
|
|
197
|
+
matched,
|
|
198
|
+
unrecognised,
|
|
199
|
+
findings,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* What a package says it documents.
|
|
204
|
+
*
|
|
205
|
+
* The manifest is the answer when it carries one, because that is the only copy a consumer of an
|
|
206
|
+
* installed package can see. The `docspack` key is read as a fallback: it is build configuration
|
|
207
|
+
* rather than payload, and a package built before `documents` reached the manifest has only that.
|
|
208
|
+
*/
|
|
209
|
+
async function documentedLibraries(packageDir, manifest) {
|
|
210
|
+
if (manifest.documents !== undefined && manifest.documents.length > 0)
|
|
211
|
+
return manifest.documents;
|
|
212
|
+
const { documents } = await readBuildConfig(packageDir);
|
|
213
|
+
return (documents ?? []).map(parseDocumentedLibrary);
|
|
181
214
|
}
|
|
182
215
|
/**
|
|
183
216
|
* The name worth checking in an entity like `client.setKey()`, or nothing when the entity is
|
|
@@ -211,12 +244,12 @@ function normalize(name) {
|
|
|
211
244
|
* is left alone. In particular a differing first word is never a match: `includeLanguages` and
|
|
212
245
|
* `excludeLanguages` are two edits apart and mean opposite things.
|
|
213
246
|
*/
|
|
214
|
-
function nearestName(symbol,
|
|
247
|
+
function nearestName(symbol, names) {
|
|
215
248
|
const lower = normalize(symbol);
|
|
216
249
|
const words = camelWords(symbol);
|
|
217
250
|
let best;
|
|
218
251
|
let bestDistance = Number.POSITIVE_INFINITY;
|
|
219
|
-
for (const candidate of
|
|
252
|
+
for (const candidate of names) {
|
|
220
253
|
if (candidate.length < MIN_SYMBOL)
|
|
221
254
|
continue;
|
|
222
255
|
const candidateWords = camelWords(candidate);
|
package/dist/verify.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/build.ts
CHANGED
|
@@ -11,11 +11,14 @@ import { fetchableLinks, parseLlmsTxt } from "./llms-txt.js";
|
|
|
11
11
|
import {
|
|
12
12
|
CHUNKS_DIR,
|
|
13
13
|
type ChunkSpec,
|
|
14
|
+
type DocumentedLibrary,
|
|
14
15
|
estimateTokens,
|
|
15
16
|
LLMS_DIR,
|
|
16
17
|
MANIFEST_FILE,
|
|
18
|
+
parseDocumentedLibrary,
|
|
17
19
|
serializeManifest,
|
|
18
20
|
} from "./spec.js";
|
|
21
|
+
import { STOPWORDS } from "./stopwords.js";
|
|
19
22
|
|
|
20
23
|
export interface BuildOptions {
|
|
21
24
|
/** Directory the docs package is written to. */
|
|
@@ -30,6 +33,14 @@ export interface BuildOptions {
|
|
|
30
33
|
readonly source?: string;
|
|
31
34
|
readonly pages?: number;
|
|
32
35
|
readonly maxChunkTokens?: number;
|
|
36
|
+
/**
|
|
37
|
+
* Merge adjacent sections until a chunk reaches this size, never crossing `maxChunkTokens`.
|
|
38
|
+
* Off unless set: splitting at every heading is right for prose, and wrong only for generated
|
|
39
|
+
* reference, where a heading is a field name.
|
|
40
|
+
*/
|
|
41
|
+
readonly minChunkTokens?: number;
|
|
42
|
+
/** Libraries this package documents, each `name` or `name@version`. Recorded in the manifest. */
|
|
43
|
+
readonly documents?: readonly string[];
|
|
33
44
|
readonly http?: HttpClient;
|
|
34
45
|
readonly onProgress?: (message: string) => void;
|
|
35
46
|
}
|
|
@@ -76,7 +87,14 @@ export async function buildPackage(input: BuildOptions): Promise<BuildResult> {
|
|
|
76
87
|
}
|
|
77
88
|
|
|
78
89
|
const maxTokens = options.maxChunkTokens ?? DEFAULT_MAX_CHUNK_TOKENS;
|
|
79
|
-
const
|
|
90
|
+
const minTokens = options.minChunkTokens;
|
|
91
|
+
if (minTokens !== undefined && minTokens > maxTokens) {
|
|
92
|
+
throw new DocspackError(
|
|
93
|
+
`minChunkTokens (${minTokens}) is larger than maxChunkTokens (${maxTokens})`,
|
|
94
|
+
{ hint: "A chunk cannot be required to be bigger than it is allowed to be." },
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
const { chunks, files } = chunkDocuments(documents, maxTokens, minTokens);
|
|
80
98
|
|
|
81
99
|
const llmsDir = join(options.out, LLMS_DIR);
|
|
82
100
|
await rm(llmsDir, { recursive: true, force: true });
|
|
@@ -85,9 +103,15 @@ export async function buildPackage(input: BuildOptions): Promise<BuildResult> {
|
|
|
85
103
|
for (const file of files) {
|
|
86
104
|
await writeFile(join(llmsDir, file.path), file.contents, "utf8");
|
|
87
105
|
}
|
|
106
|
+
const libraries: DocumentedLibrary[] = (options.documents ?? []).map(parseDocumentedLibrary);
|
|
88
107
|
await writeFile(
|
|
89
108
|
join(llmsDir, MANIFEST_FILE),
|
|
90
|
-
serializeManifest({
|
|
109
|
+
serializeManifest({
|
|
110
|
+
name: identity.name,
|
|
111
|
+
version: identity.version,
|
|
112
|
+
...(libraries.length === 0 ? {} : { documents: libraries }),
|
|
113
|
+
chunks,
|
|
114
|
+
}),
|
|
91
115
|
"utf8",
|
|
92
116
|
);
|
|
93
117
|
await writeFile(join(options.out, "llms.txt"), renderLlmsTxt(identity.name, chunks), "utf8");
|
|
@@ -114,7 +138,7 @@ export async function buildPackage(input: BuildOptions): Promise<BuildResult> {
|
|
|
114
138
|
version: identity.version,
|
|
115
139
|
dir: options.out,
|
|
116
140
|
chunks: chunks.length,
|
|
117
|
-
tokens: chunks.reduce((total, chunk) => total + chunk.tokens, 0),
|
|
141
|
+
tokens: chunks.reduce((total, chunk) => total + (chunk.tokens ?? 0), 0),
|
|
118
142
|
warnings,
|
|
119
143
|
};
|
|
120
144
|
}
|
|
@@ -142,6 +166,12 @@ async function withConfig(options: BuildOptions): Promise<BuildOptions> {
|
|
|
142
166
|
...(options.maxChunkTokens === undefined && config.maxChunkTokens !== undefined
|
|
143
167
|
? { maxChunkTokens: config.maxChunkTokens }
|
|
144
168
|
: {}),
|
|
169
|
+
...(options.minChunkTokens === undefined && config.minChunkTokens !== undefined
|
|
170
|
+
? { minChunkTokens: config.minChunkTokens }
|
|
171
|
+
: {}),
|
|
172
|
+
...(options.documents === undefined && config.documents !== undefined
|
|
173
|
+
? { documents: config.documents }
|
|
174
|
+
: {}),
|
|
145
175
|
...(options.pages === undefined && config.pages !== undefined ? { pages: config.pages } : {}),
|
|
146
176
|
};
|
|
147
177
|
}
|
|
@@ -396,16 +426,18 @@ function renderOperation(
|
|
|
396
426
|
function chunkDocuments(
|
|
397
427
|
documents: readonly SourceDocument[],
|
|
398
428
|
maxTokens: number,
|
|
429
|
+
minTokens: number | undefined,
|
|
399
430
|
): { chunks: ChunkSpec[]; files: { path: string; contents: string }[] } {
|
|
400
431
|
const chunks: ChunkSpec[] = [];
|
|
401
432
|
const files: { path: string; contents: string }[] = [];
|
|
402
433
|
const taken = new Set<string>();
|
|
403
434
|
|
|
404
435
|
for (const document of documents) {
|
|
436
|
+
const text = collapseTablePadding(document.text);
|
|
405
437
|
const sections =
|
|
406
438
|
document.atomic === true
|
|
407
|
-
? [{ heading: document.title, body:
|
|
408
|
-
: splitMarkdown(
|
|
439
|
+
? [{ heading: document.title, body: text, level: 0 }]
|
|
440
|
+
: splitMarkdown(text, document.title, maxTokens, minTokens);
|
|
409
441
|
|
|
410
442
|
for (const section of sections) {
|
|
411
443
|
const directives = readDirectives(section.body);
|
|
@@ -419,11 +451,9 @@ function chunkDocuments(
|
|
|
419
451
|
id,
|
|
420
452
|
file,
|
|
421
453
|
tokens: estimateTokens(contents),
|
|
422
|
-
|
|
423
|
-
tags: uniqueWords([
|
|
424
|
-
...directives.tags,
|
|
454
|
+
tags: chunkTags(directives.tags, [
|
|
425
455
|
document.title,
|
|
426
|
-
section.heading,
|
|
456
|
+
...(section.headings ?? [section.heading]),
|
|
427
457
|
...(document.tags ?? []),
|
|
428
458
|
]),
|
|
429
459
|
entities: [
|
|
@@ -440,17 +470,35 @@ function chunkDocuments(
|
|
|
440
470
|
return { chunks, files };
|
|
441
471
|
}
|
|
442
472
|
|
|
473
|
+
/** One piece of a document, before it becomes a chunk. */
|
|
474
|
+
interface Section {
|
|
475
|
+
readonly heading: string;
|
|
476
|
+
readonly body: string;
|
|
477
|
+
/** Depth of the heading in the source, or 0 when it was inherited rather than written. */
|
|
478
|
+
readonly level: number;
|
|
479
|
+
/**
|
|
480
|
+
* Headings to index as tags, when they are not just `heading`. A packed chunk answers for every
|
|
481
|
+
* section in it, so all of their headings are search terms for it.
|
|
482
|
+
*/
|
|
483
|
+
readonly headings?: readonly string[];
|
|
484
|
+
}
|
|
485
|
+
|
|
443
486
|
/**
|
|
444
487
|
* Splits Markdown at `##` headings, then `###`, then paragraphs, until every piece fits the token
|
|
445
488
|
* budget. Retrieval works best when a chunk answers one question.
|
|
489
|
+
*
|
|
490
|
+
* With `minTokens` set, adjacent pieces are then packed back together up to the budget. Every
|
|
491
|
+
* API generator emits pages whose headings are field names — `Category` is one line, `Sizes` is
|
|
492
|
+
* three — and one chunk per heading there is hundreds of chunks too small to answer anything.
|
|
446
493
|
*/
|
|
447
494
|
function splitMarkdown(
|
|
448
495
|
text: string,
|
|
449
496
|
title: string,
|
|
450
497
|
maxTokens: number,
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
const
|
|
498
|
+
minTokens: number | undefined,
|
|
499
|
+
): Section[] {
|
|
500
|
+
const sections = splitAtLevel(text, 2, title, 0);
|
|
501
|
+
const result: Section[] = [];
|
|
454
502
|
|
|
455
503
|
for (const section of sections) {
|
|
456
504
|
if (estimateTokens(section.body) <= maxTokens) {
|
|
@@ -458,7 +506,7 @@ function splitMarkdown(
|
|
|
458
506
|
continue;
|
|
459
507
|
}
|
|
460
508
|
|
|
461
|
-
const deeper = splitAtLevel(section.body, 3, section.heading);
|
|
509
|
+
const deeper = splitAtLevel(section.body, 3, section.heading, section.level);
|
|
462
510
|
for (const part of deeper) {
|
|
463
511
|
if (estimateTokens(part.body) <= maxTokens) {
|
|
464
512
|
if (part.body.trim().length > 0) result.push(part);
|
|
@@ -468,22 +516,26 @@ function splitMarkdown(
|
|
|
468
516
|
...splitParagraphs(part.body, maxTokens).map((body, index) => ({
|
|
469
517
|
heading: index === 0 ? part.heading : `${part.heading} (${index + 1})`,
|
|
470
518
|
body,
|
|
519
|
+
// A continuation carries no heading of its own, so packing must not re-emit one.
|
|
520
|
+
level: index === 0 ? part.level : 0,
|
|
471
521
|
})),
|
|
472
522
|
);
|
|
473
523
|
}
|
|
474
524
|
}
|
|
475
525
|
|
|
476
|
-
|
|
526
|
+
const pieces = result.length > 0 ? result : [{ heading: title, body: text, level: 0 }];
|
|
527
|
+
return minTokens === undefined ? pieces : packSections(pieces, title, minTokens, maxTokens);
|
|
477
528
|
}
|
|
478
529
|
|
|
479
530
|
function splitAtLevel(
|
|
480
531
|
text: string,
|
|
481
532
|
level: number,
|
|
482
533
|
fallbackHeading: string,
|
|
483
|
-
|
|
534
|
+
fallbackLevel: number,
|
|
535
|
+
): Section[] {
|
|
484
536
|
const marker = new RegExp(`^#{${level}}\\s+(.+?)\\s*$`);
|
|
485
|
-
const sections: { heading: string; body: string[] }[] = [];
|
|
486
|
-
let current = { heading: fallbackHeading, body: [] as string[] };
|
|
537
|
+
const sections: { heading: string; body: string[]; level: number }[] = [];
|
|
538
|
+
let current = { heading: fallbackHeading, body: [] as string[], level: fallbackLevel };
|
|
487
539
|
let inFence = false;
|
|
488
540
|
|
|
489
541
|
for (const line of text.split("\n")) {
|
|
@@ -492,7 +544,7 @@ function splitAtLevel(
|
|
|
492
544
|
const match = inFence ? null : marker.exec(line);
|
|
493
545
|
if (match?.[1] !== undefined) {
|
|
494
546
|
if (current.body.join("\n").trim().length > 0) sections.push(current);
|
|
495
|
-
current = { heading: match[1], body: [] };
|
|
547
|
+
current = { heading: match[1], body: [], level };
|
|
496
548
|
continue;
|
|
497
549
|
}
|
|
498
550
|
current.body.push(line);
|
|
@@ -501,10 +553,57 @@ function splitAtLevel(
|
|
|
501
553
|
|
|
502
554
|
return sections.map((section) => ({
|
|
503
555
|
heading: section.heading,
|
|
556
|
+
level: section.level,
|
|
504
557
|
body: section.body.join("\n").trim(),
|
|
505
558
|
}));
|
|
506
559
|
}
|
|
507
560
|
|
|
561
|
+
/**
|
|
562
|
+
* Merges adjacent sections until a group reaches `minTokens`, never crossing `maxTokens`. A
|
|
563
|
+
* merged group is headed by the document title and keeps each section's own heading inside it,
|
|
564
|
+
* so a whole component lands in one chunk without losing the structure a reader needs.
|
|
565
|
+
*/
|
|
566
|
+
function packSections(
|
|
567
|
+
sections: readonly Section[],
|
|
568
|
+
title: string,
|
|
569
|
+
minTokens: number,
|
|
570
|
+
maxTokens: number,
|
|
571
|
+
): Section[] {
|
|
572
|
+
const packed: Section[] = [];
|
|
573
|
+
let group: Section[] = [];
|
|
574
|
+
let tokens = 0;
|
|
575
|
+
|
|
576
|
+
const flush = (): void => {
|
|
577
|
+
if (group.length === 1 && group[0] !== undefined) packed.push(group[0]);
|
|
578
|
+
else if (group.length > 1) {
|
|
579
|
+
packed.push({
|
|
580
|
+
heading: title,
|
|
581
|
+
level: 0,
|
|
582
|
+
body: group.map(withHeading).join("\n\n"),
|
|
583
|
+
headings: group.map((section) => section.heading),
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
group = [];
|
|
587
|
+
tokens = 0;
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
for (const section of sections) {
|
|
591
|
+
const size = estimateTokens(withHeading(section));
|
|
592
|
+
if (group.length > 0 && (tokens >= minTokens || tokens + size > maxTokens)) flush();
|
|
593
|
+
group.push(section);
|
|
594
|
+
tokens += size;
|
|
595
|
+
}
|
|
596
|
+
flush();
|
|
597
|
+
|
|
598
|
+
return packed;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/** A section as it appeared in the source, heading included when it had one. */
|
|
602
|
+
function withHeading(section: Section): string {
|
|
603
|
+
if (section.level === 0) return section.body;
|
|
604
|
+
return `${"#".repeat(section.level)} ${section.heading}\n\n${section.body}`;
|
|
605
|
+
}
|
|
606
|
+
|
|
508
607
|
function splitParagraphs(text: string, maxTokens: number): string[] {
|
|
509
608
|
const parts: string[] = [];
|
|
510
609
|
let buffer: string[] = [];
|
|
@@ -620,14 +719,69 @@ function slugify(input: string, fallback: string): string {
|
|
|
620
719
|
return slug.length > 0 ? slug : fallback;
|
|
621
720
|
}
|
|
622
721
|
|
|
623
|
-
|
|
624
|
-
|
|
722
|
+
const MAX_TAGS = 24;
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* The words indexed alongside a chunk.
|
|
726
|
+
*
|
|
727
|
+
* Directive tags are taken as the author wrote them — those are deliberate, and one of them
|
|
728
|
+
* being a function word is the author's decision. Words lifted out of a title or a heading are
|
|
729
|
+
* filtered: nobody chose them as search terms, and the tag field is weighted 3× in the ranking,
|
|
730
|
+
* which is what made "How to use it" the answer to every question beginning "how do I".
|
|
731
|
+
*/
|
|
732
|
+
function chunkTags(authored: readonly string[], derived: readonly string[]): string[] {
|
|
733
|
+
const tags = new Set(words(authored));
|
|
734
|
+
for (const word of words(derived)) {
|
|
735
|
+
if (!STOPWORDS.has(word)) tags.add(word);
|
|
736
|
+
}
|
|
737
|
+
return [...tags].slice(0, MAX_TAGS);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
function words(inputs: readonly string[]): string[] {
|
|
741
|
+
const found: string[] = [];
|
|
625
742
|
for (const input of inputs) {
|
|
626
|
-
|
|
627
|
-
words.add(word);
|
|
628
|
-
}
|
|
743
|
+
found.push(...(input.toLowerCase().match(/[\p{L}\p{N}]{2,}/gu) ?? []));
|
|
629
744
|
}
|
|
630
|
-
return
|
|
745
|
+
return found;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/** A row of a Markdown table: `| Name | Type |`, and the `|---|---:|` rule under it. */
|
|
749
|
+
const TABLE_ROW = /^\|.*\|/;
|
|
750
|
+
const TABLE_RULE = /^[|\-: \t]+$/;
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* Collapses the alignment padding out of Markdown tables.
|
|
754
|
+
*
|
|
755
|
+
* Every API generator pads table cells into columns. That padding is whitespace to a reader and
|
|
756
|
+
* to a renderer, but tokens are estimated from characters, so a generated props table is
|
|
757
|
+
* measured at several times its real weight — which spends the response budget, trips
|
|
758
|
+
* `chunk-too-large`, and forces splits that leave fragments competing for the same query. One
|
|
759
|
+
* chart's props table measured 4,094 tokens padded and 1,245 collapsed.
|
|
760
|
+
*
|
|
761
|
+
* Runs inside a code fence are left alone; a fenced table is example text, and its alignment is
|
|
762
|
+
* the thing being shown.
|
|
763
|
+
*/
|
|
764
|
+
export function collapseTablePadding(text: string): string {
|
|
765
|
+
let inFence = false;
|
|
766
|
+
|
|
767
|
+
return text
|
|
768
|
+
.split("\n")
|
|
769
|
+
.map((line) => {
|
|
770
|
+
if (/^\s*(?:```|~~~)/.test(line)) {
|
|
771
|
+
inFence = !inFence;
|
|
772
|
+
return line;
|
|
773
|
+
}
|
|
774
|
+
if (inFence) return line;
|
|
775
|
+
|
|
776
|
+
const indent = /^[ \t]*/.exec(line)?.[0] ?? "";
|
|
777
|
+
const rest = line.slice(indent.length);
|
|
778
|
+
// Four spaces or more is an indented code block, whatever the line looks like.
|
|
779
|
+
if (indent.length >= 4 || !TABLE_ROW.test(rest)) return line;
|
|
780
|
+
|
|
781
|
+
const collapsed = rest.replace(/[ \t]{2,}/g, " ");
|
|
782
|
+
return indent + (TABLE_RULE.test(rest) ? collapsed.replace(/-{4,}/g, "---") : collapsed);
|
|
783
|
+
})
|
|
784
|
+
.join("\n");
|
|
631
785
|
}
|
|
632
786
|
|
|
633
787
|
function uniqueId(base: string, taken: Set<string>): string {
|