@doccov/sdk 0.3.7 → 0.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -116,7 +116,11 @@ interface ExportReference {
116
116
  /**
117
117
  * Change type for an impacted reference
118
118
  */
119
- type DocsChangeType = "signature-changed" | "removed" | "deprecated";
119
+ type DocsChangeType = "signature-changed" | "removed" | "deprecated" | "method-removed" | "method-changed" | "method-deprecated";
120
+ /**
121
+ * Member-level change type
122
+ */
123
+ type MemberChangeType = "added" | "removed" | "signature-changed";
120
124
  /**
121
125
  * An impacted reference in a documentation file
122
126
  */
@@ -131,6 +135,14 @@ interface DocsImpactReference {
131
135
  suggestion?: string;
132
136
  /** Context around the reference */
133
137
  context?: string;
138
+ /** Member/method name if this is a member-level change */
139
+ memberName?: string;
140
+ /** Type of member change (added, removed, signature-changed) */
141
+ memberChangeType?: MemberChangeType;
142
+ /** Suggested replacement for removed/changed members */
143
+ replacementSuggestion?: string;
144
+ /** True if this is just a class instantiation (new ClassName()) */
145
+ isInstantiation?: boolean;
134
146
  }
135
147
  /**
136
148
  * Documentation file impact summary
@@ -198,14 +210,32 @@ declare function findExportReferences(files: MarkdownDocFile[], exportNames: str
198
210
  */
199
211
  declare function blockReferencesExport(block: MarkdownCodeBlock, exportName: string): boolean;
200
212
  import { SpecDiff } from "@openpkg-ts/spec";
213
+ type MemberChangeType2 = "added" | "removed" | "signature-changed";
214
+ interface MemberChange {
215
+ /** The class this member belongs to */
216
+ className: string;
217
+ /** The member name (e.g., "evaluateChainhook") */
218
+ memberName: string;
219
+ /** Kind of member */
220
+ memberKind: "method" | "property" | "accessor" | "constructor";
221
+ /** Type of change */
222
+ changeType: MemberChangeType2;
223
+ /** Old signature string (for signature changes) */
224
+ oldSignature?: string;
225
+ /** New signature string (for signature changes) */
226
+ newSignature?: string;
227
+ /** Suggested replacement (e.g., "Use replayChainhook instead") */
228
+ suggestion?: string;
229
+ }
201
230
  /**
202
231
  * Analyze docs impact from a spec diff
203
232
  *
204
233
  * @param diff - The spec diff result
205
234
  * @param markdownFiles - Parsed markdown files
206
235
  * @param newExportNames - All names in the new spec (for missing docs detection)
236
+ * @param memberChanges - Optional member-level changes for granular detection
207
237
  */
208
- declare function analyzeDocsImpact(diff: SpecDiff, markdownFiles: MarkdownDocFile[], newExportNames?: string[]): DocsImpactResult;
238
+ declare function analyzeDocsImpact(diff: SpecDiff, markdownFiles: MarkdownDocFile[], newExportNames?: string[], memberChanges?: MemberChange[]): DocsImpactResult;
209
239
  /**
210
240
  * Find references to deprecated exports
211
241
  */
@@ -225,13 +255,15 @@ declare function getDocumentedExports(markdownFiles: MarkdownDocFile[], exportNa
225
255
  * Get all exports that lack documentation
226
256
  */
227
257
  declare function getUndocumentedExports(markdownFiles: MarkdownDocFile[], exportNames: string[]): string[];
228
- import { OpenPkg as OpenPkg2, SpecDiff as SpecDiff2 } from "@openpkg-ts/spec";
258
+ import { OpenPkg as OpenPkg3, SpecDiff as SpecDiff2 } from "@openpkg-ts/spec";
229
259
  /**
230
260
  * Extended spec diff result with docs impact
231
261
  */
232
262
  interface SpecDiffWithDocs extends SpecDiff2 {
233
263
  /** Docs impact analysis (only present if markdown files provided) */
234
264
  docsImpact?: DocsImpactResult;
265
+ /** Member-level changes for classes (methods added/removed/changed) */
266
+ memberChanges?: MemberChange[];
235
267
  }
236
268
  /**
237
269
  * Options for diffSpecWithDocs
@@ -263,7 +295,7 @@ interface DiffWithDocsOptions {
263
295
  * }
264
296
  * ```
265
297
  */
266
- declare function diffSpecWithDocs(oldSpec: OpenPkg2, newSpec: OpenPkg2, options?: DiffWithDocsOptions): SpecDiffWithDocs;
298
+ declare function diffSpecWithDocs(oldSpec: OpenPkg3, newSpec: OpenPkg3, options?: DiffWithDocsOptions): SpecDiffWithDocs;
267
299
  /**
268
300
  * Check if a diff has any docs impact
269
301
  */
@@ -276,6 +308,7 @@ declare function getDocsImpactSummary(diff: SpecDiffWithDocs): {
276
308
  impactedReferenceCount: number;
277
309
  missingDocsCount: number;
278
310
  totalIssues: number;
311
+ memberChangesCount: number;
279
312
  };
280
313
  interface DocCovOptions {
281
314
  includePrivate?: boolean;
@@ -466,7 +499,7 @@ declare class DocCov {
466
499
  declare function analyze(code: string, options?: AnalyzeOptions): Promise<OpenPkgSpec>;
467
500
  declare function analyzeFile(filePath: string, options?: AnalyzeOptions): Promise<OpenPkgSpec>;
468
501
  /** @deprecated Use DocCov instead */
469
- declare const OpenPkg3: typeof DocCov;
502
+ declare const OpenPkg4: typeof DocCov;
470
503
  /**
471
504
  * Project detection types for I/O-agnostic project analysis.
472
505
  * Used by both CLI (NodeFileSystem) and API (SandboxFileSystem).
@@ -723,4 +756,4 @@ declare function readPackageJson(fs: FileSystem, dir: string): Promise<PackageJs
723
756
  * ```
724
757
  */
725
758
  declare function analyzeProject2(fs: FileSystem, options?: AnalyzeProjectOptions): Promise<ProjectInfo>;
726
- export { serializeJSDoc, safeParseJson, runExamplesWithPackage, runExamples, runExample, readPackageJson, parseMarkdownFiles, parseMarkdownFile, parseJSDocToPatch, parseAssertions, mergeFixes, isFixableDrift, isExecutableLang, hasNonAssertionComments, hasDocsImpact, hasDocsForExport, getUndocumentedExports, getRunCommand, getPrimaryBuildScript, getInstallCommand, getDocumentedExports, getDocsImpactSummary, generateFixesForExport, generateFix, formatPackageList, findRemovedReferences, findPackageByName, findJSDocLocation, findExportReferences, findDeprecatedReferences, extractPackageSpec, extractImports, extractFunctionCalls, diffSpecWithDocs, detectPackageManager, detectMonorepo, detectExampleRuntimeErrors, detectExampleAssertionFailures, detectEntryPoint, detectBuildInfo, createSourceFile, categorizeDrifts, blockReferencesExport, applyPatchToJSDoc, applyEdits, analyzeProject2 as analyzeProject, analyzeFile, analyzeDocsImpact, analyze, WorkspacePackage, SpecDiffWithDocs, SandboxFileSystem, RunExamplesWithPackageResult, RunExamplesWithPackageOptions, RunExampleOptions, ProjectInfo, PackageManagerInfo, PackageManager, PackageJson, PackageExports, OpenPkgSpec, OpenPkgOptions, OpenPkg3 as OpenPkg, NodeFileSystem, MonorepoType, MonorepoInfo, MarkdownDocFile, MarkdownCodeBlock, JSDocTag, JSDocReturn, JSDocPatch, JSDocParam, JSDocEdit, FixType, FixSuggestion, FilterOptions, FileSystem, ExportReference, ExampleRunResult, EntryPointSource, EntryPointInfo, DocsImpactResult, DocsImpactReference, DocsImpact, DocsChangeType, DocCovOptions, DocCov, DiffWithDocsOptions, Diagnostic, BuildInfo, ApplyEditsResult, AnalyzeProjectOptions, AnalyzeOptions, AnalysisResult };
759
+ export { serializeJSDoc, safeParseJson, runExamplesWithPackage, runExamples, runExample, readPackageJson, parseMarkdownFiles, parseMarkdownFile, parseJSDocToPatch, parseAssertions, mergeFixes, isFixableDrift, isExecutableLang, hasNonAssertionComments, hasDocsImpact, hasDocsForExport, getUndocumentedExports, getRunCommand, getPrimaryBuildScript, getInstallCommand, getDocumentedExports, getDocsImpactSummary, generateFixesForExport, generateFix, formatPackageList, findRemovedReferences, findPackageByName, findJSDocLocation, findExportReferences, findDeprecatedReferences, extractPackageSpec, extractImports, extractFunctionCalls, diffSpecWithDocs, detectPackageManager, detectMonorepo, detectExampleRuntimeErrors, detectExampleAssertionFailures, detectEntryPoint, detectBuildInfo, createSourceFile, categorizeDrifts, blockReferencesExport, applyPatchToJSDoc, applyEdits, analyzeProject2 as analyzeProject, analyzeFile, analyzeDocsImpact, analyze, WorkspacePackage, SpecDiffWithDocs, SandboxFileSystem, RunExamplesWithPackageResult, RunExamplesWithPackageOptions, RunExampleOptions, ProjectInfo, PackageManagerInfo, PackageManager, PackageJson, PackageExports, OpenPkgSpec, OpenPkgOptions, OpenPkg4 as OpenPkg, NodeFileSystem, MonorepoType, MonorepoInfo, MarkdownDocFile, MarkdownCodeBlock, JSDocTag, JSDocReturn, JSDocPatch, JSDocParam, JSDocEdit, FixType, FixSuggestion, FilterOptions, FileSystem, ExportReference, ExampleRunResult, EntryPointSource, EntryPointInfo, DocsImpactResult, DocsImpactReference, DocsImpact, DocsChangeType, DocCovOptions, DocCov, DiffWithDocsOptions, Diagnostic, BuildInfo, ApplyEditsResult, AnalyzeProjectOptions, AnalyzeOptions, AnalysisResult };
package/dist/index.js CHANGED
@@ -1107,6 +1107,39 @@ function extractFunctionCalls(code) {
1107
1107
  }
1108
1108
  return Array.from(calls);
1109
1109
  }
1110
+ function extractMethodCalls(code) {
1111
+ const calls = [];
1112
+ const lines = code.split(`
1113
+ `);
1114
+ const methodCallRegex = /\b([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\.\s*([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/g;
1115
+ const skipObjects = new Set(["console", "Math", "JSON", "Object", "Array", "String", "Number"]);
1116
+ for (let lineIndex = 0;lineIndex < lines.length; lineIndex++) {
1117
+ const line = lines[lineIndex];
1118
+ let match;
1119
+ methodCallRegex.lastIndex = 0;
1120
+ while ((match = methodCallRegex.exec(line)) !== null) {
1121
+ const objectName = match[1];
1122
+ const methodName = match[2];
1123
+ if (skipObjects.has(objectName)) {
1124
+ continue;
1125
+ }
1126
+ calls.push({
1127
+ objectName,
1128
+ methodName,
1129
+ line: lineIndex,
1130
+ context: line.trim()
1131
+ });
1132
+ }
1133
+ }
1134
+ return calls;
1135
+ }
1136
+ function hasInstantiation(code, className) {
1137
+ const regex = new RegExp(`\\bnew\\s+${escapeRegex(className)}\\s*\\(`, "g");
1138
+ return regex.test(code);
1139
+ }
1140
+ function escapeRegex(str) {
1141
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1142
+ }
1110
1143
  function findExportReferences(files, exportNames) {
1111
1144
  const references = [];
1112
1145
  const exportSet = new Set(exportNames);
@@ -1174,27 +1207,99 @@ function getChangeType(exportName, diff) {
1174
1207
  }
1175
1208
  return null;
1176
1209
  }
1177
- function analyzeDocsImpact(diff, markdownFiles, newExportNames = []) {
1210
+ function mapMemberChangeType(memberChangeType) {
1211
+ switch (memberChangeType) {
1212
+ case "removed":
1213
+ return "method-removed";
1214
+ case "signature-changed":
1215
+ return "method-changed";
1216
+ default:
1217
+ return "signature-changed";
1218
+ }
1219
+ }
1220
+ function analyzeDocsImpact(diff, markdownFiles, newExportNames = [], memberChanges) {
1178
1221
  const changedExports = [
1179
1222
  ...diff.breaking
1180
1223
  ];
1181
- const references = findExportReferences(markdownFiles, changedExports);
1224
+ const memberChangesByMethod = new Map;
1225
+ if (memberChanges) {
1226
+ for (const mc of memberChanges) {
1227
+ memberChangesByMethod.set(mc.memberName, mc);
1228
+ }
1229
+ }
1230
+ const classesWithMemberChanges = new Set(memberChanges?.map((mc) => mc.className) ?? []);
1182
1231
  const impactByFile = new Map;
1183
- for (const ref of references) {
1184
- const changeType = getChangeType(ref.exportName, diff);
1185
- if (!changeType)
1186
- continue;
1187
- let impact = impactByFile.get(ref.file);
1232
+ const addReference = (file, ref) => {
1233
+ let impact = impactByFile.get(file);
1188
1234
  if (!impact) {
1189
- impact = { file: ref.file, references: [] };
1190
- impactByFile.set(ref.file, impact);
1191
- }
1192
- impact.references.push({
1193
- exportName: ref.exportName,
1194
- line: ref.line,
1195
- changeType,
1196
- context: ref.context
1197
- });
1235
+ impact = { file, references: [] };
1236
+ impactByFile.set(file, impact);
1237
+ }
1238
+ impact.references.push(ref);
1239
+ };
1240
+ for (const mdFile of markdownFiles) {
1241
+ for (const block of mdFile.codeBlocks) {
1242
+ const reportedRefs = new Set;
1243
+ if (memberChanges && memberChanges.length > 0) {
1244
+ const methodCalls = extractMethodCalls(block.code);
1245
+ for (const call of methodCalls) {
1246
+ const memberChange = memberChangesByMethod.get(call.methodName);
1247
+ if (memberChange) {
1248
+ const refKey = `${mdFile.path}:${block.lineStart + call.line}:${call.methodName}`;
1249
+ if (!reportedRefs.has(refKey)) {
1250
+ reportedRefs.add(refKey);
1251
+ addReference(mdFile.path, {
1252
+ exportName: memberChange.className,
1253
+ memberName: call.methodName,
1254
+ memberChangeType: memberChange.changeType,
1255
+ changeType: mapMemberChangeType(memberChange.changeType),
1256
+ replacementSuggestion: memberChange.suggestion,
1257
+ line: block.lineStart + call.line,
1258
+ context: call.context,
1259
+ isInstantiation: false
1260
+ });
1261
+ }
1262
+ }
1263
+ }
1264
+ for (const className of classesWithMemberChanges) {
1265
+ if (hasInstantiation(block.code, className)) {
1266
+ const refKey = `${mdFile.path}:${block.lineStart}:new ${className}`;
1267
+ if (!reportedRefs.has(refKey)) {
1268
+ const hasMethodRefs = Array.from(reportedRefs).some((key) => key.startsWith(`${mdFile.path}:`) && memberChanges.some((mc) => mc.className === className && key.includes(mc.memberName)));
1269
+ if (!hasMethodRefs) {
1270
+ reportedRefs.add(refKey);
1271
+ addReference(mdFile.path, {
1272
+ exportName: className,
1273
+ line: block.lineStart,
1274
+ changeType: "signature-changed",
1275
+ context: `new ${className}(...)`,
1276
+ isInstantiation: true
1277
+ });
1278
+ }
1279
+ }
1280
+ }
1281
+ }
1282
+ }
1283
+ const exportsWithoutMemberChanges = changedExports.filter((name) => !classesWithMemberChanges.has(name));
1284
+ if (exportsWithoutMemberChanges.length > 0) {
1285
+ const refs = findExportReferences([{ ...mdFile, codeBlocks: [block] }], exportsWithoutMemberChanges);
1286
+ for (const ref of refs) {
1287
+ const changeType = getChangeType(ref.exportName, diff);
1288
+ if (!changeType)
1289
+ continue;
1290
+ const refKey = `${ref.file}:${ref.line}:${ref.exportName}`;
1291
+ if (!reportedRefs.has(refKey)) {
1292
+ reportedRefs.add(refKey);
1293
+ addReference(ref.file, {
1294
+ exportName: ref.exportName,
1295
+ line: ref.line,
1296
+ changeType,
1297
+ context: ref.context
1298
+ });
1299
+ }
1300
+ }
1301
+ }
1302
+ }
1198
1303
  }
1199
1304
  const documentedExports = new Set;
1200
1305
  for (const file of markdownFiles) {
@@ -1208,10 +1313,11 @@ function analyzeDocsImpact(diff, markdownFiles, newExportNames = []) {
1208
1313
  }
1209
1314
  const missingDocs = diff.nonBreaking.filter((name) => !documentedExports.has(name));
1210
1315
  const totalCodeBlocks = markdownFiles.reduce((sum, f) => sum + f.codeBlocks.length, 0);
1211
- const allReferences = findExportReferences(markdownFiles, [
1212
- ...changedExports,
1213
- ...diff.nonBreaking
1214
- ]);
1316
+ const allReferences = findExportReferences(markdownFiles, [...changedExports, ...diff.nonBreaking]);
1317
+ let impactedReferences = 0;
1318
+ for (const impact of impactByFile.values()) {
1319
+ impactedReferences += impact.references.length;
1320
+ }
1215
1321
  return {
1216
1322
  impactedFiles: Array.from(impactByFile.values()),
1217
1323
  missingDocs,
@@ -1219,7 +1325,7 @@ function analyzeDocsImpact(diff, markdownFiles, newExportNames = []) {
1219
1325
  filesScanned: markdownFiles.length,
1220
1326
  codeBlocksFound: totalCodeBlocks,
1221
1327
  referencesFound: allReferences.length,
1222
- impactedReferences: references.length
1328
+ impactedReferences
1223
1329
  }
1224
1330
  };
1225
1331
  }
@@ -1246,18 +1352,213 @@ function getUndocumentedExports(markdownFiles, exportNames) {
1246
1352
  const documented = new Set(getDocumentedExports(markdownFiles, exportNames));
1247
1353
  return exportNames.filter((name) => !documented.has(name));
1248
1354
  }
1355
+ // src/markdown/member-diff.ts
1356
+ function diffMemberChanges(oldSpec, newSpec, changedClassNames) {
1357
+ const changes = [];
1358
+ const oldExportMap = toExportMap(oldSpec.exports ?? []);
1359
+ const newExportMap = toExportMap(newSpec.exports ?? []);
1360
+ for (const className of changedClassNames) {
1361
+ const oldExport = oldExportMap.get(className);
1362
+ const newExport = newExportMap.get(className);
1363
+ if (!oldExport?.members && !newExport?.members) {
1364
+ continue;
1365
+ }
1366
+ const oldMembers = toMemberMap(oldExport?.members ?? []);
1367
+ const newMembers = toMemberMap(newExport?.members ?? []);
1368
+ const addedMemberNames = [];
1369
+ for (const [memberName, newMember] of newMembers) {
1370
+ if (!oldMembers.has(memberName)) {
1371
+ addedMemberNames.push(memberName);
1372
+ changes.push({
1373
+ className,
1374
+ memberName,
1375
+ memberKind: getMemberKind(newMember),
1376
+ changeType: "added",
1377
+ newSignature: formatSignature(newMember)
1378
+ });
1379
+ }
1380
+ }
1381
+ for (const [memberName, oldMember] of oldMembers) {
1382
+ if (!newMembers.has(memberName)) {
1383
+ const suggestion = findSimilarMember(memberName, newMembers, addedMemberNames);
1384
+ changes.push({
1385
+ className,
1386
+ memberName,
1387
+ memberKind: getMemberKind(oldMember),
1388
+ changeType: "removed",
1389
+ oldSignature: formatSignature(oldMember),
1390
+ suggestion
1391
+ });
1392
+ }
1393
+ }
1394
+ for (const [memberName, oldMember] of oldMembers) {
1395
+ const newMember = newMembers.get(memberName);
1396
+ if (newMember && hasSignatureChanged(oldMember, newMember)) {
1397
+ changes.push({
1398
+ className,
1399
+ memberName,
1400
+ memberKind: getMemberKind(newMember),
1401
+ changeType: "signature-changed",
1402
+ oldSignature: formatSignature(oldMember),
1403
+ newSignature: formatSignature(newMember)
1404
+ });
1405
+ }
1406
+ }
1407
+ }
1408
+ return changes;
1409
+ }
1410
+ function toExportMap(exports) {
1411
+ const map = new Map;
1412
+ for (const exp of exports) {
1413
+ if (exp?.name) {
1414
+ map.set(exp.name, exp);
1415
+ }
1416
+ }
1417
+ return map;
1418
+ }
1419
+ function toMemberMap(members) {
1420
+ const map = new Map;
1421
+ for (const member of members) {
1422
+ if (member?.name) {
1423
+ map.set(member.name, member);
1424
+ }
1425
+ }
1426
+ return map;
1427
+ }
1428
+ function getMemberKind(member) {
1429
+ switch (member.kind) {
1430
+ case "method":
1431
+ return "method";
1432
+ case "property":
1433
+ return "property";
1434
+ case "accessor":
1435
+ return "accessor";
1436
+ case "constructor":
1437
+ return "constructor";
1438
+ default:
1439
+ return "method";
1440
+ }
1441
+ }
1442
+ function formatSignature(member) {
1443
+ if (!member.signatures?.length) {
1444
+ return member.name ?? "";
1445
+ }
1446
+ const sig = member.signatures[0];
1447
+ const params = sig.parameters?.map((p) => {
1448
+ const optional = p.required === false ? "?" : "";
1449
+ return `${p.name}${optional}`;
1450
+ }) ?? [];
1451
+ return `${member.name}(${params.join(", ")})`;
1452
+ }
1453
+ function hasSignatureChanged(oldMember, newMember) {
1454
+ const oldSigs = oldMember.signatures ?? [];
1455
+ const newSigs = newMember.signatures ?? [];
1456
+ if (oldSigs.length !== newSigs.length) {
1457
+ return true;
1458
+ }
1459
+ for (let i = 0;i < oldSigs.length; i++) {
1460
+ if (!signaturesEqual(oldSigs[i], newSigs[i])) {
1461
+ return true;
1462
+ }
1463
+ }
1464
+ return false;
1465
+ }
1466
+ function signaturesEqual(a, b) {
1467
+ const aParams = a.parameters ?? [];
1468
+ const bParams = b.parameters ?? [];
1469
+ if (aParams.length !== bParams.length) {
1470
+ return false;
1471
+ }
1472
+ for (let i = 0;i < aParams.length; i++) {
1473
+ const ap = aParams[i];
1474
+ const bp = bParams[i];
1475
+ if (ap.name !== bp.name)
1476
+ return false;
1477
+ if (ap.required !== bp.required)
1478
+ return false;
1479
+ if (JSON.stringify(ap.schema) !== JSON.stringify(bp.schema))
1480
+ return false;
1481
+ }
1482
+ if (JSON.stringify(a.returns) !== JSON.stringify(b.returns)) {
1483
+ return false;
1484
+ }
1485
+ return true;
1486
+ }
1487
+ function findSimilarMember(removedName, newMembers, addedMembers) {
1488
+ const candidates = addedMembers.length > 0 ? addedMembers : Array.from(newMembers.keys());
1489
+ let bestMatch;
1490
+ let bestScore = 0;
1491
+ for (const name of candidates) {
1492
+ if (name === removedName)
1493
+ continue;
1494
+ const removedWords = splitCamelCase(removedName);
1495
+ const newWords = splitCamelCase(name);
1496
+ let matchingWords = 0;
1497
+ let suffixMatch = false;
1498
+ if (removedWords.length > 0 && newWords.length > 0) {
1499
+ const removedSuffix = removedWords[removedWords.length - 1];
1500
+ const newSuffix = newWords[newWords.length - 1];
1501
+ if (removedSuffix === newSuffix) {
1502
+ suffixMatch = true;
1503
+ matchingWords += 2;
1504
+ }
1505
+ }
1506
+ for (const word of removedWords) {
1507
+ if (newWords.includes(word)) {
1508
+ matchingWords++;
1509
+ }
1510
+ }
1511
+ const wordScore = matchingWords / Math.max(removedWords.length, newWords.length);
1512
+ const editDistance = levenshteinDistance(removedName.toLowerCase(), name.toLowerCase());
1513
+ const maxLen = Math.max(removedName.length, name.length);
1514
+ const levenScore = 1 - editDistance / maxLen;
1515
+ const totalScore = suffixMatch ? wordScore * 1.5 + levenScore : wordScore + levenScore * 0.5;
1516
+ if (totalScore > bestScore && totalScore >= 0.5) {
1517
+ bestScore = totalScore;
1518
+ bestMatch = name;
1519
+ }
1520
+ }
1521
+ return bestMatch ? `Use ${bestMatch} instead` : undefined;
1522
+ }
1523
+ function levenshteinDistance(a, b) {
1524
+ const matrix = [];
1525
+ for (let i = 0;i <= b.length; i++) {
1526
+ matrix[i] = [i];
1527
+ }
1528
+ for (let j = 0;j <= a.length; j++) {
1529
+ matrix[0][j] = j;
1530
+ }
1531
+ for (let i = 1;i <= b.length; i++) {
1532
+ for (let j = 1;j <= a.length; j++) {
1533
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
1534
+ matrix[i][j] = matrix[i - 1][j - 1];
1535
+ } else {
1536
+ matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1);
1537
+ }
1538
+ }
1539
+ }
1540
+ return matrix[b.length][a.length];
1541
+ }
1542
+ function splitCamelCase(str) {
1543
+ return str.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase().split(" ");
1544
+ }
1249
1545
  // src/markdown/diff-with-docs.ts
1250
1546
  import { diffSpec } from "@openpkg-ts/spec";
1251
1547
  function diffSpecWithDocs(oldSpec, newSpec, options = {}) {
1252
1548
  const baseDiff = diffSpec(oldSpec, newSpec);
1549
+ const memberChanges = diffMemberChanges(oldSpec, newSpec, baseDiff.breaking);
1253
1550
  if (!options.markdownFiles?.length) {
1254
- return baseDiff;
1551
+ return {
1552
+ ...baseDiff,
1553
+ memberChanges: memberChanges.length > 0 ? memberChanges : undefined
1554
+ };
1255
1555
  }
1256
1556
  const newExportNames = newSpec.exports?.map((e) => e.name) ?? [];
1257
- const docsImpact = analyzeDocsImpact(baseDiff, options.markdownFiles, newExportNames);
1557
+ const docsImpact = analyzeDocsImpact(baseDiff, options.markdownFiles, newExportNames, memberChanges);
1258
1558
  return {
1259
1559
  ...baseDiff,
1260
- docsImpact
1560
+ docsImpact,
1561
+ memberChanges: memberChanges.length > 0 ? memberChanges : undefined
1261
1562
  };
1262
1563
  }
1263
1564
  function hasDocsImpact(diff) {
@@ -1271,7 +1572,8 @@ function getDocsImpactSummary(diff) {
1271
1572
  impactedFileCount: 0,
1272
1573
  impactedReferenceCount: 0,
1273
1574
  missingDocsCount: 0,
1274
- totalIssues: 0
1575
+ totalIssues: 0,
1576
+ memberChangesCount: diff.memberChanges?.length ?? 0
1275
1577
  };
1276
1578
  }
1277
1579
  const impactedFileCount = diff.docsImpact.impactedFiles.length;
@@ -1281,7 +1583,8 @@ function getDocsImpactSummary(diff) {
1281
1583
  impactedFileCount,
1282
1584
  impactedReferenceCount,
1283
1585
  missingDocsCount,
1284
- totalIssues: impactedReferenceCount + missingDocsCount
1586
+ totalIssues: impactedReferenceCount + missingDocsCount,
1587
+ memberChangesCount: diff.memberChanges?.length ?? 0
1285
1588
  };
1286
1589
  }
1287
1590
  // src/analysis/run-analysis.ts
@@ -4122,7 +4425,7 @@ function generateAssertionFix(drift, exportEntry) {
4122
4425
  const oldValue = oldValueMatch?.[1];
4123
4426
  if (!oldValue)
4124
4427
  return null;
4125
- const updatedExample = oldExample.replace(new RegExp(`//\\s*=>\\s*${escapeRegex(oldValue)}`, "g"), `// => ${newValue}`);
4428
+ const updatedExample = oldExample.replace(new RegExp(`//\\s*=>\\s*${escapeRegex2(oldValue)}`, "g"), `// => ${newValue}`);
4126
4429
  const updatedExamples = [...examples];
4127
4430
  updatedExamples[exampleIndex] = updatedExample;
4128
4431
  return {
@@ -4240,7 +4543,7 @@ function stringifySchema(schema) {
4240
4543
  }
4241
4544
  return "unknown";
4242
4545
  }
4243
- function escapeRegex(str) {
4546
+ function escapeRegex2(str) {
4244
4547
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4245
4548
  }
4246
4549
  function categorizeDrifts(drifts) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doccov/sdk",
3
- "version": "0.3.7",
3
+ "version": "0.5.6",
4
4
  "description": "DocCov SDK - Documentation coverage and drift detection for TypeScript",
5
5
  "keywords": [
6
6
  "typescript",