@doccov/sdk 0.15.1 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/LICENSE +1 -1
  2. package/dist/index.d.ts +785 -551
  3. package/dist/index.js +1630 -983
  4. package/package.json +8 -3
package/dist/index.js CHANGED
@@ -15,8 +15,349 @@ var __toESM = (mod, isNodeMode, target) => {
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
+ async function detectRuntimeSchemas(_context) {
22
+ return {
23
+ schemas: new Map,
24
+ errors: []
25
+ };
26
+ }
27
+ function clearSchemaCache() {}
28
+ // src/extract/schema/types.ts
29
+ function isTypeReference(type) {
30
+ return !!(type.flags & 524288 && type.objectFlags && type.objectFlags & 4);
31
+ }
32
+ function getNonNullableType(type) {
33
+ if (type.isUnion()) {
34
+ const nonNullable = type.types.filter((t) => !(t.flags & 32768) && !(t.flags & 65536));
35
+ if (nonNullable.length === 1) {
36
+ return nonNullable[0];
37
+ }
38
+ }
39
+ return type;
40
+ }
41
+ // src/extract/schema/adapters/zod.ts
42
+ var ZOD_TYPE_PATTERN = /^Zod[A-Z]/;
43
+ var zodAdapter = {
44
+ id: "zod",
45
+ packages: ["zod"],
46
+ matches(type, checker) {
47
+ const typeName = checker.typeToString(type);
48
+ return ZOD_TYPE_PATTERN.test(typeName);
49
+ },
50
+ extractOutputType(type, checker) {
51
+ const outputSymbol = type.getProperty("_output");
52
+ if (outputSymbol) {
53
+ return checker.getTypeOfSymbol(outputSymbol);
54
+ }
55
+ const typeSymbol = type.getProperty("_type");
56
+ if (typeSymbol) {
57
+ return checker.getTypeOfSymbol(typeSymbol);
58
+ }
59
+ return null;
60
+ },
61
+ extractInputType(type, checker) {
62
+ const inputSymbol = type.getProperty("_input");
63
+ if (inputSymbol) {
64
+ return checker.getTypeOfSymbol(inputSymbol);
65
+ }
66
+ return null;
67
+ }
68
+ };
69
+
70
+ // src/extract/schema/adapters/valibot.ts
71
+ var VALIBOT_TYPE_PATTERN = /Schema(<|$)/;
72
+ var valibotAdapter = {
73
+ id: "valibot",
74
+ packages: ["valibot"],
75
+ matches(type, checker) {
76
+ const typeName = checker.typeToString(type);
77
+ return VALIBOT_TYPE_PATTERN.test(typeName) && !typeName.includes("Zod");
78
+ },
79
+ extractOutputType(type, checker) {
80
+ const typesSymbol = type.getProperty("~types");
81
+ if (!typesSymbol) {
82
+ return null;
83
+ }
84
+ let typesType = checker.getTypeOfSymbol(typesSymbol);
85
+ typesType = getNonNullableType(typesType);
86
+ const outputSymbol = typesType.getProperty("output");
87
+ if (!outputSymbol) {
88
+ return null;
89
+ }
90
+ return checker.getTypeOfSymbol(outputSymbol);
91
+ },
92
+ extractInputType(type, checker) {
93
+ const typesSymbol = type.getProperty("~types");
94
+ if (!typesSymbol) {
95
+ return null;
96
+ }
97
+ let typesType = checker.getTypeOfSymbol(typesSymbol);
98
+ typesType = getNonNullableType(typesType);
99
+ const inputSymbol = typesType.getProperty("input");
100
+ if (!inputSymbol) {
101
+ return null;
102
+ }
103
+ return checker.getTypeOfSymbol(inputSymbol);
104
+ }
105
+ };
106
+
107
+ // src/extract/schema/adapters/typebox.ts
108
+ var TYPEBOX_TYPE_PATTERN = /^T[A-Z]/;
109
+ var typeboxAdapter = {
110
+ id: "typebox",
111
+ packages: ["@sinclair/typebox"],
112
+ matches(type, checker) {
113
+ const typeName = checker.typeToString(type);
114
+ if (!TYPEBOX_TYPE_PATTERN.test(typeName)) {
115
+ return false;
116
+ }
117
+ const typeProperty = type.getProperty("type");
118
+ return typeProperty !== undefined;
119
+ },
120
+ extractOutputType(type, checker) {
121
+ const staticSymbol = type.getProperty("static");
122
+ if (staticSymbol) {
123
+ return checker.getTypeOfSymbol(staticSymbol);
124
+ }
125
+ return null;
126
+ }
127
+ };
128
+
129
+ // src/extract/schema/adapters/arktype.ts
130
+ var ARKTYPE_TYPE_PATTERN = /^Type</;
131
+ var arktypeAdapter = {
132
+ id: "arktype",
133
+ packages: ["arktype"],
134
+ matches(type, checker) {
135
+ const typeName = checker.typeToString(type);
136
+ return ARKTYPE_TYPE_PATTERN.test(typeName);
137
+ },
138
+ extractOutputType(type, checker) {
139
+ if (!isTypeReference(type)) {
140
+ return null;
141
+ }
142
+ const args = checker.getTypeArguments(type);
143
+ if (args.length < 1) {
144
+ return null;
145
+ }
146
+ return args[0];
147
+ },
148
+ extractInputType(type, checker) {
149
+ if (!isTypeReference(type)) {
150
+ return null;
151
+ }
152
+ const args = checker.getTypeArguments(type);
153
+ if (args.length < 2) {
154
+ return null;
155
+ }
156
+ return args[1];
157
+ }
158
+ };
159
+
160
+ // src/extract/schema/registry.ts
161
+ var adapters = [
162
+ zodAdapter,
163
+ arktypeAdapter,
164
+ typeboxAdapter,
165
+ valibotAdapter
166
+ ];
167
+ function findAdapter(type, checker) {
168
+ for (const adapter of adapters) {
169
+ if (adapter.matches(type, checker)) {
170
+ return adapter;
171
+ }
172
+ }
173
+ return null;
174
+ }
175
+ function isSchemaType(type, checker) {
176
+ return findAdapter(type, checker) !== null;
177
+ }
178
+ function extractSchemaOutputType(type, checker) {
179
+ const adapter = findAdapter(type, checker);
180
+ if (!adapter) {
181
+ return null;
182
+ }
183
+ return adapter.extractOutputType(type, checker);
184
+ }
185
+ function extractSchemaType(type, checker) {
186
+ const adapter = findAdapter(type, checker);
187
+ if (!adapter) {
188
+ return null;
189
+ }
190
+ const outputType = adapter.extractOutputType(type, checker);
191
+ if (!outputType) {
192
+ return null;
193
+ }
194
+ const result = {
195
+ adapter,
196
+ outputType
197
+ };
198
+ if (adapter.extractInputType) {
199
+ const inputType = adapter.extractInputType(type, checker);
200
+ if (inputType) {
201
+ result.inputType = inputType;
202
+ }
203
+ }
204
+ return result;
205
+ }
206
+ function getRegisteredAdapters() {
207
+ return adapters;
208
+ }
209
+ function getSupportedLibraries() {
210
+ return adapters.flatMap((a) => a.packages);
211
+ }
212
+ // src/extract/schema/standard-schema.ts
213
+ import { spawn } from "node:child_process";
214
+ import * as fs from "node:fs";
215
+ import * as path from "node:path";
216
+ function isStandardJSONSchema(obj) {
217
+ if (typeof obj !== "object" || obj === null)
218
+ return false;
219
+ const std = obj["~standard"];
220
+ if (typeof std !== "object" || std === null)
221
+ return false;
222
+ const stdObj = std;
223
+ if (typeof stdObj.version !== "number")
224
+ return false;
225
+ if (typeof stdObj.vendor !== "string")
226
+ return false;
227
+ const jsonSchema = stdObj.jsonSchema;
228
+ if (typeof jsonSchema !== "object" || jsonSchema === null)
229
+ return false;
230
+ const jsObj = jsonSchema;
231
+ return typeof jsObj.output === "function";
232
+ }
233
+ var WORKER_SCRIPT = `
234
+ const path = require('path');
235
+
236
+ async function extract() {
237
+ // With node -e, argv is: [node, arg1, arg2, ...]
238
+ // (the -e script is NOT in argv)
239
+ const [modulePath, target] = process.argv.slice(1);
240
+
241
+ try {
242
+ // Import the module
243
+ const mod = require(path.resolve(modulePath));
244
+ const results = [];
245
+
246
+ // Check each export
247
+ for (const [name, value] of Object.entries(mod)) {
248
+ if (name.startsWith('_')) continue;
249
+ if (typeof value !== 'object' || value === null) continue;
250
+
251
+ const std = value['~standard'];
252
+ if (!std || typeof std !== 'object') continue;
253
+ if (typeof std.version !== 'number') continue;
254
+ if (typeof std.vendor !== 'string') continue;
255
+ if (!std.jsonSchema || typeof std.jsonSchema.output !== 'function') continue;
256
+
257
+ try {
258
+ const outputSchema = std.jsonSchema.output(target);
259
+ const inputSchema = std.jsonSchema.input ? std.jsonSchema.input(target) : undefined;
260
+
261
+ results.push({
262
+ exportName: name,
263
+ vendor: std.vendor,
264
+ outputSchema,
265
+ inputSchema
266
+ });
267
+ } catch (e) {
268
+ // Skip schemas that fail to extract
269
+ }
270
+ }
271
+
272
+ console.log(JSON.stringify({ success: true, results }));
273
+ } catch (e) {
274
+ console.log(JSON.stringify({ success: false, error: e.message }));
275
+ }
276
+ }
277
+
278
+ extract();
279
+ `;
280
+ function resolveCompiledPath(tsPath, baseDir) {
281
+ const relativePath = path.relative(baseDir, tsPath);
282
+ const withoutExt = relativePath.replace(/\.tsx?$/, "");
283
+ const candidates = [
284
+ path.join(baseDir, `${withoutExt}.js`),
285
+ path.join(baseDir, "dist", `${withoutExt.replace(/^src\//, "")}.js`),
286
+ path.join(baseDir, "build", `${withoutExt.replace(/^src\//, "")}.js`),
287
+ path.join(baseDir, "lib", `${withoutExt.replace(/^src\//, "")}.js`)
288
+ ];
289
+ for (const candidate of candidates) {
290
+ if (fs.existsSync(candidate)) {
291
+ return candidate;
292
+ }
293
+ }
294
+ return null;
295
+ }
296
+ async function extractStandardSchemas(compiledJsPath, options = {}) {
297
+ const { timeout = 1e4, target = "draft-2020-12" } = options;
298
+ const result = {
299
+ schemas: new Map,
300
+ errors: []
301
+ };
302
+ if (!fs.existsSync(compiledJsPath)) {
303
+ result.errors.push(`Compiled JS not found: ${compiledJsPath}`);
304
+ return result;
305
+ }
306
+ return new Promise((resolve) => {
307
+ const child = spawn("node", ["-e", WORKER_SCRIPT, compiledJsPath, target], {
308
+ timeout,
309
+ stdio: ["ignore", "pipe", "pipe"]
310
+ });
311
+ let stdout = "";
312
+ let stderr = "";
313
+ child.stdout.on("data", (data) => {
314
+ stdout += data.toString();
315
+ });
316
+ child.stderr.on("data", (data) => {
317
+ stderr += data.toString();
318
+ });
319
+ child.on("close", (code) => {
320
+ if (code !== 0) {
321
+ result.errors.push(`Extraction process failed: ${stderr || `exit code ${code}`}`);
322
+ resolve(result);
323
+ return;
324
+ }
325
+ try {
326
+ const parsed = JSON.parse(stdout);
327
+ if (!parsed.success) {
328
+ result.errors.push(`Extraction failed: ${parsed.error}`);
329
+ resolve(result);
330
+ return;
331
+ }
332
+ for (const item of parsed.results) {
333
+ result.schemas.set(item.exportName, {
334
+ exportName: item.exportName,
335
+ vendor: item.vendor,
336
+ outputSchema: item.outputSchema,
337
+ inputSchema: item.inputSchema
338
+ });
339
+ }
340
+ } catch (e) {
341
+ result.errors.push(`Failed to parse extraction output: ${e}`);
342
+ }
343
+ resolve(result);
344
+ });
345
+ child.on("error", (err) => {
346
+ result.errors.push(`Subprocess error: ${err.message}`);
347
+ resolve(result);
348
+ });
349
+ });
350
+ }
351
+ async function extractStandardSchemasFromProject(entryFile, baseDir, options = {}) {
352
+ const compiledPath = resolveCompiledPath(entryFile, baseDir);
353
+ if (!compiledPath) {
354
+ return {
355
+ schemas: new Map,
356
+ errors: [`Could not find compiled JS for ${entryFile}. Build the project first.`]
357
+ };
358
+ }
359
+ return extractStandardSchemas(compiledPath, options);
360
+ }
20
361
  // src/analysis/docs-coverage.ts
21
362
  import {
22
363
  DRIFT_CATEGORIES
@@ -446,8 +787,8 @@ function categorizeDrifts(drifts) {
446
787
  return { fixable, nonFixable };
447
788
  }
448
789
  // src/fix/jsdoc-writer.ts
449
- import * as fs from "node:fs";
450
- import * as path from "node:path";
790
+ import * as fs2 from "node:fs";
791
+ import * as path2 from "node:path";
451
792
 
452
793
  // src/ts-module.ts
453
794
  import * as tsNamespace from "typescript";
@@ -786,7 +1127,7 @@ async function applyEdits(edits) {
786
1127
  }
787
1128
  for (const [filePath, fileEdits] of editsByFile) {
788
1129
  try {
789
- const content = fs.readFileSync(filePath, "utf-8");
1130
+ const content = fs2.readFileSync(filePath, "utf-8");
790
1131
  const lines = content.split(`
791
1132
  `);
792
1133
  const sortedEdits = [...fileEdits].sort((a, b) => b.startLine - a.startLine);
@@ -800,7 +1141,7 @@ async function applyEdits(edits) {
800
1141
  }
801
1142
  result.editsApplied++;
802
1143
  }
803
- fs.writeFileSync(filePath, lines.join(`
1144
+ fs2.writeFileSync(filePath, lines.join(`
804
1145
  `));
805
1146
  result.filesModified++;
806
1147
  } catch (error) {
@@ -813,8 +1154,8 @@ async function applyEdits(edits) {
813
1154
  return result;
814
1155
  }
815
1156
  function createSourceFile(filePath) {
816
- const content = fs.readFileSync(filePath, "utf-8");
817
- return ts.createSourceFile(path.basename(filePath), content, ts.ScriptTarget.Latest, true, filePath.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
1157
+ const content = fs2.readFileSync(filePath, "utf-8");
1158
+ return ts.createSourceFile(path2.basename(filePath), content, ts.ScriptTarget.Latest, true, filePath.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
818
1159
  }
819
1160
  // src/utils/builtin-detection.ts
820
1161
  function isBuiltInTypeName(name) {
@@ -2012,271 +2353,6 @@ function ensureSpecCoverage(spec) {
2012
2353
  }
2013
2354
  };
2014
2355
  }
2015
- // src/quality/rules.ts
2016
- var CORE_RULES = [
2017
- {
2018
- id: "has-description",
2019
- name: "Has Description",
2020
- description: "Export has a description comment",
2021
- affectsCoverage: true,
2022
- defaultSeverity: "warn",
2023
- check(ctx) {
2024
- return Boolean(ctx.export.description?.trim());
2025
- },
2026
- getViolation(ctx) {
2027
- return {
2028
- ruleId: "has-description",
2029
- severity: "warn",
2030
- message: `Export '${ctx.export.name}' is missing a description`,
2031
- fixable: false
2032
- };
2033
- }
2034
- },
2035
- {
2036
- id: "has-params",
2037
- name: "Has Parameters",
2038
- description: "All parameters are documented",
2039
- appliesTo: ["function"],
2040
- affectsCoverage: true,
2041
- defaultSeverity: "off",
2042
- check(ctx) {
2043
- const parameters = (ctx.export.signatures ?? []).flatMap((sig) => sig.parameters ?? []);
2044
- if (parameters.length === 0)
2045
- return true;
2046
- return parameters.every((p) => Boolean(p.description?.trim()));
2047
- },
2048
- getViolation(ctx) {
2049
- return {
2050
- ruleId: "has-params",
2051
- severity: "warn",
2052
- message: `Function '${ctx.export.name}' has undocumented parameters`,
2053
- fixable: true
2054
- };
2055
- }
2056
- },
2057
- {
2058
- id: "has-returns",
2059
- name: "Has Returns",
2060
- description: "Return value is documented",
2061
- appliesTo: ["function"],
2062
- affectsCoverage: true,
2063
- defaultSeverity: "off",
2064
- check(ctx) {
2065
- const signatures = ctx.export.signatures ?? [];
2066
- if (signatures.length === 0)
2067
- return true;
2068
- return signatures.every((sig) => {
2069
- const text = sig.returns?.description;
2070
- return Boolean(text?.trim());
2071
- });
2072
- },
2073
- getViolation(ctx) {
2074
- return {
2075
- ruleId: "has-returns",
2076
- severity: "warn",
2077
- message: `Function '${ctx.export.name}' has undocumented return value`,
2078
- fixable: true
2079
- };
2080
- }
2081
- },
2082
- {
2083
- id: "has-examples",
2084
- name: "Has Examples",
2085
- description: "Export has at least one @example",
2086
- appliesTo: ["function", "class"],
2087
- affectsCoverage: true,
2088
- defaultSeverity: "off",
2089
- check(ctx) {
2090
- return Boolean(ctx.export.examples?.length);
2091
- },
2092
- getViolation(ctx) {
2093
- return {
2094
- ruleId: "has-examples",
2095
- severity: "warn",
2096
- message: `Export '${ctx.export.name}' is missing an @example`,
2097
- fixable: false
2098
- };
2099
- }
2100
- }
2101
- ];
2102
- var STYLE_RULES = [
2103
- {
2104
- id: "no-empty-returns",
2105
- name: "No Empty Returns",
2106
- description: "@returns tag must have a description",
2107
- appliesTo: ["function"],
2108
- affectsCoverage: false,
2109
- defaultSeverity: "warn",
2110
- check(ctx) {
2111
- if (!ctx.rawJSDoc)
2112
- return true;
2113
- const returnsMatch = ctx.rawJSDoc.match(/@returns?\s*(?:\{[^}]*\})?\s*$/m);
2114
- if (returnsMatch)
2115
- return false;
2116
- const returnsTypeOnly = ctx.rawJSDoc.match(/@returns?\s+\{[^}]+\}\s*$/m);
2117
- if (returnsTypeOnly)
2118
- return false;
2119
- return true;
2120
- },
2121
- getViolation(ctx) {
2122
- return {
2123
- ruleId: "no-empty-returns",
2124
- severity: "warn",
2125
- message: `Export '${ctx.export.name}' has @returns without a description`,
2126
- fixable: false
2127
- };
2128
- }
2129
- },
2130
- {
2131
- id: "consistent-param-style",
2132
- name: "Consistent Param Style",
2133
- description: "@param tags use dash separator",
2134
- appliesTo: ["function"],
2135
- affectsCoverage: false,
2136
- defaultSeverity: "off",
2137
- check(ctx) {
2138
- if (!ctx.rawJSDoc)
2139
- return true;
2140
- const paramRegex = /@param\s+(?:\{[^}]+\}\s+)?(\S+)\s+([^@\n]+)/g;
2141
- const matches = ctx.rawJSDoc.matchAll(paramRegex);
2142
- for (const match of matches) {
2143
- const rest = match[2]?.trim();
2144
- if (rest && !rest.startsWith("-") && !rest.startsWith("–")) {
2145
- return false;
2146
- }
2147
- }
2148
- return true;
2149
- },
2150
- getViolation(ctx) {
2151
- return {
2152
- ruleId: "consistent-param-style",
2153
- severity: "warn",
2154
- message: `Export '${ctx.export.name}' has @param without dash separator`,
2155
- fixable: true
2156
- };
2157
- },
2158
- fix(ctx) {
2159
- if (!ctx.rawJSDoc)
2160
- return null;
2161
- const patch = parseJSDocToPatch(ctx.rawJSDoc);
2162
- if (!patch.params || patch.params.length === 0)
2163
- return null;
2164
- return patch;
2165
- }
2166
- }
2167
- ];
2168
- var BUILTIN_RULES = [...CORE_RULES, ...STYLE_RULES];
2169
- function getCoverageRules() {
2170
- return BUILTIN_RULES.filter((r) => r.affectsCoverage);
2171
- }
2172
- function getRulesForKind(kind) {
2173
- return BUILTIN_RULES.filter((r) => {
2174
- if (!r.appliesTo)
2175
- return true;
2176
- return r.appliesTo.includes(kind);
2177
- });
2178
- }
2179
- function getRule(id) {
2180
- return BUILTIN_RULES.find((r) => r.id === id);
2181
- }
2182
- function getDefaultConfig() {
2183
- const config = {};
2184
- for (const rule of BUILTIN_RULES) {
2185
- config[rule.id] = rule.defaultSeverity;
2186
- }
2187
- return config;
2188
- }
2189
-
2190
- // src/quality/engine.ts
2191
- function evaluateExportQuality(exp, rawJSDoc, config = { rules: {} }) {
2192
- const kind = exp.kind ?? "variable";
2193
- const applicableRules = getRulesForKind(kind);
2194
- const defaults = getDefaultConfig();
2195
- const getSeverity = (ruleId, defaultSev) => config.rules[ruleId] ?? defaults[ruleId] ?? defaultSev;
2196
- const activeCoverageRules = applicableRules.filter((r) => {
2197
- if (!r.affectsCoverage)
2198
- return false;
2199
- const severity = getSeverity(r.id, r.defaultSeverity);
2200
- return severity !== "off";
2201
- });
2202
- const result = {
2203
- coverageScore: 0,
2204
- coverage: {
2205
- satisfied: [],
2206
- missing: [],
2207
- applicable: activeCoverageRules.map((r) => r.id)
2208
- },
2209
- violations: [],
2210
- summary: {
2211
- errorCount: 0,
2212
- warningCount: 0,
2213
- fixableCount: 0
2214
- }
2215
- };
2216
- const context = { export: exp, rawJSDoc };
2217
- for (const rule of applicableRules) {
2218
- const passed = rule.check(context);
2219
- const severity = getSeverity(rule.id, rule.defaultSeverity);
2220
- if (rule.affectsCoverage && severity !== "off") {
2221
- if (passed) {
2222
- result.coverage.satisfied.push(rule.id);
2223
- } else {
2224
- result.coverage.missing.push(rule.id);
2225
- }
2226
- }
2227
- if (!passed && severity !== "off" && rule.getViolation) {
2228
- const violation = {
2229
- ...rule.getViolation(context),
2230
- severity: severity === "error" ? "error" : "warn"
2231
- };
2232
- result.violations.push(violation);
2233
- if (violation.severity === "error") {
2234
- result.summary.errorCount++;
2235
- } else {
2236
- result.summary.warningCount++;
2237
- }
2238
- if (violation.fixable) {
2239
- result.summary.fixableCount++;
2240
- }
2241
- }
2242
- }
2243
- const { satisfied, applicable } = result.coverage;
2244
- result.coverageScore = applicable.length === 0 ? 100 : Math.round(satisfied.length / applicable.length * 100);
2245
- return result;
2246
- }
2247
- function evaluateQuality(exports, config = { rules: {} }) {
2248
- const byExport = new Map;
2249
- let totalCoverage = 0;
2250
- let totalErrors = 0;
2251
- let totalWarnings = 0;
2252
- for (const { export: exp, rawJSDoc } of exports) {
2253
- const result = evaluateExportQuality(exp, rawJSDoc, config);
2254
- byExport.set(exp.id ?? exp.name, result);
2255
- totalCoverage += result.coverageScore;
2256
- totalErrors += result.summary.errorCount;
2257
- totalWarnings += result.summary.warningCount;
2258
- }
2259
- const count = exports.length;
2260
- return {
2261
- byExport,
2262
- overall: {
2263
- coverageScore: count === 0 ? 100 : Math.round(totalCoverage / count),
2264
- totalViolations: totalErrors + totalWarnings,
2265
- errorCount: totalErrors,
2266
- warningCount: totalWarnings
2267
- }
2268
- };
2269
- }
2270
- function mergeConfig(userConfig) {
2271
- const defaults = getDefaultConfig();
2272
- return {
2273
- rules: {
2274
- ...defaults,
2275
- ...userConfig.rules
2276
- }
2277
- };
2278
- }
2279
-
2280
2356
  // src/analysis/enrich.ts
2281
2357
  function collectAllMissing(exports) {
2282
2358
  const allMissing = new Set;
@@ -2298,38 +2374,33 @@ function collectAllDrift(exports) {
2298
2374
  }
2299
2375
  return allDrift;
2300
2376
  }
2301
- function collectAllViolations(exports) {
2302
- const allViolations = [];
2303
- for (const exp of exports) {
2304
- if (exp.docs?.violations) {
2305
- allViolations.push(...exp.docs.violations);
2306
- }
2377
+ function computeExportCoverage(exp) {
2378
+ const missing = [];
2379
+ if (!exp.description) {
2380
+ missing.push("has-description");
2381
+ return { score: 0, missing };
2307
2382
  }
2308
- return allViolations;
2383
+ return { score: 100, missing: [] };
2309
2384
  }
2310
2385
  function enrichSpec(spec, options = {}) {
2311
- const { driftByExport, qualityConfig = { rules: {} }, rawJSDocByExport } = options;
2386
+ const { driftByExport } = options;
2312
2387
  const exportRegistry = buildExportRegistry(spec);
2313
2388
  let totalCoverage = 0;
2314
2389
  const enrichedExports = spec.exports.map((exp) => {
2315
- const rawJSDoc = rawJSDocByExport?.get(exp.id);
2316
- const quality = evaluateExportQuality(exp, rawJSDoc, qualityConfig);
2390
+ const coverage = computeExportCoverage(exp);
2317
2391
  const drift = computeExportDrift(exp, exportRegistry);
2318
2392
  const additionalDrift = driftByExport?.get(exp.id);
2319
2393
  const allDrift2 = additionalDrift ? [...drift, ...additionalDrift] : drift;
2320
- totalCoverage += quality.coverageScore;
2394
+ totalCoverage += coverage.score;
2321
2395
  const docs2 = {
2322
- coverageScore: quality.coverageScore
2396
+ coverageScore: coverage.score
2323
2397
  };
2324
- if (quality.coverage.missing.length > 0) {
2325
- docs2.missing = quality.coverage.missing;
2398
+ if (coverage.missing.length > 0) {
2399
+ docs2.missing = coverage.missing;
2326
2400
  }
2327
2401
  if (allDrift2.length > 0) {
2328
2402
  docs2.drift = allDrift2;
2329
2403
  }
2330
- if (quality.violations.length > 0) {
2331
- docs2.violations = quality.violations;
2332
- }
2333
2404
  return {
2334
2405
  ...exp,
2335
2406
  docs: docs2
@@ -2338,7 +2409,6 @@ function enrichSpec(spec, options = {}) {
2338
2409
  const count = enrichedExports.length;
2339
2410
  const allMissing = collectAllMissing(enrichedExports);
2340
2411
  const allDrift = collectAllDrift(enrichedExports);
2341
- const allViolations = collectAllViolations(enrichedExports);
2342
2412
  const docs = {
2343
2413
  coverageScore: count === 0 ? 100 : Math.round(totalCoverage / count)
2344
2414
  };
@@ -2348,9 +2418,6 @@ function enrichSpec(spec, options = {}) {
2348
2418
  if (allDrift.length > 0) {
2349
2419
  docs.drift = allDrift;
2350
2420
  }
2351
- if (allViolations.length > 0) {
2352
- docs.violations = allViolations;
2353
- }
2354
2421
  const driftSummary = allDrift.length > 0 ? getDriftSummary(allDrift) : undefined;
2355
2422
  return {
2356
2423
  ...spec,
@@ -2360,8 +2427,8 @@ function enrichSpec(spec, options = {}) {
2360
2427
  };
2361
2428
  }
2362
2429
  // src/analysis/report.ts
2363
- import * as fs2 from "node:fs";
2364
- import * as path2 from "node:path";
2430
+ import * as fs3 from "node:fs";
2431
+ import * as path3 from "node:path";
2365
2432
 
2366
2433
  // src/types/report.ts
2367
2434
  var REPORT_VERSION = "1.0.0";
@@ -2424,7 +2491,7 @@ function generateReportFromEnriched(enriched) {
2424
2491
  driftSummary
2425
2492
  };
2426
2493
  return {
2427
- $schema: "https://doccov.dev/schemas/v1.0.0/report.schema.json",
2494
+ $schema: "https://doccov.com/schemas/v1.0.0/report.schema.json",
2428
2495
  version: REPORT_VERSION,
2429
2496
  generatedAt: new Date().toISOString(),
2430
2497
  spec: {
@@ -2437,23 +2504,23 @@ function generateReportFromEnriched(enriched) {
2437
2504
  }
2438
2505
  function loadCachedReport(reportPath = DEFAULT_REPORT_PATH) {
2439
2506
  try {
2440
- const fullPath = path2.resolve(reportPath);
2441
- if (!fs2.existsSync(fullPath)) {
2507
+ const fullPath = path3.resolve(reportPath);
2508
+ if (!fs3.existsSync(fullPath)) {
2442
2509
  return null;
2443
2510
  }
2444
- const content = fs2.readFileSync(fullPath, "utf-8");
2511
+ const content = fs3.readFileSync(fullPath, "utf-8");
2445
2512
  return JSON.parse(content);
2446
2513
  } catch {
2447
2514
  return null;
2448
2515
  }
2449
2516
  }
2450
2517
  function saveReport(report, reportPath = DEFAULT_REPORT_PATH) {
2451
- const fullPath = path2.resolve(reportPath);
2452
- const dir = path2.dirname(fullPath);
2453
- if (!fs2.existsSync(dir)) {
2454
- fs2.mkdirSync(dir, { recursive: true });
2518
+ const fullPath = path3.resolve(reportPath);
2519
+ const dir = path3.dirname(fullPath);
2520
+ if (!fs3.existsSync(dir)) {
2521
+ fs3.mkdirSync(dir, { recursive: true });
2455
2522
  }
2456
- fs2.writeFileSync(fullPath, JSON.stringify(report, null, 2));
2523
+ fs3.writeFileSync(fullPath, JSON.stringify(report, null, 2));
2457
2524
  }
2458
2525
  function isCachedReportValid(reportPath = DEFAULT_REPORT_PATH, sourceFiles = []) {
2459
2526
  const report = loadCachedReport(reportPath);
@@ -2463,7 +2530,7 @@ function isCachedReportValid(reportPath = DEFAULT_REPORT_PATH, sourceFiles = [])
2463
2530
  const reportTime = new Date(report.generatedAt).getTime();
2464
2531
  for (const file of sourceFiles) {
2465
2532
  try {
2466
- const stat = fs2.statSync(file);
2533
+ const stat = fs3.statSync(file);
2467
2534
  if (stat.mtimeMs > reportTime) {
2468
2535
  return false;
2469
2536
  }
@@ -2473,16 +2540,418 @@ function isCachedReportValid(reportPath = DEFAULT_REPORT_PATH, sourceFiles = [])
2473
2540
  }
2474
2541
  return true;
2475
2542
  }
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;
2543
+ function formatSignature(name, signature) {
2544
+ const params = (signature.parameters ?? []).map((p) => {
2545
+ const optional = p.required === false ? "?" : "";
2546
+ const rest = p.rest ? "..." : "";
2547
+ const typeStr = typeof p.schema === "string" ? p.schema : p.schema?.type ?? "unknown";
2548
+ return `${rest}${p.name}${optional}: ${typeStr}`;
2549
+ }).join(", ");
2550
+ const returnType = signature.returns ? typeof signature.returns.schema === "string" ? signature.returns.schema : signature.returns.schema?.type ?? "unknown" : "void";
2551
+ const typeParams = signature.typeParameters?.length ? `<${signature.typeParameters.map((tp) => tp.name).join(", ")}>` : "";
2552
+ return `${name}${typeParams}(${params}): ${returnType}`;
2553
+ }
2554
+ function formatExportToApiSurface(exp) {
2555
+ const lines = [];
2556
+ lines.push(`### ${exp.name}`);
2557
+ switch (exp.kind) {
2558
+ case "function": {
2559
+ const signatures = exp.signatures ?? [];
2560
+ if (signatures.length === 0) {
2561
+ lines.push(`\`\`\`typescript
2562
+ function ${exp.name}(): unknown
2563
+ \`\`\``);
2564
+ } else {
2565
+ for (const sig of signatures) {
2566
+ lines.push(`\`\`\`typescript
2567
+ function ${formatSignature(exp.name, sig)}
2568
+ \`\`\``);
2569
+ }
2570
+ }
2571
+ break;
2572
+ }
2573
+ case "class": {
2574
+ const extendsClause = exp.extends ? ` extends ${exp.extends}` : "";
2575
+ const implementsClause = exp.implements?.length ? ` implements ${exp.implements.join(", ")}` : "";
2576
+ lines.push(`\`\`\`typescript
2577
+ class ${exp.name}${extendsClause}${implementsClause}
2578
+ \`\`\``);
2579
+ break;
2580
+ }
2581
+ case "interface":
2582
+ case "type": {
2583
+ const typeStr = typeof exp.type === "string" ? exp.type : exp.type?.type ?? "{ ... }";
2584
+ lines.push(`\`\`\`typescript
2585
+ type ${exp.name} = ${typeStr}
2586
+ \`\`\``);
2587
+ break;
2588
+ }
2589
+ case "variable": {
2590
+ const typeStr = typeof exp.type === "string" ? exp.type : exp.type?.type ?? "unknown";
2591
+ lines.push(`\`\`\`typescript
2592
+ const ${exp.name}: ${typeStr}
2593
+ \`\`\``);
2594
+ break;
2595
+ }
2596
+ case "enum": {
2597
+ lines.push(`\`\`\`typescript
2598
+ enum ${exp.name} { ... }
2599
+ \`\`\``);
2600
+ break;
2601
+ }
2602
+ default: {
2603
+ lines.push(`\`\`\`typescript
2604
+ ${exp.kind} ${exp.name}
2605
+ \`\`\``);
2606
+ }
2607
+ }
2608
+ return lines.join(`
2609
+ `);
2610
+ }
2611
+ function formatTypeToApiSurface(type) {
2612
+ const lines = [];
2613
+ lines.push(`### ${type.name}`);
2614
+ switch (type.kind) {
2615
+ case "interface": {
2616
+ const extendsClause = type.extends ? ` extends ${type.extends}` : "";
2617
+ lines.push(`\`\`\`typescript
2618
+ interface ${type.name}${extendsClause} { ... }
2619
+ \`\`\``);
2620
+ break;
2621
+ }
2622
+ case "type": {
2623
+ const typeStr = typeof type.type === "string" ? type.type : type.type?.type ?? "{ ... }";
2624
+ lines.push(`\`\`\`typescript
2625
+ type ${type.name} = ${typeStr}
2626
+ \`\`\``);
2627
+ break;
2628
+ }
2629
+ case "class": {
2630
+ const extendsClause = type.extends ? ` extends ${type.extends}` : "";
2631
+ lines.push(`\`\`\`typescript
2632
+ class ${type.name}${extendsClause}
2633
+ \`\`\``);
2634
+ break;
2635
+ }
2636
+ case "enum": {
2637
+ lines.push(`\`\`\`typescript
2638
+ enum ${type.name} { ... }
2639
+ \`\`\``);
2640
+ break;
2641
+ }
2642
+ default: {
2643
+ lines.push(`\`\`\`typescript
2644
+ ${type.kind} ${type.name}
2645
+ \`\`\``);
2646
+ }
2647
+ }
2648
+ return lines.join(`
2649
+ `);
2650
+ }
2651
+ function renderApiSurface(spec) {
2652
+ const lines = [];
2653
+ const version = spec.meta.version ? ` v${spec.meta.version}` : "";
2654
+ lines.push(`# API Surface: ${spec.meta.name}${version}`);
2655
+ lines.push("");
2656
+ lines.push("> This file is auto-generated. Do not edit manually.");
2657
+ lines.push("> Run `doccov spec --format api-surface` to regenerate.");
2658
+ lines.push("");
2659
+ const exportsByKind = {};
2660
+ for (const exp of spec.exports) {
2661
+ const kind = exp.kind;
2662
+ if (!exportsByKind[kind]) {
2663
+ exportsByKind[kind] = [];
2664
+ }
2665
+ exportsByKind[kind].push(exp);
2666
+ }
2667
+ for (const kind of Object.keys(exportsByKind)) {
2668
+ exportsByKind[kind].sort((a, b) => a.name.localeCompare(b.name));
2669
+ }
2670
+ const kindOrder = [
2671
+ "function",
2672
+ "class",
2673
+ "interface",
2674
+ "type",
2675
+ "variable",
2676
+ "enum",
2677
+ "namespace",
2678
+ "module"
2679
+ ];
2680
+ for (const kind of kindOrder) {
2681
+ const exports = exportsByKind[kind];
2682
+ if (!exports || exports.length === 0)
2683
+ continue;
2684
+ const kindTitle = kind.charAt(0).toUpperCase() + kind.slice(1) + "s";
2685
+ lines.push(`## ${kindTitle}`);
2686
+ lines.push("");
2687
+ for (const exp of exports) {
2688
+ lines.push(formatExportToApiSurface(exp));
2689
+ lines.push("");
2690
+ }
2691
+ }
2692
+ for (const kind of Object.keys(exportsByKind).sort()) {
2693
+ if (kindOrder.includes(kind))
2694
+ continue;
2695
+ const exports = exportsByKind[kind];
2696
+ if (!exports || exports.length === 0)
2697
+ continue;
2698
+ const kindTitle = kind.charAt(0).toUpperCase() + kind.slice(1) + "s";
2699
+ lines.push(`## ${kindTitle}`);
2700
+ lines.push("");
2701
+ for (const exp of exports) {
2702
+ lines.push(formatExportToApiSurface(exp));
2703
+ lines.push("");
2704
+ }
2705
+ }
2706
+ const types = spec.types ?? [];
2707
+ if (types.length > 0) {
2708
+ const sortedTypes = [...types].sort((a, b) => a.name.localeCompare(b.name));
2709
+ lines.push("## Internal Types");
2710
+ lines.push("");
2711
+ for (const type of sortedTypes) {
2712
+ lines.push(formatTypeToApiSurface(type));
2713
+ lines.push("");
2714
+ }
2715
+ }
2716
+ return lines.join(`
2717
+ `);
2718
+ }
2719
+ // src/analysis/history.ts
2720
+ import * as fs4 from "node:fs";
2721
+ import * as path4 from "node:path";
2722
+ var HISTORY_DIR = ".doccov/history";
2723
+ var RETENTION_DAYS = {
2724
+ free: 7,
2725
+ team: 30,
2726
+ pro: 90
2727
+ };
2728
+ function getSnapshotFilename(timestamp) {
2729
+ const pad = (n) => n.toString().padStart(2, "0");
2730
+ const year = timestamp.getFullYear();
2731
+ const month = pad(timestamp.getMonth() + 1);
2732
+ const day = pad(timestamp.getDate());
2733
+ const hours = pad(timestamp.getHours());
2734
+ const minutes = pad(timestamp.getMinutes());
2735
+ const seconds = pad(timestamp.getSeconds());
2736
+ return `${year}-${month}-${day}-${hours}${minutes}${seconds}.json`;
2737
+ }
2738
+ function computeSnapshot(spec, options) {
2739
+ const exports = spec.exports ?? [];
2740
+ const documented = exports.filter((e) => e.description && e.description.trim().length > 0);
2741
+ const driftCount = exports.reduce((sum, e) => {
2742
+ const docs = e.docs;
2743
+ return sum + (docs?.drift?.length ?? 0);
2744
+ }, 0);
2745
+ const coverageScore = exports.length > 0 ? Math.round(documented.length / exports.length * 100) : 100;
2746
+ return {
2747
+ timestamp: new Date().toISOString(),
2748
+ package: spec.meta.name,
2749
+ version: spec.meta.version,
2750
+ coverageScore,
2751
+ totalExports: exports.length,
2752
+ documentedExports: documented.length,
2753
+ driftCount,
2754
+ commit: options?.commit,
2755
+ branch: options?.branch
2756
+ };
2757
+ }
2758
+ function saveSnapshot(snapshot, cwd) {
2759
+ const historyDir = path4.resolve(cwd, HISTORY_DIR);
2760
+ if (!fs4.existsSync(historyDir)) {
2761
+ fs4.mkdirSync(historyDir, { recursive: true });
2762
+ }
2763
+ const filename = getSnapshotFilename(new Date(snapshot.timestamp));
2764
+ const filepath = path4.join(historyDir, filename);
2765
+ fs4.writeFileSync(filepath, JSON.stringify(snapshot, null, 2));
2766
+ }
2767
+ function loadSnapshots(cwd) {
2768
+ const historyDir = path4.resolve(cwd, HISTORY_DIR);
2769
+ if (!fs4.existsSync(historyDir)) {
2770
+ return [];
2771
+ }
2772
+ const files = fs4.readdirSync(historyDir).filter((f) => f.endsWith(".json")).sort().reverse();
2773
+ const snapshots = [];
2774
+ for (const file of files) {
2775
+ try {
2776
+ const content = fs4.readFileSync(path4.join(historyDir, file), "utf-8");
2777
+ snapshots.push(JSON.parse(content));
2778
+ } catch {}
2779
+ }
2780
+ return snapshots;
2781
+ }
2782
+ function getTrend(spec, cwd, options) {
2783
+ const current = computeSnapshot(spec, options);
2784
+ const history = loadSnapshots(cwd);
2785
+ const delta = history.length > 0 ? current.coverageScore - history[0].coverageScore : undefined;
2786
+ const sparklineHistory = history.slice(0, 9).map((s) => s.coverageScore);
2787
+ const sparkline = [current.coverageScore, ...sparklineHistory].reverse();
2788
+ return {
2789
+ current,
2790
+ history,
2791
+ delta,
2792
+ sparkline
2793
+ };
2794
+ }
2795
+ function renderSparkline(values) {
2796
+ if (values.length === 0)
2797
+ return "";
2798
+ const chars = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
2799
+ const min = Math.min(...values);
2800
+ const max = Math.max(...values);
2801
+ const range = max - min || 1;
2802
+ return values.map((v) => {
2803
+ const normalized = (v - min) / range;
2804
+ const index = Math.min(Math.floor(normalized * chars.length), chars.length - 1);
2805
+ return chars[index];
2806
+ }).join("");
2807
+ }
2808
+ function formatDelta(delta) {
2809
+ if (delta > 0)
2810
+ return `↑${delta}%`;
2811
+ if (delta < 0)
2812
+ return `↓${Math.abs(delta)}%`;
2813
+ return "→0%";
2814
+ }
2815
+ function pruneHistory(cwd, keepCount = 100) {
2816
+ const historyDir = path4.resolve(cwd, HISTORY_DIR);
2817
+ if (!fs4.existsSync(historyDir)) {
2818
+ return 0;
2819
+ }
2820
+ const files = fs4.readdirSync(historyDir).filter((f) => f.endsWith(".json")).sort().reverse();
2821
+ const toDelete = files.slice(keepCount);
2822
+ for (const file of toDelete) {
2823
+ try {
2824
+ fs4.unlinkSync(path4.join(historyDir, file));
2825
+ } catch {}
2826
+ }
2827
+ return toDelete.length;
2828
+ }
2829
+ function pruneByTier(cwd, tier) {
2830
+ const retentionDays = RETENTION_DAYS[tier];
2831
+ const cutoffDate = new Date;
2832
+ cutoffDate.setDate(cutoffDate.getDate() - retentionDays);
2833
+ const historyDir = path4.resolve(cwd, HISTORY_DIR);
2834
+ if (!fs4.existsSync(historyDir)) {
2835
+ return 0;
2836
+ }
2837
+ const files = fs4.readdirSync(historyDir).filter((f) => f.endsWith(".json"));
2838
+ let deleted = 0;
2839
+ for (const file of files) {
2840
+ try {
2841
+ const filepath = path4.join(historyDir, file);
2842
+ const content = fs4.readFileSync(filepath, "utf-8");
2843
+ const snapshot = JSON.parse(content);
2844
+ const snapshotDate = new Date(snapshot.timestamp);
2845
+ if (snapshotDate < cutoffDate) {
2846
+ fs4.unlinkSync(filepath);
2847
+ deleted++;
2848
+ }
2849
+ } catch {}
2850
+ }
2851
+ return deleted;
2852
+ }
2853
+ function loadSnapshotsForDays(cwd, days) {
2854
+ const cutoffDate = new Date;
2855
+ cutoffDate.setDate(cutoffDate.getDate() - days);
2856
+ const allSnapshots = loadSnapshots(cwd);
2857
+ return allSnapshots.filter((s) => new Date(s.timestamp) >= cutoffDate);
2858
+ }
2859
+ function calculateVelocity(snapshots) {
2860
+ if (snapshots.length < 2)
2861
+ return 0;
2862
+ const newest = snapshots[0];
2863
+ const oldest = snapshots[snapshots.length - 1];
2864
+ const newestDate = new Date(newest.timestamp);
2865
+ const oldestDate = new Date(oldest.timestamp);
2866
+ const daysDiff = (newestDate.getTime() - oldestDate.getTime()) / (1000 * 60 * 60 * 24);
2867
+ if (daysDiff < 1)
2868
+ return 0;
2869
+ const coverageDiff = newest.coverageScore - oldest.coverageScore;
2870
+ return Math.round(coverageDiff / daysDiff * 100) / 100;
2871
+ }
2872
+ function getWeekStart(date) {
2873
+ const result = new Date(date);
2874
+ result.setDate(result.getDate() - result.getDay());
2875
+ result.setHours(0, 0, 0, 0);
2876
+ return result;
2877
+ }
2878
+ function generateWeeklySummaries(snapshots) {
2879
+ if (snapshots.length === 0)
2880
+ return [];
2881
+ const weeklyGroups = new Map;
2882
+ for (const snapshot of snapshots) {
2883
+ const weekStart = getWeekStart(new Date(snapshot.timestamp));
2884
+ const weekKey = weekStart.toISOString().split("T")[0];
2885
+ const group = weeklyGroups.get(weekKey) ?? [];
2886
+ group.push(snapshot);
2887
+ weeklyGroups.set(weekKey, group);
2888
+ }
2889
+ const summaries = [];
2890
+ for (const [weekKey, weekSnapshots] of weeklyGroups) {
2891
+ const sorted = [...weekSnapshots].sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
2892
+ const weekStart = new Date(weekKey);
2893
+ const weekEnd = new Date(weekStart);
2894
+ weekEnd.setDate(weekEnd.getDate() + 6);
2895
+ const scores = sorted.map((s) => s.coverageScore);
2896
+ const avgCoverage = Math.round(scores.reduce((a, b) => a + b, 0) / scores.length);
2897
+ const startCoverage = sorted[0].coverageScore;
2898
+ const endCoverage = sorted[sorted.length - 1].coverageScore;
2899
+ summaries.push({
2900
+ weekStart: weekStart.toISOString(),
2901
+ weekEnd: weekEnd.toISOString(),
2902
+ avgCoverage,
2903
+ startCoverage,
2904
+ endCoverage,
2905
+ delta: endCoverage - startCoverage,
2906
+ snapshotCount: sorted.length
2907
+ });
2908
+ }
2909
+ return summaries.sort((a, b) => new Date(b.weekStart).getTime() - new Date(a.weekStart).getTime());
2910
+ }
2911
+ function getExtendedTrend(spec, cwd, options) {
2912
+ const tier = options?.tier ?? "pro";
2913
+ const retentionDays = RETENTION_DAYS[tier];
2914
+ const trend = getTrend(spec, cwd, options);
2915
+ const periodSnapshots = loadSnapshotsForDays(cwd, retentionDays);
2916
+ const snapshots7d = loadSnapshotsForDays(cwd, 7);
2917
+ const snapshots30d = loadSnapshotsForDays(cwd, 30);
2918
+ const snapshots90d = tier === "pro" ? loadSnapshotsForDays(cwd, 90) : [];
2919
+ const velocity7d = calculateVelocity(snapshots7d);
2920
+ const velocity30d = calculateVelocity(snapshots30d);
2921
+ const velocity90d = tier === "pro" ? calculateVelocity(snapshots90d) : undefined;
2922
+ const currentScore = trend.current.coverageScore;
2923
+ const projected30d = Math.min(100, Math.max(0, Math.round(currentScore + velocity30d * 30)));
2924
+ const allScores = [trend.current.coverageScore, ...trend.history.map((s) => s.coverageScore)];
2925
+ const allTimeHigh = Math.max(...allScores);
2926
+ const allTimeLow = Math.min(...allScores);
2927
+ const dataRange = trend.history.length > 0 ? {
2928
+ start: trend.history[trend.history.length - 1].timestamp,
2929
+ end: trend.current.timestamp
2930
+ } : null;
2931
+ const allSnapshots = [trend.current, ...periodSnapshots];
2932
+ const weeklySummaries = generateWeeklySummaries(allSnapshots);
2933
+ return {
2934
+ trend,
2935
+ weeklySummaries,
2936
+ velocity7d,
2937
+ velocity30d,
2938
+ velocity90d,
2939
+ projected30d,
2940
+ allTimeHigh,
2941
+ allTimeLow,
2942
+ dataRange
2943
+ };
2944
+ }
2945
+ // src/cache/hash.ts
2946
+ import * as crypto from "node:crypto";
2947
+ import * as fs5 from "node:fs";
2948
+ import * as path5 from "node:path";
2949
+ function hashFile(filePath) {
2950
+ try {
2951
+ const content = fs5.readFileSync(filePath);
2952
+ return crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
2953
+ } catch {
2954
+ return null;
2486
2955
  }
2487
2956
  }
2488
2957
  function hashString(content) {
@@ -2493,7 +2962,7 @@ function hashFiles(filePaths, cwd) {
2493
2962
  for (const filePath of filePaths) {
2494
2963
  const hash = hashFile(filePath);
2495
2964
  if (hash) {
2496
- const relativePath = path3.relative(cwd, filePath);
2965
+ const relativePath = path5.relative(cwd, filePath);
2497
2966
  hashes[relativePath] = hash;
2498
2967
  }
2499
2968
  }
@@ -2514,17 +2983,17 @@ function diffHashes(cached, current) {
2514
2983
  return changed;
2515
2984
  }
2516
2985
  // src/cache/spec-cache.ts
2517
- import * as fs4 from "node:fs";
2518
- import * as path4 from "node:path";
2986
+ import * as fs6 from "node:fs";
2987
+ import * as path6 from "node:path";
2519
2988
  var CACHE_VERSION = "1.0.0";
2520
2989
  var SPEC_CACHE_FILE = ".doccov/spec.cache.json";
2521
2990
  function loadSpecCache(cwd) {
2522
2991
  try {
2523
- const cachePath = path4.resolve(cwd, SPEC_CACHE_FILE);
2524
- if (!fs4.existsSync(cachePath)) {
2992
+ const cachePath = path6.resolve(cwd, SPEC_CACHE_FILE);
2993
+ if (!fs6.existsSync(cachePath)) {
2525
2994
  return null;
2526
2995
  }
2527
- const content = fs4.readFileSync(cachePath, "utf-8");
2996
+ const content = fs6.readFileSync(cachePath, "utf-8");
2528
2997
  return JSON.parse(content);
2529
2998
  } catch {
2530
2999
  return null;
@@ -2536,7 +3005,7 @@ function saveSpecCache(spec, context) {
2536
3005
  cacheVersion: CACHE_VERSION,
2537
3006
  generatedAt: new Date().toISOString(),
2538
3007
  specVersion: spec.openpkg,
2539
- entryFile: path4.relative(cwd, entryFile),
3008
+ entryFile: path6.relative(cwd, entryFile),
2540
3009
  hashes: {
2541
3010
  tsconfig: tsconfigPath ? hashFile(tsconfigPath) : null,
2542
3011
  packageJson: hashFile(packageJsonPath) ?? "",
@@ -2545,19 +3014,19 @@ function saveSpecCache(spec, context) {
2545
3014
  config,
2546
3015
  spec
2547
3016
  };
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 });
3017
+ const cachePath = path6.resolve(cwd, SPEC_CACHE_FILE);
3018
+ const dir = path6.dirname(cachePath);
3019
+ if (!fs6.existsSync(dir)) {
3020
+ fs6.mkdirSync(dir, { recursive: true });
2552
3021
  }
2553
- fs4.writeFileSync(cachePath, JSON.stringify(cache, null, 2));
3022
+ fs6.writeFileSync(cachePath, JSON.stringify(cache, null, 2));
2554
3023
  }
2555
3024
  function validateSpecCache(cache, context) {
2556
3025
  const { entryFile, sourceFiles, tsconfigPath, packageJsonPath, config, cwd } = context;
2557
3026
  if (cache.cacheVersion !== CACHE_VERSION) {
2558
3027
  return { valid: false, reason: "cache-version-mismatch" };
2559
3028
  }
2560
- const relativeEntry = path4.relative(cwd, entryFile);
3029
+ const relativeEntry = path6.relative(cwd, entryFile);
2561
3030
  if (cache.entryFile !== relativeEntry) {
2562
3031
  return { valid: false, reason: "entry-file-changed" };
2563
3032
  }
@@ -2580,34 +3049,34 @@ function validateSpecCache(cache, context) {
2580
3049
  return { valid: true };
2581
3050
  }
2582
3051
  function clearSpecCache(cwd) {
2583
- const cachePath = path4.resolve(cwd, SPEC_CACHE_FILE);
2584
- if (fs4.existsSync(cachePath)) {
2585
- fs4.unlinkSync(cachePath);
3052
+ const cachePath = path6.resolve(cwd, SPEC_CACHE_FILE);
3053
+ if (fs6.existsSync(cachePath)) {
3054
+ fs6.unlinkSync(cachePath);
2586
3055
  return true;
2587
3056
  }
2588
3057
  return false;
2589
3058
  }
2590
3059
  function getSpecCachePath(cwd) {
2591
- return path4.resolve(cwd, SPEC_CACHE_FILE);
3060
+ return path6.resolve(cwd, SPEC_CACHE_FILE);
2592
3061
  }
2593
3062
  // src/config/types.ts
2594
3063
  function defineConfig(config) {
2595
3064
  return config;
2596
3065
  }
2597
3066
  // src/detect/utils.ts
2598
- async function safeParseJson(fs5, path5) {
3067
+ async function safeParseJson(fs7, path7) {
2599
3068
  try {
2600
- if (!await fs5.exists(path5))
3069
+ if (!await fs7.exists(path7))
2601
3070
  return null;
2602
- const content = await fs5.readFile(path5);
3071
+ const content = await fs7.readFile(path7);
2603
3072
  return JSON.parse(content);
2604
3073
  } catch {
2605
3074
  return null;
2606
3075
  }
2607
3076
  }
2608
- async function readPackageJson(fs5, dir) {
2609
- const path5 = dir === "." ? "package.json" : `${dir}/package.json`;
2610
- return safeParseJson(fs5, path5);
3077
+ async function readPackageJson(fs7, dir) {
3078
+ const path7 = dir === "." ? "package.json" : `${dir}/package.json`;
3079
+ return safeParseJson(fs7, path7);
2611
3080
  }
2612
3081
 
2613
3082
  // src/detect/build.ts
@@ -2637,8 +3106,8 @@ var BUILD_TOOL_PATTERNS = [
2637
3106
  "babel",
2638
3107
  "ncc build"
2639
3108
  ];
2640
- async function detectBuildInfo(fs5, packagePath = ".") {
2641
- const pkgJson = await readPackageJson(fs5, packagePath);
3109
+ async function detectBuildInfo(fs7, packagePath = ".") {
3110
+ const pkgJson = await readPackageJson(fs7, packagePath);
2642
3111
  const scripts = pkgJson?.scripts ?? {};
2643
3112
  const scriptNames = Object.keys(scripts);
2644
3113
  const buildScriptsByName = scriptNames.filter((name) => BUILD_SCRIPT_NAMES.has(name) || BUILD_SCRIPT_PREFIXES.some((prefix) => name.startsWith(prefix)));
@@ -2650,10 +3119,10 @@ async function detectBuildInfo(fs5, packagePath = ".") {
2650
3119
  });
2651
3120
  const buildScripts = [...new Set([...buildScriptsByName, ...buildScriptsByContent])];
2652
3121
  const tsconfigPath = packagePath === "." ? "tsconfig.json" : `${packagePath}/tsconfig.json`;
2653
- const hasTsConfig = await fs5.exists(tsconfigPath);
3122
+ const hasTsConfig = await fs7.exists(tsconfigPath);
2654
3123
  const hasTsDep = pkgJson?.devDependencies?.typescript !== undefined || pkgJson?.dependencies?.typescript !== undefined;
2655
3124
  const hasTypeScript = hasTsConfig || hasTsDep;
2656
- const wasm = await detectWasmProject(fs5, packagePath, pkgJson);
3125
+ const wasm = await detectWasmProject(fs7, packagePath, pkgJson);
2657
3126
  const napi = detectNapiProject(pkgJson);
2658
3127
  return {
2659
3128
  scripts: buildScripts,
@@ -2670,11 +3139,11 @@ var WASM_PACKAGES = new Set([
2670
3139
  "wasm-bindgen",
2671
3140
  "@aspect/aspect-cli"
2672
3141
  ]);
2673
- async function detectWasmProject(fs5, packagePath, pkgJson) {
3142
+ async function detectWasmProject(fs7, packagePath, pkgJson) {
2674
3143
  const pkgCargoPath = packagePath === "." ? "Cargo.toml" : `${packagePath}/Cargo.toml`;
2675
- if (await fs5.exists(pkgCargoPath))
3144
+ if (await fs7.exists(pkgCargoPath))
2676
3145
  return true;
2677
- if (packagePath !== "." && await fs5.exists("Cargo.toml"))
3146
+ if (packagePath !== "." && await fs7.exists("Cargo.toml"))
2678
3147
  return true;
2679
3148
  if (pkgJson) {
2680
3149
  const deps = Object.keys({
@@ -2715,15 +3184,15 @@ function getPrimaryBuildScript(buildInfo) {
2715
3184
  return buildInfo.scripts[0] ?? null;
2716
3185
  }
2717
3186
  // src/detect/entry-point.ts
2718
- async function detectEntryPoint(fs5, packagePath = ".") {
2719
- const pkgJson = await readPackageJson(fs5, packagePath);
3187
+ async function detectEntryPoint(fs7, packagePath = ".") {
3188
+ const pkgJson = await readPackageJson(fs7, packagePath);
2720
3189
  if (!pkgJson) {
2721
3190
  throw new Error("No package.json found - not a valid npm package");
2722
3191
  }
2723
- const tsConfig = await parseTsConfig(fs5, packagePath);
3192
+ const tsConfig = await parseTsConfig(fs7, packagePath);
2724
3193
  const typesField = pkgJson.types || pkgJson.typings;
2725
3194
  if (typesField && typeof typesField === "string") {
2726
- const resolved = await resolveToSource(fs5, packagePath, typesField, tsConfig);
3195
+ const resolved = await resolveToSource(fs7, packagePath, typesField, tsConfig);
2727
3196
  if (resolved) {
2728
3197
  return { ...resolved, source: "types" };
2729
3198
  }
@@ -2733,7 +3202,7 @@ async function detectEntryPoint(fs5, packagePath = ".") {
2733
3202
  if (dotExport && typeof dotExport === "object" && "types" in dotExport) {
2734
3203
  const typesPath = dotExport.types;
2735
3204
  if (typesPath && typeof typesPath === "string") {
2736
- const resolved = await resolveToSource(fs5, packagePath, typesPath, tsConfig);
3205
+ const resolved = await resolveToSource(fs7, packagePath, typesPath, tsConfig);
2737
3206
  if (resolved) {
2738
3207
  return { ...resolved, source: "exports" };
2739
3208
  }
@@ -2741,13 +3210,13 @@ async function detectEntryPoint(fs5, packagePath = ".") {
2741
3210
  }
2742
3211
  }
2743
3212
  if (pkgJson.main && typeof pkgJson.main === "string") {
2744
- const resolved = await resolveToSource(fs5, packagePath, pkgJson.main, tsConfig);
3213
+ const resolved = await resolveToSource(fs7, packagePath, pkgJson.main, tsConfig);
2745
3214
  if (resolved) {
2746
3215
  return { ...resolved, source: "main" };
2747
3216
  }
2748
3217
  }
2749
3218
  if (pkgJson.module && typeof pkgJson.module === "string") {
2750
- const resolved = await resolveToSource(fs5, packagePath, pkgJson.module, tsConfig);
3219
+ const resolved = await resolveToSource(fs7, packagePath, pkgJson.module, tsConfig);
2751
3220
  if (resolved) {
2752
3221
  return { ...resolved, source: "module" };
2753
3222
  }
@@ -2765,27 +3234,27 @@ async function detectEntryPoint(fs5, packagePath = ".") {
2765
3234
  ];
2766
3235
  for (const fallback of fallbacks) {
2767
3236
  const checkPath = packagePath === "." ? fallback : `${packagePath}/${fallback}`;
2768
- if (await fs5.exists(checkPath)) {
3237
+ if (await fs7.exists(checkPath)) {
2769
3238
  return { path: fallback, source: "fallback", isDeclarationOnly: false };
2770
3239
  }
2771
3240
  }
2772
3241
  throw new Error("Could not detect TypeScript entry point. No types field in package.json and no common entry paths found.");
2773
3242
  }
2774
- async function parseTsConfig(fs5, packagePath) {
3243
+ async function parseTsConfig(fs7, packagePath) {
2775
3244
  const tsconfigPath = packagePath === "." ? "tsconfig.json" : `${packagePath}/tsconfig.json`;
2776
- const tsconfig = await safeParseJson(fs5, tsconfigPath);
3245
+ const tsconfig = await safeParseJson(fs7, tsconfigPath);
2777
3246
  if (!tsconfig?.compilerOptions) {
2778
3247
  return null;
2779
3248
  }
2780
3249
  const { outDir, rootDir, baseUrl, paths } = tsconfig.compilerOptions;
2781
3250
  return { outDir, rootDir, baseUrl, paths };
2782
3251
  }
2783
- async function resolveToSource(fs5, basePath, filePath, tsConfig) {
3252
+ async function resolveToSource(fs7, basePath, filePath, tsConfig) {
2784
3253
  const normalized = filePath.replace(/^\.\//, "");
2785
3254
  const checkPath = (p) => basePath === "." ? p : `${basePath}/${p}`;
2786
3255
  const isSourceTs = normalized.endsWith(".ts") && !normalized.endsWith(".d.ts") || normalized.endsWith(".tsx");
2787
3256
  if (isSourceTs) {
2788
- if (await fs5.exists(checkPath(normalized))) {
3257
+ if (await fs7.exists(checkPath(normalized))) {
2789
3258
  return { path: normalized, isDeclarationOnly: false };
2790
3259
  }
2791
3260
  }
@@ -2827,19 +3296,19 @@ async function resolveToSource(fs5, basePath, filePath, tsConfig) {
2827
3296
  for (const candidate of candidates) {
2828
3297
  if (candidate.endsWith(".d.ts"))
2829
3298
  continue;
2830
- if (await fs5.exists(checkPath(candidate))) {
3299
+ if (await fs7.exists(checkPath(candidate))) {
2831
3300
  return { path: candidate, isDeclarationOnly: false };
2832
3301
  }
2833
3302
  }
2834
3303
  if (normalized.endsWith(".d.ts")) {
2835
- if (await fs5.exists(checkPath(normalized))) {
3304
+ if (await fs7.exists(checkPath(normalized))) {
2836
3305
  return { path: normalized, isDeclarationOnly: true };
2837
3306
  }
2838
3307
  }
2839
3308
  return null;
2840
3309
  }
2841
3310
  // src/detect/filesystem.ts
2842
- import * as fs5 from "node:fs";
3311
+ import * as fs7 from "node:fs";
2843
3312
  import * as nodePath from "node:path";
2844
3313
  import { Writable } from "node:stream";
2845
3314
 
@@ -2852,19 +3321,19 @@ class NodeFileSystem {
2852
3321
  return nodePath.join(this.basePath, relativePath);
2853
3322
  }
2854
3323
  async exists(relativePath) {
2855
- return fs5.existsSync(this.resolve(relativePath));
3324
+ return fs7.existsSync(this.resolve(relativePath));
2856
3325
  }
2857
3326
  async readFile(relativePath) {
2858
- return fs5.readFileSync(this.resolve(relativePath), "utf-8");
3327
+ return fs7.readFileSync(this.resolve(relativePath), "utf-8");
2859
3328
  }
2860
3329
  async readDir(relativePath) {
2861
- return fs5.readdirSync(this.resolve(relativePath));
3330
+ return fs7.readdirSync(this.resolve(relativePath));
2862
3331
  }
2863
3332
  async isDirectory(relativePath) {
2864
3333
  const fullPath = this.resolve(relativePath);
2865
- if (!fs5.existsSync(fullPath))
3334
+ if (!fs7.existsSync(fullPath))
2866
3335
  return false;
2867
- return fs5.statSync(fullPath).isDirectory();
3336
+ return fs7.statSync(fullPath).isDirectory();
2868
3337
  }
2869
3338
  }
2870
3339
  function createCaptureStream() {
@@ -2880,9 +3349,9 @@ function createCaptureStream() {
2880
3349
 
2881
3350
  class FileNotFoundError extends Error {
2882
3351
  path;
2883
- constructor(path5, message) {
2884
- super(message ?? `File not found: ${path5}`);
2885
- this.path = path5;
3352
+ constructor(path7, message) {
3353
+ super(message ?? `File not found: ${path7}`);
3354
+ this.path = path7;
2886
3355
  this.name = "FileNotFoundError";
2887
3356
  }
2888
3357
  }
@@ -2892,34 +3361,34 @@ class SandboxFileSystem {
2892
3361
  constructor(sandbox) {
2893
3362
  this.sandbox = sandbox;
2894
3363
  }
2895
- async exists(path5) {
3364
+ async exists(path7) {
2896
3365
  const result = await this.sandbox.runCommand({
2897
3366
  cmd: "test",
2898
- args: ["-e", path5]
3367
+ args: ["-e", path7]
2899
3368
  });
2900
3369
  return result.exitCode === 0;
2901
3370
  }
2902
- async readFile(path5) {
2903
- const exists = await this.exists(path5);
3371
+ async readFile(path7) {
3372
+ const exists = await this.exists(path7);
2904
3373
  if (!exists) {
2905
- throw new FileNotFoundError(path5);
3374
+ throw new FileNotFoundError(path7);
2906
3375
  }
2907
3376
  const capture = createCaptureStream();
2908
3377
  const result = await this.sandbox.runCommand({
2909
3378
  cmd: "cat",
2910
- args: [path5],
3379
+ args: [path7],
2911
3380
  stdout: capture.stream
2912
3381
  });
2913
3382
  if (result.exitCode !== 0) {
2914
- throw new FileNotFoundError(path5, `Failed to read file: ${path5}`);
3383
+ throw new FileNotFoundError(path7, `Failed to read file: ${path7}`);
2915
3384
  }
2916
3385
  return capture.getOutput();
2917
3386
  }
2918
- async readDir(path5) {
3387
+ async readDir(path7) {
2919
3388
  const capture = createCaptureStream();
2920
3389
  const result = await this.sandbox.runCommand({
2921
3390
  cmd: "ls",
2922
- args: ["-1", path5],
3391
+ args: ["-1", path7],
2923
3392
  stdout: capture.stream
2924
3393
  });
2925
3394
  if (result.exitCode !== 0) {
@@ -2928,20 +3397,20 @@ class SandboxFileSystem {
2928
3397
  return capture.getOutput().split(`
2929
3398
  `).filter(Boolean);
2930
3399
  }
2931
- async isDirectory(path5) {
3400
+ async isDirectory(path7) {
2932
3401
  const result = await this.sandbox.runCommand({
2933
3402
  cmd: "test",
2934
- args: ["-d", path5]
3403
+ args: ["-d", path7]
2935
3404
  });
2936
3405
  return result.exitCode === 0;
2937
3406
  }
2938
3407
  }
2939
3408
  // src/detect/monorepo.ts
2940
- async function detectMonorepo(fs6) {
2941
- const pkgJson = await readPackageJson(fs6, ".");
3409
+ async function detectMonorepo(fs8) {
3410
+ const pkgJson = await readPackageJson(fs8, ".");
2942
3411
  if (pkgJson?.workspaces) {
2943
3412
  const patterns = extractWorkspacePatterns(pkgJson.workspaces);
2944
- const packages = await resolveWorkspacePackages(fs6, patterns, pkgJson.name, pkgJson.private);
3413
+ const packages = await resolveWorkspacePackages(fs8, patterns, pkgJson.name, pkgJson.private);
2945
3414
  return {
2946
3415
  isMonorepo: packages.length > 0,
2947
3416
  type: "npm-workspaces",
@@ -2949,10 +3418,10 @@ async function detectMonorepo(fs6) {
2949
3418
  packages
2950
3419
  };
2951
3420
  }
2952
- if (await fs6.exists("pnpm-workspace.yaml")) {
2953
- const content = await fs6.readFile("pnpm-workspace.yaml");
3421
+ if (await fs8.exists("pnpm-workspace.yaml")) {
3422
+ const content = await fs8.readFile("pnpm-workspace.yaml");
2954
3423
  const patterns = parsePnpmWorkspace(content);
2955
- const packages = await resolveWorkspacePackages(fs6, patterns, pkgJson?.name, pkgJson?.private);
3424
+ const packages = await resolveWorkspacePackages(fs8, patterns, pkgJson?.name, pkgJson?.private);
2956
3425
  return {
2957
3426
  isMonorepo: packages.length > 0,
2958
3427
  type: "pnpm-workspaces",
@@ -2960,10 +3429,10 @@ async function detectMonorepo(fs6) {
2960
3429
  packages
2961
3430
  };
2962
3431
  }
2963
- if (await fs6.exists("lerna.json")) {
2964
- const lerna = await safeParseJson(fs6, "lerna.json");
3432
+ if (await fs8.exists("lerna.json")) {
3433
+ const lerna = await safeParseJson(fs8, "lerna.json");
2965
3434
  const patterns = lerna?.packages ?? ["packages/*"];
2966
- const packages = await resolveWorkspacePackages(fs6, patterns, pkgJson?.name, pkgJson?.private);
3435
+ const packages = await resolveWorkspacePackages(fs8, patterns, pkgJson?.name, pkgJson?.private);
2967
3436
  return {
2968
3437
  isMonorepo: packages.length > 0,
2969
3438
  type: "lerna",
@@ -3037,7 +3506,7 @@ function parsePnpmWorkspace(content) {
3037
3506
  }
3038
3507
  return patterns.length > 0 ? patterns : ["packages/*"];
3039
3508
  }
3040
- async function resolveWorkspacePackages(fs6, patterns, rootPackageName, rootIsPrivate) {
3509
+ async function resolveWorkspacePackages(fs8, patterns, rootPackageName, rootIsPrivate) {
3041
3510
  const packages = [];
3042
3511
  const seen = new Set;
3043
3512
  if (rootPackageName && !rootIsPrivate && rootPackageName !== "root") {
@@ -3059,18 +3528,18 @@ async function resolveWorkspacePackages(fs6, patterns, rootPackageName, rootIsPr
3059
3528
  }
3060
3529
  dirsToScan.add("packages");
3061
3530
  for (const dir of dirsToScan) {
3062
- if (!await fs6.exists(dir))
3531
+ if (!await fs8.exists(dir))
3063
3532
  continue;
3064
- if (!await fs6.isDirectory(dir))
3533
+ if (!await fs8.isDirectory(dir))
3065
3534
  continue;
3066
- const subdirs = await fs6.readDir(dir);
3535
+ const subdirs = await fs8.readDir(dir);
3067
3536
  for (const subdir of subdirs) {
3068
3537
  const pkgPath = `${dir}/${subdir}`;
3069
3538
  const pkgJsonPath = `${pkgPath}/package.json`;
3070
- if (!await fs6.exists(pkgJsonPath))
3539
+ if (!await fs8.exists(pkgJsonPath))
3071
3540
  continue;
3072
3541
  try {
3073
- const content = await fs6.readFile(pkgJsonPath);
3542
+ const content = await fs8.readFile(pkgJsonPath);
3074
3543
  const pkg = JSON.parse(content);
3075
3544
  if (pkg.name && !seen.has(pkg.name)) {
3076
3545
  seen.add(pkg.name);
@@ -3142,14 +3611,14 @@ var DEFAULT_PM = {
3142
3611
  installArgs: ["install", "--legacy-peer-deps"],
3143
3612
  runPrefix: ["npm", "run"]
3144
3613
  };
3145
- async function detectPackageManager(fs6) {
3146
- const pkgJson = await safeParseJson(fs6, "package.json");
3614
+ async function detectPackageManager(fs8) {
3615
+ const pkgJson = await safeParseJson(fs8, "package.json");
3147
3616
  if (pkgJson?.packageManager) {
3148
3617
  const pmName = parsePackageManagerField(pkgJson.packageManager);
3149
3618
  if (pmName && PM_CONFIGS[pmName]) {
3150
3619
  const config = PM_CONFIGS[pmName];
3151
3620
  for (const lockfile of config.lockfiles) {
3152
- if (await fs6.exists(lockfile)) {
3621
+ if (await fs8.exists(lockfile)) {
3153
3622
  return { ...config.info, lockfile };
3154
3623
  }
3155
3624
  }
@@ -3159,7 +3628,7 @@ async function detectPackageManager(fs6) {
3159
3628
  const foundLockfiles = [];
3160
3629
  for (const [pmName, config] of Object.entries(PM_CONFIGS)) {
3161
3630
  for (const lockfile of config.lockfiles) {
3162
- if (await fs6.exists(lockfile)) {
3631
+ if (await fs8.exists(lockfile)) {
3163
3632
  foundLockfiles.push({ lockfile, pm: pmName });
3164
3633
  }
3165
3634
  }
@@ -3191,10 +3660,10 @@ function getRunCommand(pm, script) {
3191
3660
  return [...pm.runPrefix, script];
3192
3661
  }
3193
3662
  // src/detect/index.ts
3194
- async function analyzeProject(fs6, options = {}) {
3663
+ async function analyzeProject(fs8, options = {}) {
3195
3664
  const [packageManager, monorepo] = await Promise.all([
3196
- detectPackageManager(fs6),
3197
- detectMonorepo(fs6)
3665
+ detectPackageManager(fs8),
3666
+ detectMonorepo(fs8)
3198
3667
  ]);
3199
3668
  let targetPath = ".";
3200
3669
  if (monorepo.isMonorepo) {
@@ -3211,8 +3680,8 @@ async function analyzeProject(fs6, options = {}) {
3211
3680
  targetPath = pkg.path;
3212
3681
  }
3213
3682
  const [entryPoint, build] = await Promise.all([
3214
- detectEntryPoint(fs6, targetPath),
3215
- detectBuildInfo(fs6, targetPath)
3683
+ detectEntryPoint(fs8, targetPath),
3684
+ detectBuildInfo(fs8, targetPath)
3216
3685
  ]);
3217
3686
  return { packageManager, monorepo, entryPoint, build };
3218
3687
  }
@@ -3256,17 +3725,17 @@ function shouldValidate(validations, check) {
3256
3725
  return validations.includes(check);
3257
3726
  }
3258
3727
  // src/typecheck/example-typechecker.ts
3259
- import * as fs6 from "node:fs";
3260
- import * as path5 from "node:path";
3728
+ import * as fs8 from "node:fs";
3729
+ import * as path7 from "node:path";
3261
3730
  import ts3 from "typescript";
3262
3731
  function stripCodeBlockMarkers(code) {
3263
3732
  return code.replace(/^```(?:ts|typescript|js|javascript)?\n?/i, "").replace(/\n?```$/i, "").trim();
3264
3733
  }
3265
3734
  function getPackageName(packagePath) {
3266
- const pkgJsonPath = path5.join(packagePath, "package.json");
3267
- if (fs6.existsSync(pkgJsonPath)) {
3735
+ const pkgJsonPath = path7.join(packagePath, "package.json");
3736
+ if (fs8.existsSync(pkgJsonPath)) {
3268
3737
  try {
3269
- const pkgJson = JSON.parse(fs6.readFileSync(pkgJsonPath, "utf-8"));
3738
+ const pkgJson = JSON.parse(fs8.readFileSync(pkgJsonPath, "utf-8"));
3270
3739
  return pkgJson.name;
3271
3740
  } catch {
3272
3741
  return;
@@ -3276,12 +3745,12 @@ function getPackageName(packagePath) {
3276
3745
  }
3277
3746
  function findTsConfig(packagePath) {
3278
3747
  let dir = packagePath;
3279
- while (dir !== path5.dirname(dir)) {
3280
- const tsConfigPath = path5.join(dir, "tsconfig.json");
3281
- if (fs6.existsSync(tsConfigPath)) {
3748
+ while (dir !== path7.dirname(dir)) {
3749
+ const tsConfigPath = path7.join(dir, "tsconfig.json");
3750
+ if (fs8.existsSync(tsConfigPath)) {
3282
3751
  return tsConfigPath;
3283
3752
  }
3284
- dir = path5.dirname(dir);
3753
+ dir = path7.dirname(dir);
3285
3754
  }
3286
3755
  return;
3287
3756
  }
@@ -3297,10 +3766,10 @@ function createVirtualSource(example, packageName, exportNames) {
3297
3766
  `);
3298
3767
  }
3299
3768
  function getCompilerOptions(tsconfigPath) {
3300
- if (tsconfigPath && fs6.existsSync(tsconfigPath)) {
3769
+ if (tsconfigPath && fs8.existsSync(tsconfigPath)) {
3301
3770
  const configFile = ts3.readConfigFile(tsconfigPath, ts3.sys.readFile);
3302
3771
  if (!configFile.error) {
3303
- const parsed = ts3.parseJsonConfigFileContent(configFile.config, ts3.sys, path5.dirname(tsconfigPath));
3772
+ const parsed = ts3.parseJsonConfigFileContent(configFile.config, ts3.sys, path7.dirname(tsconfigPath));
3304
3773
  return {
3305
3774
  ...parsed.options,
3306
3775
  noEmit: true,
@@ -3326,7 +3795,7 @@ function typecheckExample(example, packagePath, options = {}) {
3326
3795
  const tsconfigPath = options.tsconfig ?? findTsConfig(packagePath);
3327
3796
  const compilerOptions = getCompilerOptions(tsconfigPath);
3328
3797
  const virtualSource = createVirtualSource(cleanCode, packageName, exportNames);
3329
- const virtualFileName = path5.join(packagePath, "__doccov_example__.ts");
3798
+ const virtualFileName = path7.join(packagePath, "__doccov_example__.ts");
3330
3799
  const hasImport = packageName !== undefined && exportNames && exportNames.length > 0;
3331
3800
  const lineOffset = hasImport ? 2 : 0;
3332
3801
  const sourceFile = ts3.createSourceFile(virtualFileName, virtualSource, ts3.ScriptTarget.ES2022, true);
@@ -3397,25 +3866,25 @@ function typecheckExamples(examples, packagePath, options = {}) {
3397
3866
  }
3398
3867
 
3399
3868
  // src/utils/example-runner.ts
3400
- import { spawn } from "node:child_process";
3401
- import * as fs7 from "node:fs";
3869
+ import { spawn as spawn2 } from "node:child_process";
3870
+ import * as fs9 from "node:fs";
3402
3871
  import * as os from "node:os";
3403
- import * as path6 from "node:path";
3872
+ import * as path8 from "node:path";
3404
3873
  function stripCodeBlockMarkers2(code) {
3405
3874
  return code.replace(/^```(?:ts|typescript|js|javascript)?\n?/i, "").replace(/\n?```$/i, "").trim();
3406
3875
  }
3407
3876
  async function runExample(code, options = {}) {
3408
3877
  const { timeout = 5000, cwd = process.cwd() } = options;
3409
3878
  const cleanCode = stripCodeBlockMarkers2(code);
3410
- const tmpFile = path6.join(cwd, `doccov-example-${Date.now()}-${Math.random().toString(36).slice(2)}.ts`);
3879
+ const tmpFile = path8.join(cwd, `doccov-example-${Date.now()}-${Math.random().toString(36).slice(2)}.ts`);
3411
3880
  try {
3412
- fs7.writeFileSync(tmpFile, cleanCode, "utf-8");
3881
+ fs9.writeFileSync(tmpFile, cleanCode, "utf-8");
3413
3882
  const startTime = Date.now();
3414
- return await new Promise((resolve4) => {
3883
+ return await new Promise((resolve5) => {
3415
3884
  let stdout = "";
3416
3885
  let stderr = "";
3417
3886
  let killed = false;
3418
- const proc = spawn("node", ["--experimental-strip-types", tmpFile], {
3887
+ const proc = spawn2("node", ["--experimental-strip-types", tmpFile], {
3419
3888
  cwd,
3420
3889
  timeout,
3421
3890
  stdio: ["ignore", "pipe", "pipe"]
@@ -3434,7 +3903,7 @@ async function runExample(code, options = {}) {
3434
3903
  clearTimeout(timeoutId);
3435
3904
  const duration = Date.now() - startTime;
3436
3905
  if (killed) {
3437
- resolve4({
3906
+ resolve5({
3438
3907
  success: false,
3439
3908
  stdout,
3440
3909
  stderr: stderr || `Example timed out after ${timeout}ms`,
@@ -3442,7 +3911,7 @@ async function runExample(code, options = {}) {
3442
3911
  duration
3443
3912
  });
3444
3913
  } else {
3445
- resolve4({
3914
+ resolve5({
3446
3915
  success: exitCode === 0,
3447
3916
  stdout,
3448
3917
  stderr,
@@ -3453,7 +3922,7 @@ async function runExample(code, options = {}) {
3453
3922
  });
3454
3923
  proc.on("error", (error) => {
3455
3924
  clearTimeout(timeoutId);
3456
- resolve4({
3925
+ resolve5({
3457
3926
  success: false,
3458
3927
  stdout,
3459
3928
  stderr: error.message,
@@ -3464,7 +3933,7 @@ async function runExample(code, options = {}) {
3464
3933
  });
3465
3934
  } finally {
3466
3935
  try {
3467
- fs7.unlinkSync(tmpFile);
3936
+ fs9.unlinkSync(tmpFile);
3468
3937
  } catch {}
3469
3938
  }
3470
3939
  }
@@ -3479,15 +3948,15 @@ async function runExamples(examples, options = {}) {
3479
3948
  return results;
3480
3949
  }
3481
3950
  function detectPackageManager2(cwd) {
3482
- if (fs7.existsSync(path6.join(cwd, "bun.lockb")))
3951
+ if (fs9.existsSync(path8.join(cwd, "bun.lockb")))
3483
3952
  return "bun";
3484
- if (fs7.existsSync(path6.join(cwd, "bun.lock")))
3953
+ if (fs9.existsSync(path8.join(cwd, "bun.lock")))
3485
3954
  return "bun";
3486
- if (fs7.existsSync(path6.join(cwd, "pnpm-lock.yaml")))
3955
+ if (fs9.existsSync(path8.join(cwd, "pnpm-lock.yaml")))
3487
3956
  return "pnpm";
3488
- if (fs7.existsSync(path6.join(cwd, "yarn.lock")))
3957
+ if (fs9.existsSync(path8.join(cwd, "yarn.lock")))
3489
3958
  return "yarn";
3490
- if (fs7.existsSync(path6.join(cwd, "package-lock.json")))
3959
+ if (fs9.existsSync(path8.join(cwd, "package-lock.json")))
3491
3960
  return "npm";
3492
3961
  return "npm";
3493
3962
  }
@@ -3504,11 +3973,11 @@ function getInstallCommand2(pm, packagePath) {
3504
3973
  }
3505
3974
  }
3506
3975
  async function runCommand(cmd, args, options) {
3507
- return new Promise((resolve4) => {
3976
+ return new Promise((resolve5) => {
3508
3977
  let stdout = "";
3509
3978
  let stderr = "";
3510
3979
  let killed = false;
3511
- const proc = spawn(cmd, args, {
3980
+ const proc = spawn2(cmd, args, {
3512
3981
  cwd: options.cwd,
3513
3982
  stdio: ["ignore", "pipe", "pipe"]
3514
3983
  });
@@ -3525,14 +3994,14 @@ async function runCommand(cmd, args, options) {
3525
3994
  proc.on("close", (exitCode) => {
3526
3995
  clearTimeout(timeoutId);
3527
3996
  if (killed) {
3528
- resolve4({
3997
+ resolve5({
3529
3998
  success: false,
3530
3999
  stdout,
3531
4000
  stderr: stderr || `Command timed out after ${options.timeout}ms`,
3532
4001
  exitCode: exitCode ?? 1
3533
4002
  });
3534
4003
  } else {
3535
- resolve4({
4004
+ resolve5({
3536
4005
  success: exitCode === 0,
3537
4006
  stdout,
3538
4007
  stderr,
@@ -3542,7 +4011,7 @@ async function runCommand(cmd, args, options) {
3542
4011
  });
3543
4012
  proc.on("error", (error) => {
3544
4013
  clearTimeout(timeoutId);
3545
- resolve4({
4014
+ resolve5({
3546
4015
  success: false,
3547
4016
  stdout,
3548
4017
  stderr: error.message,
@@ -3555,12 +4024,12 @@ async function runExamplesWithPackage(examples, options) {
3555
4024
  const { packagePath, packageManager, installTimeout = 60000, timeout = 5000 } = options;
3556
4025
  const startTime = Date.now();
3557
4026
  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)}`);
4027
+ const absolutePackagePath = path8.resolve(packagePath);
4028
+ const workDir = path8.join(os.tmpdir(), `doccov-examples-${Date.now()}-${Math.random().toString(36).slice(2)}`);
3560
4029
  try {
3561
- fs7.mkdirSync(workDir, { recursive: true });
4030
+ fs9.mkdirSync(workDir, { recursive: true });
3562
4031
  const pkgJson = { name: "doccov-example-runner", type: "module" };
3563
- fs7.writeFileSync(path6.join(workDir, "package.json"), JSON.stringify(pkgJson, null, 2));
4032
+ fs9.writeFileSync(path8.join(workDir, "package.json"), JSON.stringify(pkgJson, null, 2));
3564
4033
  const pm = packageManager ?? detectPackageManager2(options.cwd ?? process.cwd());
3565
4034
  const { cmd, args } = getInstallCommand2(pm, absolutePackagePath);
3566
4035
  const installResult = await runCommand(cmd, args, {
@@ -3588,7 +4057,7 @@ async function runExamplesWithPackage(examples, options) {
3588
4057
  };
3589
4058
  } finally {
3590
4059
  try {
3591
- fs7.rmSync(workDir, { recursive: true, force: true });
4060
+ fs9.rmSync(workDir, { recursive: true, force: true });
3592
4061
  } catch {}
3593
4062
  }
3594
4063
  }
@@ -3767,9 +4236,12 @@ async function validateExamples(exports, options) {
3767
4236
  }
3768
4237
  return result;
3769
4238
  }
4239
+ // src/extractor.ts
4240
+ import * as path13 from "node:path";
4241
+
3770
4242
  // src/analysis/run-analysis.ts
3771
- import * as fs9 from "node:fs";
3772
- import * as path10 from "node:path";
4243
+ import * as fs11 from "node:fs";
4244
+ import * as path12 from "node:path";
3773
4245
  // src/utils/type-utils.ts
3774
4246
  function getTypeId(type, typeChecker) {
3775
4247
  const internalId = type.id;
@@ -3892,7 +4364,7 @@ function collectReferencedTypesFromNode(node, typeChecker, referencedTypes) {
3892
4364
  }
3893
4365
 
3894
4366
  // src/analysis/context.ts
3895
- import * as path8 from "node:path";
4367
+ import * as path10 from "node:path";
3896
4368
 
3897
4369
  // src/options.ts
3898
4370
  var DEFAULT_MAX_TYPE_DEPTH = 20;
@@ -3913,7 +4385,7 @@ function normalizeDocCovOptions(options = {}) {
3913
4385
  }
3914
4386
 
3915
4387
  // src/analysis/program.ts
3916
- import * as path7 from "node:path";
4388
+ import * as path9 from "node:path";
3917
4389
  var DEFAULT_COMPILER_OPTIONS = {
3918
4390
  target: ts.ScriptTarget.Latest,
3919
4391
  module: ts.ModuleKind.CommonJS,
@@ -3923,14 +4395,14 @@ var DEFAULT_COMPILER_OPTIONS = {
3923
4395
  };
3924
4396
  function createProgram({
3925
4397
  entryFile,
3926
- baseDir = path7.dirname(entryFile),
4398
+ baseDir = path9.dirname(entryFile),
3927
4399
  content
3928
4400
  }) {
3929
4401
  const configPath = ts.findConfigFile(baseDir, ts.sys.fileExists, "tsconfig.json");
3930
4402
  let compilerOptions = { ...DEFAULT_COMPILER_OPTIONS };
3931
4403
  if (configPath) {
3932
4404
  const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
3933
- const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, path7.dirname(configPath));
4405
+ const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, path9.dirname(configPath));
3934
4406
  compilerOptions = { ...compilerOptions, ...parsedConfig.options };
3935
4407
  }
3936
4408
  const allowJsVal = compilerOptions.allowJs;
@@ -3965,9 +4437,10 @@ function createAnalysisContext({
3965
4437
  entryFile,
3966
4438
  packageDir,
3967
4439
  content,
3968
- options
4440
+ options,
4441
+ detectedSchemas
3969
4442
  }) {
3970
- const baseDir = packageDir ?? path8.dirname(entryFile);
4443
+ const baseDir = packageDir ?? path10.dirname(entryFile);
3971
4444
  const normalizedOptions = normalizeDocCovOptions(options);
3972
4445
  const programResult = createProgram({ entryFile, baseDir, content });
3973
4446
  if (!programResult.sourceFile) {
@@ -3982,13 +4455,14 @@ function createAnalysisContext({
3982
4455
  compilerOptions: programResult.compilerOptions,
3983
4456
  compilerHost: programResult.compilerHost,
3984
4457
  options: normalizedOptions,
3985
- configPath: programResult.configPath
4458
+ configPath: programResult.configPath,
4459
+ detectedSchemas
3986
4460
  };
3987
4461
  }
3988
4462
 
3989
4463
  // src/analysis/spec-builder.ts
3990
- import * as fs8 from "node:fs";
3991
- import * as path9 from "node:path";
4464
+ import * as fs10 from "node:fs";
4465
+ import * as path11 from "node:path";
3992
4466
  import { SCHEMA_URL, SCHEMA_VERSION } from "@openpkg-ts/spec";
3993
4467
 
3994
4468
  // src/analysis/decorator-utils.ts
@@ -4020,354 +4494,100 @@ function extractParameterDecorators(param) {
4020
4494
  return extractDecorators(param);
4021
4495
  }
4022
4496
 
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" }
4497
+ // src/utils/schema-builder.ts
4498
+ var BUILTIN_TYPE_SCHEMAS = {
4499
+ Date: { type: "string", format: "date-time" },
4500
+ RegExp: { type: "object", description: "RegExp" },
4501
+ Error: { type: "object" },
4502
+ Promise: { type: "object" },
4503
+ Map: { type: "object" },
4504
+ Set: { type: "object" },
4505
+ WeakMap: { type: "object" },
4506
+ WeakSet: { type: "object" },
4507
+ Function: { type: "object" },
4508
+ ArrayBuffer: { type: "string", format: "binary" },
4509
+ ArrayBufferLike: { type: "string", format: "binary" },
4510
+ DataView: { type: "string", format: "binary" },
4511
+ Uint8Array: { type: "string", format: "byte" },
4512
+ Uint16Array: { type: "string", format: "byte" },
4513
+ Uint32Array: { type: "string", format: "byte" },
4514
+ Int8Array: { type: "string", format: "byte" },
4515
+ Int16Array: { type: "string", format: "byte" },
4516
+ Int32Array: { type: "string", format: "byte" },
4517
+ Float32Array: { type: "string", format: "byte" },
4518
+ Float64Array: { type: "string", format: "byte" },
4519
+ BigInt64Array: { type: "string", format: "byte" },
4520
+ BigUint64Array: { type: "string", format: "byte" }
4035
4521
  };
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)
4522
+ function isObjectLiteralType(type) {
4523
+ if (!(type.getFlags() & ts.TypeFlags.Object)) {
4045
4524
  return false;
4046
- return isInternalProperty(props[0].getName());
4525
+ }
4526
+ const objectFlags = type.objectFlags;
4527
+ return (objectFlags & ts.ObjectFlags.ObjectLiteral) !== 0;
4047
4528
  }
4048
- function unwrapTypeBoxOptional(type) {
4049
- if (!type.isIntersection()) {
4050
- return { innerTypes: [type], isOptional: false };
4529
+ function isPureRefSchema(value) {
4530
+ return Object.keys(value).length === 1 && "$ref" in value;
4531
+ }
4532
+ function withDescription(schema, description) {
4533
+ if (isPureRefSchema(schema)) {
4534
+ return {
4535
+ allOf: [schema],
4536
+ description
4537
+ };
4051
4538
  }
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 };
4539
+ return {
4540
+ ...schema,
4541
+ description
4542
+ };
4056
4543
  }
4057
- function getPropertyType(prop, parentType, typeChecker) {
4058
- if (prop.valueDeclaration) {
4059
- return typeChecker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration);
4544
+ function propertiesToSchema(properties, description) {
4545
+ const schema = {
4546
+ type: "object",
4547
+ properties: {}
4548
+ };
4549
+ const required = [];
4550
+ for (const prop of properties) {
4551
+ const propType = prop.type;
4552
+ let propSchema;
4553
+ if (typeof propType === "string") {
4554
+ if (["string", "number", "boolean", "bigint", "null"].includes(propType)) {
4555
+ propSchema = { type: propType === "bigint" ? "string" : propType };
4556
+ } else {
4557
+ propSchema = { type: propType };
4558
+ }
4559
+ } else if (propType && typeof propType === "object") {
4560
+ propSchema = propType;
4561
+ } else {
4562
+ propSchema = { type: "any" };
4563
+ }
4564
+ if (prop.description && typeof propSchema === "object") {
4565
+ propSchema = withDescription(propSchema, prop.description);
4566
+ }
4567
+ schema.properties[prop.name] = propSchema;
4568
+ if (!prop.optional) {
4569
+ required.push(prop.name);
4570
+ }
4060
4571
  }
4061
- const propType = typeChecker.getTypeOfPropertyOfType(parentType, prop.getName());
4062
- if (propType) {
4063
- return propType;
4572
+ if (required.length > 0) {
4573
+ schema.required = required;
4064
4574
  }
4065
- const decl = prop.declarations?.[0];
4066
- if (decl) {
4067
- return typeChecker.getTypeOfSymbolAtLocation(prop, decl);
4575
+ if (description) {
4576
+ return withDescription(schema, description);
4068
4577
  }
4069
- return typeChecker.getAnyType();
4578
+ return schema;
4070
4579
  }
4071
4580
  var _formatTypeReference = null;
4072
- function setFormatTypeReference(fn) {
4581
+ function setSchemaBuilderFormatTypeReference(fn) {
4073
4582
  _formatTypeReference = fn;
4074
4583
  }
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);
4584
+ function buildSchemaFromTypeNode(node, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName) {
4585
+ if (ts.isParenthesizedTypeNode(node)) {
4586
+ return buildSchemaFromTypeNode(node.type, typeChecker, typeRefs, referencedTypes, functionDoc ?? null, parentParamName);
4078
4587
  }
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;
4308
- }
4309
- function isPureRefSchema(value) {
4310
- return Object.keys(value).length === 1 && "$ref" in value;
4311
- }
4312
- function withDescription(schema, description) {
4313
- if (isPureRefSchema(schema)) {
4314
- return {
4315
- allOf: [schema],
4316
- description
4317
- };
4318
- }
4319
- return {
4320
- ...schema,
4321
- description
4322
- };
4323
- }
4324
- function propertiesToSchema(properties, description) {
4325
- const schema = {
4326
- type: "object",
4327
- properties: {}
4328
- };
4329
- const required = [];
4330
- for (const prop of properties) {
4331
- const propType = prop.type;
4332
- let propSchema;
4333
- if (typeof propType === "string") {
4334
- if (["string", "number", "boolean", "bigint", "null"].includes(propType)) {
4335
- propSchema = { type: propType === "bigint" ? "string" : propType };
4336
- } else {
4337
- propSchema = { type: propType };
4338
- }
4339
- } else if (propType && typeof propType === "object") {
4340
- propSchema = propType;
4341
- } else {
4342
- propSchema = { type: "any" };
4343
- }
4344
- if (prop.description && typeof propSchema === "object") {
4345
- propSchema = withDescription(propSchema, prop.description);
4346
- }
4347
- schema.properties[prop.name] = propSchema;
4348
- if (!prop.optional) {
4349
- required.push(prop.name);
4350
- }
4351
- }
4352
- if (required.length > 0) {
4353
- schema.required = required;
4354
- }
4355
- if (description) {
4356
- return withDescription(schema, description);
4357
- }
4358
- return schema;
4359
- }
4360
- var _formatTypeReference2 = null;
4361
- function setSchemaBuilderFormatTypeReference(fn) {
4362
- _formatTypeReference2 = fn;
4363
- }
4364
- function buildSchemaFromTypeNode(node, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName) {
4365
- if (ts.isParenthesizedTypeNode(node)) {
4366
- return buildSchemaFromTypeNode(node.type, typeChecker, typeRefs, referencedTypes, functionDoc ?? null, parentParamName);
4367
- }
4368
- if (ts.isIntersectionTypeNode(node)) {
4369
- const schemas = node.types.map((type) => buildSchemaFromTypeNode(type, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName));
4370
- return { allOf: schemas };
4588
+ if (ts.isIntersectionTypeNode(node)) {
4589
+ const schemas = node.types.map((type) => buildSchemaFromTypeNode(type, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName));
4590
+ return { allOf: schemas };
4371
4591
  }
4372
4592
  if (ts.isUnionTypeNode(node)) {
4373
4593
  const schemas = node.types.map((type) => buildSchemaFromTypeNode(type, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName));
@@ -4390,7 +4610,7 @@ function buildSchemaFromTypeNode(node, typeChecker, typeRefs, referencedTypes, f
4390
4610
  let schema2 = "any";
4391
4611
  if (member.type) {
4392
4612
  const memberType = typeChecker.getTypeFromTypeNode(member.type);
4393
- const formatted = _formatTypeReference2 ? _formatTypeReference2(memberType, typeChecker, typeRefs, referencedTypes) : { type: "any" };
4613
+ const formatted = _formatTypeReference ? _formatTypeReference(memberType, typeChecker, typeRefs, referencedTypes) : { type: "any" };
4394
4614
  if (typeof formatted === "string") {
4395
4615
  if (formatted === "any") {
4396
4616
  schema2 = buildSchemaFromTypeNode(member.type, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName);
@@ -4501,75 +4721,328 @@ function findDiscriminatorProperty(unionTypes, typeChecker) {
4501
4721
  } else if (propType.isNumberLiteral()) {
4502
4722
  propValues.set(prop.getName(), propType.value);
4503
4723
  }
4504
- } catch {}
4724
+ } catch {}
4725
+ }
4726
+ memberProps.push(propValues);
4727
+ }
4728
+ if (memberProps.length < 2) {
4729
+ return;
4730
+ }
4731
+ const firstMember = memberProps[0];
4732
+ for (const [propName, firstValue] of firstMember) {
4733
+ const values = new Set([firstValue]);
4734
+ let isDiscriminator = true;
4735
+ for (let i = 1;i < memberProps.length; i++) {
4736
+ const value = memberProps[i].get(propName);
4737
+ if (value === undefined) {
4738
+ isDiscriminator = false;
4739
+ break;
4740
+ }
4741
+ if (values.has(value)) {
4742
+ isDiscriminator = false;
4743
+ break;
4744
+ }
4745
+ values.add(value);
4746
+ }
4747
+ if (isDiscriminator) {
4748
+ return propName;
4749
+ }
4750
+ }
4751
+ return;
4752
+ }
4753
+ function schemaIsAny(schema) {
4754
+ if (typeof schema === "string") {
4755
+ return schema === "any";
4756
+ }
4757
+ if ("type" in schema && schema.type === "any" && Object.keys(schema).length === 1) {
4758
+ return true;
4759
+ }
4760
+ return false;
4761
+ }
4762
+ function schemasAreEqual(left, right) {
4763
+ if (typeof left !== typeof right) {
4764
+ return false;
4765
+ }
4766
+ if (typeof left === "string" && typeof right === "string") {
4767
+ return left === right;
4768
+ }
4769
+ if (left == null || right == null) {
4770
+ return left === right;
4771
+ }
4772
+ const normalize = (value) => {
4773
+ if (Array.isArray(value)) {
4774
+ return value.map((item) => normalize(item));
4775
+ }
4776
+ if (value && typeof value === "object") {
4777
+ const sortedEntries = Object.entries(value).map(([key, val]) => [key, normalize(val)]).sort(([keyA], [keyB]) => keyA.localeCompare(keyB));
4778
+ return Object.fromEntries(sortedEntries);
4779
+ }
4780
+ return value;
4781
+ };
4782
+ return JSON.stringify(normalize(left)) === JSON.stringify(normalize(right));
4783
+ }
4784
+ function deduplicateSchemas(schemas) {
4785
+ const result = [];
4786
+ for (const schema of schemas) {
4787
+ const isDuplicate = result.some((existing) => schemasAreEqual(existing, schema));
4788
+ if (!isDuplicate) {
4789
+ result.push(schema);
4790
+ }
4791
+ }
4792
+ return result;
4793
+ }
4794
+ // src/utils/typebox-handler.ts
4795
+ var TYPEBOX_PRIMITIVE_MAP = {
4796
+ TString: { type: "string" },
4797
+ TNumber: { type: "number" },
4798
+ TBoolean: { type: "boolean" },
4799
+ TInteger: { type: "integer" },
4800
+ TNull: { type: "null" },
4801
+ TAny: {},
4802
+ TUnknown: {},
4803
+ TNever: { not: {} },
4804
+ TVoid: { type: "null" },
4805
+ TUndefined: { type: "null" }
4806
+ };
4807
+ function isTypeBoxSchemaType(symbolName) {
4808
+ return /^T[A-Z][a-zA-Z]*$/.test(symbolName);
4809
+ }
4810
+ function isInternalProperty(name) {
4811
+ return name.startsWith("__@");
4812
+ }
4813
+ function isTypeBoxOptionalMarker(type) {
4814
+ const props = type.getProperties();
4815
+ if (props.length !== 1)
4816
+ return false;
4817
+ return isInternalProperty(props[0].getName());
4818
+ }
4819
+ function unwrapTypeBoxOptional(type) {
4820
+ if (!type.isIntersection()) {
4821
+ return { innerTypes: [type], isOptional: false };
4822
+ }
4823
+ const intersectionType = type;
4824
+ const filtered = intersectionType.types.filter((t) => !isTypeBoxOptionalMarker(t));
4825
+ const hadMarker = filtered.length < intersectionType.types.length;
4826
+ return { innerTypes: filtered, isOptional: hadMarker };
4827
+ }
4828
+ function getPropertyType(prop, parentType, typeChecker) {
4829
+ if (prop.valueDeclaration) {
4830
+ return typeChecker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration);
4831
+ }
4832
+ const propType = typeChecker.getTypeOfPropertyOfType(parentType, prop.getName());
4833
+ if (propType) {
4834
+ return propType;
4835
+ }
4836
+ const decl = prop.declarations?.[0];
4837
+ if (decl) {
4838
+ return typeChecker.getTypeOfSymbolAtLocation(prop, decl);
4839
+ }
4840
+ return typeChecker.getAnyType();
4841
+ }
4842
+ var _formatTypeReference2 = null;
4843
+ function setFormatTypeReference(fn) {
4844
+ _formatTypeReference2 = fn;
4845
+ }
4846
+ function formatTypeReferenceInternal(type, typeChecker, typeRefs, referencedTypes, visited, depth, maxDepth, typeIds) {
4847
+ if (_formatTypeReference2) {
4848
+ return _formatTypeReference2(type, typeChecker, typeRefs, referencedTypes, visited, depth, maxDepth, typeIds);
4849
+ }
4850
+ return { type: "object" };
4851
+ }
4852
+ function formatTypeBoxSchema(type, typeChecker, typeRefs, referencedTypes, visited, depth = 0, maxDepth = DEFAULT_MAX_TYPE_DEPTH, typeIds) {
4853
+ if (depth > maxDepth) {
4854
+ return { type: "unknown" };
4855
+ }
4856
+ const symbol = type.getSymbol();
4857
+ if (!symbol)
4858
+ return null;
4859
+ const symbolName = symbol.getName();
4860
+ if (TYPEBOX_PRIMITIVE_MAP[symbolName]) {
4861
+ return { ...TYPEBOX_PRIMITIVE_MAP[symbolName] };
4862
+ }
4863
+ const objectType = type;
4864
+ if (!(objectType.objectFlags & ts.ObjectFlags.Reference)) {
4865
+ return null;
4866
+ }
4867
+ const typeRef = type;
4868
+ const typeArgs = typeRef.typeArguments;
4869
+ switch (symbolName) {
4870
+ case "TObject": {
4871
+ if (!typeArgs || typeArgs.length === 0) {
4872
+ return { type: "object" };
4873
+ }
4874
+ const propsType = typeArgs[0];
4875
+ const properties = {};
4876
+ const required = [];
4877
+ for (const prop of propsType.getProperties()) {
4878
+ const propName = prop.getName();
4879
+ if (isInternalProperty(propName)) {
4880
+ continue;
4881
+ }
4882
+ const propType = getPropertyType(prop, propsType, typeChecker);
4883
+ const propSymbol = propType.getSymbol();
4884
+ const propSymbolName = propSymbol?.getName();
4885
+ if (propSymbolName && typeRefs.has(propSymbolName)) {
4886
+ properties[propName] = { $ref: `#/types/${propSymbolName}` };
4887
+ } else if (propSymbolName && isTypeBoxSchemaType(propSymbolName)) {
4888
+ const nested = formatTypeBoxSchema(propType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
4889
+ properties[propName] = nested ?? { type: "object" };
4890
+ } else {
4891
+ properties[propName] = formatTypeReferenceInternal(propType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
4892
+ }
4893
+ const { isOptional } = unwrapTypeBoxOptional(propType);
4894
+ if (propSymbolName !== "TOptional" && !isOptional) {
4895
+ required.push(propName);
4896
+ }
4897
+ }
4898
+ const schema = { type: "object", properties };
4899
+ if (required.length > 0) {
4900
+ schema.required = required;
4901
+ }
4902
+ return schema;
4903
+ }
4904
+ case "TArray": {
4905
+ if (!typeArgs || typeArgs.length === 0) {
4906
+ return { type: "array" };
4907
+ }
4908
+ const itemType = typeArgs[0];
4909
+ const itemSymbol = itemType.getSymbol();
4910
+ const itemSymbolName = itemSymbol?.getName();
4911
+ let items;
4912
+ if (itemSymbolName && typeRefs.has(itemSymbolName)) {
4913
+ items = { $ref: `#/types/${itemSymbolName}` };
4914
+ } else if (itemSymbolName && isTypeBoxSchemaType(itemSymbolName)) {
4915
+ items = formatTypeBoxSchema(itemType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
4916
+ type: "object"
4917
+ };
4918
+ } else {
4919
+ items = formatTypeReferenceInternal(itemType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
4920
+ }
4921
+ return { type: "array", items };
4922
+ }
4923
+ case "TUnion": {
4924
+ if (!typeArgs || typeArgs.length === 0) {
4925
+ return { anyOf: [] };
4926
+ }
4927
+ const tupleType = typeArgs[0];
4928
+ const members = [];
4929
+ if (tupleType.isUnion()) {
4930
+ for (const memberType of tupleType.types) {
4931
+ const memberSymbol = memberType.getSymbol();
4932
+ const memberSymbolName = memberSymbol?.getName();
4933
+ if (memberSymbolName && typeRefs.has(memberSymbolName)) {
4934
+ members.push({ $ref: `#/types/${memberSymbolName}` });
4935
+ } else if (memberSymbolName && isTypeBoxSchemaType(memberSymbolName)) {
4936
+ members.push(formatTypeBoxSchema(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
4937
+ type: "object"
4938
+ });
4939
+ } else {
4940
+ members.push(formatTypeReferenceInternal(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds));
4941
+ }
4942
+ }
4943
+ } else if (tupleType.typeArguments) {
4944
+ for (const memberType of tupleType.typeArguments) {
4945
+ const memberSymbol = memberType.getSymbol();
4946
+ const memberSymbolName = memberSymbol?.getName();
4947
+ if (memberSymbolName && typeRefs.has(memberSymbolName)) {
4948
+ members.push({ $ref: `#/types/${memberSymbolName}` });
4949
+ } else if (memberSymbolName && isTypeBoxSchemaType(memberSymbolName)) {
4950
+ members.push(formatTypeBoxSchema(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
4951
+ type: "object"
4952
+ });
4953
+ } else {
4954
+ members.push(formatTypeReferenceInternal(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds));
4955
+ }
4956
+ }
4957
+ }
4958
+ return { anyOf: members };
4959
+ }
4960
+ case "TIntersect": {
4961
+ if (!typeArgs || typeArgs.length === 0) {
4962
+ return { allOf: [] };
4963
+ }
4964
+ const tupleType = typeArgs[0];
4965
+ const members = [];
4966
+ if (tupleType.typeArguments) {
4967
+ for (const memberType of tupleType.typeArguments) {
4968
+ const memberSymbol = memberType.getSymbol();
4969
+ const memberSymbolName = memberSymbol?.getName();
4970
+ if (memberSymbolName && typeRefs.has(memberSymbolName)) {
4971
+ members.push({ $ref: `#/types/${memberSymbolName}` });
4972
+ } else if (memberSymbolName && isTypeBoxSchemaType(memberSymbolName)) {
4973
+ members.push(formatTypeBoxSchema(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
4974
+ type: "object"
4975
+ });
4976
+ } else {
4977
+ members.push(formatTypeReferenceInternal(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds));
4978
+ }
4979
+ }
4980
+ }
4981
+ return { allOf: members };
4505
4982
  }
4506
- memberProps.push(propValues);
4507
- }
4508
- if (memberProps.length < 2) {
4509
- return;
4510
- }
4511
- const firstMember = memberProps[0];
4512
- for (const [propName, firstValue] of firstMember) {
4513
- const values = new Set([firstValue]);
4514
- let isDiscriminator = true;
4515
- for (let i = 1;i < memberProps.length; i++) {
4516
- const value = memberProps[i].get(propName);
4517
- if (value === undefined) {
4518
- isDiscriminator = false;
4519
- break;
4983
+ case "TOptional": {
4984
+ if (!typeArgs || typeArgs.length === 0) {
4985
+ return {};
4520
4986
  }
4521
- if (values.has(value)) {
4522
- isDiscriminator = false;
4523
- break;
4987
+ const innerType = typeArgs[0];
4988
+ const innerSymbol = innerType.getSymbol();
4989
+ const innerSymbolName = innerSymbol?.getName();
4990
+ if (innerSymbolName && typeRefs.has(innerSymbolName)) {
4991
+ return { $ref: `#/types/${innerSymbolName}` };
4992
+ } else if (innerSymbolName && isTypeBoxSchemaType(innerSymbolName)) {
4993
+ return formatTypeBoxSchema(innerType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
4994
+ type: "object"
4995
+ };
4524
4996
  }
4525
- values.add(value);
4526
- }
4527
- if (isDiscriminator) {
4528
- return propName;
4997
+ return formatTypeReferenceInternal(innerType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
4529
4998
  }
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));
4999
+ case "TLiteral": {
5000
+ if (!typeArgs || typeArgs.length === 0) {
5001
+ return { enum: [] };
5002
+ }
5003
+ const literalType = typeArgs[0];
5004
+ if (literalType.isLiteral()) {
5005
+ const value = literalType.value;
5006
+ return { enum: [value] };
5007
+ }
5008
+ const literalStr = typeChecker.typeToString(literalType);
5009
+ if (literalStr.startsWith('"') && literalStr.endsWith('"')) {
5010
+ return { enum: [literalStr.slice(1, -1)] };
5011
+ }
5012
+ return { enum: [literalStr] };
4555
5013
  }
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);
5014
+ case "TRecord": {
5015
+ if (!typeArgs || typeArgs.length < 2) {
5016
+ return { type: "object", additionalProperties: true };
5017
+ }
5018
+ const valueType = typeArgs[1];
5019
+ const valueSymbol = valueType.getSymbol();
5020
+ const valueSymbolName = valueSymbol?.getName();
5021
+ let additionalProperties;
5022
+ if (valueSymbolName && typeRefs.has(valueSymbolName)) {
5023
+ additionalProperties = { $ref: `#/types/${valueSymbolName}` };
5024
+ } else if (valueSymbolName && isTypeBoxSchemaType(valueSymbolName)) {
5025
+ additionalProperties = formatTypeBoxSchema(valueType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? true;
5026
+ } else {
5027
+ additionalProperties = formatTypeReferenceInternal(valueType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
5028
+ }
5029
+ return { type: "object", additionalProperties };
4559
5030
  }
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);
5031
+ case "TRef": {
5032
+ if (!typeArgs || typeArgs.length === 0) {
5033
+ return { $ref: "#/types/unknown" };
5034
+ }
5035
+ const refType = typeArgs[0];
5036
+ const refSymbol = refType.getSymbol();
5037
+ const refSymbolName = refSymbol?.getName();
5038
+ if (refSymbolName) {
5039
+ return { $ref: `#/types/${refSymbolName}` };
5040
+ }
5041
+ return { type: "object" };
4570
5042
  }
5043
+ default:
5044
+ return null;
4571
5045
  }
4572
- return result;
4573
5046
  }
4574
5047
 
4575
5048
  // src/utils/type-formatter.ts
@@ -6509,9 +6982,52 @@ function serializeVariable(declaration, symbol, context) {
6509
6982
  }
6510
6983
  const typeRefs = typeRegistry.getTypeRefs();
6511
6984
  const referencedTypes = typeRegistry.getReferencedTypes();
6985
+ const symbolName = symbol.getName();
6986
+ const standardSchema = context.detectedSchemas?.get(symbolName);
6987
+ if (standardSchema) {
6988
+ return {
6989
+ id: symbolName,
6990
+ name: symbolName,
6991
+ ...metadata,
6992
+ kind: "variable",
6993
+ deprecated: isSymbolDeprecated(symbol),
6994
+ schema: standardSchema.schema,
6995
+ description,
6996
+ source: getSourceLocation(declaration),
6997
+ tags: [
6998
+ ...parsedDoc?.tags ?? [],
6999
+ { name: "schemaLibrary", text: standardSchema.vendor },
7000
+ { name: "schemaSource", text: "standard-schema" }
7001
+ ],
7002
+ examples: parsedDoc?.examples
7003
+ };
7004
+ }
7005
+ if (isSchemaType(variableType, checker)) {
7006
+ const schemaResult = extractSchemaType(variableType, checker);
7007
+ if (schemaResult?.outputType) {
7008
+ collectReferencedTypes(schemaResult.outputType, checker, referencedTypes);
7009
+ const outputTypeRef = formatTypeReference(schemaResult.outputType, checker, typeRefs, referencedTypes);
7010
+ return {
7011
+ id: symbolName,
7012
+ name: symbolName,
7013
+ ...metadata,
7014
+ kind: "variable",
7015
+ deprecated: isSymbolDeprecated(symbol),
7016
+ type: outputTypeRef,
7017
+ description,
7018
+ source: getSourceLocation(declaration),
7019
+ tags: [
7020
+ ...parsedDoc?.tags ?? [],
7021
+ { name: "schemaLibrary", text: schemaResult.adapter.id },
7022
+ { name: "schemaSource", text: "static-ast" }
7023
+ ],
7024
+ examples: parsedDoc?.examples
7025
+ };
7026
+ }
7027
+ }
6512
7028
  return {
6513
- id: symbol.getName(),
6514
- name: symbol.getName(),
7029
+ id: symbolName,
7030
+ name: symbolName,
6515
7031
  ...metadata,
6516
7032
  kind: "variable",
6517
7033
  deprecated: isSymbolDeprecated(symbol),
@@ -6597,9 +7113,9 @@ function createDefaultGenerationInfo(entryFile) {
6597
7113
  }
6598
7114
  function buildOpenPkgSpec(context, resolveExternalTypes, generation) {
6599
7115
  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));
7116
+ const packageJsonPath = path11.join(baseDir, "package.json");
7117
+ const packageJson = fs10.existsSync(packageJsonPath) ? JSON.parse(fs10.readFileSync(packageJsonPath, "utf-8")) : {};
7118
+ const generationInfo = generation ?? createDefaultGenerationInfo(path11.relative(baseDir, entryFile));
6603
7119
  const spec = {
6604
7120
  $schema: SCHEMA_URL,
6605
7121
  openpkg: SCHEMA_VERSION,
@@ -6619,7 +7135,8 @@ function buildOpenPkgSpec(context, resolveExternalTypes, generation) {
6619
7135
  const serializerContext = {
6620
7136
  checker: typeChecker,
6621
7137
  typeRegistry,
6622
- maxTypeDepth: context.options.maxDepth
7138
+ maxTypeDepth: context.options.maxDepth,
7139
+ detectedSchemas: context.detectedSchemas
6623
7140
  };
6624
7141
  const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile);
6625
7142
  if (!moduleSymbol) {
@@ -6797,11 +7314,11 @@ function deriveImportPath(sourceFile, baseDir) {
6797
7314
  if (!sourceFile) {
6798
7315
  return;
6799
7316
  }
6800
- const relative4 = path9.relative(baseDir, sourceFile);
6801
- if (!relative4 || relative4.startsWith("..")) {
7317
+ const relative5 = path11.relative(baseDir, sourceFile);
7318
+ if (!relative5 || relative5.startsWith("..")) {
6802
7319
  return;
6803
7320
  }
6804
- const normalized = relative4.replace(/\\/g, "/");
7321
+ const normalized = relative5.replace(/\\/g, "/");
6805
7322
  const withoutExt = stripExtensions(normalized);
6806
7323
  if (!withoutExt) {
6807
7324
  return;
@@ -6840,11 +7357,11 @@ function withExportName(entry, exportName) {
6840
7357
  function findNearestPackageJson(startDir) {
6841
7358
  let current = startDir;
6842
7359
  while (true) {
6843
- const candidate = path10.join(current, "package.json");
6844
- if (fs9.existsSync(candidate)) {
7360
+ const candidate = path12.join(current, "package.json");
7361
+ if (fs11.existsSync(candidate)) {
6845
7362
  return candidate;
6846
7363
  }
6847
- const parent = path10.dirname(current);
7364
+ const parent = path12.dirname(current);
6848
7365
  if (parent === current) {
6849
7366
  return;
6850
7367
  }
@@ -6872,11 +7389,11 @@ function canResolveExternalModules(program, baseDir) {
6872
7389
  function hasNodeModulesDirectoryFallback(startDir) {
6873
7390
  let current = startDir;
6874
7391
  while (true) {
6875
- const candidate = path10.join(current, "node_modules");
6876
- if (fs9.existsSync(candidate)) {
7392
+ const candidate = path12.join(current, "node_modules");
7393
+ if (fs11.existsSync(candidate)) {
6877
7394
  return true;
6878
7395
  }
6879
- const parent = path10.dirname(current);
7396
+ const parent = path12.dirname(current);
6880
7397
  if (parent === current) {
6881
7398
  break;
6882
7399
  }
@@ -6977,6 +7494,17 @@ function runAnalysis(input, generationInput) {
6977
7494
  issues: generationIssues
6978
7495
  } : undefined;
6979
7496
  const spec = buildOpenPkgSpec(context, resolveExternalTypes, generation);
7497
+ if (input.detectedSchemas && input.detectedSchemas.size > 0 && generation) {
7498
+ const vendors = new Set;
7499
+ for (const entry of input.detectedSchemas.values()) {
7500
+ vendors.add(entry.vendor);
7501
+ }
7502
+ generation.analysis.schemaExtraction = {
7503
+ method: "hybrid",
7504
+ runtimeCount: input.detectedSchemas.size,
7505
+ vendors: Array.from(vendors)
7506
+ };
7507
+ }
6980
7508
  const danglingRefs = collectDanglingRefs(spec);
6981
7509
  for (const ref of danglingRefs) {
6982
7510
  const issue = {
@@ -7021,11 +7549,27 @@ function runAnalysis(input, generationInput) {
7021
7549
 
7022
7550
  // src/extractor.ts
7023
7551
  async function extractPackageSpec(entryFile, packageDir, content, options) {
7552
+ const baseDir = packageDir ?? path13.dirname(entryFile);
7553
+ const schemaMode = options?.schemaExtraction ?? "static";
7554
+ let detectedSchemas;
7555
+ if (schemaMode === "runtime" || schemaMode === "hybrid") {
7556
+ const extraction = await extractStandardSchemasFromProject(entryFile, baseDir);
7557
+ if (extraction.schemas.size > 0) {
7558
+ detectedSchemas = new Map;
7559
+ for (const [name, result2] of extraction.schemas) {
7560
+ detectedSchemas.set(name, {
7561
+ schema: result2.outputSchema,
7562
+ vendor: result2.vendor
7563
+ });
7564
+ }
7565
+ }
7566
+ }
7024
7567
  const result = runAnalysis({
7025
7568
  entryFile,
7026
7569
  packageDir,
7027
7570
  content,
7028
- options
7571
+ options,
7572
+ detectedSchemas
7029
7573
  });
7030
7574
  return result.spec;
7031
7575
  }
@@ -7118,16 +7662,33 @@ async function fetchSpecFromGitHub(parsed) {
7118
7662
  }
7119
7663
  return null;
7120
7664
  }
7121
- async function fetchSpec(owner, repo, branch = "main") {
7122
- return fetchSpecFromGitHub({ owner, repo, ref: branch });
7665
+ async function fetchSpec(owner, repo, branchOrOptions = "main") {
7666
+ const options = typeof branchOrOptions === "string" ? { ref: branchOrOptions, path: "openpkg.json" } : { ref: branchOrOptions.ref ?? "main", path: branchOrOptions.path ?? "openpkg.json" };
7667
+ const parsed = { owner, repo, ref: options.ref };
7668
+ return fetchSpecFromGitHubWithPath(parsed, options.path);
7669
+ }
7670
+ async function fetchSpecFromGitHubWithPath(parsed, specPath = "openpkg.json") {
7671
+ const urls = [
7672
+ buildRawUrl(parsed, specPath),
7673
+ `https://raw.githubusercontent.com/${parsed.owner}/${parsed.repo}/master/${specPath}`
7674
+ ];
7675
+ for (const url of urls) {
7676
+ try {
7677
+ const response = await fetch(url);
7678
+ if (response.ok) {
7679
+ return await response.json();
7680
+ }
7681
+ } catch {}
7682
+ }
7683
+ return null;
7123
7684
  }
7124
7685
  // src/install/index.ts
7125
7686
  var DEFAULT_FALLBACK_ORDER = ["bun", "npm"];
7126
- async function installDependencies(fs10, cwd, runCommand2, options = {}) {
7687
+ async function installDependencies(fs12, cwd, runCommand2, options = {}) {
7127
7688
  const { timeout = 180000, fallbackOrder = DEFAULT_FALLBACK_ORDER, onProgress } = options;
7128
7689
  const errors = [];
7129
7690
  onProgress?.({ stage: "installing", message: "Detecting package manager..." });
7130
- const pmInfo = await detectPackageManager(fs10);
7691
+ const pmInfo = await detectPackageManager(fs12);
7131
7692
  if (pmInfo.lockfile) {
7132
7693
  onProgress?.({
7133
7694
  stage: "installing",
@@ -7782,7 +8343,7 @@ function diffMemberChanges(oldSpec, newSpec, changedClassNames) {
7782
8343
  memberName,
7783
8344
  memberKind: getMemberKind(newMember),
7784
8345
  changeType: "added",
7785
- newSignature: formatSignature(newMember)
8346
+ newSignature: formatSignature2(newMember)
7786
8347
  });
7787
8348
  }
7788
8349
  }
@@ -7794,7 +8355,7 @@ function diffMemberChanges(oldSpec, newSpec, changedClassNames) {
7794
8355
  memberName,
7795
8356
  memberKind: getMemberKind(oldMember),
7796
8357
  changeType: "removed",
7797
- oldSignature: formatSignature(oldMember),
8358
+ oldSignature: formatSignature2(oldMember),
7798
8359
  suggestion
7799
8360
  });
7800
8361
  }
@@ -7807,8 +8368,8 @@ function diffMemberChanges(oldSpec, newSpec, changedClassNames) {
7807
8368
  memberName,
7808
8369
  memberKind: getMemberKind(newMember),
7809
8370
  changeType: "signature-changed",
7810
- oldSignature: formatSignature(oldMember),
7811
- newSignature: formatSignature(newMember)
8371
+ oldSignature: formatSignature2(oldMember),
8372
+ newSignature: formatSignature2(newMember)
7812
8373
  });
7813
8374
  }
7814
8375
  }
@@ -7857,7 +8418,7 @@ function getMemberKind(member) {
7857
8418
  return "method";
7858
8419
  }
7859
8420
  }
7860
- function formatSignature(member) {
8421
+ function formatSignature2(member) {
7861
8422
  if (!member.signatures?.length) {
7862
8423
  return member.name ?? "";
7863
8424
  }
@@ -8032,11 +8593,34 @@ function getDocsImpactSummary(diff) {
8032
8593
  }
8033
8594
  // src/openpkg.ts
8034
8595
  import * as fsSync from "node:fs";
8035
- import * as fs10 from "node:fs/promises";
8036
- import * as path11 from "node:path";
8596
+ import * as fs12 from "node:fs/promises";
8597
+ import * as path14 from "node:path";
8037
8598
 
8038
8599
  // src/filtering/apply-filters.ts
8039
8600
  var TYPE_REF_PREFIX = "#/types/";
8601
+ var getExportReleaseTag = (exp) => {
8602
+ const tags = exp.tags ?? [];
8603
+ for (const tag of tags) {
8604
+ const tagName = tag.name?.toLowerCase();
8605
+ if (tagName === "public")
8606
+ return "public";
8607
+ if (tagName === "beta")
8608
+ return "beta";
8609
+ if (tagName === "alpha")
8610
+ return "alpha";
8611
+ if (tagName === "internal")
8612
+ return "internal";
8613
+ }
8614
+ return;
8615
+ };
8616
+ var matchesVisibility = (exp, visibility) => {
8617
+ if (!visibility || visibility.length === 0) {
8618
+ return true;
8619
+ }
8620
+ const tag = getExportReleaseTag(exp);
8621
+ const effectiveTag = tag ?? "public";
8622
+ return visibility.includes(effectiveTag);
8623
+ };
8040
8624
  var toLowerKey = (value) => value.trim().toLowerCase();
8041
8625
  var buildLookupMap = (values) => {
8042
8626
  const map = new Map;
@@ -8098,7 +8682,8 @@ var collectTypeRefs = (value, refs, seen = new Set) => {
8098
8682
  var applyFilters = (spec, options) => {
8099
8683
  const includeLookup = buildLookupMap(options.include);
8100
8684
  const excludeLookup = buildLookupMap(options.exclude);
8101
- if (includeLookup.size === 0 && excludeLookup.size === 0) {
8685
+ const visibility = options.visibility;
8686
+ if (includeLookup.size === 0 && excludeLookup.size === 0 && (!visibility || visibility.length === 0)) {
8102
8687
  return { spec, diagnostics: [], changed: false };
8103
8688
  }
8104
8689
  const includeMatches = new Set;
@@ -8111,10 +8696,11 @@ var applyFilters = (spec, options) => {
8111
8696
  const excludeMatch = matches(entry, excludeLookup);
8112
8697
  const allowedByInclude = includeLookup.size === 0 || Boolean(includeMatch);
8113
8698
  const allowedByExclude = !excludeMatch;
8699
+ const allowedByVisibility = matchesVisibility(entry, visibility);
8114
8700
  if (includeMatch) {
8115
8701
  includeMatches.add(includeMatch);
8116
8702
  }
8117
- if (allowedByInclude && allowedByExclude) {
8703
+ if (allowedByInclude && allowedByExclude && allowedByVisibility) {
8118
8704
  keptExports.push(entry);
8119
8705
  }
8120
8706
  }
@@ -8216,14 +8802,14 @@ class DocCov {
8216
8802
  this.options = normalizeDocCovOptions(options);
8217
8803
  }
8218
8804
  async analyze(code, fileName = "temp.ts", analyzeOptions = {}) {
8219
- const resolvedFileName = path11.resolve(fileName);
8220
- const tempDir = path11.dirname(resolvedFileName);
8805
+ const resolvedFileName = path14.resolve(fileName);
8806
+ const tempDir = path14.dirname(resolvedFileName);
8221
8807
  const spec = await extractPackageSpec(resolvedFileName, tempDir, code, this.options);
8222
8808
  return this.applySpecFilters(spec, analyzeOptions.filters).spec;
8223
8809
  }
8224
8810
  async analyzeFile(filePath, analyzeOptions = {}) {
8225
- const resolvedPath = path11.resolve(filePath);
8226
- const content = await fs10.readFile(resolvedPath, "utf-8");
8811
+ const resolvedPath = path14.resolve(filePath);
8812
+ const content = await fs12.readFile(resolvedPath, "utf-8");
8227
8813
  const packageDir = resolvePackageDir(resolvedPath);
8228
8814
  const spec = await extractPackageSpec(resolvedPath, packageDir, content, this.options);
8229
8815
  return this.applySpecFilters(spec, analyzeOptions.filters).spec;
@@ -8232,13 +8818,16 @@ class DocCov {
8232
8818
  return this.analyzeFile(entryPath, analyzeOptions);
8233
8819
  }
8234
8820
  async analyzeWithDiagnostics(code, fileName, analyzeOptions = {}) {
8235
- const resolvedFileName = path11.resolve(fileName ?? "temp.ts");
8821
+ const resolvedFileName = path14.resolve(fileName ?? "temp.ts");
8236
8822
  const packageDir = resolvePackageDir(resolvedFileName);
8823
+ const isRealFile = fileName && !fileName.includes("temp.ts");
8824
+ const detectedSchemas = isRealFile ? await this.detectSchemas(resolvedFileName, packageDir) : undefined;
8237
8825
  const analysis = runAnalysis({
8238
8826
  entryFile: resolvedFileName,
8239
8827
  packageDir,
8240
8828
  content: code,
8241
- options: this.options
8829
+ options: this.options,
8830
+ detectedSchemas
8242
8831
  }, analyzeOptions.generationInput);
8243
8832
  const filterOutcome = this.applySpecFilters(analysis.spec, analyzeOptions.filters);
8244
8833
  return {
@@ -8252,7 +8841,7 @@ class DocCov {
8252
8841
  };
8253
8842
  }
8254
8843
  async analyzeFileWithDiagnostics(filePath, analyzeOptions = {}) {
8255
- const resolvedPath = path11.resolve(filePath);
8844
+ const resolvedPath = path14.resolve(filePath);
8256
8845
  const packageDir = resolvePackageDir(resolvedPath);
8257
8846
  const { useCache, resolveExternalTypes } = this.options;
8258
8847
  if (useCache) {
@@ -8268,12 +8857,14 @@ class DocCov {
8268
8857
  };
8269
8858
  }
8270
8859
  }
8271
- const content = await fs10.readFile(resolvedPath, "utf-8");
8860
+ const content = await fs12.readFile(resolvedPath, "utf-8");
8861
+ const detectedSchemas = await this.detectSchemas(resolvedPath, packageDir);
8272
8862
  const analysis = runAnalysis({
8273
8863
  entryFile: resolvedPath,
8274
8864
  packageDir,
8275
8865
  content,
8276
- options: this.options
8866
+ options: this.options,
8867
+ detectedSchemas
8277
8868
  }, analyzeOptions.generationInput);
8278
8869
  const filterOutcome = this.applySpecFilters(analysis.spec, analyzeOptions.filters);
8279
8870
  const metadata = this.normalizeMetadata(analysis.metadata);
@@ -8303,10 +8894,10 @@ class DocCov {
8303
8894
  if (!packageJsonPath) {
8304
8895
  return null;
8305
8896
  }
8306
- const sourceFiles = Object.keys(cache.hashes.sourceFiles).map((relativePath) => path11.resolve(cwd, relativePath));
8897
+ const currentSourceFiles = this.getCurrentSourceFiles(entryFile, packageDir);
8307
8898
  const cacheContext = {
8308
8899
  entryFile,
8309
- sourceFiles,
8900
+ sourceFiles: currentSourceFiles,
8310
8901
  tsconfigPath,
8311
8902
  packageJsonPath,
8312
8903
  config: {
@@ -8318,6 +8909,7 @@ class DocCov {
8318
8909
  if (!validation.valid) {
8319
8910
  return null;
8320
8911
  }
8912
+ const cachedSourceFiles = Object.keys(cache.hashes.sourceFiles).map((relativePath) => path14.resolve(cwd, relativePath));
8321
8913
  return {
8322
8914
  spec: cache.spec,
8323
8915
  metadata: {
@@ -8326,10 +8918,18 @@ class DocCov {
8326
8918
  packageJsonPath,
8327
8919
  hasNodeModules: true,
8328
8920
  resolveExternalTypes: cache.config.resolveExternalTypes,
8329
- sourceFiles
8921
+ sourceFiles: cachedSourceFiles
8330
8922
  }
8331
8923
  };
8332
8924
  }
8925
+ getCurrentSourceFiles(entryFile, baseDir) {
8926
+ try {
8927
+ const { program } = createProgram({ entryFile, baseDir });
8928
+ return program.getSourceFiles().filter((sf) => !sf.isDeclarationFile && sf.fileName.startsWith(baseDir)).map((sf) => sf.fileName);
8929
+ } catch {
8930
+ return [];
8931
+ }
8932
+ }
8333
8933
  saveToCache(result, entryFile, metadata) {
8334
8934
  const { cwd } = this.options;
8335
8935
  if (!metadata.packageJsonPath) {
@@ -8352,11 +8952,11 @@ class DocCov {
8352
8952
  findTsConfig(startDir) {
8353
8953
  let current = startDir;
8354
8954
  while (true) {
8355
- const candidate = path11.join(current, "tsconfig.json");
8955
+ const candidate = path14.join(current, "tsconfig.json");
8356
8956
  if (fsSync.existsSync(candidate)) {
8357
8957
  return candidate;
8358
8958
  }
8359
- const parent = path11.dirname(current);
8959
+ const parent = path14.dirname(current);
8360
8960
  if (parent === current) {
8361
8961
  return null;
8362
8962
  }
@@ -8366,17 +8966,38 @@ class DocCov {
8366
8966
  findPackageJson(startDir) {
8367
8967
  let current = startDir;
8368
8968
  while (true) {
8369
- const candidate = path11.join(current, "package.json");
8969
+ const candidate = path14.join(current, "package.json");
8370
8970
  if (fsSync.existsSync(candidate)) {
8371
8971
  return candidate;
8372
8972
  }
8373
- const parent = path11.dirname(current);
8973
+ const parent = path14.dirname(current);
8374
8974
  if (parent === current) {
8375
8975
  return null;
8376
8976
  }
8377
8977
  current = parent;
8378
8978
  }
8379
8979
  }
8980
+ async detectSchemas(entryFile, packageDir) {
8981
+ try {
8982
+ const result = await detectRuntimeSchemas({
8983
+ baseDir: packageDir,
8984
+ entryFile
8985
+ });
8986
+ if (result.schemas.size === 0) {
8987
+ return;
8988
+ }
8989
+ const detected = new Map;
8990
+ for (const [name, schema] of result.schemas) {
8991
+ detected.set(name, {
8992
+ schema: schema.schema,
8993
+ vendor: schema.vendor
8994
+ });
8995
+ }
8996
+ return detected;
8997
+ } catch {
8998
+ return;
8999
+ }
9000
+ }
8380
9001
  normalizeDiagnostic(tsDiagnostic) {
8381
9002
  const message = ts.flattenDiagnosticMessageText(tsDiagnostic.messageText, `
8382
9003
  `);
@@ -8438,14 +9059,14 @@ async function analyzeFile(filePath, options = {}) {
8438
9059
  return new DocCov().analyzeFile(filePath, options);
8439
9060
  }
8440
9061
  function resolvePackageDir(entryFile) {
8441
- const fallbackDir = path11.dirname(entryFile);
9062
+ const fallbackDir = path14.dirname(entryFile);
8442
9063
  let currentDir = fallbackDir;
8443
9064
  while (true) {
8444
- const candidate = path11.join(currentDir, "package.json");
9065
+ const candidate = path14.join(currentDir, "package.json");
8445
9066
  if (fsSync.existsSync(candidate)) {
8446
9067
  return currentDir;
8447
9068
  }
8448
- const parentDir = path11.dirname(currentDir);
9069
+ const parentDir = path14.dirname(currentDir);
8449
9070
  if (parentDir === currentDir) {
8450
9071
  return fallbackDir;
8451
9072
  }
@@ -8453,12 +9074,12 @@ function resolvePackageDir(entryFile) {
8453
9074
  }
8454
9075
  }
8455
9076
  // src/resolve/index.ts
8456
- import * as path12 from "node:path";
8457
- async function resolveTarget(fs11, options) {
9077
+ import * as path15 from "node:path";
9078
+ async function resolveTarget(fs13, options) {
8458
9079
  let targetDir = options.cwd;
8459
9080
  let packageInfo;
8460
9081
  if (options.package) {
8461
- const mono = await detectMonorepo(fs11);
9082
+ const mono = await detectMonorepo(fs13);
8462
9083
  if (!mono.isMonorepo) {
8463
9084
  throw new Error("Not a monorepo. Remove --package flag for single-package repos.");
8464
9085
  }
@@ -8467,21 +9088,21 @@ async function resolveTarget(fs11, options) {
8467
9088
  const available = mono.packages.map((p) => p.name).join(", ");
8468
9089
  throw new Error(`Package "${options.package}" not found. Available: ${available}`);
8469
9090
  }
8470
- targetDir = path12.join(options.cwd, pkg.path);
9091
+ targetDir = path15.join(options.cwd, pkg.path);
8471
9092
  packageInfo = pkg;
8472
9093
  }
8473
9094
  let entryFile;
8474
9095
  let entryPointInfo;
8475
9096
  if (!options.entry) {
8476
- entryPointInfo = await detectEntryPoint(fs11, getRelativePath(options.cwd, targetDir));
8477
- entryFile = path12.join(targetDir, entryPointInfo.path);
9097
+ entryPointInfo = await detectEntryPoint(fs13, getRelativePath(options.cwd, targetDir));
9098
+ entryFile = path15.join(targetDir, entryPointInfo.path);
8478
9099
  } else {
8479
- const explicitPath = path12.resolve(targetDir, options.entry);
8480
- const isDirectory = await isDir(fs11, getRelativePath(options.cwd, explicitPath));
9100
+ const explicitPath = path15.resolve(targetDir, options.entry);
9101
+ const isDirectory = await isDir(fs13, getRelativePath(options.cwd, explicitPath));
8481
9102
  if (isDirectory) {
8482
9103
  targetDir = explicitPath;
8483
- entryPointInfo = await detectEntryPoint(fs11, getRelativePath(options.cwd, explicitPath));
8484
- entryFile = path12.join(explicitPath, entryPointInfo.path);
9104
+ entryPointInfo = await detectEntryPoint(fs13, getRelativePath(options.cwd, explicitPath));
9105
+ entryFile = path15.join(explicitPath, entryPointInfo.path);
8485
9106
  } else {
8486
9107
  entryFile = explicitPath;
8487
9108
  entryPointInfo = {
@@ -8501,52 +9122,20 @@ async function resolveTarget(fs11, options) {
8501
9122
  function getRelativePath(base, target) {
8502
9123
  if (base === target)
8503
9124
  return ".";
8504
- const rel = path12.relative(base, target);
9125
+ const rel = path15.relative(base, target);
8505
9126
  return rel || ".";
8506
9127
  }
8507
- async function isDir(fs11, relativePath) {
8508
- const hasPackageJson = await fs11.exists(path12.join(relativePath, "package.json"));
9128
+ async function isDir(fs13, relativePath) {
9129
+ const hasPackageJson = await fs13.exists(path15.join(relativePath, "package.json"));
8509
9130
  if (hasPackageJson)
8510
9131
  return true;
8511
9132
  const commonEntryFiles = ["index.ts", "index.tsx", "src/index.ts", "main.ts"];
8512
9133
  for (const entry of commonEntryFiles) {
8513
- if (await fs11.exists(path12.join(relativePath, entry))) {
9134
+ if (await fs13.exists(path15.join(relativePath, entry))) {
8514
9135
  return true;
8515
9136
  }
8516
9137
  }
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
- };
9138
+ return !path15.extname(relativePath);
8550
9139
  }
8551
9140
  // src/scan/github-context.ts
8552
9141
  function parseGitHubUrl2(url) {
@@ -8557,10 +9146,14 @@ function parseGitHubUrl2(url) {
8557
9146
  return null;
8558
9147
  }
8559
9148
  }
8560
- async function fetchRawFile(owner, repo, ref, path13) {
8561
- const url = `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${path13}`;
9149
+ async function fetchRawFile(owner, repo, ref, path16, authToken) {
9150
+ const url = `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${path16}`;
8562
9151
  try {
8563
- const response = await fetch(url);
9152
+ const headers = {};
9153
+ if (authToken) {
9154
+ headers.Authorization = `Bearer ${authToken}`;
9155
+ }
9156
+ const response = await fetch(url, { headers });
8564
9157
  if (response.ok) {
8565
9158
  return await response.text();
8566
9159
  }
@@ -8569,14 +9162,16 @@ async function fetchRawFile(owner, repo, ref, path13) {
8569
9162
  return null;
8570
9163
  }
8571
9164
  }
8572
- async function fetchRepoMetadata(owner, repo) {
9165
+ async function fetchRepoMetadata(owner, repo, authToken) {
8573
9166
  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
- });
9167
+ const headers = {
9168
+ Accept: "application/vnd.github.v3+json",
9169
+ "User-Agent": "DocCov-Scanner"
9170
+ };
9171
+ if (authToken) {
9172
+ headers.Authorization = `Bearer ${authToken}`;
9173
+ }
9174
+ const response = await fetch(url, { headers });
8580
9175
  if (!response.ok) {
8581
9176
  throw new Error(`Failed to fetch repository: ${response.status} ${response.statusText}`);
8582
9177
  }
@@ -8591,7 +9186,7 @@ async function fetchRepoMetadata(owner, repo) {
8591
9186
  isPrivate: data.private
8592
9187
  };
8593
9188
  }
8594
- async function detectPackageManager3(owner, repo, ref) {
9189
+ async function detectPackageManager3(owner, repo, ref, authToken) {
8595
9190
  const lockfiles = [
8596
9191
  { name: "bun.lockb", manager: "bun" },
8597
9192
  { name: "pnpm-lock.yaml", manager: "pnpm" },
@@ -8599,15 +9194,15 @@ async function detectPackageManager3(owner, repo, ref) {
8599
9194
  { name: "package-lock.json", manager: "npm" }
8600
9195
  ];
8601
9196
  for (const { name, manager } of lockfiles) {
8602
- const content = await fetchRawFile(owner, repo, ref, name);
9197
+ const content = await fetchRawFile(owner, repo, ref, name, authToken);
8603
9198
  if (content !== null) {
8604
9199
  return { manager, lockfile: { name, content: content.slice(0, 1e4) } };
8605
9200
  }
8606
9201
  }
8607
9202
  return { manager: "unknown" };
8608
9203
  }
8609
- async function detectWorkspace(packageJson, owner, repo, ref) {
8610
- const pnpmWorkspace = await fetchRawFile(owner, repo, ref, "pnpm-workspace.yaml");
9204
+ async function detectWorkspace(packageJson, owner, repo, ref, authToken) {
9205
+ const pnpmWorkspace = await fetchRawFile(owner, repo, ref, "pnpm-workspace.yaml", authToken);
8611
9206
  if (pnpmWorkspace) {
8612
9207
  const packagesMatch = pnpmWorkspace.match(/packages:\s*\n((?:\s+-\s+['"]?[^\n]+['"]?\n?)+)/);
8613
9208
  const packages = packagesMatch ? packagesMatch[1].split(`
@@ -8618,7 +9213,7 @@ async function detectWorkspace(packageJson, owner, repo, ref) {
8618
9213
  packages
8619
9214
  };
8620
9215
  }
8621
- const lernaJson = await fetchRawFile(owner, repo, ref, "lerna.json");
9216
+ const lernaJson = await fetchRawFile(owner, repo, ref, "lerna.json", authToken);
8622
9217
  if (lernaJson) {
8623
9218
  return { isMonorepo: true, tool: "lerna" };
8624
9219
  }
@@ -8678,18 +9273,20 @@ function detectBuildHints(packageJson, tsconfigJson) {
8678
9273
  }
8679
9274
  return hints;
8680
9275
  }
8681
- async function fetchGitHubContext(repoUrl, ref) {
9276
+ async function fetchGitHubContext(repoUrl, refOrOptions) {
8682
9277
  const parsed = parseGitHubUrl2(repoUrl);
8683
9278
  if (!parsed) {
8684
9279
  throw new Error(`Invalid GitHub URL: ${repoUrl}`);
8685
9280
  }
9281
+ const options = typeof refOrOptions === "string" ? { ref: refOrOptions } : refOrOptions ?? {};
9282
+ const { authToken } = options;
8686
9283
  const { owner, repo } = parsed;
8687
- const metadata = await fetchRepoMetadata(owner, repo);
8688
- const targetRef = ref ?? metadata.defaultBranch;
9284
+ const metadata = await fetchRepoMetadata(owner, repo, authToken);
9285
+ const targetRef = options.ref ?? metadata.defaultBranch;
8689
9286
  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)
9287
+ fetchRawFile(owner, repo, targetRef, "package.json", authToken),
9288
+ fetchRawFile(owner, repo, targetRef, "tsconfig.json", authToken),
9289
+ detectPackageManager3(owner, repo, targetRef, authToken)
8693
9290
  ]);
8694
9291
  let packageJson = null;
8695
9292
  let tsconfigJson = null;
@@ -8703,7 +9300,7 @@ async function fetchGitHubContext(repoUrl, ref) {
8703
9300
  tsconfigJson = JSON.parse(tsconfigJsonRaw);
8704
9301
  } catch {}
8705
9302
  }
8706
- const workspace = await detectWorkspace(packageJson, owner, repo, targetRef);
9303
+ const workspace = await detectWorkspace(packageJson, owner, repo, targetRef, authToken);
8707
9304
  const buildHints = detectBuildHints(packageJson, tsconfigJson);
8708
9305
  return {
8709
9306
  metadata,
@@ -8718,18 +9315,20 @@ async function fetchGitHubContext(repoUrl, ref) {
8718
9315
  }
8719
9316
  };
8720
9317
  }
8721
- async function listWorkspacePackages(owner, repo, ref, patterns) {
9318
+ async function listWorkspacePackages(owner, repo, ref, patterns, authToken) {
8722
9319
  const packages = [];
8723
9320
  for (const pattern of patterns) {
8724
9321
  const baseDir = pattern.replace(/\/\*.*$/, "");
8725
9322
  try {
8726
9323
  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
- });
9324
+ const headers = {
9325
+ Accept: "application/vnd.github.v3+json",
9326
+ "User-Agent": "DocCov-Scanner"
9327
+ };
9328
+ if (authToken) {
9329
+ headers.Authorization = `Bearer ${authToken}`;
9330
+ }
9331
+ const response = await fetch(url, { headers });
8733
9332
  if (response.ok) {
8734
9333
  const contents = await response.json();
8735
9334
  for (const item of contents) {
@@ -8742,6 +9341,38 @@ async function listWorkspacePackages(owner, repo, ref, patterns) {
8742
9341
  }
8743
9342
  return packages;
8744
9343
  }
9344
+ // src/scan/summary.ts
9345
+ function extractSpecSummary(spec) {
9346
+ const exports = spec.exports ?? [];
9347
+ const undocumented = [];
9348
+ const drift = [];
9349
+ for (const exp of exports) {
9350
+ const docs = exp.docs;
9351
+ if (!docs)
9352
+ continue;
9353
+ const hasMissing = (docs.missing?.length ?? 0) > 0;
9354
+ const isPartial = (docs.coverageScore ?? 0) < 100;
9355
+ if (hasMissing || isPartial) {
9356
+ undocumented.push(exp.name);
9357
+ }
9358
+ for (const d of docs.drift ?? []) {
9359
+ drift.push({
9360
+ export: exp.name,
9361
+ type: d.type,
9362
+ issue: d.issue,
9363
+ suggestion: d.suggestion
9364
+ });
9365
+ }
9366
+ }
9367
+ return {
9368
+ coverage: spec.docs?.coverageScore ?? 0,
9369
+ exportCount: exports.length,
9370
+ typeCount: spec.types?.length ?? 0,
9371
+ driftCount: drift.length,
9372
+ undocumented,
9373
+ drift
9374
+ };
9375
+ }
8745
9376
  export {
8746
9377
  validateSpecCache,
8747
9378
  validateExamples,
@@ -8750,13 +9381,19 @@ export {
8750
9381
  shouldValidate,
8751
9382
  serializeJSDoc,
8752
9383
  saveSpecCache,
9384
+ saveSnapshot,
8753
9385
  saveReport,
8754
9386
  safeParseJson,
8755
9387
  runExamplesWithPackage,
8756
9388
  runExamples,
8757
9389
  runExample,
8758
9390
  resolveTarget,
9391
+ resolveCompiledPath,
9392
+ renderSparkline,
9393
+ renderApiSurface,
8759
9394
  readPackageJson,
9395
+ pruneHistory,
9396
+ pruneByTier,
8760
9397
  parseGitHubUrl2 as parseScanGitHubUrl,
8761
9398
  parseMarkdownFiles,
8762
9399
  parseMarkdownFile,
@@ -8767,10 +9404,13 @@ export {
8767
9404
  parseAssertions,
8768
9405
  mergeFixes,
8769
9406
  mergeFilters,
8770
- mergeConfig,
8771
9407
  loadSpecCache,
9408
+ loadSnapshotsForDays,
9409
+ loadSnapshots,
8772
9410
  loadCachedReport,
8773
9411
  listWorkspacePackages,
9412
+ isStandardJSONSchema,
9413
+ isSchemaType,
8774
9414
  isFixableDrift,
8775
9415
  isExecutableLang,
8776
9416
  isCachedReportValid,
@@ -8783,43 +9423,49 @@ export {
8783
9423
  hasDocsForExport,
8784
9424
  groupDriftsByCategory,
8785
9425
  getUndocumentedExports,
9426
+ getTrend,
9427
+ getSupportedLibraries,
8786
9428
  getSpecCachePath,
8787
9429
  getRunCommand,
8788
- getRulesForKind,
8789
- getRule,
8790
9430
  getReportPath,
9431
+ getRegisteredAdapters,
8791
9432
  getPrimaryBuildScript,
8792
9433
  getInstallCommand,
9434
+ getExtendedTrend,
8793
9435
  getDriftSummary,
8794
9436
  getDocumentedExports,
8795
9437
  getDocsImpactSummary,
8796
9438
  getDiffReportPath,
8797
- getDefaultConfig,
8798
- getCoverageRules,
9439
+ generateWeeklySummaries,
8799
9440
  generateReportFromEnriched,
8800
9441
  generateReport,
8801
9442
  generateFixesForExport,
8802
9443
  generateFix,
8803
9444
  formatPackageList,
8804
9445
  formatDriftSummaryLine,
9446
+ formatDelta,
8805
9447
  findRemovedReferences,
8806
9448
  findPackageByName,
8807
9449
  findJSDocLocation,
8808
9450
  findExportReferences,
8809
9451
  findDeprecatedReferences,
9452
+ findAdapter,
8810
9453
  fetchSpecFromGitHub,
8811
9454
  fetchSpec,
8812
9455
  fetchGitHubContext,
9456
+ extractStandardSchemasFromProject,
9457
+ extractStandardSchemas,
8813
9458
  extractSpecSummary,
9459
+ extractSchemaType,
9460
+ extractSchemaOutputType,
8814
9461
  extractPackageSpec,
8815
9462
  extractImports,
8816
9463
  extractFunctionCalls,
8817
- evaluateQuality,
8818
- evaluateExportQuality,
8819
9464
  ensureSpecCoverage,
8820
9465
  enrichSpec,
8821
9466
  diffSpecWithDocs,
8822
9467
  diffHashes,
9468
+ detectRuntimeSchemas,
8823
9469
  detectPackageManager,
8824
9470
  detectMonorepo,
8825
9471
  detectExampleRuntimeErrors,
@@ -8829,9 +9475,11 @@ export {
8829
9475
  defineConfig,
8830
9476
  createSourceFile,
8831
9477
  createNodeCommandRunner,
9478
+ computeSnapshot,
8832
9479
  computeExportDrift,
8833
9480
  computeDrift,
8834
9481
  clearSpecCache,
9482
+ clearSchemaCache,
8835
9483
  categorizeDrifts,
8836
9484
  categorizeDrift,
8837
9485
  calculateAggregateCoverage,
@@ -8848,16 +9496,15 @@ export {
8848
9496
  analyze,
8849
9497
  VALIDATION_INFO,
8850
9498
  SandboxFileSystem,
8851
- STYLE_RULES,
8852
9499
  SPEC_CACHE_FILE,
9500
+ RETENTION_DAYS,
8853
9501
  REPORT_VERSION,
8854
9502
  REPORT_EXTENSIONS,
8855
9503
  NodeFileSystem,
9504
+ HISTORY_DIR,
8856
9505
  DocCov,
8857
9506
  DEFAULT_REPORT_PATH,
8858
9507
  DEFAULT_REPORT_DIR,
8859
- CORE_RULES,
8860
9508
  CACHE_VERSION,
8861
- BUILTIN_RULES,
8862
9509
  ALL_VALIDATIONS
8863
9510
  };