@doccov/sdk 0.15.1 → 0.18.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/dist/index.js CHANGED
@@ -4,19 +4,169 @@ var __getProtoOf = Object.getPrototypeOf;
4
4
  var __defProp = Object.defineProperty;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __toESM = (mod, isNodeMode, target) => {
8
- target = mod != null ? __create(__getProtoOf(mod)) : {};
9
- const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
10
- for (let key of __getOwnPropNames(mod))
7
+ var __toESM = (mod2, isNodeMode, target) => {
8
+ target = mod2 != null ? __create(__getProtoOf(mod2)) : {};
9
+ const to = isNodeMode || !mod2 || !mod2.__esModule ? __defProp(target, "default", { value: mod2, enumerable: true }) : target;
10
+ for (let key of __getOwnPropNames(mod2))
11
11
  if (!__hasOwnProp.call(to, key))
12
12
  __defProp(to, key, {
13
- get: () => mod[key],
13
+ get: () => mod2[key],
14
14
  enumerable: true
15
15
  });
16
16
  return to;
17
17
  };
18
- var __require = createRequire(import.meta.url);
18
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
19
19
 
20
+ // src/analysis/schema-detection.ts
21
+ import * as fs from "node:fs";
22
+
23
+ // src/extraction/standard-schema.ts
24
+ function isStandardJSONSchema(value) {
25
+ if (typeof value !== "object" || value === null) {
26
+ return false;
27
+ }
28
+ const obj = value;
29
+ const standard = obj["~standard"];
30
+ if (typeof standard !== "object" || standard === null) {
31
+ return false;
32
+ }
33
+ const stdObj = standard;
34
+ if (typeof stdObj.version !== "number") {
35
+ return false;
36
+ }
37
+ const jsonSchema = stdObj.jsonSchema;
38
+ if (typeof jsonSchema !== "object" || jsonSchema === null) {
39
+ return false;
40
+ }
41
+ const jsonSchemaObj = jsonSchema;
42
+ return typeof jsonSchemaObj.output === "function";
43
+ }
44
+ function extractViaStandardSchema(schema, options = {}) {
45
+ const standard = schema["~standard"];
46
+ const target = options.target ?? "draft-2020-12";
47
+ const extractedSchema = standard.jsonSchema.output({ target });
48
+ return {
49
+ schema: extractedSchema,
50
+ vendor: standard.vendor,
51
+ version: standard.version
52
+ };
53
+ }
54
+ function tryExtractStandardSchema(value, options = {}) {
55
+ if (!isStandardJSONSchema(value)) {
56
+ return null;
57
+ }
58
+ try {
59
+ return extractViaStandardSchema(value, options);
60
+ } catch {
61
+ return null;
62
+ }
63
+ }
64
+ var KNOWN_VENDORS = {
65
+ zod: { minVersion: "4.2.0", homepage: "https://zod.dev" },
66
+ arktype: { minVersion: "2.0.0", homepage: "https://arktype.io" },
67
+ valibot: { minVersion: "1.0.0", homepage: "https://valibot.dev" }
68
+ };
69
+ // src/analysis/schema-detection.ts
70
+ var moduleCache = new Map;
71
+ function getFileMtime(filePath) {
72
+ try {
73
+ return fs.statSync(filePath).mtimeMs;
74
+ } catch {
75
+ return 0;
76
+ }
77
+ }
78
+ async function loadModule(modulePath) {
79
+ const currentMtime = getFileMtime(modulePath);
80
+ const cached = moduleCache.get(modulePath);
81
+ if (cached && cached.mtime === currentMtime && currentMtime > 0) {
82
+ return cached.module;
83
+ }
84
+ try {
85
+ const requireCache = eval("require").cache;
86
+ if (requireCache && requireCache[modulePath]) {
87
+ delete requireCache[modulePath];
88
+ }
89
+ } catch {}
90
+ let mod;
91
+ try {
92
+ mod = eval("require")(modulePath);
93
+ } catch (err) {
94
+ const nodeErr = err;
95
+ if (nodeErr.code === "ERR_REQUIRE_ESM") {
96
+ const cacheBuster = currentMtime > 0 ? `?t=${currentMtime}` : "";
97
+ mod = await import(`${modulePath}${cacheBuster}`);
98
+ } else {
99
+ throw err;
100
+ }
101
+ }
102
+ moduleCache.set(modulePath, { module: mod, mtime: currentMtime });
103
+ return mod;
104
+ }
105
+ async function detectRuntimeSchemas(context) {
106
+ const result = {
107
+ schemas: new Map,
108
+ errors: []
109
+ };
110
+ try {
111
+ const modulePath2 = resolveModulePath(context.entryFile, context.baseDir);
112
+ if (!modulePath2) {
113
+ result.errors.push("Could not resolve compiled module path");
114
+ return result;
115
+ }
116
+ const mod2 = await loadModule(modulePath2);
117
+ if (!mod2 || typeof mod2 !== "object") {
118
+ result.errors.push("Module did not export an object");
119
+ return result;
120
+ }
121
+ const exports = mod2;
122
+ for (const [name, value] of Object.entries(exports)) {
123
+ if (name.startsWith("_") || name === "default") {
124
+ continue;
125
+ }
126
+ const schemaResult = tryExtractStandardSchema(value);
127
+ if (schemaResult) {
128
+ result.schemas.set(name, schemaResult);
129
+ }
130
+ }
131
+ if ("default" in exports && exports.default && typeof exports.default === "object") {
132
+ const defaultExports = exports.default;
133
+ for (const [name, value] of Object.entries(defaultExports)) {
134
+ if (name.startsWith("_"))
135
+ continue;
136
+ const schemaResult = tryExtractStandardSchema(value);
137
+ if (schemaResult && !result.schemas.has(name)) {
138
+ result.schemas.set(name, schemaResult);
139
+ }
140
+ }
141
+ }
142
+ } catch (err) {
143
+ result.errors.push(`Runtime detection failed: ${err instanceof Error ? err.message : String(err)}`);
144
+ }
145
+ return result;
146
+ }
147
+ function resolveModulePath(entryFile, baseDir) {
148
+ if (entryFile.endsWith(".js") || entryFile.endsWith(".mjs") || entryFile.endsWith(".cjs")) {
149
+ return entryFile;
150
+ }
151
+ const tsFile = entryFile;
152
+ const possiblePaths = [
153
+ tsFile.replace(/\.tsx?$/, ".js"),
154
+ tsFile.replace(/\.tsx?$/, ".mjs"),
155
+ tsFile.replace(/\/src\//, "/dist/").replace(/\.tsx?$/, ".js"),
156
+ tsFile.replace(/\/src\//, "/build/").replace(/\.tsx?$/, ".js"),
157
+ tsFile.replace(/\/src\//, "/lib/").replace(/\.tsx?$/, ".js")
158
+ ];
159
+ for (const testPath of possiblePaths) {
160
+ try {
161
+ __require.resolve(testPath, { paths: [baseDir] });
162
+ return testPath;
163
+ } catch {}
164
+ }
165
+ return null;
166
+ }
167
+ function clearSchemaCache() {
168
+ moduleCache.clear();
169
+ }
20
170
  // src/analysis/docs-coverage.ts
21
171
  import {
22
172
  DRIFT_CATEGORIES
@@ -446,7 +596,7 @@ function categorizeDrifts(drifts) {
446
596
  return { fixable, nonFixable };
447
597
  }
448
598
  // src/fix/jsdoc-writer.ts
449
- import * as fs from "node:fs";
599
+ import * as fs2 from "node:fs";
450
600
  import * as path from "node:path";
451
601
 
452
602
  // src/ts-module.ts
@@ -786,7 +936,7 @@ async function applyEdits(edits) {
786
936
  }
787
937
  for (const [filePath, fileEdits] of editsByFile) {
788
938
  try {
789
- const content = fs.readFileSync(filePath, "utf-8");
939
+ const content = fs2.readFileSync(filePath, "utf-8");
790
940
  const lines = content.split(`
791
941
  `);
792
942
  const sortedEdits = [...fileEdits].sort((a, b) => b.startLine - a.startLine);
@@ -800,7 +950,7 @@ async function applyEdits(edits) {
800
950
  }
801
951
  result.editsApplied++;
802
952
  }
803
- fs.writeFileSync(filePath, lines.join(`
953
+ fs2.writeFileSync(filePath, lines.join(`
804
954
  `));
805
955
  result.filesModified++;
806
956
  } catch (error) {
@@ -813,7 +963,7 @@ async function applyEdits(edits) {
813
963
  return result;
814
964
  }
815
965
  function createSourceFile(filePath) {
816
- const content = fs.readFileSync(filePath, "utf-8");
966
+ const content = fs2.readFileSync(filePath, "utf-8");
817
967
  return ts.createSourceFile(path.basename(filePath), content, ts.ScriptTarget.Latest, true, filePath.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
818
968
  }
819
969
  // src/utils/builtin-detection.ts
@@ -2013,6 +2163,122 @@ function ensureSpecCoverage(spec) {
2013
2163
  };
2014
2164
  }
2015
2165
  // src/quality/rules.ts
2166
+ var BUILTIN_TYPES = new Set([
2167
+ "string",
2168
+ "number",
2169
+ "boolean",
2170
+ "object",
2171
+ "any",
2172
+ "unknown",
2173
+ "void",
2174
+ "never",
2175
+ "null",
2176
+ "undefined",
2177
+ "symbol",
2178
+ "bigint",
2179
+ "Array",
2180
+ "Promise",
2181
+ "Map",
2182
+ "Set",
2183
+ "Record",
2184
+ "Partial",
2185
+ "Required",
2186
+ "Readonly",
2187
+ "Pick",
2188
+ "Omit",
2189
+ "Exclude",
2190
+ "Extract",
2191
+ "NonNullable",
2192
+ "ReturnType",
2193
+ "Parameters",
2194
+ "InstanceType",
2195
+ "ConstructorParameters",
2196
+ "Awaited"
2197
+ ]);
2198
+ function extractTypeReferences(exp) {
2199
+ const refs = new Set;
2200
+ function collectFromSchema(schema) {
2201
+ if (!schema)
2202
+ return;
2203
+ if (typeof schema === "string") {
2204
+ if (!BUILTIN_TYPES.has(schema)) {
2205
+ refs.add(schema);
2206
+ }
2207
+ return;
2208
+ }
2209
+ if (typeof schema === "object") {
2210
+ const obj = schema;
2211
+ if (typeof obj.$ref === "string") {
2212
+ const ref = obj.$ref;
2213
+ const name = ref.startsWith("#/types/") ? ref.slice("#/types/".length) : ref;
2214
+ if (!BUILTIN_TYPES.has(name)) {
2215
+ refs.add(name);
2216
+ }
2217
+ }
2218
+ if (typeof obj.type === "string" && !BUILTIN_TYPES.has(obj.type)) {
2219
+ refs.add(obj.type);
2220
+ }
2221
+ if (obj.items) {
2222
+ collectFromSchema(obj.items);
2223
+ }
2224
+ if (obj.properties && typeof obj.properties === "object") {
2225
+ for (const prop of Object.values(obj.properties)) {
2226
+ collectFromSchema(prop);
2227
+ }
2228
+ }
2229
+ if (Array.isArray(obj.anyOf)) {
2230
+ for (const item of obj.anyOf) {
2231
+ collectFromSchema(item);
2232
+ }
2233
+ }
2234
+ if (Array.isArray(obj.oneOf)) {
2235
+ for (const item of obj.oneOf) {
2236
+ collectFromSchema(item);
2237
+ }
2238
+ }
2239
+ if (Array.isArray(obj.allOf)) {
2240
+ for (const item of obj.allOf) {
2241
+ collectFromSchema(item);
2242
+ }
2243
+ }
2244
+ if (obj.additionalProperties && typeof obj.additionalProperties === "object") {
2245
+ collectFromSchema(obj.additionalProperties);
2246
+ }
2247
+ }
2248
+ }
2249
+ for (const sig of exp.signatures ?? []) {
2250
+ for (const param of sig.parameters ?? []) {
2251
+ collectFromSchema(param.schema);
2252
+ }
2253
+ if (sig.returns?.schema) {
2254
+ collectFromSchema(sig.returns.schema);
2255
+ }
2256
+ }
2257
+ for (const member of exp.members ?? []) {
2258
+ collectFromSchema(member.schema);
2259
+ for (const sig of member.signatures ?? []) {
2260
+ for (const param of sig.parameters ?? []) {
2261
+ collectFromSchema(param.schema);
2262
+ }
2263
+ if (sig.returns?.schema) {
2264
+ collectFromSchema(sig.returns.schema);
2265
+ }
2266
+ }
2267
+ }
2268
+ collectFromSchema(exp.schema);
2269
+ if (typeof exp.type === "string" && !BUILTIN_TYPES.has(exp.type)) {
2270
+ refs.add(exp.type);
2271
+ }
2272
+ if (exp.extends && !BUILTIN_TYPES.has(exp.extends)) {
2273
+ refs.add(exp.extends);
2274
+ }
2275
+ for (const impl of exp.implements ?? []) {
2276
+ if (!BUILTIN_TYPES.has(impl)) {
2277
+ refs.add(impl);
2278
+ }
2279
+ }
2280
+ return refs;
2281
+ }
2016
2282
  var CORE_RULES = [
2017
2283
  {
2018
2284
  id: "has-description",
@@ -2099,6 +2365,99 @@ var CORE_RULES = [
2099
2365
  }
2100
2366
  }
2101
2367
  ];
2368
+ var TSDOC_RULES = [
2369
+ {
2370
+ id: "require-release-tag",
2371
+ name: "Require Release Tag",
2372
+ description: "All exports must have @public, @beta, @alpha, or @internal",
2373
+ affectsCoverage: false,
2374
+ defaultSeverity: "off",
2375
+ check(ctx) {
2376
+ const tags = ctx.export.tags ?? [];
2377
+ return tags.some((t) => ["public", "beta", "alpha", "internal"].includes(t.name.toLowerCase()));
2378
+ },
2379
+ getViolation(ctx) {
2380
+ return {
2381
+ ruleId: "require-release-tag",
2382
+ severity: "warn",
2383
+ message: `Export '${ctx.export.name}' is missing a release tag (@public, @beta, @alpha, or @internal)`,
2384
+ fixable: true
2385
+ };
2386
+ }
2387
+ },
2388
+ {
2389
+ id: "internal-underscore",
2390
+ name: "Internal Underscore Prefix",
2391
+ description: "@internal exports should have underscore prefix",
2392
+ affectsCoverage: false,
2393
+ defaultSeverity: "off",
2394
+ check(ctx) {
2395
+ const tags = ctx.export.tags ?? [];
2396
+ const isInternal = tags.some((t) => t.name.toLowerCase() === "internal");
2397
+ if (!isInternal)
2398
+ return true;
2399
+ return ctx.export.name.startsWith("_");
2400
+ },
2401
+ getViolation(ctx) {
2402
+ return {
2403
+ ruleId: "internal-underscore",
2404
+ severity: "warn",
2405
+ message: `Internal export '${ctx.export.name}' should have underscore prefix (_${ctx.export.name})`,
2406
+ fixable: false
2407
+ };
2408
+ }
2409
+ },
2410
+ {
2411
+ id: "no-conflicting-tags",
2412
+ name: "No Conflicting Tags",
2413
+ description: "Cannot have both @internal and @public/@beta/@alpha",
2414
+ affectsCoverage: false,
2415
+ defaultSeverity: "warn",
2416
+ check(ctx) {
2417
+ const tags = ctx.export.tags ?? [];
2418
+ const tagNames = tags.map((t) => t.name.toLowerCase());
2419
+ const hasInternal = tagNames.includes("internal");
2420
+ const hasPublicish = tagNames.some((n) => ["public", "beta", "alpha"].includes(n));
2421
+ return !(hasInternal && hasPublicish);
2422
+ },
2423
+ getViolation(ctx) {
2424
+ return {
2425
+ ruleId: "no-conflicting-tags",
2426
+ severity: "error",
2427
+ message: `Export '${ctx.export.name}' has conflicting release tags (@internal with @public/@beta/@alpha)`,
2428
+ fixable: false
2429
+ };
2430
+ }
2431
+ },
2432
+ {
2433
+ id: "no-forgotten-export",
2434
+ name: "No Forgotten Export",
2435
+ description: "All referenced types must be exported",
2436
+ affectsCoverage: false,
2437
+ defaultSeverity: "off",
2438
+ check(ctx) {
2439
+ if (!ctx.exportRegistry)
2440
+ return true;
2441
+ const refs = extractTypeReferences(ctx.export);
2442
+ for (const ref of refs) {
2443
+ if (!ctx.exportRegistry.has(ref)) {
2444
+ return false;
2445
+ }
2446
+ }
2447
+ return true;
2448
+ },
2449
+ getViolation(ctx) {
2450
+ const refs = extractTypeReferences(ctx.export);
2451
+ const missing = ctx.exportRegistry ? [...refs].filter((r) => !ctx.exportRegistry.has(r)) : [];
2452
+ return {
2453
+ ruleId: "no-forgotten-export",
2454
+ severity: "warn",
2455
+ message: missing.length > 0 ? `Export '${ctx.export.name}' references unexported types: ${missing.join(", ")}` : `Export '${ctx.export.name}' references types that are not exported`,
2456
+ fixable: false
2457
+ };
2458
+ }
2459
+ }
2460
+ ];
2102
2461
  var STYLE_RULES = [
2103
2462
  {
2104
2463
  id: "no-empty-returns",
@@ -2165,7 +2524,7 @@ var STYLE_RULES = [
2165
2524
  }
2166
2525
  }
2167
2526
  ];
2168
- var BUILTIN_RULES = [...CORE_RULES, ...STYLE_RULES];
2527
+ var BUILTIN_RULES = [...CORE_RULES, ...TSDOC_RULES, ...STYLE_RULES];
2169
2528
  function getCoverageRules() {
2170
2529
  return BUILTIN_RULES.filter((r) => r.affectsCoverage);
2171
2530
  }
@@ -2188,7 +2547,7 @@ function getDefaultConfig() {
2188
2547
  }
2189
2548
 
2190
2549
  // src/quality/engine.ts
2191
- function evaluateExportQuality(exp, rawJSDoc, config = { rules: {} }) {
2550
+ function evaluateExportQuality(exp, rawJSDoc, config = { rules: {} }, exportRegistry) {
2192
2551
  const kind = exp.kind ?? "variable";
2193
2552
  const applicableRules = getRulesForKind(kind);
2194
2553
  const defaults = getDefaultConfig();
@@ -2213,7 +2572,7 @@ function evaluateExportQuality(exp, rawJSDoc, config = { rules: {} }) {
2213
2572
  fixableCount: 0
2214
2573
  }
2215
2574
  };
2216
- const context = { export: exp, rawJSDoc };
2575
+ const context = { export: exp, rawJSDoc, exportRegistry };
2217
2576
  for (const rule of applicableRules) {
2218
2577
  const passed = rule.check(context);
2219
2578
  const severity = getSeverity(rule.id, rule.defaultSeverity);
@@ -2313,7 +2672,7 @@ function enrichSpec(spec, options = {}) {
2313
2672
  let totalCoverage = 0;
2314
2673
  const enrichedExports = spec.exports.map((exp) => {
2315
2674
  const rawJSDoc = rawJSDocByExport?.get(exp.id);
2316
- const quality = evaluateExportQuality(exp, rawJSDoc, qualityConfig);
2675
+ const quality = evaluateExportQuality(exp, rawJSDoc, qualityConfig, exportRegistry);
2317
2676
  const drift = computeExportDrift(exp, exportRegistry);
2318
2677
  const additionalDrift = driftByExport?.get(exp.id);
2319
2678
  const allDrift2 = additionalDrift ? [...drift, ...additionalDrift] : drift;
@@ -2360,7 +2719,7 @@ function enrichSpec(spec, options = {}) {
2360
2719
  };
2361
2720
  }
2362
2721
  // src/analysis/report.ts
2363
- import * as fs2 from "node:fs";
2722
+ import * as fs3 from "node:fs";
2364
2723
  import * as path2 from "node:path";
2365
2724
 
2366
2725
  // src/types/report.ts
@@ -2424,7 +2783,7 @@ function generateReportFromEnriched(enriched) {
2424
2783
  driftSummary
2425
2784
  };
2426
2785
  return {
2427
- $schema: "https://doccov.dev/schemas/v1.0.0/report.schema.json",
2786
+ $schema: "https://doccov.com/schemas/v1.0.0/report.schema.json",
2428
2787
  version: REPORT_VERSION,
2429
2788
  generatedAt: new Date().toISOString(),
2430
2789
  spec: {
@@ -2438,10 +2797,10 @@ function generateReportFromEnriched(enriched) {
2438
2797
  function loadCachedReport(reportPath = DEFAULT_REPORT_PATH) {
2439
2798
  try {
2440
2799
  const fullPath = path2.resolve(reportPath);
2441
- if (!fs2.existsSync(fullPath)) {
2800
+ if (!fs3.existsSync(fullPath)) {
2442
2801
  return null;
2443
2802
  }
2444
- const content = fs2.readFileSync(fullPath, "utf-8");
2803
+ const content = fs3.readFileSync(fullPath, "utf-8");
2445
2804
  return JSON.parse(content);
2446
2805
  } catch {
2447
2806
  return null;
@@ -2450,10 +2809,10 @@ function loadCachedReport(reportPath = DEFAULT_REPORT_PATH) {
2450
2809
  function saveReport(report, reportPath = DEFAULT_REPORT_PATH) {
2451
2810
  const fullPath = path2.resolve(reportPath);
2452
2811
  const dir = path2.dirname(fullPath);
2453
- if (!fs2.existsSync(dir)) {
2454
- fs2.mkdirSync(dir, { recursive: true });
2812
+ if (!fs3.existsSync(dir)) {
2813
+ fs3.mkdirSync(dir, { recursive: true });
2455
2814
  }
2456
- fs2.writeFileSync(fullPath, JSON.stringify(report, null, 2));
2815
+ fs3.writeFileSync(fullPath, JSON.stringify(report, null, 2));
2457
2816
  }
2458
2817
  function isCachedReportValid(reportPath = DEFAULT_REPORT_PATH, sourceFiles = []) {
2459
2818
  const report = loadCachedReport(reportPath);
@@ -2463,7 +2822,7 @@ function isCachedReportValid(reportPath = DEFAULT_REPORT_PATH, sourceFiles = [])
2463
2822
  const reportTime = new Date(report.generatedAt).getTime();
2464
2823
  for (const file of sourceFiles) {
2465
2824
  try {
2466
- const stat = fs2.statSync(file);
2825
+ const stat = fs3.statSync(file);
2467
2826
  if (stat.mtimeMs > reportTime) {
2468
2827
  return false;
2469
2828
  }
@@ -2473,58 +2832,460 @@ function isCachedReportValid(reportPath = DEFAULT_REPORT_PATH, sourceFiles = [])
2473
2832
  }
2474
2833
  return true;
2475
2834
  }
2476
- // src/cache/hash.ts
2477
- import * as crypto from "node:crypto";
2478
- import * as fs3 from "node:fs";
2479
- import * as path3 from "node:path";
2480
- function hashFile(filePath) {
2481
- try {
2482
- const content = fs3.readFileSync(filePath);
2483
- return crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
2484
- } catch {
2485
- return null;
2835
+ function formatSignature(name, signature) {
2836
+ const params = (signature.parameters ?? []).map((p) => {
2837
+ const optional = p.required === false ? "?" : "";
2838
+ const rest = p.rest ? "..." : "";
2839
+ const typeStr = typeof p.schema === "string" ? p.schema : p.schema?.type ?? "unknown";
2840
+ return `${rest}${p.name}${optional}: ${typeStr}`;
2841
+ }).join(", ");
2842
+ const returnType = signature.returns ? typeof signature.returns.schema === "string" ? signature.returns.schema : signature.returns.schema?.type ?? "unknown" : "void";
2843
+ const typeParams = signature.typeParameters?.length ? `<${signature.typeParameters.map((tp) => tp.name).join(", ")}>` : "";
2844
+ return `${name}${typeParams}(${params}): ${returnType}`;
2845
+ }
2846
+ function formatExportToApiSurface(exp) {
2847
+ const lines = [];
2848
+ lines.push(`### ${exp.name}`);
2849
+ switch (exp.kind) {
2850
+ case "function": {
2851
+ const signatures = exp.signatures ?? [];
2852
+ if (signatures.length === 0) {
2853
+ lines.push(`\`\`\`typescript
2854
+ function ${exp.name}(): unknown
2855
+ \`\`\``);
2856
+ } else {
2857
+ for (const sig of signatures) {
2858
+ lines.push(`\`\`\`typescript
2859
+ function ${formatSignature(exp.name, sig)}
2860
+ \`\`\``);
2861
+ }
2862
+ }
2863
+ break;
2864
+ }
2865
+ case "class": {
2866
+ const extendsClause = exp.extends ? ` extends ${exp.extends}` : "";
2867
+ const implementsClause = exp.implements?.length ? ` implements ${exp.implements.join(", ")}` : "";
2868
+ lines.push(`\`\`\`typescript
2869
+ class ${exp.name}${extendsClause}${implementsClause}
2870
+ \`\`\``);
2871
+ break;
2872
+ }
2873
+ case "interface":
2874
+ case "type": {
2875
+ const typeStr = typeof exp.type === "string" ? exp.type : exp.type?.type ?? "{ ... }";
2876
+ lines.push(`\`\`\`typescript
2877
+ type ${exp.name} = ${typeStr}
2878
+ \`\`\``);
2879
+ break;
2880
+ }
2881
+ case "variable": {
2882
+ const typeStr = typeof exp.type === "string" ? exp.type : exp.type?.type ?? "unknown";
2883
+ lines.push(`\`\`\`typescript
2884
+ const ${exp.name}: ${typeStr}
2885
+ \`\`\``);
2886
+ break;
2887
+ }
2888
+ case "enum": {
2889
+ lines.push(`\`\`\`typescript
2890
+ enum ${exp.name} { ... }
2891
+ \`\`\``);
2892
+ break;
2893
+ }
2894
+ default: {
2895
+ lines.push(`\`\`\`typescript
2896
+ ${exp.kind} ${exp.name}
2897
+ \`\`\``);
2898
+ }
2486
2899
  }
2900
+ return lines.join(`
2901
+ `);
2487
2902
  }
2488
- function hashString(content) {
2489
- return crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
2490
- }
2491
- function hashFiles(filePaths, cwd) {
2492
- const hashes = {};
2493
- for (const filePath of filePaths) {
2494
- const hash = hashFile(filePath);
2495
- if (hash) {
2496
- const relativePath = path3.relative(cwd, filePath);
2497
- hashes[relativePath] = hash;
2903
+ function formatTypeToApiSurface(type) {
2904
+ const lines = [];
2905
+ lines.push(`### ${type.name}`);
2906
+ switch (type.kind) {
2907
+ case "interface": {
2908
+ const extendsClause = type.extends ? ` extends ${type.extends}` : "";
2909
+ lines.push(`\`\`\`typescript
2910
+ interface ${type.name}${extendsClause} { ... }
2911
+ \`\`\``);
2912
+ break;
2913
+ }
2914
+ case "type": {
2915
+ const typeStr = typeof type.type === "string" ? type.type : type.type?.type ?? "{ ... }";
2916
+ lines.push(`\`\`\`typescript
2917
+ type ${type.name} = ${typeStr}
2918
+ \`\`\``);
2919
+ break;
2920
+ }
2921
+ case "class": {
2922
+ const extendsClause = type.extends ? ` extends ${type.extends}` : "";
2923
+ lines.push(`\`\`\`typescript
2924
+ class ${type.name}${extendsClause}
2925
+ \`\`\``);
2926
+ break;
2927
+ }
2928
+ case "enum": {
2929
+ lines.push(`\`\`\`typescript
2930
+ enum ${type.name} { ... }
2931
+ \`\`\``);
2932
+ break;
2933
+ }
2934
+ default: {
2935
+ lines.push(`\`\`\`typescript
2936
+ ${type.kind} ${type.name}
2937
+ \`\`\``);
2498
2938
  }
2499
2939
  }
2500
- return hashes;
2940
+ return lines.join(`
2941
+ `);
2501
2942
  }
2502
- function diffHashes(cached, current) {
2503
- const changed = [];
2504
- for (const [file, hash] of Object.entries(cached)) {
2505
- if (current[file] !== hash) {
2506
- changed.push(file);
2943
+ function renderApiSurface(spec) {
2944
+ const lines = [];
2945
+ const version = spec.meta.version ? ` v${spec.meta.version}` : "";
2946
+ lines.push(`# API Surface: ${spec.meta.name}${version}`);
2947
+ lines.push("");
2948
+ lines.push("> This file is auto-generated. Do not edit manually.");
2949
+ lines.push("> Run `doccov spec --format api-surface` to regenerate.");
2950
+ lines.push("");
2951
+ const exportsByKind = {};
2952
+ for (const exp of spec.exports) {
2953
+ const kind = exp.kind;
2954
+ if (!exportsByKind[kind]) {
2955
+ exportsByKind[kind] = [];
2956
+ }
2957
+ exportsByKind[kind].push(exp);
2958
+ }
2959
+ for (const kind of Object.keys(exportsByKind)) {
2960
+ exportsByKind[kind].sort((a, b) => a.name.localeCompare(b.name));
2961
+ }
2962
+ const kindOrder = [
2963
+ "function",
2964
+ "class",
2965
+ "interface",
2966
+ "type",
2967
+ "variable",
2968
+ "enum",
2969
+ "namespace",
2970
+ "module"
2971
+ ];
2972
+ for (const kind of kindOrder) {
2973
+ const exports = exportsByKind[kind];
2974
+ if (!exports || exports.length === 0)
2975
+ continue;
2976
+ const kindTitle = kind.charAt(0).toUpperCase() + kind.slice(1) + "s";
2977
+ lines.push(`## ${kindTitle}`);
2978
+ lines.push("");
2979
+ for (const exp of exports) {
2980
+ lines.push(formatExportToApiSurface(exp));
2981
+ lines.push("");
2507
2982
  }
2508
2983
  }
2509
- for (const file of Object.keys(current)) {
2510
- if (!(file in cached)) {
2511
- changed.push(file);
2984
+ for (const kind of Object.keys(exportsByKind).sort()) {
2985
+ if (kindOrder.includes(kind))
2986
+ continue;
2987
+ const exports = exportsByKind[kind];
2988
+ if (!exports || exports.length === 0)
2989
+ continue;
2990
+ const kindTitle = kind.charAt(0).toUpperCase() + kind.slice(1) + "s";
2991
+ lines.push(`## ${kindTitle}`);
2992
+ lines.push("");
2993
+ for (const exp of exports) {
2994
+ lines.push(formatExportToApiSurface(exp));
2995
+ lines.push("");
2512
2996
  }
2513
2997
  }
2514
- return changed;
2998
+ const types = spec.types ?? [];
2999
+ if (types.length > 0) {
3000
+ const sortedTypes = [...types].sort((a, b) => a.name.localeCompare(b.name));
3001
+ lines.push("## Internal Types");
3002
+ lines.push("");
3003
+ for (const type of sortedTypes) {
3004
+ lines.push(formatTypeToApiSurface(type));
3005
+ lines.push("");
3006
+ }
3007
+ }
3008
+ return lines.join(`
3009
+ `);
2515
3010
  }
2516
- // src/cache/spec-cache.ts
3011
+ // src/analysis/history.ts
2517
3012
  import * as fs4 from "node:fs";
2518
- import * as path4 from "node:path";
2519
- var CACHE_VERSION = "1.0.0";
3013
+ import * as path3 from "node:path";
3014
+ var HISTORY_DIR = ".doccov/history";
3015
+ var RETENTION_DAYS = {
3016
+ free: 7,
3017
+ team: 30,
3018
+ pro: 90
3019
+ };
3020
+ function getSnapshotFilename(timestamp) {
3021
+ const pad = (n) => n.toString().padStart(2, "0");
3022
+ const year = timestamp.getFullYear();
3023
+ const month = pad(timestamp.getMonth() + 1);
3024
+ const day = pad(timestamp.getDate());
3025
+ const hours = pad(timestamp.getHours());
3026
+ const minutes = pad(timestamp.getMinutes());
3027
+ const seconds = pad(timestamp.getSeconds());
3028
+ return `${year}-${month}-${day}-${hours}${minutes}${seconds}.json`;
3029
+ }
3030
+ function computeSnapshot(spec, options) {
3031
+ const exports = spec.exports ?? [];
3032
+ const documented = exports.filter((e) => e.description && e.description.trim().length > 0);
3033
+ const driftCount = exports.reduce((sum, e) => {
3034
+ const docs = e.docs;
3035
+ return sum + (docs?.drift?.length ?? 0);
3036
+ }, 0);
3037
+ const coverageScore = exports.length > 0 ? Math.round(documented.length / exports.length * 100) : 100;
3038
+ return {
3039
+ timestamp: new Date().toISOString(),
3040
+ package: spec.meta.name,
3041
+ version: spec.meta.version,
3042
+ coverageScore,
3043
+ totalExports: exports.length,
3044
+ documentedExports: documented.length,
3045
+ driftCount,
3046
+ commit: options?.commit,
3047
+ branch: options?.branch
3048
+ };
3049
+ }
3050
+ function saveSnapshot(snapshot, cwd) {
3051
+ const historyDir = path3.resolve(cwd, HISTORY_DIR);
3052
+ if (!fs4.existsSync(historyDir)) {
3053
+ fs4.mkdirSync(historyDir, { recursive: true });
3054
+ }
3055
+ const filename = getSnapshotFilename(new Date(snapshot.timestamp));
3056
+ const filepath = path3.join(historyDir, filename);
3057
+ fs4.writeFileSync(filepath, JSON.stringify(snapshot, null, 2));
3058
+ }
3059
+ function loadSnapshots(cwd) {
3060
+ const historyDir = path3.resolve(cwd, HISTORY_DIR);
3061
+ if (!fs4.existsSync(historyDir)) {
3062
+ return [];
3063
+ }
3064
+ const files = fs4.readdirSync(historyDir).filter((f) => f.endsWith(".json")).sort().reverse();
3065
+ const snapshots = [];
3066
+ for (const file of files) {
3067
+ try {
3068
+ const content = fs4.readFileSync(path3.join(historyDir, file), "utf-8");
3069
+ snapshots.push(JSON.parse(content));
3070
+ } catch {}
3071
+ }
3072
+ return snapshots;
3073
+ }
3074
+ function getTrend(spec, cwd, options) {
3075
+ const current = computeSnapshot(spec, options);
3076
+ const history = loadSnapshots(cwd);
3077
+ const delta = history.length > 0 ? current.coverageScore - history[0].coverageScore : undefined;
3078
+ const sparklineHistory = history.slice(0, 9).map((s) => s.coverageScore);
3079
+ const sparkline = [current.coverageScore, ...sparklineHistory].reverse();
3080
+ return {
3081
+ current,
3082
+ history,
3083
+ delta,
3084
+ sparkline
3085
+ };
3086
+ }
3087
+ function renderSparkline(values) {
3088
+ if (values.length === 0)
3089
+ return "";
3090
+ const chars = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
3091
+ const min = Math.min(...values);
3092
+ const max = Math.max(...values);
3093
+ const range = max - min || 1;
3094
+ return values.map((v) => {
3095
+ const normalized = (v - min) / range;
3096
+ const index = Math.min(Math.floor(normalized * chars.length), chars.length - 1);
3097
+ return chars[index];
3098
+ }).join("");
3099
+ }
3100
+ function formatDelta(delta) {
3101
+ if (delta > 0)
3102
+ return `↑${delta}%`;
3103
+ if (delta < 0)
3104
+ return `↓${Math.abs(delta)}%`;
3105
+ return "→0%";
3106
+ }
3107
+ function pruneHistory(cwd, keepCount = 100) {
3108
+ const historyDir = path3.resolve(cwd, HISTORY_DIR);
3109
+ if (!fs4.existsSync(historyDir)) {
3110
+ return 0;
3111
+ }
3112
+ const files = fs4.readdirSync(historyDir).filter((f) => f.endsWith(".json")).sort().reverse();
3113
+ const toDelete = files.slice(keepCount);
3114
+ for (const file of toDelete) {
3115
+ try {
3116
+ fs4.unlinkSync(path3.join(historyDir, file));
3117
+ } catch {}
3118
+ }
3119
+ return toDelete.length;
3120
+ }
3121
+ function pruneByTier(cwd, tier) {
3122
+ const retentionDays = RETENTION_DAYS[tier];
3123
+ const cutoffDate = new Date;
3124
+ cutoffDate.setDate(cutoffDate.getDate() - retentionDays);
3125
+ const historyDir = path3.resolve(cwd, HISTORY_DIR);
3126
+ if (!fs4.existsSync(historyDir)) {
3127
+ return 0;
3128
+ }
3129
+ const files = fs4.readdirSync(historyDir).filter((f) => f.endsWith(".json"));
3130
+ let deleted = 0;
3131
+ for (const file of files) {
3132
+ try {
3133
+ const filepath = path3.join(historyDir, file);
3134
+ const content = fs4.readFileSync(filepath, "utf-8");
3135
+ const snapshot = JSON.parse(content);
3136
+ const snapshotDate = new Date(snapshot.timestamp);
3137
+ if (snapshotDate < cutoffDate) {
3138
+ fs4.unlinkSync(filepath);
3139
+ deleted++;
3140
+ }
3141
+ } catch {}
3142
+ }
3143
+ return deleted;
3144
+ }
3145
+ function loadSnapshotsForDays(cwd, days) {
3146
+ const cutoffDate = new Date;
3147
+ cutoffDate.setDate(cutoffDate.getDate() - days);
3148
+ const allSnapshots = loadSnapshots(cwd);
3149
+ return allSnapshots.filter((s) => new Date(s.timestamp) >= cutoffDate);
3150
+ }
3151
+ function calculateVelocity(snapshots) {
3152
+ if (snapshots.length < 2)
3153
+ return 0;
3154
+ const newest = snapshots[0];
3155
+ const oldest = snapshots[snapshots.length - 1];
3156
+ const newestDate = new Date(newest.timestamp);
3157
+ const oldestDate = new Date(oldest.timestamp);
3158
+ const daysDiff = (newestDate.getTime() - oldestDate.getTime()) / (1000 * 60 * 60 * 24);
3159
+ if (daysDiff < 1)
3160
+ return 0;
3161
+ const coverageDiff = newest.coverageScore - oldest.coverageScore;
3162
+ return Math.round(coverageDiff / daysDiff * 100) / 100;
3163
+ }
3164
+ function getWeekStart(date) {
3165
+ const result = new Date(date);
3166
+ result.setDate(result.getDate() - result.getDay());
3167
+ result.setHours(0, 0, 0, 0);
3168
+ return result;
3169
+ }
3170
+ function generateWeeklySummaries(snapshots) {
3171
+ if (snapshots.length === 0)
3172
+ return [];
3173
+ const weeklyGroups = new Map;
3174
+ for (const snapshot of snapshots) {
3175
+ const weekStart = getWeekStart(new Date(snapshot.timestamp));
3176
+ const weekKey = weekStart.toISOString().split("T")[0];
3177
+ const group = weeklyGroups.get(weekKey) ?? [];
3178
+ group.push(snapshot);
3179
+ weeklyGroups.set(weekKey, group);
3180
+ }
3181
+ const summaries = [];
3182
+ for (const [weekKey, weekSnapshots] of weeklyGroups) {
3183
+ const sorted = [...weekSnapshots].sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
3184
+ const weekStart = new Date(weekKey);
3185
+ const weekEnd = new Date(weekStart);
3186
+ weekEnd.setDate(weekEnd.getDate() + 6);
3187
+ const scores = sorted.map((s) => s.coverageScore);
3188
+ const avgCoverage = Math.round(scores.reduce((a, b) => a + b, 0) / scores.length);
3189
+ const startCoverage = sorted[0].coverageScore;
3190
+ const endCoverage = sorted[sorted.length - 1].coverageScore;
3191
+ summaries.push({
3192
+ weekStart: weekStart.toISOString(),
3193
+ weekEnd: weekEnd.toISOString(),
3194
+ avgCoverage,
3195
+ startCoverage,
3196
+ endCoverage,
3197
+ delta: endCoverage - startCoverage,
3198
+ snapshotCount: sorted.length
3199
+ });
3200
+ }
3201
+ return summaries.sort((a, b) => new Date(b.weekStart).getTime() - new Date(a.weekStart).getTime());
3202
+ }
3203
+ function getExtendedTrend(spec, cwd, options) {
3204
+ const tier = options?.tier ?? "pro";
3205
+ const retentionDays = RETENTION_DAYS[tier];
3206
+ const trend = getTrend(spec, cwd, options);
3207
+ const periodSnapshots = loadSnapshotsForDays(cwd, retentionDays);
3208
+ const snapshots7d = loadSnapshotsForDays(cwd, 7);
3209
+ const snapshots30d = loadSnapshotsForDays(cwd, 30);
3210
+ const snapshots90d = tier === "pro" ? loadSnapshotsForDays(cwd, 90) : [];
3211
+ const velocity7d = calculateVelocity(snapshots7d);
3212
+ const velocity30d = calculateVelocity(snapshots30d);
3213
+ const velocity90d = tier === "pro" ? calculateVelocity(snapshots90d) : undefined;
3214
+ const currentScore = trend.current.coverageScore;
3215
+ const projected30d = Math.min(100, Math.max(0, Math.round(currentScore + velocity30d * 30)));
3216
+ const allScores = [trend.current.coverageScore, ...trend.history.map((s) => s.coverageScore)];
3217
+ const allTimeHigh = Math.max(...allScores);
3218
+ const allTimeLow = Math.min(...allScores);
3219
+ const dataRange = trend.history.length > 0 ? {
3220
+ start: trend.history[trend.history.length - 1].timestamp,
3221
+ end: trend.current.timestamp
3222
+ } : null;
3223
+ const allSnapshots = [trend.current, ...periodSnapshots];
3224
+ const weeklySummaries = generateWeeklySummaries(allSnapshots);
3225
+ return {
3226
+ trend,
3227
+ weeklySummaries,
3228
+ velocity7d,
3229
+ velocity30d,
3230
+ velocity90d,
3231
+ projected30d,
3232
+ allTimeHigh,
3233
+ allTimeLow,
3234
+ dataRange
3235
+ };
3236
+ }
3237
+ // src/cache/hash.ts
3238
+ import * as crypto from "node:crypto";
3239
+ import * as fs5 from "node:fs";
3240
+ import * as path4 from "node:path";
3241
+ function hashFile(filePath) {
3242
+ try {
3243
+ const content = fs5.readFileSync(filePath);
3244
+ return crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
3245
+ } catch {
3246
+ return null;
3247
+ }
3248
+ }
3249
+ function hashString(content) {
3250
+ return crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
3251
+ }
3252
+ function hashFiles(filePaths, cwd) {
3253
+ const hashes = {};
3254
+ for (const filePath of filePaths) {
3255
+ const hash = hashFile(filePath);
3256
+ if (hash) {
3257
+ const relativePath = path4.relative(cwd, filePath);
3258
+ hashes[relativePath] = hash;
3259
+ }
3260
+ }
3261
+ return hashes;
3262
+ }
3263
+ function diffHashes(cached2, current) {
3264
+ const changed = [];
3265
+ for (const [file, hash] of Object.entries(cached2)) {
3266
+ if (current[file] !== hash) {
3267
+ changed.push(file);
3268
+ }
3269
+ }
3270
+ for (const file of Object.keys(current)) {
3271
+ if (!(file in cached2)) {
3272
+ changed.push(file);
3273
+ }
3274
+ }
3275
+ return changed;
3276
+ }
3277
+ // src/cache/spec-cache.ts
3278
+ import * as fs6 from "node:fs";
3279
+ import * as path5 from "node:path";
3280
+ var CACHE_VERSION = "1.0.0";
2520
3281
  var SPEC_CACHE_FILE = ".doccov/spec.cache.json";
2521
3282
  function loadSpecCache(cwd) {
2522
3283
  try {
2523
- const cachePath = path4.resolve(cwd, SPEC_CACHE_FILE);
2524
- if (!fs4.existsSync(cachePath)) {
3284
+ const cachePath = path5.resolve(cwd, SPEC_CACHE_FILE);
3285
+ if (!fs6.existsSync(cachePath)) {
2525
3286
  return null;
2526
3287
  }
2527
- const content = fs4.readFileSync(cachePath, "utf-8");
3288
+ const content = fs6.readFileSync(cachePath, "utf-8");
2528
3289
  return JSON.parse(content);
2529
3290
  } catch {
2530
3291
  return null;
@@ -2536,7 +3297,7 @@ function saveSpecCache(spec, context) {
2536
3297
  cacheVersion: CACHE_VERSION,
2537
3298
  generatedAt: new Date().toISOString(),
2538
3299
  specVersion: spec.openpkg,
2539
- entryFile: path4.relative(cwd, entryFile),
3300
+ entryFile: path5.relative(cwd, entryFile),
2540
3301
  hashes: {
2541
3302
  tsconfig: tsconfigPath ? hashFile(tsconfigPath) : null,
2542
3303
  packageJson: hashFile(packageJsonPath) ?? "",
@@ -2545,19 +3306,19 @@ function saveSpecCache(spec, context) {
2545
3306
  config,
2546
3307
  spec
2547
3308
  };
2548
- const cachePath = path4.resolve(cwd, SPEC_CACHE_FILE);
2549
- const dir = path4.dirname(cachePath);
2550
- if (!fs4.existsSync(dir)) {
2551
- fs4.mkdirSync(dir, { recursive: true });
3309
+ const cachePath = path5.resolve(cwd, SPEC_CACHE_FILE);
3310
+ const dir = path5.dirname(cachePath);
3311
+ if (!fs6.existsSync(dir)) {
3312
+ fs6.mkdirSync(dir, { recursive: true });
2552
3313
  }
2553
- fs4.writeFileSync(cachePath, JSON.stringify(cache, null, 2));
3314
+ fs6.writeFileSync(cachePath, JSON.stringify(cache, null, 2));
2554
3315
  }
2555
3316
  function validateSpecCache(cache, context) {
2556
3317
  const { entryFile, sourceFiles, tsconfigPath, packageJsonPath, config, cwd } = context;
2557
3318
  if (cache.cacheVersion !== CACHE_VERSION) {
2558
3319
  return { valid: false, reason: "cache-version-mismatch" };
2559
3320
  }
2560
- const relativeEntry = path4.relative(cwd, entryFile);
3321
+ const relativeEntry = path5.relative(cwd, entryFile);
2561
3322
  if (cache.entryFile !== relativeEntry) {
2562
3323
  return { valid: false, reason: "entry-file-changed" };
2563
3324
  }
@@ -2580,34 +3341,333 @@ function validateSpecCache(cache, context) {
2580
3341
  return { valid: true };
2581
3342
  }
2582
3343
  function clearSpecCache(cwd) {
2583
- const cachePath = path4.resolve(cwd, SPEC_CACHE_FILE);
2584
- if (fs4.existsSync(cachePath)) {
2585
- fs4.unlinkSync(cachePath);
3344
+ const cachePath = path5.resolve(cwd, SPEC_CACHE_FILE);
3345
+ if (fs6.existsSync(cachePath)) {
3346
+ fs6.unlinkSync(cachePath);
2586
3347
  return true;
2587
3348
  }
2588
3349
  return false;
2589
3350
  }
2590
3351
  function getSpecCachePath(cwd) {
2591
- return path4.resolve(cwd, SPEC_CACHE_FILE);
3352
+ return path5.resolve(cwd, SPEC_CACHE_FILE);
3353
+ }
3354
+ // src/codeowners/index.ts
3355
+ import * as fs7 from "node:fs";
3356
+ import * as path6 from "node:path";
3357
+ import { minimatch } from "minimatch";
3358
+ var CODEOWNERS_LOCATIONS = [
3359
+ "CODEOWNERS",
3360
+ ".github/CODEOWNERS",
3361
+ "docs/CODEOWNERS"
3362
+ ];
3363
+ function parseCodeOwners(content) {
3364
+ const rules = [];
3365
+ const lines = content.split(`
3366
+ `);
3367
+ for (let i = 0;i < lines.length; i++) {
3368
+ const line = lines[i].trim();
3369
+ if (!line || line.startsWith("#"))
3370
+ continue;
3371
+ const parts = line.split(/\s+/);
3372
+ if (parts.length < 2)
3373
+ continue;
3374
+ const pattern = parts[0];
3375
+ const owners = parts.slice(1).filter((o) => o.startsWith("@") || o.includes("@"));
3376
+ if (owners.length > 0) {
3377
+ rules.push({
3378
+ line: i + 1,
3379
+ pattern,
3380
+ owners
3381
+ });
3382
+ }
3383
+ }
3384
+ return rules;
3385
+ }
3386
+ function loadCodeOwners(baseDir) {
3387
+ for (const location of CODEOWNERS_LOCATIONS) {
3388
+ const filePath = path6.join(baseDir, location);
3389
+ if (fs7.existsSync(filePath)) {
3390
+ const content = fs7.readFileSync(filePath, "utf-8");
3391
+ return {
3392
+ filePath,
3393
+ rules: parseCodeOwners(content)
3394
+ };
3395
+ }
3396
+ }
3397
+ return null;
3398
+ }
3399
+ function findOwners(filePath, rules) {
3400
+ const normalizedPath = filePath.replace(/^\.\//, "");
3401
+ let matchingOwners = [];
3402
+ for (const rule of rules) {
3403
+ let pattern = rule.pattern;
3404
+ if (pattern.endsWith("/")) {
3405
+ pattern = pattern + "**";
3406
+ }
3407
+ if (pattern.startsWith("/")) {
3408
+ pattern = pattern.slice(1);
3409
+ } else if (!pattern.startsWith("*")) {
3410
+ pattern = "**/" + pattern;
3411
+ }
3412
+ if (minimatch(normalizedPath, pattern, { matchBase: true, dot: true })) {
3413
+ matchingOwners = rule.owners;
3414
+ }
3415
+ }
3416
+ return matchingOwners;
3417
+ }
3418
+ function attributeOwners(exports, rules, baseDir) {
3419
+ const ownershipMap = new Map;
3420
+ for (const exp of exports) {
3421
+ const filePath = exp.source?.file;
3422
+ if (!filePath) {
3423
+ ownershipMap.set(exp, []);
3424
+ continue;
3425
+ }
3426
+ let relativePath = filePath;
3427
+ if (path6.isAbsolute(filePath)) {
3428
+ relativePath = path6.relative(baseDir, filePath);
3429
+ }
3430
+ const owners = findOwners(relativePath, rules);
3431
+ ownershipMap.set(exp, owners);
3432
+ }
3433
+ return ownershipMap;
3434
+ }
3435
+ function analyzeOwnership(spec, codeowners, baseDir) {
3436
+ const exports = spec.exports ?? [];
3437
+ const ownershipMap = attributeOwners(exports, codeowners.rules, baseDir);
3438
+ const byOwnerExports = new Map;
3439
+ const unowned = [];
3440
+ for (const [exp, owners] of ownershipMap) {
3441
+ if (owners.length === 0) {
3442
+ unowned.push(exp);
3443
+ } else {
3444
+ for (const owner of owners) {
3445
+ const existing = byOwnerExports.get(owner) ?? [];
3446
+ existing.push(exp);
3447
+ byOwnerExports.set(owner, existing);
3448
+ }
3449
+ }
3450
+ }
3451
+ const byOwner = new Map;
3452
+ for (const [owner, ownerExports] of byOwnerExports) {
3453
+ const documented = ownerExports.filter((e) => e.description);
3454
+ const withDrift = ownerExports.filter((e) => e.docs?.drift && e.docs.drift.length > 0);
3455
+ const withExamples = ownerExports.filter((e) => e.examples && e.examples.length > 0);
3456
+ const undocumented = ownerExports.filter((e) => !e.description);
3457
+ const coverageScore = ownerExports.length === 0 ? 100 : Math.round(documented.length / ownerExports.length * 100);
3458
+ const driftScore = ownerExports.length === 0 ? 0 : Math.round(withDrift.length / ownerExports.length * 100);
3459
+ byOwner.set(owner, {
3460
+ owner,
3461
+ totalExports: ownerExports.length,
3462
+ documentedExports: documented.length,
3463
+ coverageScore,
3464
+ exportsWithDrift: withDrift.length,
3465
+ driftScore,
3466
+ missingExamples: ownerExports.length - withExamples.length,
3467
+ undocumentedExports: undocumented.map((e) => e.name)
3468
+ });
3469
+ }
3470
+ return {
3471
+ codeownersPath: codeowners.filePath,
3472
+ byOwner,
3473
+ unowned,
3474
+ totalExports: exports.length
3475
+ };
3476
+ }
3477
+ function analyzeSpecOwnership(spec, options) {
3478
+ const codeowners = loadCodeOwners(options.baseDir);
3479
+ if (!codeowners)
3480
+ return null;
3481
+ return analyzeOwnership(spec, codeowners, options.baseDir);
3482
+ }
3483
+ // src/contributors/index.ts
3484
+ import { execSync } from "node:child_process";
3485
+ import * as path7 from "node:path";
3486
+ function parseBlameOutput(output) {
3487
+ const results = [];
3488
+ const lines = output.split(`
3489
+ `);
3490
+ let currentCommit = "";
3491
+ let currentAuthor = "";
3492
+ let currentEmail = "";
3493
+ let currentTime = 0;
3494
+ let lineNumber = 0;
3495
+ for (const line of lines) {
3496
+ if (line.match(/^[0-9a-f]{40}/)) {
3497
+ const parts = line.split(" ");
3498
+ currentCommit = parts[0];
3499
+ lineNumber = parseInt(parts[2], 10);
3500
+ } else if (line.startsWith("author ")) {
3501
+ currentAuthor = line.slice(7);
3502
+ } else if (line.startsWith("author-mail ")) {
3503
+ currentEmail = line.slice(12).replace(/[<>]/g, "");
3504
+ } else if (line.startsWith("author-time ")) {
3505
+ currentTime = parseInt(line.slice(12), 10);
3506
+ } else if (line.startsWith("\t")) {
3507
+ results.push({
3508
+ commit: currentCommit,
3509
+ author: currentAuthor,
3510
+ email: currentEmail,
3511
+ line: lineNumber,
3512
+ timestamp: currentTime
3513
+ });
3514
+ }
3515
+ }
3516
+ return results;
3517
+ }
3518
+ function getFileBlame(filePath, cwd) {
3519
+ try {
3520
+ const relativePath = path7.isAbsolute(filePath) ? path7.relative(cwd, filePath) : filePath;
3521
+ const output = execSync(`git blame --porcelain "${relativePath}"`, {
3522
+ cwd,
3523
+ encoding: "utf-8",
3524
+ maxBuffer: 10 * 1024 * 1024,
3525
+ stdio: ["pipe", "pipe", "pipe"]
3526
+ });
3527
+ return parseBlameOutput(output);
3528
+ } catch {
3529
+ return null;
3530
+ }
3531
+ }
3532
+ function getBlameForLines(filePath, startLine, endLine, cwd) {
3533
+ try {
3534
+ const relativePath = path7.isAbsolute(filePath) ? path7.relative(cwd, filePath) : filePath;
3535
+ const output = execSync(`git blame --porcelain -L ${startLine},${endLine} "${relativePath}"`, {
3536
+ cwd,
3537
+ encoding: "utf-8",
3538
+ maxBuffer: 10 * 1024 * 1024,
3539
+ stdio: ["pipe", "pipe", "pipe"]
3540
+ });
3541
+ return parseBlameOutput(output);
3542
+ } catch {
3543
+ return null;
3544
+ }
3545
+ }
3546
+ function findPrimaryAuthor(blameLines) {
3547
+ if (blameLines.length === 0)
3548
+ return null;
3549
+ const authorCounts = new Map;
3550
+ for (const line of blameLines) {
3551
+ const key = line.email;
3552
+ const existing = authorCounts.get(key);
3553
+ if (existing) {
3554
+ existing.count++;
3555
+ if (line.timestamp > existing.info.timestamp) {
3556
+ existing.info = line;
3557
+ }
3558
+ } else {
3559
+ authorCounts.set(key, { count: 1, info: line });
3560
+ }
3561
+ }
3562
+ let maxCount = 0;
3563
+ let primaryAuthor = null;
3564
+ for (const { count, info } of authorCounts.values()) {
3565
+ if (count > maxCount) {
3566
+ maxCount = count;
3567
+ primaryAuthor = info;
3568
+ }
3569
+ }
3570
+ return primaryAuthor;
3571
+ }
3572
+ function analyzeContributors(spec, baseDir) {
3573
+ const exports = spec.exports ?? [];
3574
+ const byContributor = new Map;
3575
+ const unattributed = [];
3576
+ let totalDocumented = 0;
3577
+ const fileBlameCache = new Map;
3578
+ for (const exp of exports) {
3579
+ if (!exp.description)
3580
+ continue;
3581
+ const filePath = exp.source?.file;
3582
+ const line = exp.source?.line;
3583
+ if (!filePath || !line) {
3584
+ unattributed.push(exp.name);
3585
+ continue;
3586
+ }
3587
+ let fileBlame = fileBlameCache.get(filePath);
3588
+ if (fileBlame === undefined) {
3589
+ fileBlame = getFileBlame(filePath, baseDir);
3590
+ fileBlameCache.set(filePath, fileBlame);
3591
+ }
3592
+ if (!fileBlame) {
3593
+ unattributed.push(exp.name);
3594
+ continue;
3595
+ }
3596
+ const jsDocStartLine = Math.max(1, line - 20);
3597
+ const declarationLine = line;
3598
+ const jsDocBlame = fileBlame.filter((b) => b.line >= jsDocStartLine && b.line < declarationLine);
3599
+ const primaryAuthor = findPrimaryAuthor(jsDocBlame);
3600
+ if (!primaryAuthor) {
3601
+ const declBlame = fileBlame.find((b) => b.line === declarationLine);
3602
+ if (declBlame) {
3603
+ attributeToContributor(byContributor, declBlame, exp.name, 1);
3604
+ totalDocumented++;
3605
+ } else {
3606
+ unattributed.push(exp.name);
3607
+ }
3608
+ continue;
3609
+ }
3610
+ const linesAuthored = jsDocBlame.filter((b) => b.email === primaryAuthor.email).length;
3611
+ attributeToContributor(byContributor, primaryAuthor, exp.name, linesAuthored);
3612
+ totalDocumented++;
3613
+ }
3614
+ return {
3615
+ byContributor,
3616
+ totalDocumented,
3617
+ unattributed
3618
+ };
3619
+ }
3620
+ function attributeToContributor(byContributor, blame, exportName, lines) {
3621
+ const key = blame.email;
3622
+ const existing = byContributor.get(key);
3623
+ if (existing) {
3624
+ existing.documentedExports++;
3625
+ existing.exports.push(exportName);
3626
+ existing.linesAuthored += lines;
3627
+ const blameDate = new Date(blame.timestamp * 1000);
3628
+ if (!existing.lastContribution || blameDate > existing.lastContribution) {
3629
+ existing.lastContribution = blameDate;
3630
+ }
3631
+ } else {
3632
+ byContributor.set(key, {
3633
+ name: blame.author,
3634
+ email: blame.email,
3635
+ documentedExports: 1,
3636
+ exports: [exportName],
3637
+ linesAuthored: lines,
3638
+ lastContribution: new Date(blame.timestamp * 1000)
3639
+ });
3640
+ }
3641
+ }
3642
+ function analyzeSpecContributors(spec, options) {
3643
+ try {
3644
+ execSync("git rev-parse --git-dir", {
3645
+ cwd: options.baseDir,
3646
+ stdio: ["pipe", "pipe", "pipe"]
3647
+ });
3648
+ } catch {
3649
+ return null;
3650
+ }
3651
+ return analyzeContributors(spec, options.baseDir);
2592
3652
  }
2593
3653
  // src/config/types.ts
2594
3654
  function defineConfig(config) {
2595
3655
  return config;
2596
3656
  }
2597
3657
  // src/detect/utils.ts
2598
- async function safeParseJson(fs5, path5) {
3658
+ async function safeParseJson(fs8, path8) {
2599
3659
  try {
2600
- if (!await fs5.exists(path5))
3660
+ if (!await fs8.exists(path8))
2601
3661
  return null;
2602
- const content = await fs5.readFile(path5);
3662
+ const content = await fs8.readFile(path8);
2603
3663
  return JSON.parse(content);
2604
3664
  } catch {
2605
3665
  return null;
2606
3666
  }
2607
3667
  }
2608
- async function readPackageJson(fs5, dir) {
2609
- const path5 = dir === "." ? "package.json" : `${dir}/package.json`;
2610
- return safeParseJson(fs5, path5);
3668
+ async function readPackageJson(fs8, dir) {
3669
+ const path8 = dir === "." ? "package.json" : `${dir}/package.json`;
3670
+ return safeParseJson(fs8, path8);
2611
3671
  }
2612
3672
 
2613
3673
  // src/detect/build.ts
@@ -2637,8 +3697,8 @@ var BUILD_TOOL_PATTERNS = [
2637
3697
  "babel",
2638
3698
  "ncc build"
2639
3699
  ];
2640
- async function detectBuildInfo(fs5, packagePath = ".") {
2641
- const pkgJson = await readPackageJson(fs5, packagePath);
3700
+ async function detectBuildInfo(fs8, packagePath = ".") {
3701
+ const pkgJson = await readPackageJson(fs8, packagePath);
2642
3702
  const scripts = pkgJson?.scripts ?? {};
2643
3703
  const scriptNames = Object.keys(scripts);
2644
3704
  const buildScriptsByName = scriptNames.filter((name) => BUILD_SCRIPT_NAMES.has(name) || BUILD_SCRIPT_PREFIXES.some((prefix) => name.startsWith(prefix)));
@@ -2650,10 +3710,10 @@ async function detectBuildInfo(fs5, packagePath = ".") {
2650
3710
  });
2651
3711
  const buildScripts = [...new Set([...buildScriptsByName, ...buildScriptsByContent])];
2652
3712
  const tsconfigPath = packagePath === "." ? "tsconfig.json" : `${packagePath}/tsconfig.json`;
2653
- const hasTsConfig = await fs5.exists(tsconfigPath);
3713
+ const hasTsConfig = await fs8.exists(tsconfigPath);
2654
3714
  const hasTsDep = pkgJson?.devDependencies?.typescript !== undefined || pkgJson?.dependencies?.typescript !== undefined;
2655
3715
  const hasTypeScript = hasTsConfig || hasTsDep;
2656
- const wasm = await detectWasmProject(fs5, packagePath, pkgJson);
3716
+ const wasm = await detectWasmProject(fs8, packagePath, pkgJson);
2657
3717
  const napi = detectNapiProject(pkgJson);
2658
3718
  return {
2659
3719
  scripts: buildScripts,
@@ -2670,11 +3730,11 @@ var WASM_PACKAGES = new Set([
2670
3730
  "wasm-bindgen",
2671
3731
  "@aspect/aspect-cli"
2672
3732
  ]);
2673
- async function detectWasmProject(fs5, packagePath, pkgJson) {
3733
+ async function detectWasmProject(fs8, packagePath, pkgJson) {
2674
3734
  const pkgCargoPath = packagePath === "." ? "Cargo.toml" : `${packagePath}/Cargo.toml`;
2675
- if (await fs5.exists(pkgCargoPath))
3735
+ if (await fs8.exists(pkgCargoPath))
2676
3736
  return true;
2677
- if (packagePath !== "." && await fs5.exists("Cargo.toml"))
3737
+ if (packagePath !== "." && await fs8.exists("Cargo.toml"))
2678
3738
  return true;
2679
3739
  if (pkgJson) {
2680
3740
  const deps = Object.keys({
@@ -2715,15 +3775,15 @@ function getPrimaryBuildScript(buildInfo) {
2715
3775
  return buildInfo.scripts[0] ?? null;
2716
3776
  }
2717
3777
  // src/detect/entry-point.ts
2718
- async function detectEntryPoint(fs5, packagePath = ".") {
2719
- const pkgJson = await readPackageJson(fs5, packagePath);
3778
+ async function detectEntryPoint(fs8, packagePath = ".") {
3779
+ const pkgJson = await readPackageJson(fs8, packagePath);
2720
3780
  if (!pkgJson) {
2721
3781
  throw new Error("No package.json found - not a valid npm package");
2722
3782
  }
2723
- const tsConfig = await parseTsConfig(fs5, packagePath);
3783
+ const tsConfig = await parseTsConfig(fs8, packagePath);
2724
3784
  const typesField = pkgJson.types || pkgJson.typings;
2725
3785
  if (typesField && typeof typesField === "string") {
2726
- const resolved = await resolveToSource(fs5, packagePath, typesField, tsConfig);
3786
+ const resolved = await resolveToSource(fs8, packagePath, typesField, tsConfig);
2727
3787
  if (resolved) {
2728
3788
  return { ...resolved, source: "types" };
2729
3789
  }
@@ -2733,7 +3793,7 @@ async function detectEntryPoint(fs5, packagePath = ".") {
2733
3793
  if (dotExport && typeof dotExport === "object" && "types" in dotExport) {
2734
3794
  const typesPath = dotExport.types;
2735
3795
  if (typesPath && typeof typesPath === "string") {
2736
- const resolved = await resolveToSource(fs5, packagePath, typesPath, tsConfig);
3796
+ const resolved = await resolveToSource(fs8, packagePath, typesPath, tsConfig);
2737
3797
  if (resolved) {
2738
3798
  return { ...resolved, source: "exports" };
2739
3799
  }
@@ -2741,13 +3801,13 @@ async function detectEntryPoint(fs5, packagePath = ".") {
2741
3801
  }
2742
3802
  }
2743
3803
  if (pkgJson.main && typeof pkgJson.main === "string") {
2744
- const resolved = await resolveToSource(fs5, packagePath, pkgJson.main, tsConfig);
3804
+ const resolved = await resolveToSource(fs8, packagePath, pkgJson.main, tsConfig);
2745
3805
  if (resolved) {
2746
3806
  return { ...resolved, source: "main" };
2747
3807
  }
2748
3808
  }
2749
3809
  if (pkgJson.module && typeof pkgJson.module === "string") {
2750
- const resolved = await resolveToSource(fs5, packagePath, pkgJson.module, tsConfig);
3810
+ const resolved = await resolveToSource(fs8, packagePath, pkgJson.module, tsConfig);
2751
3811
  if (resolved) {
2752
3812
  return { ...resolved, source: "module" };
2753
3813
  }
@@ -2765,27 +3825,27 @@ async function detectEntryPoint(fs5, packagePath = ".") {
2765
3825
  ];
2766
3826
  for (const fallback of fallbacks) {
2767
3827
  const checkPath = packagePath === "." ? fallback : `${packagePath}/${fallback}`;
2768
- if (await fs5.exists(checkPath)) {
3828
+ if (await fs8.exists(checkPath)) {
2769
3829
  return { path: fallback, source: "fallback", isDeclarationOnly: false };
2770
3830
  }
2771
3831
  }
2772
3832
  throw new Error("Could not detect TypeScript entry point. No types field in package.json and no common entry paths found.");
2773
3833
  }
2774
- async function parseTsConfig(fs5, packagePath) {
3834
+ async function parseTsConfig(fs8, packagePath) {
2775
3835
  const tsconfigPath = packagePath === "." ? "tsconfig.json" : `${packagePath}/tsconfig.json`;
2776
- const tsconfig = await safeParseJson(fs5, tsconfigPath);
3836
+ const tsconfig = await safeParseJson(fs8, tsconfigPath);
2777
3837
  if (!tsconfig?.compilerOptions) {
2778
3838
  return null;
2779
3839
  }
2780
3840
  const { outDir, rootDir, baseUrl, paths } = tsconfig.compilerOptions;
2781
3841
  return { outDir, rootDir, baseUrl, paths };
2782
3842
  }
2783
- async function resolveToSource(fs5, basePath, filePath, tsConfig) {
3843
+ async function resolveToSource(fs8, basePath, filePath, tsConfig) {
2784
3844
  const normalized = filePath.replace(/^\.\//, "");
2785
3845
  const checkPath = (p) => basePath === "." ? p : `${basePath}/${p}`;
2786
3846
  const isSourceTs = normalized.endsWith(".ts") && !normalized.endsWith(".d.ts") || normalized.endsWith(".tsx");
2787
3847
  if (isSourceTs) {
2788
- if (await fs5.exists(checkPath(normalized))) {
3848
+ if (await fs8.exists(checkPath(normalized))) {
2789
3849
  return { path: normalized, isDeclarationOnly: false };
2790
3850
  }
2791
3851
  }
@@ -2827,19 +3887,19 @@ async function resolveToSource(fs5, basePath, filePath, tsConfig) {
2827
3887
  for (const candidate of candidates) {
2828
3888
  if (candidate.endsWith(".d.ts"))
2829
3889
  continue;
2830
- if (await fs5.exists(checkPath(candidate))) {
3890
+ if (await fs8.exists(checkPath(candidate))) {
2831
3891
  return { path: candidate, isDeclarationOnly: false };
2832
3892
  }
2833
3893
  }
2834
3894
  if (normalized.endsWith(".d.ts")) {
2835
- if (await fs5.exists(checkPath(normalized))) {
3895
+ if (await fs8.exists(checkPath(normalized))) {
2836
3896
  return { path: normalized, isDeclarationOnly: true };
2837
3897
  }
2838
3898
  }
2839
3899
  return null;
2840
3900
  }
2841
3901
  // src/detect/filesystem.ts
2842
- import * as fs5 from "node:fs";
3902
+ import * as fs8 from "node:fs";
2843
3903
  import * as nodePath from "node:path";
2844
3904
  import { Writable } from "node:stream";
2845
3905
 
@@ -2852,19 +3912,19 @@ class NodeFileSystem {
2852
3912
  return nodePath.join(this.basePath, relativePath);
2853
3913
  }
2854
3914
  async exists(relativePath) {
2855
- return fs5.existsSync(this.resolve(relativePath));
3915
+ return fs8.existsSync(this.resolve(relativePath));
2856
3916
  }
2857
3917
  async readFile(relativePath) {
2858
- return fs5.readFileSync(this.resolve(relativePath), "utf-8");
3918
+ return fs8.readFileSync(this.resolve(relativePath), "utf-8");
2859
3919
  }
2860
3920
  async readDir(relativePath) {
2861
- return fs5.readdirSync(this.resolve(relativePath));
3921
+ return fs8.readdirSync(this.resolve(relativePath));
2862
3922
  }
2863
3923
  async isDirectory(relativePath) {
2864
3924
  const fullPath = this.resolve(relativePath);
2865
- if (!fs5.existsSync(fullPath))
3925
+ if (!fs8.existsSync(fullPath))
2866
3926
  return false;
2867
- return fs5.statSync(fullPath).isDirectory();
3927
+ return fs8.statSync(fullPath).isDirectory();
2868
3928
  }
2869
3929
  }
2870
3930
  function createCaptureStream() {
@@ -2880,9 +3940,9 @@ function createCaptureStream() {
2880
3940
 
2881
3941
  class FileNotFoundError extends Error {
2882
3942
  path;
2883
- constructor(path5, message) {
2884
- super(message ?? `File not found: ${path5}`);
2885
- this.path = path5;
3943
+ constructor(path8, message) {
3944
+ super(message ?? `File not found: ${path8}`);
3945
+ this.path = path8;
2886
3946
  this.name = "FileNotFoundError";
2887
3947
  }
2888
3948
  }
@@ -2892,34 +3952,34 @@ class SandboxFileSystem {
2892
3952
  constructor(sandbox) {
2893
3953
  this.sandbox = sandbox;
2894
3954
  }
2895
- async exists(path5) {
3955
+ async exists(path8) {
2896
3956
  const result = await this.sandbox.runCommand({
2897
3957
  cmd: "test",
2898
- args: ["-e", path5]
3958
+ args: ["-e", path8]
2899
3959
  });
2900
3960
  return result.exitCode === 0;
2901
3961
  }
2902
- async readFile(path5) {
2903
- const exists = await this.exists(path5);
3962
+ async readFile(path8) {
3963
+ const exists = await this.exists(path8);
2904
3964
  if (!exists) {
2905
- throw new FileNotFoundError(path5);
3965
+ throw new FileNotFoundError(path8);
2906
3966
  }
2907
3967
  const capture = createCaptureStream();
2908
3968
  const result = await this.sandbox.runCommand({
2909
3969
  cmd: "cat",
2910
- args: [path5],
3970
+ args: [path8],
2911
3971
  stdout: capture.stream
2912
3972
  });
2913
3973
  if (result.exitCode !== 0) {
2914
- throw new FileNotFoundError(path5, `Failed to read file: ${path5}`);
3974
+ throw new FileNotFoundError(path8, `Failed to read file: ${path8}`);
2915
3975
  }
2916
3976
  return capture.getOutput();
2917
3977
  }
2918
- async readDir(path5) {
3978
+ async readDir(path8) {
2919
3979
  const capture = createCaptureStream();
2920
3980
  const result = await this.sandbox.runCommand({
2921
3981
  cmd: "ls",
2922
- args: ["-1", path5],
3982
+ args: ["-1", path8],
2923
3983
  stdout: capture.stream
2924
3984
  });
2925
3985
  if (result.exitCode !== 0) {
@@ -2928,20 +3988,20 @@ class SandboxFileSystem {
2928
3988
  return capture.getOutput().split(`
2929
3989
  `).filter(Boolean);
2930
3990
  }
2931
- async isDirectory(path5) {
3991
+ async isDirectory(path8) {
2932
3992
  const result = await this.sandbox.runCommand({
2933
3993
  cmd: "test",
2934
- args: ["-d", path5]
3994
+ args: ["-d", path8]
2935
3995
  });
2936
3996
  return result.exitCode === 0;
2937
3997
  }
2938
3998
  }
2939
3999
  // src/detect/monorepo.ts
2940
- async function detectMonorepo(fs6) {
2941
- const pkgJson = await readPackageJson(fs6, ".");
4000
+ async function detectMonorepo(fs9) {
4001
+ const pkgJson = await readPackageJson(fs9, ".");
2942
4002
  if (pkgJson?.workspaces) {
2943
4003
  const patterns = extractWorkspacePatterns(pkgJson.workspaces);
2944
- const packages = await resolveWorkspacePackages(fs6, patterns, pkgJson.name, pkgJson.private);
4004
+ const packages = await resolveWorkspacePackages(fs9, patterns, pkgJson.name, pkgJson.private);
2945
4005
  return {
2946
4006
  isMonorepo: packages.length > 0,
2947
4007
  type: "npm-workspaces",
@@ -2949,10 +4009,10 @@ async function detectMonorepo(fs6) {
2949
4009
  packages
2950
4010
  };
2951
4011
  }
2952
- if (await fs6.exists("pnpm-workspace.yaml")) {
2953
- const content = await fs6.readFile("pnpm-workspace.yaml");
4012
+ if (await fs9.exists("pnpm-workspace.yaml")) {
4013
+ const content = await fs9.readFile("pnpm-workspace.yaml");
2954
4014
  const patterns = parsePnpmWorkspace(content);
2955
- const packages = await resolveWorkspacePackages(fs6, patterns, pkgJson?.name, pkgJson?.private);
4015
+ const packages = await resolveWorkspacePackages(fs9, patterns, pkgJson?.name, pkgJson?.private);
2956
4016
  return {
2957
4017
  isMonorepo: packages.length > 0,
2958
4018
  type: "pnpm-workspaces",
@@ -2960,10 +4020,10 @@ async function detectMonorepo(fs6) {
2960
4020
  packages
2961
4021
  };
2962
4022
  }
2963
- if (await fs6.exists("lerna.json")) {
2964
- const lerna = await safeParseJson(fs6, "lerna.json");
4023
+ if (await fs9.exists("lerna.json")) {
4024
+ const lerna = await safeParseJson(fs9, "lerna.json");
2965
4025
  const patterns = lerna?.packages ?? ["packages/*"];
2966
- const packages = await resolveWorkspacePackages(fs6, patterns, pkgJson?.name, pkgJson?.private);
4026
+ const packages = await resolveWorkspacePackages(fs9, patterns, pkgJson?.name, pkgJson?.private);
2967
4027
  return {
2968
4028
  isMonorepo: packages.length > 0,
2969
4029
  type: "lerna",
@@ -3037,7 +4097,7 @@ function parsePnpmWorkspace(content) {
3037
4097
  }
3038
4098
  return patterns.length > 0 ? patterns : ["packages/*"];
3039
4099
  }
3040
- async function resolveWorkspacePackages(fs6, patterns, rootPackageName, rootIsPrivate) {
4100
+ async function resolveWorkspacePackages(fs9, patterns, rootPackageName, rootIsPrivate) {
3041
4101
  const packages = [];
3042
4102
  const seen = new Set;
3043
4103
  if (rootPackageName && !rootIsPrivate && rootPackageName !== "root") {
@@ -3059,18 +4119,18 @@ async function resolveWorkspacePackages(fs6, patterns, rootPackageName, rootIsPr
3059
4119
  }
3060
4120
  dirsToScan.add("packages");
3061
4121
  for (const dir of dirsToScan) {
3062
- if (!await fs6.exists(dir))
4122
+ if (!await fs9.exists(dir))
3063
4123
  continue;
3064
- if (!await fs6.isDirectory(dir))
4124
+ if (!await fs9.isDirectory(dir))
3065
4125
  continue;
3066
- const subdirs = await fs6.readDir(dir);
4126
+ const subdirs = await fs9.readDir(dir);
3067
4127
  for (const subdir of subdirs) {
3068
4128
  const pkgPath = `${dir}/${subdir}`;
3069
4129
  const pkgJsonPath = `${pkgPath}/package.json`;
3070
- if (!await fs6.exists(pkgJsonPath))
4130
+ if (!await fs9.exists(pkgJsonPath))
3071
4131
  continue;
3072
4132
  try {
3073
- const content = await fs6.readFile(pkgJsonPath);
4133
+ const content = await fs9.readFile(pkgJsonPath);
3074
4134
  const pkg = JSON.parse(content);
3075
4135
  if (pkg.name && !seen.has(pkg.name)) {
3076
4136
  seen.add(pkg.name);
@@ -3142,14 +4202,14 @@ var DEFAULT_PM = {
3142
4202
  installArgs: ["install", "--legacy-peer-deps"],
3143
4203
  runPrefix: ["npm", "run"]
3144
4204
  };
3145
- async function detectPackageManager(fs6) {
3146
- const pkgJson = await safeParseJson(fs6, "package.json");
4205
+ async function detectPackageManager(fs9) {
4206
+ const pkgJson = await safeParseJson(fs9, "package.json");
3147
4207
  if (pkgJson?.packageManager) {
3148
4208
  const pmName = parsePackageManagerField(pkgJson.packageManager);
3149
4209
  if (pmName && PM_CONFIGS[pmName]) {
3150
4210
  const config = PM_CONFIGS[pmName];
3151
4211
  for (const lockfile of config.lockfiles) {
3152
- if (await fs6.exists(lockfile)) {
4212
+ if (await fs9.exists(lockfile)) {
3153
4213
  return { ...config.info, lockfile };
3154
4214
  }
3155
4215
  }
@@ -3159,7 +4219,7 @@ async function detectPackageManager(fs6) {
3159
4219
  const foundLockfiles = [];
3160
4220
  for (const [pmName, config] of Object.entries(PM_CONFIGS)) {
3161
4221
  for (const lockfile of config.lockfiles) {
3162
- if (await fs6.exists(lockfile)) {
4222
+ if (await fs9.exists(lockfile)) {
3163
4223
  foundLockfiles.push({ lockfile, pm: pmName });
3164
4224
  }
3165
4225
  }
@@ -3191,10 +4251,10 @@ function getRunCommand(pm, script) {
3191
4251
  return [...pm.runPrefix, script];
3192
4252
  }
3193
4253
  // src/detect/index.ts
3194
- async function analyzeProject(fs6, options = {}) {
4254
+ async function analyzeProject(fs9, options = {}) {
3195
4255
  const [packageManager, monorepo] = await Promise.all([
3196
- detectPackageManager(fs6),
3197
- detectMonorepo(fs6)
4256
+ detectPackageManager(fs9),
4257
+ detectMonorepo(fs9)
3198
4258
  ]);
3199
4259
  let targetPath = ".";
3200
4260
  if (monorepo.isMonorepo) {
@@ -3211,8 +4271,8 @@ async function analyzeProject(fs6, options = {}) {
3211
4271
  targetPath = pkg.path;
3212
4272
  }
3213
4273
  const [entryPoint, build] = await Promise.all([
3214
- detectEntryPoint(fs6, targetPath),
3215
- detectBuildInfo(fs6, targetPath)
4274
+ detectEntryPoint(fs9, targetPath),
4275
+ detectBuildInfo(fs9, targetPath)
3216
4276
  ]);
3217
4277
  return { packageManager, monorepo, entryPoint, build };
3218
4278
  }
@@ -3256,17 +4316,17 @@ function shouldValidate(validations, check) {
3256
4316
  return validations.includes(check);
3257
4317
  }
3258
4318
  // src/typecheck/example-typechecker.ts
3259
- import * as fs6 from "node:fs";
3260
- import * as path5 from "node:path";
4319
+ import * as fs9 from "node:fs";
4320
+ import * as path8 from "node:path";
3261
4321
  import ts3 from "typescript";
3262
4322
  function stripCodeBlockMarkers(code) {
3263
4323
  return code.replace(/^```(?:ts|typescript|js|javascript)?\n?/i, "").replace(/\n?```$/i, "").trim();
3264
4324
  }
3265
4325
  function getPackageName(packagePath) {
3266
- const pkgJsonPath = path5.join(packagePath, "package.json");
3267
- if (fs6.existsSync(pkgJsonPath)) {
4326
+ const pkgJsonPath = path8.join(packagePath, "package.json");
4327
+ if (fs9.existsSync(pkgJsonPath)) {
3268
4328
  try {
3269
- const pkgJson = JSON.parse(fs6.readFileSync(pkgJsonPath, "utf-8"));
4329
+ const pkgJson = JSON.parse(fs9.readFileSync(pkgJsonPath, "utf-8"));
3270
4330
  return pkgJson.name;
3271
4331
  } catch {
3272
4332
  return;
@@ -3276,12 +4336,12 @@ function getPackageName(packagePath) {
3276
4336
  }
3277
4337
  function findTsConfig(packagePath) {
3278
4338
  let dir = packagePath;
3279
- while (dir !== path5.dirname(dir)) {
3280
- const tsConfigPath = path5.join(dir, "tsconfig.json");
3281
- if (fs6.existsSync(tsConfigPath)) {
4339
+ while (dir !== path8.dirname(dir)) {
4340
+ const tsConfigPath = path8.join(dir, "tsconfig.json");
4341
+ if (fs9.existsSync(tsConfigPath)) {
3282
4342
  return tsConfigPath;
3283
4343
  }
3284
- dir = path5.dirname(dir);
4344
+ dir = path8.dirname(dir);
3285
4345
  }
3286
4346
  return;
3287
4347
  }
@@ -3297,10 +4357,10 @@ function createVirtualSource(example, packageName, exportNames) {
3297
4357
  `);
3298
4358
  }
3299
4359
  function getCompilerOptions(tsconfigPath) {
3300
- if (tsconfigPath && fs6.existsSync(tsconfigPath)) {
4360
+ if (tsconfigPath && fs9.existsSync(tsconfigPath)) {
3301
4361
  const configFile = ts3.readConfigFile(tsconfigPath, ts3.sys.readFile);
3302
4362
  if (!configFile.error) {
3303
- const parsed = ts3.parseJsonConfigFileContent(configFile.config, ts3.sys, path5.dirname(tsconfigPath));
4363
+ const parsed = ts3.parseJsonConfigFileContent(configFile.config, ts3.sys, path8.dirname(tsconfigPath));
3304
4364
  return {
3305
4365
  ...parsed.options,
3306
4366
  noEmit: true,
@@ -3326,7 +4386,7 @@ function typecheckExample(example, packagePath, options = {}) {
3326
4386
  const tsconfigPath = options.tsconfig ?? findTsConfig(packagePath);
3327
4387
  const compilerOptions = getCompilerOptions(tsconfigPath);
3328
4388
  const virtualSource = createVirtualSource(cleanCode, packageName, exportNames);
3329
- const virtualFileName = path5.join(packagePath, "__doccov_example__.ts");
4389
+ const virtualFileName = path8.join(packagePath, "__doccov_example__.ts");
3330
4390
  const hasImport = packageName !== undefined && exportNames && exportNames.length > 0;
3331
4391
  const lineOffset = hasImport ? 2 : 0;
3332
4392
  const sourceFile = ts3.createSourceFile(virtualFileName, virtualSource, ts3.ScriptTarget.ES2022, true);
@@ -3398,20 +4458,20 @@ function typecheckExamples(examples, packagePath, options = {}) {
3398
4458
 
3399
4459
  // src/utils/example-runner.ts
3400
4460
  import { spawn } from "node:child_process";
3401
- import * as fs7 from "node:fs";
4461
+ import * as fs10 from "node:fs";
3402
4462
  import * as os from "node:os";
3403
- import * as path6 from "node:path";
4463
+ import * as path9 from "node:path";
3404
4464
  function stripCodeBlockMarkers2(code) {
3405
4465
  return code.replace(/^```(?:ts|typescript|js|javascript)?\n?/i, "").replace(/\n?```$/i, "").trim();
3406
4466
  }
3407
4467
  async function runExample(code, options = {}) {
3408
4468
  const { timeout = 5000, cwd = process.cwd() } = options;
3409
4469
  const cleanCode = stripCodeBlockMarkers2(code);
3410
- const tmpFile = path6.join(cwd, `doccov-example-${Date.now()}-${Math.random().toString(36).slice(2)}.ts`);
4470
+ const tmpFile = path9.join(cwd, `doccov-example-${Date.now()}-${Math.random().toString(36).slice(2)}.ts`);
3411
4471
  try {
3412
- fs7.writeFileSync(tmpFile, cleanCode, "utf-8");
4472
+ fs10.writeFileSync(tmpFile, cleanCode, "utf-8");
3413
4473
  const startTime = Date.now();
3414
- return await new Promise((resolve4) => {
4474
+ return await new Promise((resolve5) => {
3415
4475
  let stdout = "";
3416
4476
  let stderr = "";
3417
4477
  let killed = false;
@@ -3434,7 +4494,7 @@ async function runExample(code, options = {}) {
3434
4494
  clearTimeout(timeoutId);
3435
4495
  const duration = Date.now() - startTime;
3436
4496
  if (killed) {
3437
- resolve4({
4497
+ resolve5({
3438
4498
  success: false,
3439
4499
  stdout,
3440
4500
  stderr: stderr || `Example timed out after ${timeout}ms`,
@@ -3442,7 +4502,7 @@ async function runExample(code, options = {}) {
3442
4502
  duration
3443
4503
  });
3444
4504
  } else {
3445
- resolve4({
4505
+ resolve5({
3446
4506
  success: exitCode === 0,
3447
4507
  stdout,
3448
4508
  stderr,
@@ -3453,7 +4513,7 @@ async function runExample(code, options = {}) {
3453
4513
  });
3454
4514
  proc.on("error", (error) => {
3455
4515
  clearTimeout(timeoutId);
3456
- resolve4({
4516
+ resolve5({
3457
4517
  success: false,
3458
4518
  stdout,
3459
4519
  stderr: error.message,
@@ -3464,7 +4524,7 @@ async function runExample(code, options = {}) {
3464
4524
  });
3465
4525
  } finally {
3466
4526
  try {
3467
- fs7.unlinkSync(tmpFile);
4527
+ fs10.unlinkSync(tmpFile);
3468
4528
  } catch {}
3469
4529
  }
3470
4530
  }
@@ -3479,15 +4539,15 @@ async function runExamples(examples, options = {}) {
3479
4539
  return results;
3480
4540
  }
3481
4541
  function detectPackageManager2(cwd) {
3482
- if (fs7.existsSync(path6.join(cwd, "bun.lockb")))
4542
+ if (fs10.existsSync(path9.join(cwd, "bun.lockb")))
3483
4543
  return "bun";
3484
- if (fs7.existsSync(path6.join(cwd, "bun.lock")))
4544
+ if (fs10.existsSync(path9.join(cwd, "bun.lock")))
3485
4545
  return "bun";
3486
- if (fs7.existsSync(path6.join(cwd, "pnpm-lock.yaml")))
4546
+ if (fs10.existsSync(path9.join(cwd, "pnpm-lock.yaml")))
3487
4547
  return "pnpm";
3488
- if (fs7.existsSync(path6.join(cwd, "yarn.lock")))
4548
+ if (fs10.existsSync(path9.join(cwd, "yarn.lock")))
3489
4549
  return "yarn";
3490
- if (fs7.existsSync(path6.join(cwd, "package-lock.json")))
4550
+ if (fs10.existsSync(path9.join(cwd, "package-lock.json")))
3491
4551
  return "npm";
3492
4552
  return "npm";
3493
4553
  }
@@ -3504,7 +4564,7 @@ function getInstallCommand2(pm, packagePath) {
3504
4564
  }
3505
4565
  }
3506
4566
  async function runCommand(cmd, args, options) {
3507
- return new Promise((resolve4) => {
4567
+ return new Promise((resolve5) => {
3508
4568
  let stdout = "";
3509
4569
  let stderr = "";
3510
4570
  let killed = false;
@@ -3525,14 +4585,14 @@ async function runCommand(cmd, args, options) {
3525
4585
  proc.on("close", (exitCode) => {
3526
4586
  clearTimeout(timeoutId);
3527
4587
  if (killed) {
3528
- resolve4({
4588
+ resolve5({
3529
4589
  success: false,
3530
4590
  stdout,
3531
4591
  stderr: stderr || `Command timed out after ${options.timeout}ms`,
3532
4592
  exitCode: exitCode ?? 1
3533
4593
  });
3534
4594
  } else {
3535
- resolve4({
4595
+ resolve5({
3536
4596
  success: exitCode === 0,
3537
4597
  stdout,
3538
4598
  stderr,
@@ -3542,7 +4602,7 @@ async function runCommand(cmd, args, options) {
3542
4602
  });
3543
4603
  proc.on("error", (error) => {
3544
4604
  clearTimeout(timeoutId);
3545
- resolve4({
4605
+ resolve5({
3546
4606
  success: false,
3547
4607
  stdout,
3548
4608
  stderr: error.message,
@@ -3555,12 +4615,12 @@ async function runExamplesWithPackage(examples, options) {
3555
4615
  const { packagePath, packageManager, installTimeout = 60000, timeout = 5000 } = options;
3556
4616
  const startTime = Date.now();
3557
4617
  const results = new Map;
3558
- const absolutePackagePath = path6.resolve(packagePath);
3559
- const workDir = path6.join(os.tmpdir(), `doccov-examples-${Date.now()}-${Math.random().toString(36).slice(2)}`);
4618
+ const absolutePackagePath = path9.resolve(packagePath);
4619
+ const workDir = path9.join(os.tmpdir(), `doccov-examples-${Date.now()}-${Math.random().toString(36).slice(2)}`);
3560
4620
  try {
3561
- fs7.mkdirSync(workDir, { recursive: true });
4621
+ fs10.mkdirSync(workDir, { recursive: true });
3562
4622
  const pkgJson = { name: "doccov-example-runner", type: "module" };
3563
- fs7.writeFileSync(path6.join(workDir, "package.json"), JSON.stringify(pkgJson, null, 2));
4623
+ fs10.writeFileSync(path9.join(workDir, "package.json"), JSON.stringify(pkgJson, null, 2));
3564
4624
  const pm = packageManager ?? detectPackageManager2(options.cwd ?? process.cwd());
3565
4625
  const { cmd, args } = getInstallCommand2(pm, absolutePackagePath);
3566
4626
  const installResult = await runCommand(cmd, args, {
@@ -3588,7 +4648,7 @@ async function runExamplesWithPackage(examples, options) {
3588
4648
  };
3589
4649
  } finally {
3590
4650
  try {
3591
- fs7.rmSync(workDir, { recursive: true, force: true });
4651
+ fs10.rmSync(workDir, { recursive: true, force: true });
3592
4652
  } catch {}
3593
4653
  }
3594
4654
  }
@@ -3768,8 +4828,8 @@ async function validateExamples(exports, options) {
3768
4828
  return result;
3769
4829
  }
3770
4830
  // src/analysis/run-analysis.ts
3771
- import * as fs9 from "node:fs";
3772
- import * as path10 from "node:path";
4831
+ import * as fs12 from "node:fs";
4832
+ import * as path13 from "node:path";
3773
4833
  // src/utils/type-utils.ts
3774
4834
  function getTypeId(type, typeChecker) {
3775
4835
  const internalId = type.id;
@@ -3892,7 +4952,7 @@ function collectReferencedTypesFromNode(node, typeChecker, referencedTypes) {
3892
4952
  }
3893
4953
 
3894
4954
  // src/analysis/context.ts
3895
- import * as path8 from "node:path";
4955
+ import * as path11 from "node:path";
3896
4956
 
3897
4957
  // src/options.ts
3898
4958
  var DEFAULT_MAX_TYPE_DEPTH = 20;
@@ -3913,7 +4973,7 @@ function normalizeDocCovOptions(options = {}) {
3913
4973
  }
3914
4974
 
3915
4975
  // src/analysis/program.ts
3916
- import * as path7 from "node:path";
4976
+ import * as path10 from "node:path";
3917
4977
  var DEFAULT_COMPILER_OPTIONS = {
3918
4978
  target: ts.ScriptTarget.Latest,
3919
4979
  module: ts.ModuleKind.CommonJS,
@@ -3923,14 +4983,14 @@ var DEFAULT_COMPILER_OPTIONS = {
3923
4983
  };
3924
4984
  function createProgram({
3925
4985
  entryFile,
3926
- baseDir = path7.dirname(entryFile),
4986
+ baseDir = path10.dirname(entryFile),
3927
4987
  content
3928
4988
  }) {
3929
4989
  const configPath = ts.findConfigFile(baseDir, ts.sys.fileExists, "tsconfig.json");
3930
4990
  let compilerOptions = { ...DEFAULT_COMPILER_OPTIONS };
3931
4991
  if (configPath) {
3932
4992
  const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
3933
- const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, path7.dirname(configPath));
4993
+ const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, path10.dirname(configPath));
3934
4994
  compilerOptions = { ...compilerOptions, ...parsedConfig.options };
3935
4995
  }
3936
4996
  const allowJsVal = compilerOptions.allowJs;
@@ -3965,9 +5025,10 @@ function createAnalysisContext({
3965
5025
  entryFile,
3966
5026
  packageDir,
3967
5027
  content,
3968
- options
5028
+ options,
5029
+ detectedSchemas
3969
5030
  }) {
3970
- const baseDir = packageDir ?? path8.dirname(entryFile);
5031
+ const baseDir = packageDir ?? path11.dirname(entryFile);
3971
5032
  const normalizedOptions = normalizeDocCovOptions(options);
3972
5033
  const programResult = createProgram({ entryFile, baseDir, content });
3973
5034
  if (!programResult.sourceFile) {
@@ -3982,13 +5043,14 @@ function createAnalysisContext({
3982
5043
  compilerOptions: programResult.compilerOptions,
3983
5044
  compilerHost: programResult.compilerHost,
3984
5045
  options: normalizedOptions,
3985
- configPath: programResult.configPath
5046
+ configPath: programResult.configPath,
5047
+ detectedSchemas
3986
5048
  };
3987
5049
  }
3988
5050
 
3989
5051
  // src/analysis/spec-builder.ts
3990
- import * as fs8 from "node:fs";
3991
- import * as path9 from "node:path";
5052
+ import * as fs11 from "node:fs";
5053
+ import * as path12 from "node:path";
3992
5054
  import { SCHEMA_URL, SCHEMA_VERSION } from "@openpkg-ts/spec";
3993
5055
 
3994
5056
  // src/analysis/decorator-utils.ts
@@ -4020,291 +5082,37 @@ function extractParameterDecorators(param) {
4020
5082
  return extractDecorators(param);
4021
5083
  }
4022
5084
 
4023
- // src/utils/typebox-handler.ts
4024
- var TYPEBOX_PRIMITIVE_MAP = {
4025
- TString: { type: "string" },
4026
- TNumber: { type: "number" },
4027
- TBoolean: { type: "boolean" },
4028
- TInteger: { type: "integer" },
4029
- TNull: { type: "null" },
4030
- TAny: {},
4031
- TUnknown: {},
4032
- TNever: { not: {} },
4033
- TVoid: { type: "null" },
4034
- TUndefined: { type: "null" }
5085
+ // src/utils/schema-builder.ts
5086
+ var BUILTIN_TYPE_SCHEMAS = {
5087
+ Date: { type: "string", format: "date-time" },
5088
+ RegExp: { type: "object", description: "RegExp" },
5089
+ Error: { type: "object" },
5090
+ Promise: { type: "object" },
5091
+ Map: { type: "object" },
5092
+ Set: { type: "object" },
5093
+ WeakMap: { type: "object" },
5094
+ WeakSet: { type: "object" },
5095
+ Function: { type: "object" },
5096
+ ArrayBuffer: { type: "string", format: "binary" },
5097
+ ArrayBufferLike: { type: "string", format: "binary" },
5098
+ DataView: { type: "string", format: "binary" },
5099
+ Uint8Array: { type: "string", format: "byte" },
5100
+ Uint16Array: { type: "string", format: "byte" },
5101
+ Uint32Array: { type: "string", format: "byte" },
5102
+ Int8Array: { type: "string", format: "byte" },
5103
+ Int16Array: { type: "string", format: "byte" },
5104
+ Int32Array: { type: "string", format: "byte" },
5105
+ Float32Array: { type: "string", format: "byte" },
5106
+ Float64Array: { type: "string", format: "byte" },
5107
+ BigInt64Array: { type: "string", format: "byte" },
5108
+ BigUint64Array: { type: "string", format: "byte" }
4035
5109
  };
4036
- function isTypeBoxSchemaType(symbolName) {
4037
- return /^T[A-Z][a-zA-Z]*$/.test(symbolName);
4038
- }
4039
- function isInternalProperty(name) {
4040
- return name.startsWith("__@");
4041
- }
4042
- function isTypeBoxOptionalMarker(type) {
4043
- const props = type.getProperties();
4044
- if (props.length !== 1)
5110
+ function isObjectLiteralType(type) {
5111
+ if (!(type.getFlags() & ts.TypeFlags.Object)) {
4045
5112
  return false;
4046
- return isInternalProperty(props[0].getName());
4047
- }
4048
- function unwrapTypeBoxOptional(type) {
4049
- if (!type.isIntersection()) {
4050
- return { innerTypes: [type], isOptional: false };
4051
5113
  }
4052
- const intersectionType = type;
4053
- const filtered = intersectionType.types.filter((t) => !isTypeBoxOptionalMarker(t));
4054
- const hadMarker = filtered.length < intersectionType.types.length;
4055
- return { innerTypes: filtered, isOptional: hadMarker };
4056
- }
4057
- function getPropertyType(prop, parentType, typeChecker) {
4058
- if (prop.valueDeclaration) {
4059
- return typeChecker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration);
4060
- }
4061
- const propType = typeChecker.getTypeOfPropertyOfType(parentType, prop.getName());
4062
- if (propType) {
4063
- return propType;
4064
- }
4065
- const decl = prop.declarations?.[0];
4066
- if (decl) {
4067
- return typeChecker.getTypeOfSymbolAtLocation(prop, decl);
4068
- }
4069
- return typeChecker.getAnyType();
4070
- }
4071
- var _formatTypeReference = null;
4072
- function setFormatTypeReference(fn) {
4073
- _formatTypeReference = fn;
4074
- }
4075
- function formatTypeReferenceInternal(type, typeChecker, typeRefs, referencedTypes, visited, depth, maxDepth, typeIds) {
4076
- if (_formatTypeReference) {
4077
- return _formatTypeReference(type, typeChecker, typeRefs, referencedTypes, visited, depth, maxDepth, typeIds);
4078
- }
4079
- return { type: "object" };
4080
- }
4081
- function formatTypeBoxSchema(type, typeChecker, typeRefs, referencedTypes, visited, depth = 0, maxDepth = DEFAULT_MAX_TYPE_DEPTH, typeIds) {
4082
- if (depth > maxDepth) {
4083
- return { type: "unknown" };
4084
- }
4085
- const symbol = type.getSymbol();
4086
- if (!symbol)
4087
- return null;
4088
- const symbolName = symbol.getName();
4089
- if (TYPEBOX_PRIMITIVE_MAP[symbolName]) {
4090
- return { ...TYPEBOX_PRIMITIVE_MAP[symbolName] };
4091
- }
4092
- const objectType = type;
4093
- if (!(objectType.objectFlags & ts.ObjectFlags.Reference)) {
4094
- return null;
4095
- }
4096
- const typeRef = type;
4097
- const typeArgs = typeRef.typeArguments;
4098
- switch (symbolName) {
4099
- case "TObject": {
4100
- if (!typeArgs || typeArgs.length === 0) {
4101
- return { type: "object" };
4102
- }
4103
- const propsType = typeArgs[0];
4104
- const properties = {};
4105
- const required = [];
4106
- for (const prop of propsType.getProperties()) {
4107
- const propName = prop.getName();
4108
- if (isInternalProperty(propName)) {
4109
- continue;
4110
- }
4111
- const propType = getPropertyType(prop, propsType, typeChecker);
4112
- const propSymbol = propType.getSymbol();
4113
- const propSymbolName = propSymbol?.getName();
4114
- if (propSymbolName && typeRefs.has(propSymbolName)) {
4115
- properties[propName] = { $ref: `#/types/${propSymbolName}` };
4116
- } else if (propSymbolName && isTypeBoxSchemaType(propSymbolName)) {
4117
- const nested = formatTypeBoxSchema(propType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
4118
- properties[propName] = nested ?? { type: "object" };
4119
- } else {
4120
- properties[propName] = formatTypeReferenceInternal(propType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
4121
- }
4122
- const { isOptional } = unwrapTypeBoxOptional(propType);
4123
- if (propSymbolName !== "TOptional" && !isOptional) {
4124
- required.push(propName);
4125
- }
4126
- }
4127
- const schema = { type: "object", properties };
4128
- if (required.length > 0) {
4129
- schema.required = required;
4130
- }
4131
- return schema;
4132
- }
4133
- case "TArray": {
4134
- if (!typeArgs || typeArgs.length === 0) {
4135
- return { type: "array" };
4136
- }
4137
- const itemType = typeArgs[0];
4138
- const itemSymbol = itemType.getSymbol();
4139
- const itemSymbolName = itemSymbol?.getName();
4140
- let items;
4141
- if (itemSymbolName && typeRefs.has(itemSymbolName)) {
4142
- items = { $ref: `#/types/${itemSymbolName}` };
4143
- } else if (itemSymbolName && isTypeBoxSchemaType(itemSymbolName)) {
4144
- items = formatTypeBoxSchema(itemType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
4145
- type: "object"
4146
- };
4147
- } else {
4148
- items = formatTypeReferenceInternal(itemType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
4149
- }
4150
- return { type: "array", items };
4151
- }
4152
- case "TUnion": {
4153
- if (!typeArgs || typeArgs.length === 0) {
4154
- return { anyOf: [] };
4155
- }
4156
- const tupleType = typeArgs[0];
4157
- const members = [];
4158
- if (tupleType.isUnion()) {
4159
- for (const memberType of tupleType.types) {
4160
- const memberSymbol = memberType.getSymbol();
4161
- const memberSymbolName = memberSymbol?.getName();
4162
- if (memberSymbolName && typeRefs.has(memberSymbolName)) {
4163
- members.push({ $ref: `#/types/${memberSymbolName}` });
4164
- } else if (memberSymbolName && isTypeBoxSchemaType(memberSymbolName)) {
4165
- members.push(formatTypeBoxSchema(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
4166
- type: "object"
4167
- });
4168
- } else {
4169
- members.push(formatTypeReferenceInternal(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds));
4170
- }
4171
- }
4172
- } else if (tupleType.typeArguments) {
4173
- for (const memberType of tupleType.typeArguments) {
4174
- const memberSymbol = memberType.getSymbol();
4175
- const memberSymbolName = memberSymbol?.getName();
4176
- if (memberSymbolName && typeRefs.has(memberSymbolName)) {
4177
- members.push({ $ref: `#/types/${memberSymbolName}` });
4178
- } else if (memberSymbolName && isTypeBoxSchemaType(memberSymbolName)) {
4179
- members.push(formatTypeBoxSchema(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
4180
- type: "object"
4181
- });
4182
- } else {
4183
- members.push(formatTypeReferenceInternal(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds));
4184
- }
4185
- }
4186
- }
4187
- return { anyOf: members };
4188
- }
4189
- case "TIntersect": {
4190
- if (!typeArgs || typeArgs.length === 0) {
4191
- return { allOf: [] };
4192
- }
4193
- const tupleType = typeArgs[0];
4194
- const members = [];
4195
- if (tupleType.typeArguments) {
4196
- for (const memberType of tupleType.typeArguments) {
4197
- const memberSymbol = memberType.getSymbol();
4198
- const memberSymbolName = memberSymbol?.getName();
4199
- if (memberSymbolName && typeRefs.has(memberSymbolName)) {
4200
- members.push({ $ref: `#/types/${memberSymbolName}` });
4201
- } else if (memberSymbolName && isTypeBoxSchemaType(memberSymbolName)) {
4202
- members.push(formatTypeBoxSchema(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
4203
- type: "object"
4204
- });
4205
- } else {
4206
- members.push(formatTypeReferenceInternal(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds));
4207
- }
4208
- }
4209
- }
4210
- return { allOf: members };
4211
- }
4212
- case "TOptional": {
4213
- if (!typeArgs || typeArgs.length === 0) {
4214
- return {};
4215
- }
4216
- const innerType = typeArgs[0];
4217
- const innerSymbol = innerType.getSymbol();
4218
- const innerSymbolName = innerSymbol?.getName();
4219
- if (innerSymbolName && typeRefs.has(innerSymbolName)) {
4220
- return { $ref: `#/types/${innerSymbolName}` };
4221
- } else if (innerSymbolName && isTypeBoxSchemaType(innerSymbolName)) {
4222
- return formatTypeBoxSchema(innerType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
4223
- type: "object"
4224
- };
4225
- }
4226
- return formatTypeReferenceInternal(innerType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
4227
- }
4228
- case "TLiteral": {
4229
- if (!typeArgs || typeArgs.length === 0) {
4230
- return { enum: [] };
4231
- }
4232
- const literalType = typeArgs[0];
4233
- if (literalType.isLiteral()) {
4234
- const value = literalType.value;
4235
- return { enum: [value] };
4236
- }
4237
- const literalStr = typeChecker.typeToString(literalType);
4238
- if (literalStr.startsWith('"') && literalStr.endsWith('"')) {
4239
- return { enum: [literalStr.slice(1, -1)] };
4240
- }
4241
- return { enum: [literalStr] };
4242
- }
4243
- case "TRecord": {
4244
- if (!typeArgs || typeArgs.length < 2) {
4245
- return { type: "object", additionalProperties: true };
4246
- }
4247
- const valueType = typeArgs[1];
4248
- const valueSymbol = valueType.getSymbol();
4249
- const valueSymbolName = valueSymbol?.getName();
4250
- let additionalProperties;
4251
- if (valueSymbolName && typeRefs.has(valueSymbolName)) {
4252
- additionalProperties = { $ref: `#/types/${valueSymbolName}` };
4253
- } else if (valueSymbolName && isTypeBoxSchemaType(valueSymbolName)) {
4254
- additionalProperties = formatTypeBoxSchema(valueType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? true;
4255
- } else {
4256
- additionalProperties = formatTypeReferenceInternal(valueType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
4257
- }
4258
- return { type: "object", additionalProperties };
4259
- }
4260
- case "TRef": {
4261
- if (!typeArgs || typeArgs.length === 0) {
4262
- return { $ref: "#/types/unknown" };
4263
- }
4264
- const refType = typeArgs[0];
4265
- const refSymbol = refType.getSymbol();
4266
- const refSymbolName = refSymbol?.getName();
4267
- if (refSymbolName) {
4268
- return { $ref: `#/types/${refSymbolName}` };
4269
- }
4270
- return { type: "object" };
4271
- }
4272
- default:
4273
- return null;
4274
- }
4275
- }
4276
-
4277
- // src/utils/schema-builder.ts
4278
- var BUILTIN_TYPE_SCHEMAS = {
4279
- Date: { type: "string", format: "date-time" },
4280
- RegExp: { type: "object", description: "RegExp" },
4281
- Error: { type: "object" },
4282
- Promise: { type: "object" },
4283
- Map: { type: "object" },
4284
- Set: { type: "object" },
4285
- WeakMap: { type: "object" },
4286
- WeakSet: { type: "object" },
4287
- Function: { type: "object" },
4288
- ArrayBuffer: { type: "string", format: "binary" },
4289
- ArrayBufferLike: { type: "string", format: "binary" },
4290
- DataView: { type: "string", format: "binary" },
4291
- Uint8Array: { type: "string", format: "byte" },
4292
- Uint16Array: { type: "string", format: "byte" },
4293
- Uint32Array: { type: "string", format: "byte" },
4294
- Int8Array: { type: "string", format: "byte" },
4295
- Int16Array: { type: "string", format: "byte" },
4296
- Int32Array: { type: "string", format: "byte" },
4297
- Float32Array: { type: "string", format: "byte" },
4298
- Float64Array: { type: "string", format: "byte" },
4299
- BigInt64Array: { type: "string", format: "byte" },
4300
- BigUint64Array: { type: "string", format: "byte" }
4301
- };
4302
- function isObjectLiteralType(type) {
4303
- if (!(type.getFlags() & ts.TypeFlags.Object)) {
4304
- return false;
4305
- }
4306
- const objectFlags = type.objectFlags;
4307
- return (objectFlags & ts.ObjectFlags.ObjectLiteral) !== 0;
5114
+ const objectFlags = type.objectFlags;
5115
+ return (objectFlags & ts.ObjectFlags.ObjectLiteral) !== 0;
4308
5116
  }
4309
5117
  function isPureRefSchema(value) {
4310
5118
  return Object.keys(value).length === 1 && "$ref" in value;
@@ -4357,9 +5165,9 @@ function propertiesToSchema(properties, description) {
4357
5165
  }
4358
5166
  return schema;
4359
5167
  }
4360
- var _formatTypeReference2 = null;
5168
+ var _formatTypeReference = null;
4361
5169
  function setSchemaBuilderFormatTypeReference(fn) {
4362
- _formatTypeReference2 = fn;
5170
+ _formatTypeReference = fn;
4363
5171
  }
4364
5172
  function buildSchemaFromTypeNode(node, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName) {
4365
5173
  if (ts.isParenthesizedTypeNode(node)) {
@@ -4390,7 +5198,7 @@ function buildSchemaFromTypeNode(node, typeChecker, typeRefs, referencedTypes, f
4390
5198
  let schema2 = "any";
4391
5199
  if (member.type) {
4392
5200
  const memberType = typeChecker.getTypeFromTypeNode(member.type);
4393
- const formatted = _formatTypeReference2 ? _formatTypeReference2(memberType, typeChecker, typeRefs, referencedTypes) : { type: "any" };
5201
+ const formatted = _formatTypeReference ? _formatTypeReference(memberType, typeChecker, typeRefs, referencedTypes) : { type: "any" };
4394
5202
  if (typeof formatted === "string") {
4395
5203
  if (formatted === "any") {
4396
5204
  schema2 = buildSchemaFromTypeNode(member.type, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName);
@@ -4522,54 +5330,307 @@ function findDiscriminatorProperty(unionTypes, typeChecker) {
4522
5330
  isDiscriminator = false;
4523
5331
  break;
4524
5332
  }
4525
- values.add(value);
5333
+ values.add(value);
5334
+ }
5335
+ if (isDiscriminator) {
5336
+ return propName;
5337
+ }
5338
+ }
5339
+ return;
5340
+ }
5341
+ function schemaIsAny(schema) {
5342
+ if (typeof schema === "string") {
5343
+ return schema === "any";
5344
+ }
5345
+ if ("type" in schema && schema.type === "any" && Object.keys(schema).length === 1) {
5346
+ return true;
5347
+ }
5348
+ return false;
5349
+ }
5350
+ function schemasAreEqual(left, right) {
5351
+ if (typeof left !== typeof right) {
5352
+ return false;
5353
+ }
5354
+ if (typeof left === "string" && typeof right === "string") {
5355
+ return left === right;
5356
+ }
5357
+ if (left == null || right == null) {
5358
+ return left === right;
5359
+ }
5360
+ const normalize = (value) => {
5361
+ if (Array.isArray(value)) {
5362
+ return value.map((item) => normalize(item));
5363
+ }
5364
+ if (value && typeof value === "object") {
5365
+ const sortedEntries = Object.entries(value).map(([key, val]) => [key, normalize(val)]).sort(([keyA], [keyB]) => keyA.localeCompare(keyB));
5366
+ return Object.fromEntries(sortedEntries);
5367
+ }
5368
+ return value;
5369
+ };
5370
+ return JSON.stringify(normalize(left)) === JSON.stringify(normalize(right));
5371
+ }
5372
+ function deduplicateSchemas(schemas) {
5373
+ const result = [];
5374
+ for (const schema of schemas) {
5375
+ const isDuplicate = result.some((existing) => schemasAreEqual(existing, schema));
5376
+ if (!isDuplicate) {
5377
+ result.push(schema);
5378
+ }
5379
+ }
5380
+ return result;
5381
+ }
5382
+ // src/utils/typebox-handler.ts
5383
+ var TYPEBOX_PRIMITIVE_MAP = {
5384
+ TString: { type: "string" },
5385
+ TNumber: { type: "number" },
5386
+ TBoolean: { type: "boolean" },
5387
+ TInteger: { type: "integer" },
5388
+ TNull: { type: "null" },
5389
+ TAny: {},
5390
+ TUnknown: {},
5391
+ TNever: { not: {} },
5392
+ TVoid: { type: "null" },
5393
+ TUndefined: { type: "null" }
5394
+ };
5395
+ function isTypeBoxSchemaType(symbolName) {
5396
+ return /^T[A-Z][a-zA-Z]*$/.test(symbolName);
5397
+ }
5398
+ function isInternalProperty(name) {
5399
+ return name.startsWith("__@");
5400
+ }
5401
+ function isTypeBoxOptionalMarker(type) {
5402
+ const props = type.getProperties();
5403
+ if (props.length !== 1)
5404
+ return false;
5405
+ return isInternalProperty(props[0].getName());
5406
+ }
5407
+ function unwrapTypeBoxOptional(type) {
5408
+ if (!type.isIntersection()) {
5409
+ return { innerTypes: [type], isOptional: false };
5410
+ }
5411
+ const intersectionType = type;
5412
+ const filtered = intersectionType.types.filter((t) => !isTypeBoxOptionalMarker(t));
5413
+ const hadMarker = filtered.length < intersectionType.types.length;
5414
+ return { innerTypes: filtered, isOptional: hadMarker };
5415
+ }
5416
+ function getPropertyType(prop, parentType, typeChecker) {
5417
+ if (prop.valueDeclaration) {
5418
+ return typeChecker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration);
5419
+ }
5420
+ const propType = typeChecker.getTypeOfPropertyOfType(parentType, prop.getName());
5421
+ if (propType) {
5422
+ return propType;
5423
+ }
5424
+ const decl = prop.declarations?.[0];
5425
+ if (decl) {
5426
+ return typeChecker.getTypeOfSymbolAtLocation(prop, decl);
5427
+ }
5428
+ return typeChecker.getAnyType();
5429
+ }
5430
+ var _formatTypeReference2 = null;
5431
+ function setFormatTypeReference(fn) {
5432
+ _formatTypeReference2 = fn;
5433
+ }
5434
+ function formatTypeReferenceInternal(type, typeChecker, typeRefs, referencedTypes, visited, depth, maxDepth, typeIds) {
5435
+ if (_formatTypeReference2) {
5436
+ return _formatTypeReference2(type, typeChecker, typeRefs, referencedTypes, visited, depth, maxDepth, typeIds);
5437
+ }
5438
+ return { type: "object" };
5439
+ }
5440
+ function formatTypeBoxSchema(type, typeChecker, typeRefs, referencedTypes, visited, depth = 0, maxDepth = DEFAULT_MAX_TYPE_DEPTH, typeIds) {
5441
+ if (depth > maxDepth) {
5442
+ return { type: "unknown" };
5443
+ }
5444
+ const symbol = type.getSymbol();
5445
+ if (!symbol)
5446
+ return null;
5447
+ const symbolName = symbol.getName();
5448
+ if (TYPEBOX_PRIMITIVE_MAP[symbolName]) {
5449
+ return { ...TYPEBOX_PRIMITIVE_MAP[symbolName] };
5450
+ }
5451
+ const objectType = type;
5452
+ if (!(objectType.objectFlags & ts.ObjectFlags.Reference)) {
5453
+ return null;
5454
+ }
5455
+ const typeRef = type;
5456
+ const typeArgs = typeRef.typeArguments;
5457
+ switch (symbolName) {
5458
+ case "TObject": {
5459
+ if (!typeArgs || typeArgs.length === 0) {
5460
+ return { type: "object" };
5461
+ }
5462
+ const propsType = typeArgs[0];
5463
+ const properties = {};
5464
+ const required = [];
5465
+ for (const prop of propsType.getProperties()) {
5466
+ const propName = prop.getName();
5467
+ if (isInternalProperty(propName)) {
5468
+ continue;
5469
+ }
5470
+ const propType = getPropertyType(prop, propsType, typeChecker);
5471
+ const propSymbol = propType.getSymbol();
5472
+ const propSymbolName = propSymbol?.getName();
5473
+ if (propSymbolName && typeRefs.has(propSymbolName)) {
5474
+ properties[propName] = { $ref: `#/types/${propSymbolName}` };
5475
+ } else if (propSymbolName && isTypeBoxSchemaType(propSymbolName)) {
5476
+ const nested = formatTypeBoxSchema(propType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
5477
+ properties[propName] = nested ?? { type: "object" };
5478
+ } else {
5479
+ properties[propName] = formatTypeReferenceInternal(propType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
5480
+ }
5481
+ const { isOptional } = unwrapTypeBoxOptional(propType);
5482
+ if (propSymbolName !== "TOptional" && !isOptional) {
5483
+ required.push(propName);
5484
+ }
5485
+ }
5486
+ const schema = { type: "object", properties };
5487
+ if (required.length > 0) {
5488
+ schema.required = required;
5489
+ }
5490
+ return schema;
5491
+ }
5492
+ case "TArray": {
5493
+ if (!typeArgs || typeArgs.length === 0) {
5494
+ return { type: "array" };
5495
+ }
5496
+ const itemType = typeArgs[0];
5497
+ const itemSymbol = itemType.getSymbol();
5498
+ const itemSymbolName = itemSymbol?.getName();
5499
+ let items;
5500
+ if (itemSymbolName && typeRefs.has(itemSymbolName)) {
5501
+ items = { $ref: `#/types/${itemSymbolName}` };
5502
+ } else if (itemSymbolName && isTypeBoxSchemaType(itemSymbolName)) {
5503
+ items = formatTypeBoxSchema(itemType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
5504
+ type: "object"
5505
+ };
5506
+ } else {
5507
+ items = formatTypeReferenceInternal(itemType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
5508
+ }
5509
+ return { type: "array", items };
5510
+ }
5511
+ case "TUnion": {
5512
+ if (!typeArgs || typeArgs.length === 0) {
5513
+ return { anyOf: [] };
5514
+ }
5515
+ const tupleType = typeArgs[0];
5516
+ const members = [];
5517
+ if (tupleType.isUnion()) {
5518
+ for (const memberType of tupleType.types) {
5519
+ const memberSymbol = memberType.getSymbol();
5520
+ const memberSymbolName = memberSymbol?.getName();
5521
+ if (memberSymbolName && typeRefs.has(memberSymbolName)) {
5522
+ members.push({ $ref: `#/types/${memberSymbolName}` });
5523
+ } else if (memberSymbolName && isTypeBoxSchemaType(memberSymbolName)) {
5524
+ members.push(formatTypeBoxSchema(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
5525
+ type: "object"
5526
+ });
5527
+ } else {
5528
+ members.push(formatTypeReferenceInternal(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds));
5529
+ }
5530
+ }
5531
+ } else if (tupleType.typeArguments) {
5532
+ for (const memberType of tupleType.typeArguments) {
5533
+ const memberSymbol = memberType.getSymbol();
5534
+ const memberSymbolName = memberSymbol?.getName();
5535
+ if (memberSymbolName && typeRefs.has(memberSymbolName)) {
5536
+ members.push({ $ref: `#/types/${memberSymbolName}` });
5537
+ } else if (memberSymbolName && isTypeBoxSchemaType(memberSymbolName)) {
5538
+ members.push(formatTypeBoxSchema(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
5539
+ type: "object"
5540
+ });
5541
+ } else {
5542
+ members.push(formatTypeReferenceInternal(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds));
5543
+ }
5544
+ }
5545
+ }
5546
+ return { anyOf: members };
5547
+ }
5548
+ case "TIntersect": {
5549
+ if (!typeArgs || typeArgs.length === 0) {
5550
+ return { allOf: [] };
5551
+ }
5552
+ const tupleType = typeArgs[0];
5553
+ const members = [];
5554
+ if (tupleType.typeArguments) {
5555
+ for (const memberType of tupleType.typeArguments) {
5556
+ const memberSymbol = memberType.getSymbol();
5557
+ const memberSymbolName = memberSymbol?.getName();
5558
+ if (memberSymbolName && typeRefs.has(memberSymbolName)) {
5559
+ members.push({ $ref: `#/types/${memberSymbolName}` });
5560
+ } else if (memberSymbolName && isTypeBoxSchemaType(memberSymbolName)) {
5561
+ members.push(formatTypeBoxSchema(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
5562
+ type: "object"
5563
+ });
5564
+ } else {
5565
+ members.push(formatTypeReferenceInternal(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds));
5566
+ }
5567
+ }
5568
+ }
5569
+ return { allOf: members };
4526
5570
  }
4527
- if (isDiscriminator) {
4528
- return propName;
5571
+ case "TOptional": {
5572
+ if (!typeArgs || typeArgs.length === 0) {
5573
+ return {};
5574
+ }
5575
+ const innerType = typeArgs[0];
5576
+ const innerSymbol = innerType.getSymbol();
5577
+ const innerSymbolName = innerSymbol?.getName();
5578
+ if (innerSymbolName && typeRefs.has(innerSymbolName)) {
5579
+ return { $ref: `#/types/${innerSymbolName}` };
5580
+ } else if (innerSymbolName && isTypeBoxSchemaType(innerSymbolName)) {
5581
+ return formatTypeBoxSchema(innerType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
5582
+ type: "object"
5583
+ };
5584
+ }
5585
+ return formatTypeReferenceInternal(innerType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
4529
5586
  }
4530
- }
4531
- return;
4532
- }
4533
- function schemaIsAny(schema) {
4534
- if (typeof schema === "string") {
4535
- return schema === "any";
4536
- }
4537
- if ("type" in schema && schema.type === "any" && Object.keys(schema).length === 1) {
4538
- return true;
4539
- }
4540
- return false;
4541
- }
4542
- function schemasAreEqual(left, right) {
4543
- if (typeof left !== typeof right) {
4544
- return false;
4545
- }
4546
- if (typeof left === "string" && typeof right === "string") {
4547
- return left === right;
4548
- }
4549
- if (left == null || right == null) {
4550
- return left === right;
4551
- }
4552
- const normalize = (value) => {
4553
- if (Array.isArray(value)) {
4554
- return value.map((item) => normalize(item));
5587
+ case "TLiteral": {
5588
+ if (!typeArgs || typeArgs.length === 0) {
5589
+ return { enum: [] };
5590
+ }
5591
+ const literalType = typeArgs[0];
5592
+ if (literalType.isLiteral()) {
5593
+ const value = literalType.value;
5594
+ return { enum: [value] };
5595
+ }
5596
+ const literalStr = typeChecker.typeToString(literalType);
5597
+ if (literalStr.startsWith('"') && literalStr.endsWith('"')) {
5598
+ return { enum: [literalStr.slice(1, -1)] };
5599
+ }
5600
+ return { enum: [literalStr] };
4555
5601
  }
4556
- if (value && typeof value === "object") {
4557
- const sortedEntries = Object.entries(value).map(([key, val]) => [key, normalize(val)]).sort(([keyA], [keyB]) => keyA.localeCompare(keyB));
4558
- return Object.fromEntries(sortedEntries);
5602
+ case "TRecord": {
5603
+ if (!typeArgs || typeArgs.length < 2) {
5604
+ return { type: "object", additionalProperties: true };
5605
+ }
5606
+ const valueType = typeArgs[1];
5607
+ const valueSymbol = valueType.getSymbol();
5608
+ const valueSymbolName = valueSymbol?.getName();
5609
+ let additionalProperties;
5610
+ if (valueSymbolName && typeRefs.has(valueSymbolName)) {
5611
+ additionalProperties = { $ref: `#/types/${valueSymbolName}` };
5612
+ } else if (valueSymbolName && isTypeBoxSchemaType(valueSymbolName)) {
5613
+ additionalProperties = formatTypeBoxSchema(valueType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? true;
5614
+ } else {
5615
+ additionalProperties = formatTypeReferenceInternal(valueType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
5616
+ }
5617
+ return { type: "object", additionalProperties };
4559
5618
  }
4560
- return value;
4561
- };
4562
- return JSON.stringify(normalize(left)) === JSON.stringify(normalize(right));
4563
- }
4564
- function deduplicateSchemas(schemas) {
4565
- const result = [];
4566
- for (const schema of schemas) {
4567
- const isDuplicate = result.some((existing) => schemasAreEqual(existing, schema));
4568
- if (!isDuplicate) {
4569
- result.push(schema);
5619
+ case "TRef": {
5620
+ if (!typeArgs || typeArgs.length === 0) {
5621
+ return { $ref: "#/types/unknown" };
5622
+ }
5623
+ const refType = typeArgs[0];
5624
+ const refSymbol = refType.getSymbol();
5625
+ const refSymbolName = refSymbol?.getName();
5626
+ if (refSymbolName) {
5627
+ return { $ref: `#/types/${refSymbolName}` };
5628
+ }
5629
+ return { type: "object" };
4570
5630
  }
5631
+ default:
5632
+ return null;
4571
5633
  }
4572
- return result;
4573
5634
  }
4574
5635
 
4575
5636
  // src/utils/type-formatter.ts
@@ -5679,10 +6740,10 @@ function serializeClassMembers(declaration, checker, typeRefs, referencedTypes)
5679
6740
  if (member.questionToken || isOptionalSymbol) {
5680
6741
  flags.optional = true;
5681
6742
  }
5682
- if (member.modifiers?.some((mod) => mod.kind === ts.SyntaxKind.ReadonlyKeyword)) {
6743
+ if (member.modifiers?.some((mod2) => mod2.kind === ts.SyntaxKind.ReadonlyKeyword)) {
5683
6744
  flags.readonly = true;
5684
6745
  }
5685
- if (member.modifiers?.some((mod) => mod.kind === ts.SyntaxKind.StaticKeyword)) {
6746
+ if (member.modifiers?.some((mod2) => mod2.kind === ts.SyntaxKind.StaticKeyword)) {
5686
6747
  flags.static = true;
5687
6748
  }
5688
6749
  members.push({
@@ -5804,20 +6865,20 @@ function serializeSignature(signature, checker, typeRefs, referencedTypes, doc,
5804
6865
  function getMemberVisibility(modifiers) {
5805
6866
  if (!modifiers)
5806
6867
  return;
5807
- if (modifiers.some((mod) => mod.kind === ts.SyntaxKind.PrivateKeyword)) {
6868
+ if (modifiers.some((mod2) => mod2.kind === ts.SyntaxKind.PrivateKeyword)) {
5808
6869
  return "private";
5809
6870
  }
5810
- if (modifiers.some((mod) => mod.kind === ts.SyntaxKind.ProtectedKeyword)) {
6871
+ if (modifiers.some((mod2) => mod2.kind === ts.SyntaxKind.ProtectedKeyword)) {
5811
6872
  return "protected";
5812
6873
  }
5813
- if (modifiers.some((mod) => mod.kind === ts.SyntaxKind.PublicKeyword)) {
6874
+ if (modifiers.some((mod2) => mod2.kind === ts.SyntaxKind.PublicKeyword)) {
5814
6875
  return "public";
5815
6876
  }
5816
6877
  return;
5817
6878
  }
5818
6879
  function getMethodFlags(member) {
5819
6880
  const flags = {};
5820
- if (member.modifiers?.some((mod) => mod.kind === ts.SyntaxKind.StaticKeyword)) {
6881
+ if (member.modifiers?.some((mod2) => mod2.kind === ts.SyntaxKind.StaticKeyword)) {
5821
6882
  flags.static = true;
5822
6883
  }
5823
6884
  if (member.asteriskToken) {
@@ -6002,7 +7063,7 @@ function serializeInterfaceMembers(declaration, checker, typeRefs, referencedTyp
6002
7063
  if (member.questionToken) {
6003
7064
  flags.optional = true;
6004
7065
  }
6005
- if (member.modifiers?.some((mod) => mod.kind === ts.SyntaxKind.ReadonlyKeyword)) {
7066
+ if (member.modifiers?.some((mod2) => mod2.kind === ts.SyntaxKind.ReadonlyKeyword)) {
6006
7067
  flags.readonly = true;
6007
7068
  }
6008
7069
  members.push({
@@ -6300,7 +7361,7 @@ function extractNamespaceMembers(declaration, checker) {
6300
7361
  return members;
6301
7362
  }
6302
7363
  for (const statement of body.statements) {
6303
- const hasExportModifier = ts.canHaveModifiers(statement) && ts.getModifiers(statement)?.some((mod) => mod.kind === ts.SyntaxKind.ExportKeyword);
7364
+ const hasExportModifier = ts.canHaveModifiers(statement) && ts.getModifiers(statement)?.some((mod2) => mod2.kind === ts.SyntaxKind.ExportKeyword);
6304
7365
  if (!hasExportModifier) {
6305
7366
  continue;
6306
7367
  }
@@ -6509,6 +7570,25 @@ function serializeVariable(declaration, symbol, context) {
6509
7570
  }
6510
7571
  const typeRefs = typeRegistry.getTypeRefs();
6511
7572
  const referencedTypes = typeRegistry.getReferencedTypes();
7573
+ const symbolName = symbol.getName();
7574
+ const detectedSchema = context.detectedSchemas?.get(symbolName);
7575
+ if (detectedSchema) {
7576
+ return {
7577
+ id: symbolName,
7578
+ name: symbolName,
7579
+ ...metadata,
7580
+ kind: "variable",
7581
+ deprecated: isSymbolDeprecated(symbol),
7582
+ schema: detectedSchema.schema,
7583
+ description,
7584
+ source: getSourceLocation(declaration),
7585
+ tags: [
7586
+ ...parsedDoc?.tags ?? [],
7587
+ { name: "standardSchema", text: detectedSchema.vendor }
7588
+ ],
7589
+ examples: parsedDoc?.examples
7590
+ };
7591
+ }
6512
7592
  return {
6513
7593
  id: symbol.getName(),
6514
7594
  name: symbol.getName(),
@@ -6597,9 +7677,9 @@ function createDefaultGenerationInfo(entryFile) {
6597
7677
  }
6598
7678
  function buildOpenPkgSpec(context, resolveExternalTypes, generation) {
6599
7679
  const { baseDir, checker: typeChecker, sourceFile, program, entryFile } = context;
6600
- const packageJsonPath = path9.join(baseDir, "package.json");
6601
- const packageJson = fs8.existsSync(packageJsonPath) ? JSON.parse(fs8.readFileSync(packageJsonPath, "utf-8")) : {};
6602
- const generationInfo = generation ?? createDefaultGenerationInfo(path9.relative(baseDir, entryFile));
7680
+ const packageJsonPath = path12.join(baseDir, "package.json");
7681
+ const packageJson = fs11.existsSync(packageJsonPath) ? JSON.parse(fs11.readFileSync(packageJsonPath, "utf-8")) : {};
7682
+ const generationInfo = generation ?? createDefaultGenerationInfo(path12.relative(baseDir, entryFile));
6603
7683
  const spec = {
6604
7684
  $schema: SCHEMA_URL,
6605
7685
  openpkg: SCHEMA_VERSION,
@@ -6619,7 +7699,8 @@ function buildOpenPkgSpec(context, resolveExternalTypes, generation) {
6619
7699
  const serializerContext = {
6620
7700
  checker: typeChecker,
6621
7701
  typeRegistry,
6622
- maxTypeDepth: context.options.maxDepth
7702
+ maxTypeDepth: context.options.maxDepth,
7703
+ detectedSchemas: context.detectedSchemas
6623
7704
  };
6624
7705
  const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile);
6625
7706
  if (!moduleSymbol) {
@@ -6797,11 +7878,11 @@ function deriveImportPath(sourceFile, baseDir) {
6797
7878
  if (!sourceFile) {
6798
7879
  return;
6799
7880
  }
6800
- const relative4 = path9.relative(baseDir, sourceFile);
6801
- if (!relative4 || relative4.startsWith("..")) {
7881
+ const relative6 = path12.relative(baseDir, sourceFile);
7882
+ if (!relative6 || relative6.startsWith("..")) {
6802
7883
  return;
6803
7884
  }
6804
- const normalized = relative4.replace(/\\/g, "/");
7885
+ const normalized = relative6.replace(/\\/g, "/");
6805
7886
  const withoutExt = stripExtensions(normalized);
6806
7887
  if (!withoutExt) {
6807
7888
  return;
@@ -6840,11 +7921,11 @@ function withExportName(entry, exportName) {
6840
7921
  function findNearestPackageJson(startDir) {
6841
7922
  let current = startDir;
6842
7923
  while (true) {
6843
- const candidate = path10.join(current, "package.json");
6844
- if (fs9.existsSync(candidate)) {
7924
+ const candidate = path13.join(current, "package.json");
7925
+ if (fs12.existsSync(candidate)) {
6845
7926
  return candidate;
6846
7927
  }
6847
- const parent = path10.dirname(current);
7928
+ const parent = path13.dirname(current);
6848
7929
  if (parent === current) {
6849
7930
  return;
6850
7931
  }
@@ -6872,11 +7953,11 @@ function canResolveExternalModules(program, baseDir) {
6872
7953
  function hasNodeModulesDirectoryFallback(startDir) {
6873
7954
  let current = startDir;
6874
7955
  while (true) {
6875
- const candidate = path10.join(current, "node_modules");
6876
- if (fs9.existsSync(candidate)) {
7956
+ const candidate = path13.join(current, "node_modules");
7957
+ if (fs12.existsSync(candidate)) {
6877
7958
  return true;
6878
7959
  }
6879
- const parent = path10.dirname(current);
7960
+ const parent = path13.dirname(current);
6880
7961
  if (parent === current) {
6881
7962
  break;
6882
7963
  }
@@ -6921,8 +8002,8 @@ function hasExternalImports(sourceFile) {
6921
8002
  if (ts.isImportDeclaration(node) && node.moduleSpecifier) {
6922
8003
  const specifier = node.moduleSpecifier;
6923
8004
  if (ts.isStringLiteral(specifier)) {
6924
- const modulePath = specifier.text;
6925
- if (!modulePath.startsWith(".") && !modulePath.startsWith("/")) {
8005
+ const modulePath2 = specifier.text;
8006
+ if (!modulePath2.startsWith(".") && !modulePath2.startsWith("/")) {
6926
8007
  found = true;
6927
8008
  }
6928
8009
  }
@@ -6977,6 +8058,17 @@ function runAnalysis(input, generationInput) {
6977
8058
  issues: generationIssues
6978
8059
  } : undefined;
6979
8060
  const spec = buildOpenPkgSpec(context, resolveExternalTypes, generation);
8061
+ if (input.detectedSchemas && input.detectedSchemas.size > 0 && generation) {
8062
+ const vendors = new Set;
8063
+ for (const entry of input.detectedSchemas.values()) {
8064
+ vendors.add(entry.vendor);
8065
+ }
8066
+ generation.analysis.schemaExtraction = {
8067
+ method: "hybrid",
8068
+ runtimeCount: input.detectedSchemas.size,
8069
+ vendors: Array.from(vendors)
8070
+ };
8071
+ }
6980
8072
  const danglingRefs = collectDanglingRefs(spec);
6981
8073
  for (const ref of danglingRefs) {
6982
8074
  const issue = {
@@ -7118,16 +8210,33 @@ async function fetchSpecFromGitHub(parsed) {
7118
8210
  }
7119
8211
  return null;
7120
8212
  }
7121
- async function fetchSpec(owner, repo, branch = "main") {
7122
- return fetchSpecFromGitHub({ owner, repo, ref: branch });
8213
+ async function fetchSpec(owner, repo, branchOrOptions = "main") {
8214
+ const options = typeof branchOrOptions === "string" ? { ref: branchOrOptions, path: "openpkg.json" } : { ref: branchOrOptions.ref ?? "main", path: branchOrOptions.path ?? "openpkg.json" };
8215
+ const parsed = { owner, repo, ref: options.ref };
8216
+ return fetchSpecFromGitHubWithPath(parsed, options.path);
8217
+ }
8218
+ async function fetchSpecFromGitHubWithPath(parsed, specPath = "openpkg.json") {
8219
+ const urls = [
8220
+ buildRawUrl(parsed, specPath),
8221
+ `https://raw.githubusercontent.com/${parsed.owner}/${parsed.repo}/master/${specPath}`
8222
+ ];
8223
+ for (const url of urls) {
8224
+ try {
8225
+ const response = await fetch(url);
8226
+ if (response.ok) {
8227
+ return await response.json();
8228
+ }
8229
+ } catch {}
8230
+ }
8231
+ return null;
7123
8232
  }
7124
8233
  // src/install/index.ts
7125
8234
  var DEFAULT_FALLBACK_ORDER = ["bun", "npm"];
7126
- async function installDependencies(fs10, cwd, runCommand2, options = {}) {
8235
+ async function installDependencies(fs13, cwd, runCommand2, options = {}) {
7127
8236
  const { timeout = 180000, fallbackOrder = DEFAULT_FALLBACK_ORDER, onProgress } = options;
7128
8237
  const errors = [];
7129
8238
  onProgress?.({ stage: "installing", message: "Detecting package manager..." });
7130
- const pmInfo = await detectPackageManager(fs10);
8239
+ const pmInfo = await detectPackageManager(fs13);
7131
8240
  if (pmInfo.lockfile) {
7132
8241
  onProgress?.({
7133
8242
  stage: "installing",
@@ -7202,10 +8311,10 @@ function getFallbackInstallCommand(pm) {
7202
8311
  }
7203
8312
  function createNodeCommandRunner() {
7204
8313
  return async (cmd, args, options) => {
7205
- const { execSync } = await import("node:child_process");
8314
+ const { execSync: execSync2 } = await import("node:child_process");
7206
8315
  const fullCmd = [cmd, ...args].join(" ");
7207
8316
  try {
7208
- const stdout = execSync(fullCmd, {
8317
+ const stdout = execSync2(fullCmd, {
7209
8318
  cwd: options.cwd,
7210
8319
  stdio: "pipe",
7211
8320
  timeout: options.timeout ?? 180000
@@ -7782,7 +8891,7 @@ function diffMemberChanges(oldSpec, newSpec, changedClassNames) {
7782
8891
  memberName,
7783
8892
  memberKind: getMemberKind(newMember),
7784
8893
  changeType: "added",
7785
- newSignature: formatSignature(newMember)
8894
+ newSignature: formatSignature2(newMember)
7786
8895
  });
7787
8896
  }
7788
8897
  }
@@ -7794,7 +8903,7 @@ function diffMemberChanges(oldSpec, newSpec, changedClassNames) {
7794
8903
  memberName,
7795
8904
  memberKind: getMemberKind(oldMember),
7796
8905
  changeType: "removed",
7797
- oldSignature: formatSignature(oldMember),
8906
+ oldSignature: formatSignature2(oldMember),
7798
8907
  suggestion
7799
8908
  });
7800
8909
  }
@@ -7807,8 +8916,8 @@ function diffMemberChanges(oldSpec, newSpec, changedClassNames) {
7807
8916
  memberName,
7808
8917
  memberKind: getMemberKind(newMember),
7809
8918
  changeType: "signature-changed",
7810
- oldSignature: formatSignature(oldMember),
7811
- newSignature: formatSignature(newMember)
8919
+ oldSignature: formatSignature2(oldMember),
8920
+ newSignature: formatSignature2(newMember)
7812
8921
  });
7813
8922
  }
7814
8923
  }
@@ -7857,7 +8966,7 @@ function getMemberKind(member) {
7857
8966
  return "method";
7858
8967
  }
7859
8968
  }
7860
- function formatSignature(member) {
8969
+ function formatSignature2(member) {
7861
8970
  if (!member.signatures?.length) {
7862
8971
  return member.name ?? "";
7863
8972
  }
@@ -8032,11 +9141,34 @@ function getDocsImpactSummary(diff) {
8032
9141
  }
8033
9142
  // src/openpkg.ts
8034
9143
  import * as fsSync from "node:fs";
8035
- import * as fs10 from "node:fs/promises";
8036
- import * as path11 from "node:path";
9144
+ import * as fs13 from "node:fs/promises";
9145
+ import * as path14 from "node:path";
8037
9146
 
8038
9147
  // src/filtering/apply-filters.ts
8039
9148
  var TYPE_REF_PREFIX = "#/types/";
9149
+ var getExportReleaseTag = (exp) => {
9150
+ const tags = exp.tags ?? [];
9151
+ for (const tag of tags) {
9152
+ const tagName = tag.name?.toLowerCase();
9153
+ if (tagName === "public")
9154
+ return "public";
9155
+ if (tagName === "beta")
9156
+ return "beta";
9157
+ if (tagName === "alpha")
9158
+ return "alpha";
9159
+ if (tagName === "internal")
9160
+ return "internal";
9161
+ }
9162
+ return;
9163
+ };
9164
+ var matchesVisibility = (exp, visibility) => {
9165
+ if (!visibility || visibility.length === 0) {
9166
+ return true;
9167
+ }
9168
+ const tag = getExportReleaseTag(exp);
9169
+ const effectiveTag = tag ?? "public";
9170
+ return visibility.includes(effectiveTag);
9171
+ };
8040
9172
  var toLowerKey = (value) => value.trim().toLowerCase();
8041
9173
  var buildLookupMap = (values) => {
8042
9174
  const map = new Map;
@@ -8098,7 +9230,8 @@ var collectTypeRefs = (value, refs, seen = new Set) => {
8098
9230
  var applyFilters = (spec, options) => {
8099
9231
  const includeLookup = buildLookupMap(options.include);
8100
9232
  const excludeLookup = buildLookupMap(options.exclude);
8101
- if (includeLookup.size === 0 && excludeLookup.size === 0) {
9233
+ const visibility = options.visibility;
9234
+ if (includeLookup.size === 0 && excludeLookup.size === 0 && (!visibility || visibility.length === 0)) {
8102
9235
  return { spec, diagnostics: [], changed: false };
8103
9236
  }
8104
9237
  const includeMatches = new Set;
@@ -8111,10 +9244,11 @@ var applyFilters = (spec, options) => {
8111
9244
  const excludeMatch = matches(entry, excludeLookup);
8112
9245
  const allowedByInclude = includeLookup.size === 0 || Boolean(includeMatch);
8113
9246
  const allowedByExclude = !excludeMatch;
9247
+ const allowedByVisibility = matchesVisibility(entry, visibility);
8114
9248
  if (includeMatch) {
8115
9249
  includeMatches.add(includeMatch);
8116
9250
  }
8117
- if (allowedByInclude && allowedByExclude) {
9251
+ if (allowedByInclude && allowedByExclude && allowedByVisibility) {
8118
9252
  keptExports.push(entry);
8119
9253
  }
8120
9254
  }
@@ -8216,14 +9350,14 @@ class DocCov {
8216
9350
  this.options = normalizeDocCovOptions(options);
8217
9351
  }
8218
9352
  async analyze(code, fileName = "temp.ts", analyzeOptions = {}) {
8219
- const resolvedFileName = path11.resolve(fileName);
8220
- const tempDir = path11.dirname(resolvedFileName);
9353
+ const resolvedFileName = path14.resolve(fileName);
9354
+ const tempDir = path14.dirname(resolvedFileName);
8221
9355
  const spec = await extractPackageSpec(resolvedFileName, tempDir, code, this.options);
8222
9356
  return this.applySpecFilters(spec, analyzeOptions.filters).spec;
8223
9357
  }
8224
9358
  async analyzeFile(filePath, analyzeOptions = {}) {
8225
- const resolvedPath = path11.resolve(filePath);
8226
- const content = await fs10.readFile(resolvedPath, "utf-8");
9359
+ const resolvedPath = path14.resolve(filePath);
9360
+ const content = await fs13.readFile(resolvedPath, "utf-8");
8227
9361
  const packageDir = resolvePackageDir(resolvedPath);
8228
9362
  const spec = await extractPackageSpec(resolvedPath, packageDir, content, this.options);
8229
9363
  return this.applySpecFilters(spec, analyzeOptions.filters).spec;
@@ -8232,13 +9366,16 @@ class DocCov {
8232
9366
  return this.analyzeFile(entryPath, analyzeOptions);
8233
9367
  }
8234
9368
  async analyzeWithDiagnostics(code, fileName, analyzeOptions = {}) {
8235
- const resolvedFileName = path11.resolve(fileName ?? "temp.ts");
9369
+ const resolvedFileName = path14.resolve(fileName ?? "temp.ts");
8236
9370
  const packageDir = resolvePackageDir(resolvedFileName);
9371
+ const isRealFile = fileName && !fileName.includes("temp.ts");
9372
+ const detectedSchemas = isRealFile ? await this.detectSchemas(resolvedFileName, packageDir) : undefined;
8237
9373
  const analysis = runAnalysis({
8238
9374
  entryFile: resolvedFileName,
8239
9375
  packageDir,
8240
9376
  content: code,
8241
- options: this.options
9377
+ options: this.options,
9378
+ detectedSchemas
8242
9379
  }, analyzeOptions.generationInput);
8243
9380
  const filterOutcome = this.applySpecFilters(analysis.spec, analyzeOptions.filters);
8244
9381
  return {
@@ -8252,7 +9389,7 @@ class DocCov {
8252
9389
  };
8253
9390
  }
8254
9391
  async analyzeFileWithDiagnostics(filePath, analyzeOptions = {}) {
8255
- const resolvedPath = path11.resolve(filePath);
9392
+ const resolvedPath = path14.resolve(filePath);
8256
9393
  const packageDir = resolvePackageDir(resolvedPath);
8257
9394
  const { useCache, resolveExternalTypes } = this.options;
8258
9395
  if (useCache) {
@@ -8268,12 +9405,14 @@ class DocCov {
8268
9405
  };
8269
9406
  }
8270
9407
  }
8271
- const content = await fs10.readFile(resolvedPath, "utf-8");
9408
+ const content = await fs13.readFile(resolvedPath, "utf-8");
9409
+ const detectedSchemas = await this.detectSchemas(resolvedPath, packageDir);
8272
9410
  const analysis = runAnalysis({
8273
9411
  entryFile: resolvedPath,
8274
9412
  packageDir,
8275
9413
  content,
8276
- options: this.options
9414
+ options: this.options,
9415
+ detectedSchemas
8277
9416
  }, analyzeOptions.generationInput);
8278
9417
  const filterOutcome = this.applySpecFilters(analysis.spec, analyzeOptions.filters);
8279
9418
  const metadata = this.normalizeMetadata(analysis.metadata);
@@ -8303,10 +9442,10 @@ class DocCov {
8303
9442
  if (!packageJsonPath) {
8304
9443
  return null;
8305
9444
  }
8306
- const sourceFiles = Object.keys(cache.hashes.sourceFiles).map((relativePath) => path11.resolve(cwd, relativePath));
9445
+ const currentSourceFiles = this.getCurrentSourceFiles(entryFile, packageDir);
8307
9446
  const cacheContext = {
8308
9447
  entryFile,
8309
- sourceFiles,
9448
+ sourceFiles: currentSourceFiles,
8310
9449
  tsconfigPath,
8311
9450
  packageJsonPath,
8312
9451
  config: {
@@ -8318,6 +9457,7 @@ class DocCov {
8318
9457
  if (!validation.valid) {
8319
9458
  return null;
8320
9459
  }
9460
+ const cachedSourceFiles = Object.keys(cache.hashes.sourceFiles).map((relativePath) => path14.resolve(cwd, relativePath));
8321
9461
  return {
8322
9462
  spec: cache.spec,
8323
9463
  metadata: {
@@ -8326,10 +9466,18 @@ class DocCov {
8326
9466
  packageJsonPath,
8327
9467
  hasNodeModules: true,
8328
9468
  resolveExternalTypes: cache.config.resolveExternalTypes,
8329
- sourceFiles
9469
+ sourceFiles: cachedSourceFiles
8330
9470
  }
8331
9471
  };
8332
9472
  }
9473
+ getCurrentSourceFiles(entryFile, baseDir) {
9474
+ try {
9475
+ const { program } = createProgram({ entryFile, baseDir });
9476
+ return program.getSourceFiles().filter((sf) => !sf.isDeclarationFile && sf.fileName.startsWith(baseDir)).map((sf) => sf.fileName);
9477
+ } catch {
9478
+ return [];
9479
+ }
9480
+ }
8333
9481
  saveToCache(result, entryFile, metadata) {
8334
9482
  const { cwd } = this.options;
8335
9483
  if (!metadata.packageJsonPath) {
@@ -8352,11 +9500,11 @@ class DocCov {
8352
9500
  findTsConfig(startDir) {
8353
9501
  let current = startDir;
8354
9502
  while (true) {
8355
- const candidate = path11.join(current, "tsconfig.json");
9503
+ const candidate = path14.join(current, "tsconfig.json");
8356
9504
  if (fsSync.existsSync(candidate)) {
8357
9505
  return candidate;
8358
9506
  }
8359
- const parent = path11.dirname(current);
9507
+ const parent = path14.dirname(current);
8360
9508
  if (parent === current) {
8361
9509
  return null;
8362
9510
  }
@@ -8366,17 +9514,38 @@ class DocCov {
8366
9514
  findPackageJson(startDir) {
8367
9515
  let current = startDir;
8368
9516
  while (true) {
8369
- const candidate = path11.join(current, "package.json");
9517
+ const candidate = path14.join(current, "package.json");
8370
9518
  if (fsSync.existsSync(candidate)) {
8371
9519
  return candidate;
8372
9520
  }
8373
- const parent = path11.dirname(current);
9521
+ const parent = path14.dirname(current);
8374
9522
  if (parent === current) {
8375
9523
  return null;
8376
9524
  }
8377
9525
  current = parent;
8378
9526
  }
8379
9527
  }
9528
+ async detectSchemas(entryFile, packageDir) {
9529
+ try {
9530
+ const result = await detectRuntimeSchemas({
9531
+ baseDir: packageDir,
9532
+ entryFile
9533
+ });
9534
+ if (result.schemas.size === 0) {
9535
+ return;
9536
+ }
9537
+ const detected = new Map;
9538
+ for (const [name, schema] of result.schemas) {
9539
+ detected.set(name, {
9540
+ schema: schema.schema,
9541
+ vendor: schema.vendor
9542
+ });
9543
+ }
9544
+ return detected;
9545
+ } catch {
9546
+ return;
9547
+ }
9548
+ }
8380
9549
  normalizeDiagnostic(tsDiagnostic) {
8381
9550
  const message = ts.flattenDiagnosticMessageText(tsDiagnostic.messageText, `
8382
9551
  `);
@@ -8438,27 +9607,127 @@ async function analyzeFile(filePath, options = {}) {
8438
9607
  return new DocCov().analyzeFile(filePath, options);
8439
9608
  }
8440
9609
  function resolvePackageDir(entryFile) {
8441
- const fallbackDir = path11.dirname(entryFile);
9610
+ const fallbackDir = path14.dirname(entryFile);
8442
9611
  let currentDir = fallbackDir;
8443
9612
  while (true) {
8444
- const candidate = path11.join(currentDir, "package.json");
9613
+ const candidate = path14.join(currentDir, "package.json");
8445
9614
  if (fsSync.existsSync(candidate)) {
8446
9615
  return currentDir;
8447
9616
  }
8448
- const parentDir = path11.dirname(currentDir);
9617
+ const parentDir = path14.dirname(currentDir);
8449
9618
  if (parentDir === currentDir) {
8450
9619
  return fallbackDir;
8451
9620
  }
8452
9621
  currentDir = parentDir;
8453
9622
  }
8454
9623
  }
9624
+ // src/policies/index.ts
9625
+ import * as path15 from "node:path";
9626
+ import { minimatch as minimatch2 } from "minimatch";
9627
+ function matchesPolicy(exp, policy, baseDir) {
9628
+ const filePath = exp.source?.file;
9629
+ if (!filePath)
9630
+ return false;
9631
+ let normalizedPath = filePath;
9632
+ if (baseDir && path15.isAbsolute(filePath)) {
9633
+ normalizedPath = path15.relative(baseDir, filePath);
9634
+ }
9635
+ normalizedPath = normalizedPath.replace(/^\.\//, "");
9636
+ return minimatch2(normalizedPath, policy.path, { matchBase: true });
9637
+ }
9638
+ function calculateCoverage(exports) {
9639
+ if (exports.length === 0)
9640
+ return 100;
9641
+ let documented = 0;
9642
+ for (const exp of exports) {
9643
+ if (exp.description) {
9644
+ documented++;
9645
+ }
9646
+ }
9647
+ return Math.round(documented / exports.length * 100);
9648
+ }
9649
+ function calculateDrift(exports) {
9650
+ if (exports.length === 0)
9651
+ return 0;
9652
+ let withDrift = 0;
9653
+ for (const exp of exports) {
9654
+ if (exp.docs?.drift && exp.docs.drift.length > 0) {
9655
+ withDrift++;
9656
+ }
9657
+ }
9658
+ return Math.round(withDrift / exports.length * 100);
9659
+ }
9660
+ function countMissingExamples(exports) {
9661
+ let missing = 0;
9662
+ for (const exp of exports) {
9663
+ const hasExample = exp.examples && exp.examples.length > 0;
9664
+ if (!hasExample) {
9665
+ missing++;
9666
+ }
9667
+ }
9668
+ return missing;
9669
+ }
9670
+ function evaluatePolicy(policy, allExports, baseDir) {
9671
+ const matchedExports = allExports.filter((exp) => matchesPolicy(exp, policy, baseDir));
9672
+ const coverageScore = calculateCoverage(matchedExports);
9673
+ const driftScore = calculateDrift(matchedExports);
9674
+ const missingExamples = countMissingExamples(matchedExports);
9675
+ const failures = [];
9676
+ if (policy.minCoverage !== undefined && coverageScore < policy.minCoverage) {
9677
+ failures.push({
9678
+ type: "coverage",
9679
+ message: `Coverage ${coverageScore}% below minimum ${policy.minCoverage}%`,
9680
+ actual: coverageScore,
9681
+ threshold: policy.minCoverage
9682
+ });
9683
+ }
9684
+ if (policy.maxDrift !== undefined && driftScore > policy.maxDrift) {
9685
+ failures.push({
9686
+ type: "drift",
9687
+ message: `Drift ${driftScore}% exceeds maximum ${policy.maxDrift}%`,
9688
+ actual: driftScore,
9689
+ threshold: policy.maxDrift
9690
+ });
9691
+ }
9692
+ if (policy.requireExamples && missingExamples > 0) {
9693
+ failures.push({
9694
+ type: "examples",
9695
+ message: `${missingExamples} exports missing @example`,
9696
+ actual: missingExamples,
9697
+ threshold: 0
9698
+ });
9699
+ }
9700
+ return {
9701
+ policy,
9702
+ matchedExports,
9703
+ coverageScore,
9704
+ driftScore,
9705
+ missingExamples,
9706
+ passed: failures.length === 0,
9707
+ failures
9708
+ };
9709
+ }
9710
+ function evaluatePolicies(policies, spec, options = {}) {
9711
+ const { baseDir } = options;
9712
+ const exports = spec.exports ?? [];
9713
+ const results = policies.map((policy) => evaluatePolicy(policy, exports, baseDir));
9714
+ const passedCount = results.filter((r) => r.passed).length;
9715
+ const failedCount = results.length - passedCount;
9716
+ return {
9717
+ results,
9718
+ allPassed: failedCount === 0,
9719
+ totalPolicies: policies.length,
9720
+ passedCount,
9721
+ failedCount
9722
+ };
9723
+ }
8455
9724
  // src/resolve/index.ts
8456
- import * as path12 from "node:path";
8457
- async function resolveTarget(fs11, options) {
9725
+ import * as path16 from "node:path";
9726
+ async function resolveTarget(fs14, options) {
8458
9727
  let targetDir = options.cwd;
8459
9728
  let packageInfo;
8460
9729
  if (options.package) {
8461
- const mono = await detectMonorepo(fs11);
9730
+ const mono = await detectMonorepo(fs14);
8462
9731
  if (!mono.isMonorepo) {
8463
9732
  throw new Error("Not a monorepo. Remove --package flag for single-package repos.");
8464
9733
  }
@@ -8467,21 +9736,21 @@ async function resolveTarget(fs11, options) {
8467
9736
  const available = mono.packages.map((p) => p.name).join(", ");
8468
9737
  throw new Error(`Package "${options.package}" not found. Available: ${available}`);
8469
9738
  }
8470
- targetDir = path12.join(options.cwd, pkg.path);
9739
+ targetDir = path16.join(options.cwd, pkg.path);
8471
9740
  packageInfo = pkg;
8472
9741
  }
8473
9742
  let entryFile;
8474
9743
  let entryPointInfo;
8475
9744
  if (!options.entry) {
8476
- entryPointInfo = await detectEntryPoint(fs11, getRelativePath(options.cwd, targetDir));
8477
- entryFile = path12.join(targetDir, entryPointInfo.path);
9745
+ entryPointInfo = await detectEntryPoint(fs14, getRelativePath(options.cwd, targetDir));
9746
+ entryFile = path16.join(targetDir, entryPointInfo.path);
8478
9747
  } else {
8479
- const explicitPath = path12.resolve(targetDir, options.entry);
8480
- const isDirectory = await isDir(fs11, getRelativePath(options.cwd, explicitPath));
9748
+ const explicitPath = path16.resolve(targetDir, options.entry);
9749
+ const isDirectory = await isDir(fs14, getRelativePath(options.cwd, explicitPath));
8481
9750
  if (isDirectory) {
8482
9751
  targetDir = explicitPath;
8483
- entryPointInfo = await detectEntryPoint(fs11, getRelativePath(options.cwd, explicitPath));
8484
- entryFile = path12.join(explicitPath, entryPointInfo.path);
9752
+ entryPointInfo = await detectEntryPoint(fs14, getRelativePath(options.cwd, explicitPath));
9753
+ entryFile = path16.join(explicitPath, entryPointInfo.path);
8485
9754
  } else {
8486
9755
  entryFile = explicitPath;
8487
9756
  entryPointInfo = {
@@ -8501,52 +9770,20 @@ async function resolveTarget(fs11, options) {
8501
9770
  function getRelativePath(base, target) {
8502
9771
  if (base === target)
8503
9772
  return ".";
8504
- const rel = path12.relative(base, target);
9773
+ const rel = path16.relative(base, target);
8505
9774
  return rel || ".";
8506
9775
  }
8507
- async function isDir(fs11, relativePath) {
8508
- const hasPackageJson = await fs11.exists(path12.join(relativePath, "package.json"));
9776
+ async function isDir(fs14, relativePath) {
9777
+ const hasPackageJson = await fs14.exists(path16.join(relativePath, "package.json"));
8509
9778
  if (hasPackageJson)
8510
9779
  return true;
8511
9780
  const commonEntryFiles = ["index.ts", "index.tsx", "src/index.ts", "main.ts"];
8512
9781
  for (const entry of commonEntryFiles) {
8513
- if (await fs11.exists(path12.join(relativePath, entry))) {
9782
+ if (await fs14.exists(path16.join(relativePath, entry))) {
8514
9783
  return true;
8515
9784
  }
8516
9785
  }
8517
- return !path12.extname(relativePath);
8518
- }
8519
- // src/scan/summary.ts
8520
- function extractSpecSummary(spec) {
8521
- const exports = spec.exports ?? [];
8522
- const undocumented = [];
8523
- const drift = [];
8524
- for (const exp of exports) {
8525
- const docs = exp.docs;
8526
- if (!docs)
8527
- continue;
8528
- const hasMissing = (docs.missing?.length ?? 0) > 0;
8529
- const isPartial = (docs.coverageScore ?? 0) < 100;
8530
- if (hasMissing || isPartial) {
8531
- undocumented.push(exp.name);
8532
- }
8533
- for (const d of docs.drift ?? []) {
8534
- drift.push({
8535
- export: exp.name,
8536
- type: d.type,
8537
- issue: d.issue,
8538
- suggestion: d.suggestion
8539
- });
8540
- }
8541
- }
8542
- return {
8543
- coverage: spec.docs?.coverageScore ?? 0,
8544
- exportCount: exports.length,
8545
- typeCount: spec.types?.length ?? 0,
8546
- driftCount: drift.length,
8547
- undocumented,
8548
- drift
8549
- };
9786
+ return !path16.extname(relativePath);
8550
9787
  }
8551
9788
  // src/scan/github-context.ts
8552
9789
  function parseGitHubUrl2(url) {
@@ -8557,10 +9794,14 @@ function parseGitHubUrl2(url) {
8557
9794
  return null;
8558
9795
  }
8559
9796
  }
8560
- async function fetchRawFile(owner, repo, ref, path13) {
8561
- const url = `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${path13}`;
9797
+ async function fetchRawFile(owner, repo, ref, path17, authToken) {
9798
+ const url = `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${path17}`;
8562
9799
  try {
8563
- const response = await fetch(url);
9800
+ const headers = {};
9801
+ if (authToken) {
9802
+ headers.Authorization = `Bearer ${authToken}`;
9803
+ }
9804
+ const response = await fetch(url, { headers });
8564
9805
  if (response.ok) {
8565
9806
  return await response.text();
8566
9807
  }
@@ -8569,14 +9810,16 @@ async function fetchRawFile(owner, repo, ref, path13) {
8569
9810
  return null;
8570
9811
  }
8571
9812
  }
8572
- async function fetchRepoMetadata(owner, repo) {
9813
+ async function fetchRepoMetadata(owner, repo, authToken) {
8573
9814
  const url = `https://api.github.com/repos/${owner}/${repo}`;
8574
- const response = await fetch(url, {
8575
- headers: {
8576
- Accept: "application/vnd.github.v3+json",
8577
- "User-Agent": "DocCov-Scanner"
8578
- }
8579
- });
9815
+ const headers = {
9816
+ Accept: "application/vnd.github.v3+json",
9817
+ "User-Agent": "DocCov-Scanner"
9818
+ };
9819
+ if (authToken) {
9820
+ headers.Authorization = `Bearer ${authToken}`;
9821
+ }
9822
+ const response = await fetch(url, { headers });
8580
9823
  if (!response.ok) {
8581
9824
  throw new Error(`Failed to fetch repository: ${response.status} ${response.statusText}`);
8582
9825
  }
@@ -8591,7 +9834,7 @@ async function fetchRepoMetadata(owner, repo) {
8591
9834
  isPrivate: data.private
8592
9835
  };
8593
9836
  }
8594
- async function detectPackageManager3(owner, repo, ref) {
9837
+ async function detectPackageManager3(owner, repo, ref, authToken) {
8595
9838
  const lockfiles = [
8596
9839
  { name: "bun.lockb", manager: "bun" },
8597
9840
  { name: "pnpm-lock.yaml", manager: "pnpm" },
@@ -8599,15 +9842,15 @@ async function detectPackageManager3(owner, repo, ref) {
8599
9842
  { name: "package-lock.json", manager: "npm" }
8600
9843
  ];
8601
9844
  for (const { name, manager } of lockfiles) {
8602
- const content = await fetchRawFile(owner, repo, ref, name);
9845
+ const content = await fetchRawFile(owner, repo, ref, name, authToken);
8603
9846
  if (content !== null) {
8604
9847
  return { manager, lockfile: { name, content: content.slice(0, 1e4) } };
8605
9848
  }
8606
9849
  }
8607
9850
  return { manager: "unknown" };
8608
9851
  }
8609
- async function detectWorkspace(packageJson, owner, repo, ref) {
8610
- const pnpmWorkspace = await fetchRawFile(owner, repo, ref, "pnpm-workspace.yaml");
9852
+ async function detectWorkspace(packageJson, owner, repo, ref, authToken) {
9853
+ const pnpmWorkspace = await fetchRawFile(owner, repo, ref, "pnpm-workspace.yaml", authToken);
8611
9854
  if (pnpmWorkspace) {
8612
9855
  const packagesMatch = pnpmWorkspace.match(/packages:\s*\n((?:\s+-\s+['"]?[^\n]+['"]?\n?)+)/);
8613
9856
  const packages = packagesMatch ? packagesMatch[1].split(`
@@ -8618,7 +9861,7 @@ async function detectWorkspace(packageJson, owner, repo, ref) {
8618
9861
  packages
8619
9862
  };
8620
9863
  }
8621
- const lernaJson = await fetchRawFile(owner, repo, ref, "lerna.json");
9864
+ const lernaJson = await fetchRawFile(owner, repo, ref, "lerna.json", authToken);
8622
9865
  if (lernaJson) {
8623
9866
  return { isMonorepo: true, tool: "lerna" };
8624
9867
  }
@@ -8678,18 +9921,20 @@ function detectBuildHints(packageJson, tsconfigJson) {
8678
9921
  }
8679
9922
  return hints;
8680
9923
  }
8681
- async function fetchGitHubContext(repoUrl, ref) {
9924
+ async function fetchGitHubContext(repoUrl, refOrOptions) {
8682
9925
  const parsed = parseGitHubUrl2(repoUrl);
8683
9926
  if (!parsed) {
8684
9927
  throw new Error(`Invalid GitHub URL: ${repoUrl}`);
8685
9928
  }
9929
+ const options = typeof refOrOptions === "string" ? { ref: refOrOptions } : refOrOptions ?? {};
9930
+ const { authToken } = options;
8686
9931
  const { owner, repo } = parsed;
8687
- const metadata = await fetchRepoMetadata(owner, repo);
8688
- const targetRef = ref ?? metadata.defaultBranch;
9932
+ const metadata = await fetchRepoMetadata(owner, repo, authToken);
9933
+ const targetRef = options.ref ?? metadata.defaultBranch;
8689
9934
  const [packageJsonRaw, tsconfigJsonRaw, pmResult] = await Promise.all([
8690
- fetchRawFile(owner, repo, targetRef, "package.json"),
8691
- fetchRawFile(owner, repo, targetRef, "tsconfig.json"),
8692
- detectPackageManager3(owner, repo, targetRef)
9935
+ fetchRawFile(owner, repo, targetRef, "package.json", authToken),
9936
+ fetchRawFile(owner, repo, targetRef, "tsconfig.json", authToken),
9937
+ detectPackageManager3(owner, repo, targetRef, authToken)
8693
9938
  ]);
8694
9939
  let packageJson = null;
8695
9940
  let tsconfigJson = null;
@@ -8703,7 +9948,7 @@ async function fetchGitHubContext(repoUrl, ref) {
8703
9948
  tsconfigJson = JSON.parse(tsconfigJsonRaw);
8704
9949
  } catch {}
8705
9950
  }
8706
- const workspace = await detectWorkspace(packageJson, owner, repo, targetRef);
9951
+ const workspace = await detectWorkspace(packageJson, owner, repo, targetRef, authToken);
8707
9952
  const buildHints = detectBuildHints(packageJson, tsconfigJson);
8708
9953
  return {
8709
9954
  metadata,
@@ -8718,18 +9963,20 @@ async function fetchGitHubContext(repoUrl, ref) {
8718
9963
  }
8719
9964
  };
8720
9965
  }
8721
- async function listWorkspacePackages(owner, repo, ref, patterns) {
9966
+ async function listWorkspacePackages(owner, repo, ref, patterns, authToken) {
8722
9967
  const packages = [];
8723
9968
  for (const pattern of patterns) {
8724
9969
  const baseDir = pattern.replace(/\/\*.*$/, "");
8725
9970
  try {
8726
9971
  const url = `https://api.github.com/repos/${owner}/${repo}/contents/${baseDir}?ref=${ref}`;
8727
- const response = await fetch(url, {
8728
- headers: {
8729
- Accept: "application/vnd.github.v3+json",
8730
- "User-Agent": "DocCov-Scanner"
8731
- }
8732
- });
9972
+ const headers = {
9973
+ Accept: "application/vnd.github.v3+json",
9974
+ "User-Agent": "DocCov-Scanner"
9975
+ };
9976
+ if (authToken) {
9977
+ headers.Authorization = `Bearer ${authToken}`;
9978
+ }
9979
+ const response = await fetch(url, { headers });
8733
9980
  if (response.ok) {
8734
9981
  const contents = await response.json();
8735
9982
  for (const item of contents) {
@@ -8742,21 +9989,59 @@ async function listWorkspacePackages(owner, repo, ref, patterns) {
8742
9989
  }
8743
9990
  return packages;
8744
9991
  }
9992
+ // src/scan/summary.ts
9993
+ function extractSpecSummary(spec) {
9994
+ const exports = spec.exports ?? [];
9995
+ const undocumented = [];
9996
+ const drift = [];
9997
+ for (const exp of exports) {
9998
+ const docs = exp.docs;
9999
+ if (!docs)
10000
+ continue;
10001
+ const hasMissing = (docs.missing?.length ?? 0) > 0;
10002
+ const isPartial = (docs.coverageScore ?? 0) < 100;
10003
+ if (hasMissing || isPartial) {
10004
+ undocumented.push(exp.name);
10005
+ }
10006
+ for (const d of docs.drift ?? []) {
10007
+ drift.push({
10008
+ export: exp.name,
10009
+ type: d.type,
10010
+ issue: d.issue,
10011
+ suggestion: d.suggestion
10012
+ });
10013
+ }
10014
+ }
10015
+ return {
10016
+ coverage: spec.docs?.coverageScore ?? 0,
10017
+ exportCount: exports.length,
10018
+ typeCount: spec.types?.length ?? 0,
10019
+ driftCount: drift.length,
10020
+ undocumented,
10021
+ drift
10022
+ };
10023
+ }
8745
10024
  export {
8746
10025
  validateSpecCache,
8747
10026
  validateExamples,
8748
10027
  typecheckExamples,
8749
10028
  typecheckExample,
10029
+ tryExtractStandardSchema,
8750
10030
  shouldValidate,
8751
10031
  serializeJSDoc,
8752
10032
  saveSpecCache,
10033
+ saveSnapshot,
8753
10034
  saveReport,
8754
10035
  safeParseJson,
8755
10036
  runExamplesWithPackage,
8756
10037
  runExamples,
8757
10038
  runExample,
8758
10039
  resolveTarget,
10040
+ renderSparkline,
10041
+ renderApiSurface,
8759
10042
  readPackageJson,
10043
+ pruneHistory,
10044
+ pruneByTier,
8760
10045
  parseGitHubUrl2 as parseScanGitHubUrl,
8761
10046
  parseMarkdownFiles,
8762
10047
  parseMarkdownFile,
@@ -8764,13 +10049,18 @@ export {
8764
10049
  parseJSDocToPatch,
8765
10050
  parseGitHubUrl,
8766
10051
  parseExamplesFlag,
10052
+ parseCodeOwners,
8767
10053
  parseAssertions,
8768
10054
  mergeFixes,
8769
10055
  mergeFilters,
8770
10056
  mergeConfig,
8771
10057
  loadSpecCache,
10058
+ loadSnapshotsForDays,
10059
+ loadSnapshots,
10060
+ loadCodeOwners,
8772
10061
  loadCachedReport,
8773
10062
  listWorkspacePackages,
10063
+ isStandardJSONSchema,
8774
10064
  isFixableDrift,
8775
10065
  isExecutableLang,
8776
10066
  isCachedReportValid,
@@ -8783,6 +10073,7 @@ export {
8783
10073
  hasDocsForExport,
8784
10074
  groupDriftsByCategory,
8785
10075
  getUndocumentedExports,
10076
+ getTrend,
8786
10077
  getSpecCachePath,
8787
10078
  getRunCommand,
8788
10079
  getRulesForKind,
@@ -8790,36 +10081,46 @@ export {
8790
10081
  getReportPath,
8791
10082
  getPrimaryBuildScript,
8792
10083
  getInstallCommand,
10084
+ getFileBlame,
10085
+ getExtendedTrend,
8793
10086
  getDriftSummary,
8794
10087
  getDocumentedExports,
8795
10088
  getDocsImpactSummary,
8796
10089
  getDiffReportPath,
8797
10090
  getDefaultConfig,
8798
10091
  getCoverageRules,
10092
+ getBlameForLines,
10093
+ generateWeeklySummaries,
8799
10094
  generateReportFromEnriched,
8800
10095
  generateReport,
8801
10096
  generateFixesForExport,
8802
10097
  generateFix,
8803
10098
  formatPackageList,
8804
10099
  formatDriftSummaryLine,
10100
+ formatDelta,
8805
10101
  findRemovedReferences,
8806
10102
  findPackageByName,
10103
+ findOwners,
8807
10104
  findJSDocLocation,
8808
10105
  findExportReferences,
8809
10106
  findDeprecatedReferences,
8810
10107
  fetchSpecFromGitHub,
8811
10108
  fetchSpec,
8812
10109
  fetchGitHubContext,
10110
+ extractViaStandardSchema,
8813
10111
  extractSpecSummary,
8814
10112
  extractPackageSpec,
8815
10113
  extractImports,
8816
10114
  extractFunctionCalls,
8817
10115
  evaluateQuality,
10116
+ evaluatePolicy,
10117
+ evaluatePolicies,
8818
10118
  evaluateExportQuality,
8819
10119
  ensureSpecCoverage,
8820
10120
  enrichSpec,
8821
10121
  diffSpecWithDocs,
8822
10122
  diffHashes,
10123
+ detectRuntimeSchemas,
8823
10124
  detectPackageManager,
8824
10125
  detectMonorepo,
8825
10126
  detectExampleRuntimeErrors,
@@ -8829,9 +10130,11 @@ export {
8829
10130
  defineConfig,
8830
10131
  createSourceFile,
8831
10132
  createNodeCommandRunner,
10133
+ computeSnapshot,
8832
10134
  computeExportDrift,
8833
10135
  computeDrift,
8834
10136
  clearSpecCache,
10137
+ clearSchemaCache,
8835
10138
  categorizeDrifts,
8836
10139
  categorizeDrift,
8837
10140
  calculateAggregateCoverage,
@@ -8840,19 +10143,28 @@ export {
8840
10143
  buildDisplayUrl,
8841
10144
  buildCloneUrl,
8842
10145
  blockReferencesExport,
10146
+ attributeOwners,
8843
10147
  applyPatchToJSDoc,
8844
10148
  applyEdits,
10149
+ analyzeSpecOwnership,
10150
+ analyzeSpecContributors,
8845
10151
  analyzeProject,
10152
+ analyzeOwnership,
8846
10153
  analyzeFile,
8847
10154
  analyzeDocsImpact,
10155
+ analyzeContributors,
8848
10156
  analyze,
8849
10157
  VALIDATION_INFO,
10158
+ TSDOC_RULES,
8850
10159
  SandboxFileSystem,
8851
10160
  STYLE_RULES,
8852
10161
  SPEC_CACHE_FILE,
10162
+ RETENTION_DAYS,
8853
10163
  REPORT_VERSION,
8854
10164
  REPORT_EXTENSIONS,
8855
10165
  NodeFileSystem,
10166
+ KNOWN_VENDORS,
10167
+ HISTORY_DIR,
8856
10168
  DocCov,
8857
10169
  DEFAULT_REPORT_PATH,
8858
10170
  DEFAULT_REPORT_DIR,