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.
Files changed (65) hide show
  1. package/README.md +9 -3
  2. package/dist/build.d.ts +21 -0
  3. package/dist/build.d.ts.map +1 -1
  4. package/dist/build.js +138 -24
  5. package/dist/build.js.map +1 -1
  6. package/dist/cli.js +74 -131
  7. package/dist/cli.js.map +1 -1
  8. package/dist/config.d.ts +7 -2
  9. package/dist/config.d.ts.map +1 -1
  10. package/dist/config.js +20 -1
  11. package/dist/config.js.map +1 -1
  12. package/dist/db.d.ts +8 -4
  13. package/dist/db.d.ts.map +1 -1
  14. package/dist/db.js +13 -7
  15. package/dist/db.js.map +1 -1
  16. package/dist/doctor.js +1 -1
  17. package/dist/doctor.js.map +1 -1
  18. package/dist/eval.d.ts +52 -0
  19. package/dist/eval.d.ts.map +1 -0
  20. package/dist/eval.js +101 -0
  21. package/dist/eval.js.map +1 -0
  22. package/dist/help.d.ts +31 -0
  23. package/dist/help.d.ts.map +1 -0
  24. package/dist/help.js +342 -0
  25. package/dist/help.js.map +1 -0
  26. package/dist/index.d.ts +4 -1
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +4 -1
  29. package/dist/index.js.map +1 -1
  30. package/dist/preview.d.ts.map +1 -1
  31. package/dist/preview.js +4 -2
  32. package/dist/preview.js.map +1 -1
  33. package/dist/search.d.ts +6 -0
  34. package/dist/search.d.ts.map +1 -1
  35. package/dist/search.js +25 -3
  36. package/dist/search.js.map +1 -1
  37. package/dist/spec.d.ts +21 -1
  38. package/dist/spec.d.ts.map +1 -1
  39. package/dist/spec.js +44 -5
  40. package/dist/spec.js.map +1 -1
  41. package/dist/stopwords.d.ts +14 -0
  42. package/dist/stopwords.d.ts.map +1 -0
  43. package/dist/stopwords.js +121 -0
  44. package/dist/stopwords.js.map +1 -0
  45. package/dist/sync.js +1 -1
  46. package/dist/sync.js.map +1 -1
  47. package/dist/verify.d.ts +8 -2
  48. package/dist/verify.d.ts.map +1 -1
  49. package/dist/verify.js +48 -15
  50. package/dist/verify.js.map +1 -1
  51. package/package.json +1 -1
  52. package/src/build.ts +178 -24
  53. package/src/cli.ts +91 -138
  54. package/src/config.ts +35 -5
  55. package/src/db.ts +13 -7
  56. package/src/doctor.ts +1 -1
  57. package/src/eval.ts +149 -0
  58. package/src/help.ts +382 -0
  59. package/src/index.ts +20 -0
  60. package/src/preview.ts +4 -1
  61. package/src/search.ts +33 -3
  62. package/src/spec.ts +66 -6
  63. package/src/stopwords.ts +120 -0
  64. package/src/sync.ts +1 -1
  65. package/src/verify.ts +69 -19
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Words that carry no discrimination in a documentation corpus.
3
+ *
4
+ * Heading words become tags, and tags are weighted 3× against prose in the ranking. A section
5
+ * titled "How to use it" therefore carried `how`, `to` and `it` as high-weight terms, and every
6
+ * question beginning "how do I…" matched it — ahead of the chunk that answers the question.
7
+ *
8
+ * The list is closed-class only: articles, pronouns, prepositions, conjunctions, auxiliaries,
9
+ * modals, demonstratives and question words. No content word is in it, however generic it
10
+ * looks. `use`, `add`, `get` and `set` all name real APIs, and a list that grows past the
11
+ * function words starts deciding which of an author's own words count.
12
+ */
13
+ export const STOPWORDS: ReadonlySet<string> = new Set([
14
+ "about",
15
+ "after",
16
+ "all",
17
+ "also",
18
+ "an",
19
+ "and",
20
+ "any",
21
+ "are",
22
+ "as",
23
+ "at",
24
+ "be",
25
+ "been",
26
+ "before",
27
+ "being",
28
+ "between",
29
+ "both",
30
+ "but",
31
+ "by",
32
+ "can",
33
+ "could",
34
+ "did",
35
+ "do",
36
+ "does",
37
+ "doing",
38
+ "each",
39
+ "for",
40
+ "from",
41
+ "had",
42
+ "has",
43
+ "have",
44
+ "he",
45
+ "her",
46
+ "here",
47
+ "hers",
48
+ "him",
49
+ "his",
50
+ "how",
51
+ "if",
52
+ "in",
53
+ "into",
54
+ "is",
55
+ "it",
56
+ "its",
57
+ "may",
58
+ "me",
59
+ "might",
60
+ "must",
61
+ "my",
62
+ "no",
63
+ "nor",
64
+ "not",
65
+ "of",
66
+ "off",
67
+ "on",
68
+ "once",
69
+ "only",
70
+ "or",
71
+ "other",
72
+ "our",
73
+ "ours",
74
+ "out",
75
+ "over",
76
+ "own",
77
+ "same",
78
+ "shall",
79
+ "she",
80
+ "should",
81
+ "so",
82
+ "some",
83
+ "such",
84
+ "than",
85
+ "that",
86
+ "the",
87
+ "their",
88
+ "theirs",
89
+ "them",
90
+ "then",
91
+ "there",
92
+ "these",
93
+ "they",
94
+ "this",
95
+ "those",
96
+ "through",
97
+ "to",
98
+ "too",
99
+ "under",
100
+ "until",
101
+ "up",
102
+ "very",
103
+ "was",
104
+ "we",
105
+ "were",
106
+ "what",
107
+ "when",
108
+ "where",
109
+ "which",
110
+ "while",
111
+ "who",
112
+ "whom",
113
+ "why",
114
+ "will",
115
+ "with",
116
+ "would",
117
+ "you",
118
+ "your",
119
+ "yours",
120
+ ]);
package/src/sync.ts CHANGED
@@ -102,7 +102,7 @@ async function readChunks(
102
102
  chunks.push({
103
103
  chunkId: chunkId(pkg.id, chunk.id),
104
104
  filePath: chunk.file,
105
- tokens: chunk.tokens > 0 ? chunk.tokens : estimateTokens(text),
105
+ tokens: chunk.tokens ?? estimateTokens(text),
106
106
  content: text,
107
107
  tags: [...chunk.tags, ...chunk.entities],
108
108
  });
package/src/verify.ts CHANGED
@@ -3,8 +3,17 @@ import { join } from "node:path";
3
3
  import { readBuildConfig } from "./config.js";
4
4
  import { discoverPackages, resolvePackageDir } from "./discovery.js";
5
5
  import { DocspackError } from "./errors.js";
6
- import { type ExportSurface, readExportSurface } from "./exports.js";
7
- import { chunkId, LLMS_DIR, MANIFEST_FILE, type PackageManifest, parseManifest } from "./spec.js";
6
+ import { readExportSurface } from "./exports.js";
7
+ import {
8
+ chunkId,
9
+ type DocumentedLibrary,
10
+ formatDocumentedLibrary,
11
+ LLMS_DIR,
12
+ MANIFEST_FILE,
13
+ type PackageManifest,
14
+ parseDocumentedLibrary,
15
+ parseManifest,
16
+ } from "./spec.js";
8
17
 
9
18
  export interface DriftFinding {
10
19
  readonly chunkId: string;
@@ -22,8 +31,14 @@ export type VerifyStatus = "verified" | "skipped";
22
31
  export interface VerifiedPackage {
23
32
  readonly id: string;
24
33
  readonly status: VerifyStatus;
25
- /** Library the package declares it documents. */
26
- readonly documents?: string;
34
+ /** Libraries the package declares it documents, as written. */
35
+ readonly documents?: readonly string[];
36
+ /**
37
+ * Documented libraries whose declarations could not be read, when others could. The check ran
38
+ * against a smaller surface than the package claims, which is worth saying: an entity those
39
+ * libraries export is one this run could have accused.
40
+ */
41
+ readonly unchecked?: readonly string[];
27
42
  /** Why nothing was checked, when status is `skipped`. */
28
43
  readonly reason?: string;
29
44
  readonly checked: number;
@@ -181,32 +196,42 @@ async function verifyPackage(
181
196
  ): Promise<VerifiedPackage> {
182
197
  const empty = { id, checked: 0, matched: 0, unrecognised: 0, findings: [] };
183
198
 
184
- const { documents } = await readBuildConfig(packageDir);
185
- if (documents === undefined) {
199
+ const declaredLibraries = await documentedLibraries(packageDir, manifest);
200
+ if (declaredLibraries.length === 0) {
186
201
  return {
187
202
  ...empty,
188
203
  status: "skipped",
189
- reason: `declares no "docspack.documents", so there is nothing to check against`,
204
+ reason: `declares no "documents", so there is nothing to check against`,
190
205
  };
191
206
  }
192
207
 
193
- const libraryDir =
194
- (await resolvePackageDir(documents, packageDir)) ?? (await resolvePackageDir(documents, cwd));
195
- if (libraryDir === undefined) {
196
- return { ...empty, status: "skipped", documents, reason: `${documents} is not installed` };
208
+ const documents = declaredLibraries.map(formatDocumentedLibrary);
209
+ // An entity is fine if any documented library declares it: a monorepo documents eighteen
210
+ // packages from one surface, and a name belongs to whichever of them exports it.
211
+ const names = new Set<string>();
212
+ const missing: string[] = [];
213
+ for (const library of declaredLibraries) {
214
+ const libraryDir =
215
+ (await resolvePackageDir(library.name, packageDir)) ??
216
+ (await resolvePackageDir(library.name, cwd));
217
+ const surface = libraryDir === undefined ? undefined : await readExportSurface(libraryDir);
218
+ if (surface === undefined || surface.names.size === 0) {
219
+ missing.push(library.name);
220
+ continue;
221
+ }
222
+ for (const name of surface.names) names.add(name);
197
223
  }
198
224
 
199
- const surface = await readExportSurface(libraryDir);
200
- if (surface === undefined || surface.names.size === 0) {
225
+ if (names.size === 0) {
201
226
  return {
202
227
  ...empty,
203
228
  status: "skipped",
204
229
  documents,
205
- reason: `${documents} ships no type declarations to check against`,
230
+ reason: `${missing.join(", ")} ${missing.length === 1 ? "is" : "are"} not installed, or ship no type declarations`,
206
231
  };
207
232
  }
208
233
 
209
- const declared = new Set([...surface.names].map(normalize));
234
+ const declared = new Set([...names].map(normalize));
210
235
  const findings: DriftFinding[] = [];
211
236
  let checked = 0;
212
237
  let matched = 0;
@@ -225,7 +250,7 @@ async function verifyPackage(
225
250
  continue;
226
251
  }
227
252
 
228
- const suggestion = nearestName(symbol, surface);
253
+ const suggestion = nearestName(symbol, names);
229
254
  if (suggestion === undefined) {
230
255
  unrecognised += 1;
231
256
  continue;
@@ -241,7 +266,32 @@ async function verifyPackage(
241
266
  }
242
267
  }
243
268
 
244
- return { id, status: "verified", documents, checked, matched, unrecognised, findings };
269
+ return {
270
+ id,
271
+ status: "verified",
272
+ documents,
273
+ ...(missing.length === 0 ? {} : { unchecked: missing }),
274
+ checked,
275
+ matched,
276
+ unrecognised,
277
+ findings,
278
+ };
279
+ }
280
+
281
+ /**
282
+ * What a package says it documents.
283
+ *
284
+ * The manifest is the answer when it carries one, because that is the only copy a consumer of an
285
+ * installed package can see. The `docspack` key is read as a fallback: it is build configuration
286
+ * rather than payload, and a package built before `documents` reached the manifest has only that.
287
+ */
288
+ async function documentedLibraries(
289
+ packageDir: string,
290
+ manifest: PackageManifest,
291
+ ): Promise<readonly DocumentedLibrary[]> {
292
+ if (manifest.documents !== undefined && manifest.documents.length > 0) return manifest.documents;
293
+ const { documents } = await readBuildConfig(packageDir);
294
+ return (documents ?? []).map(parseDocumentedLibrary);
245
295
  }
246
296
 
247
297
  /**
@@ -275,13 +325,13 @@ function normalize(name: string): string {
275
325
  * is left alone. In particular a differing first word is never a match: `includeLanguages` and
276
326
  * `excludeLanguages` are two edits apart and mean opposite things.
277
327
  */
278
- function nearestName(symbol: string, surface: ExportSurface): string | undefined {
328
+ function nearestName(symbol: string, names: ReadonlySet<string>): string | undefined {
279
329
  const lower = normalize(symbol);
280
330
  const words = camelWords(symbol);
281
331
  let best: string | undefined;
282
332
  let bestDistance = Number.POSITIVE_INFINITY;
283
333
 
284
- for (const candidate of surface.names) {
334
+ for (const candidate of names) {
285
335
  if (candidate.length < MIN_SYMBOL) continue;
286
336
  const candidateWords = camelWords(candidate);
287
337
  if (isRequalified(words, candidateWords)) return candidate;