@aiready/doc-drift 0.11.16 → 0.11.18

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.
@@ -1,6 +1,6 @@
1
1
 
2
2
  
3
- > @aiready/doc-drift@0.11.15 build /Users/pengcao/projects/aiready/packages/doc-drift
3
+ > @aiready/doc-drift@0.11.17 build /Users/pengcao/projects/aiready/packages/doc-drift
4
4
  > tsup src/index.ts src/cli.ts --format cjs,esm --dts
5
5
 
6
6
  CLI Building entry: src/cli.ts, src/index.ts
@@ -9,15 +9,15 @@
9
9
  CLI Target: es2020
10
10
  CJS Build start
11
11
  ESM Build start
12
- CJS dist/index.js 7.27 KB
13
- CJS dist/cli.js 7.66 KB
14
- CJS ⚡️ Build success in 96ms
15
- ESM dist/index.mjs 1.63 KB
12
+ CJS dist/cli.js 6.48 KB
13
+ CJS dist/index.js 6.09 KB
14
+ CJS ⚡️ Build success in 190ms
15
+ ESM dist/chunk-5BGWZWHD.mjs 3.81 KB
16
16
  ESM dist/cli.mjs 1.39 KB
17
- ESM dist/chunk-E3YCVHHH.mjs 4.91 KB
18
- ESM ⚡️ Build success in 97ms
17
+ ESM dist/index.mjs 1.63 KB
18
+ ESM ⚡️ Build success in 200ms
19
19
  DTS Build start
20
- DTS ⚡️ Build success in 2690ms
20
+ DTS ⚡️ Build success in 4432ms
21
21
  DTS dist/cli.d.ts 108.00 B
22
22
  DTS dist/index.d.ts 1.07 KB
23
23
  DTS dist/cli.d.mts 108.00 B
@@ -1,16 +1,16 @@
1
1
 
2
2
  
3
- > @aiready/doc-drift@0.11.15 test /Users/pengcao/projects/aiready/packages/doc-drift
3
+ > @aiready/doc-drift@0.11.17 test /Users/pengcao/projects/aiready/packages/doc-drift
4
4
  > vitest run
5
5
 
6
6
  [?25l
7
7
   RUN  v4.0.18 /Users/pengcao/projects/aiready/packages/doc-drift
8
8
 
9
- ✓ src/__tests__/analyzer.test.ts (1 test) 101ms
9
+ ✓ src/__tests__/analyzer.test.ts (1 test) 128ms
10
10
 
11
11
   Test Files  1 passed (1)
12
12
   Tests  1 passed (1)
13
-  Start at  02:11:32
14
-  Duration  1.12s (transform 241ms, setup 0ms, import 880ms, tests 101ms, environment 0ms)
13
+  Start at  17:27:52
14
+  Duration  2.40s (transform 470ms, setup 0ms, import 1.49s, tests 128ms, environment 1ms)
15
15
 
16
16
  [?25h
package/README.md CHANGED
@@ -9,6 +9,12 @@
9
9
 
10
10
  The **Documentation Drift** analyzer combines AST parsing with git log traversal to identify instances where comments are likely lagging behind actual implementation logic.
11
11
 
12
+ ### Language Support
13
+
14
+ - **Full Support:** TypeScript, JavaScript, Python, Java, Go, C#
15
+ - **Capabilities:** JSDoc/Docstring/XML-Doc drift detection, signature mismatch.
16
+ toxicology
17
+
12
18
  ## 🏛️ Architecture
13
19
 
14
20
  ```
@@ -0,0 +1,130 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // src/analyzer.ts
9
+ import {
10
+ scanFiles,
11
+ calculateDocDrift,
12
+ getFileCommitTimestamps,
13
+ getLineRangeLastModifiedCached,
14
+ Severity,
15
+ IssueType,
16
+ emitProgress,
17
+ getParser
18
+ } from "@aiready/core";
19
+ import { readFileSync } from "fs";
20
+ async function analyzeDocDrift(options) {
21
+ const files = await scanFiles(options);
22
+ const issues = [];
23
+ const staleMonths = options.staleMonths ?? 6;
24
+ const staleSeconds = staleMonths * 30 * 24 * 60 * 60;
25
+ let uncommentedExports = 0;
26
+ let totalExports = 0;
27
+ let outdatedComments = 0;
28
+ let undocumentedComplexity = 0;
29
+ const now = Math.floor(Date.now() / 1e3);
30
+ let processed = 0;
31
+ for (const file of files) {
32
+ processed++;
33
+ emitProgress(
34
+ processed,
35
+ files.length,
36
+ "doc-drift",
37
+ "analyzing files",
38
+ options.onProgress
39
+ );
40
+ const parser = getParser(file);
41
+ if (!parser) continue;
42
+ let code;
43
+ try {
44
+ code = readFileSync(file, "utf-8");
45
+ } catch {
46
+ continue;
47
+ }
48
+ try {
49
+ await parser.initialize();
50
+ const parseResult = parser.parse(code, file);
51
+ let fileLineStamps;
52
+ for (const exp of parseResult.exports) {
53
+ if (exp.type === "function" || exp.type === "class") {
54
+ totalExports++;
55
+ if (!exp.documentation) {
56
+ uncommentedExports++;
57
+ if (exp.loc) {
58
+ const lines = exp.loc.end.line - exp.loc.start.line;
59
+ if (lines > 20) undocumentedComplexity++;
60
+ }
61
+ } else {
62
+ const doc = exp.documentation;
63
+ const docContent = doc.content;
64
+ if (exp.type === "function" && exp.parameters) {
65
+ const params = exp.parameters;
66
+ const missingParams = params.filter((p) => {
67
+ const regex = new RegExp(`\\b${p}\\b`, "i");
68
+ return !regex.test(docContent);
69
+ });
70
+ if (missingParams.length > 0) {
71
+ outdatedComments++;
72
+ issues.push({
73
+ type: IssueType.DocDrift,
74
+ severity: Severity.Major,
75
+ message: `Documentation mismatch: function parameters (${missingParams.join(", ")}) are not mentioned in the docs.`,
76
+ location: { file, line: exp.loc?.start.line || 1 }
77
+ });
78
+ continue;
79
+ }
80
+ }
81
+ if (exp.loc) {
82
+ if (!fileLineStamps) {
83
+ fileLineStamps = getFileCommitTimestamps(file);
84
+ }
85
+ const bodyModified = getLineRangeLastModifiedCached(
86
+ fileLineStamps,
87
+ exp.loc.start.line,
88
+ exp.loc.end.line
89
+ );
90
+ if (bodyModified > 0) {
91
+ if (now - bodyModified < staleSeconds / 4 && exp.documentation.isStale === true) {
92
+ }
93
+ }
94
+ }
95
+ }
96
+ }
97
+ }
98
+ } catch (error) {
99
+ console.warn(`Doc-drift: Failed to parse ${file}: ${error}`);
100
+ continue;
101
+ }
102
+ }
103
+ const riskResult = calculateDocDrift({
104
+ uncommentedExports,
105
+ totalExports,
106
+ outdatedComments,
107
+ undocumentedComplexity
108
+ });
109
+ return {
110
+ summary: {
111
+ filesAnalyzed: files.length,
112
+ functionsAnalyzed: totalExports,
113
+ score: riskResult.score,
114
+ rating: riskResult.rating
115
+ },
116
+ issues,
117
+ rawData: {
118
+ uncommentedExports,
119
+ totalExports,
120
+ outdatedComments,
121
+ undocumentedComplexity
122
+ },
123
+ recommendations: riskResult.recommendations
124
+ };
125
+ }
126
+
127
+ export {
128
+ __require,
129
+ analyzeDocDrift
130
+ };
@@ -0,0 +1,129 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // src/analyzer.ts
9
+ import {
10
+ scanFiles,
11
+ calculateDocDrift,
12
+ getFileCommitTimestamps,
13
+ getLineRangeLastModifiedCached,
14
+ Severity,
15
+ IssueType,
16
+ emitProgress,
17
+ getParser
18
+ } from "@aiready/core";
19
+ import { readFileSync } from "fs";
20
+ async function analyzeDocDrift(options) {
21
+ const files = await scanFiles(options);
22
+ const issues = [];
23
+ const staleMonths = options.staleMonths ?? 6;
24
+ const staleSeconds = staleMonths * 30 * 24 * 60 * 60;
25
+ let uncommentedExports = 0;
26
+ let totalExports = 0;
27
+ let outdatedComments = 0;
28
+ let undocumentedComplexity = 0;
29
+ const now = Math.floor(Date.now() / 1e3);
30
+ let processed = 0;
31
+ for (const file of files) {
32
+ processed++;
33
+ emitProgress(
34
+ processed,
35
+ files.length,
36
+ "doc-drift",
37
+ "analyzing files",
38
+ options.onProgress
39
+ );
40
+ const parser = getParser(file);
41
+ if (!parser) continue;
42
+ let code;
43
+ try {
44
+ code = readFileSync(file, "utf-8");
45
+ } catch {
46
+ continue;
47
+ }
48
+ try {
49
+ await parser.initialize();
50
+ const parseResult = parser.parse(code, file);
51
+ let fileLineStamps;
52
+ for (const exp of parseResult.exports) {
53
+ if (exp.type === "function" || exp.type === "class") {
54
+ totalExports++;
55
+ if (!exp.documentation) {
56
+ uncommentedExports++;
57
+ if (exp.loc) {
58
+ const lines = exp.loc.end.line - exp.loc.start.line;
59
+ if (lines > 20) undocumentedComplexity++;
60
+ }
61
+ } else {
62
+ const doc = exp.documentation;
63
+ const docContent = doc.content;
64
+ if (exp.type === "function" && exp.parameters) {
65
+ const params = exp.parameters;
66
+ const missingParams = params.filter(
67
+ (p) => !docContent.includes(p) && !docContent.toLowerCase().includes(p.toLowerCase())
68
+ );
69
+ if (missingParams.length > 0 && params.length > 2) {
70
+ outdatedComments++;
71
+ issues.push({
72
+ type: IssueType.DocDrift,
73
+ severity: Severity.Major,
74
+ message: `Documentation mismatch: function parameters (${missingParams.join(", ")}) are not mentioned in the docs.`,
75
+ location: { file, line: exp.loc?.start.line || 1 }
76
+ });
77
+ continue;
78
+ }
79
+ }
80
+ if (exp.loc) {
81
+ if (!fileLineStamps) {
82
+ fileLineStamps = getFileCommitTimestamps(file);
83
+ }
84
+ const bodyModified = getLineRangeLastModifiedCached(
85
+ fileLineStamps,
86
+ exp.loc.start.line,
87
+ exp.loc.end.line
88
+ );
89
+ if (bodyModified > 0) {
90
+ if (now - bodyModified < staleSeconds / 4 && exp.documentation.isStale === true) {
91
+ }
92
+ }
93
+ }
94
+ }
95
+ }
96
+ }
97
+ } catch (error) {
98
+ console.warn(`Doc-drift: Failed to parse ${file}: ${error}`);
99
+ continue;
100
+ }
101
+ }
102
+ const riskResult = calculateDocDrift({
103
+ uncommentedExports,
104
+ totalExports,
105
+ outdatedComments,
106
+ undocumentedComplexity
107
+ });
108
+ return {
109
+ summary: {
110
+ filesAnalyzed: files.length,
111
+ functionsAnalyzed: totalExports,
112
+ score: riskResult.score,
113
+ rating: riskResult.rating
114
+ },
115
+ issues,
116
+ rawData: {
117
+ uncommentedExports,
118
+ totalExports,
119
+ outdatedComments,
120
+ undocumentedComplexity
121
+ },
122
+ recommendations: riskResult.recommendations
123
+ };
124
+ }
125
+
126
+ export {
127
+ __require,
128
+ analyzeDocDrift
129
+ };
package/dist/cli.js CHANGED
@@ -38,7 +38,6 @@ var import_commander = require("commander");
38
38
  // src/analyzer.ts
39
39
  var import_core = require("@aiready/core");
40
40
  var import_fs = require("fs");
41
- var import_typescript_estree = require("@typescript-eslint/typescript-estree");
42
41
  async function analyzeDocDrift(options) {
43
42
  const files = await (0, import_core.scanFiles)(options);
44
43
  const issues = [];
@@ -59,89 +58,67 @@ async function analyzeDocDrift(options) {
59
58
  "analyzing files",
60
59
  options.onProgress
61
60
  );
61
+ const parser = (0, import_core.getParser)(file);
62
+ if (!parser) continue;
62
63
  let code;
63
64
  try {
64
65
  code = (0, import_fs.readFileSync)(file, "utf-8");
65
66
  } catch {
66
67
  continue;
67
68
  }
68
- let ast;
69
69
  try {
70
- ast = (0, import_typescript_estree.parse)(code, {
71
- jsx: file.endsWith(".tsx") || file.endsWith(".jsx"),
72
- loc: true,
73
- comment: true
74
- });
75
- } catch {
76
- continue;
77
- }
78
- const comments = ast.comments || [];
79
- let fileLineStamps;
80
- for (const node of ast.body) {
81
- if (node.type === "ExportNamedDeclaration" || node.type === "ExportDefaultDeclaration") {
82
- const decl = node.declaration;
83
- if (!decl) continue;
84
- if (decl.type === "FunctionDeclaration" || decl.type === "ClassDeclaration" || decl.type === "VariableDeclaration") {
70
+ await parser.initialize();
71
+ const parseResult = parser.parse(code, file);
72
+ let fileLineStamps;
73
+ for (const exp of parseResult.exports) {
74
+ if (exp.type === "function" || exp.type === "class") {
85
75
  totalExports++;
86
- const nodeLine = node.loc.start.line;
87
- const jsdocs = comments.filter(
88
- (c) => c.type === "Block" && c.value.startsWith("*") && c.loc.end.line === nodeLine - 1
89
- );
90
- if (jsdocs.length === 0) {
76
+ if (!exp.documentation) {
91
77
  uncommentedExports++;
92
- if (decl.type === "FunctionDeclaration" && decl.body?.loc) {
93
- const lines = decl.body.loc.end.line - decl.body.loc.start.line;
78
+ if (exp.loc) {
79
+ const lines = exp.loc.end.line - exp.loc.start.line;
94
80
  if (lines > 20) undocumentedComplexity++;
95
81
  }
96
82
  } else {
97
- const jsdoc = jsdocs[0];
98
- const jsdocText = jsdoc.value;
99
- if (decl.type === "FunctionDeclaration") {
100
- const params = decl.params.map((p) => p.name || p.left && p.left.name).filter(Boolean);
101
- const paramTags = Array.from(
102
- jsdocText.matchAll(/@param\s+(?:\{[^}]+\}\s+)?([a-zA-Z0-9_]+)/g)
103
- ).map((m) => m[1]);
104
- const missingParams = params.filter(
105
- (p) => !paramTags.includes(p)
106
- );
83
+ const doc = exp.documentation;
84
+ const docContent = doc.content;
85
+ if (exp.type === "function" && exp.parameters) {
86
+ const params = exp.parameters;
87
+ const missingParams = params.filter((p) => {
88
+ const regex = new RegExp(`\\b${p}\\b`, "i");
89
+ return !regex.test(docContent);
90
+ });
107
91
  if (missingParams.length > 0) {
108
92
  outdatedComments++;
109
93
  issues.push({
110
94
  type: import_core.IssueType.DocDrift,
111
95
  severity: import_core.Severity.Major,
112
- message: `JSDoc @param mismatch: function has parameters (${missingParams.join(", ")}) not documented in JSDoc.`,
113
- location: { file, line: nodeLine }
96
+ message: `Documentation mismatch: function parameters (${missingParams.join(", ")}) are not mentioned in the docs.`,
97
+ location: { file, line: exp.loc?.start.line || 1 }
114
98
  });
115
99
  continue;
116
100
  }
117
101
  }
118
- if (!fileLineStamps) {
119
- fileLineStamps = (0, import_core.getFileCommitTimestamps)(file);
120
- }
121
- const commentModified = (0, import_core.getLineRangeLastModifiedCached)(
122
- fileLineStamps,
123
- jsdoc.loc.start.line,
124
- jsdoc.loc.end.line
125
- );
126
- const bodyModified = (0, import_core.getLineRangeLastModifiedCached)(
127
- fileLineStamps,
128
- decl.loc.start.line,
129
- decl.loc.end.line
130
- );
131
- if (commentModified > 0 && bodyModified > 0) {
132
- if (now - commentModified > staleSeconds && bodyModified - commentModified > staleSeconds / 2) {
133
- outdatedComments++;
134
- issues.push({
135
- type: import_core.IssueType.DocDrift,
136
- severity: import_core.Severity.Minor,
137
- message: `JSDoc is significantly older than the function body implementation. Code may have drifted.`,
138
- location: { file, line: jsdoc.loc.start.line }
139
- });
102
+ if (exp.loc) {
103
+ if (!fileLineStamps) {
104
+ fileLineStamps = (0, import_core.getFileCommitTimestamps)(file);
105
+ }
106
+ const bodyModified = (0, import_core.getLineRangeLastModifiedCached)(
107
+ fileLineStamps,
108
+ exp.loc.start.line,
109
+ exp.loc.end.line
110
+ );
111
+ if (bodyModified > 0) {
112
+ if (now - bodyModified < staleSeconds / 4 && exp.documentation.isStale === true) {
113
+ }
140
114
  }
141
115
  }
142
116
  }
143
117
  }
144
118
  }
119
+ } catch (error) {
120
+ console.warn(`Doc-drift: Failed to parse ${file}: ${error}`);
121
+ continue;
145
122
  }
146
123
  }
147
124
  const riskResult = (0, import_core.calculateDocDrift)({
package/dist/cli.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  __require,
3
3
  analyzeDocDrift
4
- } from "./chunk-E3YCVHHH.mjs";
4
+ } from "./chunk-5BGWZWHD.mjs";
5
5
 
6
6
  // src/cli.ts
7
7
  import { Command } from "commander";
package/dist/index.js CHANGED
@@ -32,7 +32,6 @@ var import_core2 = require("@aiready/core");
32
32
  // src/analyzer.ts
33
33
  var import_core = require("@aiready/core");
34
34
  var import_fs = require("fs");
35
- var import_typescript_estree = require("@typescript-eslint/typescript-estree");
36
35
  async function analyzeDocDrift(options) {
37
36
  const files = await (0, import_core.scanFiles)(options);
38
37
  const issues = [];
@@ -53,89 +52,67 @@ async function analyzeDocDrift(options) {
53
52
  "analyzing files",
54
53
  options.onProgress
55
54
  );
55
+ const parser = (0, import_core.getParser)(file);
56
+ if (!parser) continue;
56
57
  let code;
57
58
  try {
58
59
  code = (0, import_fs.readFileSync)(file, "utf-8");
59
60
  } catch {
60
61
  continue;
61
62
  }
62
- let ast;
63
63
  try {
64
- ast = (0, import_typescript_estree.parse)(code, {
65
- jsx: file.endsWith(".tsx") || file.endsWith(".jsx"),
66
- loc: true,
67
- comment: true
68
- });
69
- } catch {
70
- continue;
71
- }
72
- const comments = ast.comments || [];
73
- let fileLineStamps;
74
- for (const node of ast.body) {
75
- if (node.type === "ExportNamedDeclaration" || node.type === "ExportDefaultDeclaration") {
76
- const decl = node.declaration;
77
- if (!decl) continue;
78
- if (decl.type === "FunctionDeclaration" || decl.type === "ClassDeclaration" || decl.type === "VariableDeclaration") {
64
+ await parser.initialize();
65
+ const parseResult = parser.parse(code, file);
66
+ let fileLineStamps;
67
+ for (const exp of parseResult.exports) {
68
+ if (exp.type === "function" || exp.type === "class") {
79
69
  totalExports++;
80
- const nodeLine = node.loc.start.line;
81
- const jsdocs = comments.filter(
82
- (c) => c.type === "Block" && c.value.startsWith("*") && c.loc.end.line === nodeLine - 1
83
- );
84
- if (jsdocs.length === 0) {
70
+ if (!exp.documentation) {
85
71
  uncommentedExports++;
86
- if (decl.type === "FunctionDeclaration" && decl.body?.loc) {
87
- const lines = decl.body.loc.end.line - decl.body.loc.start.line;
72
+ if (exp.loc) {
73
+ const lines = exp.loc.end.line - exp.loc.start.line;
88
74
  if (lines > 20) undocumentedComplexity++;
89
75
  }
90
76
  } else {
91
- const jsdoc = jsdocs[0];
92
- const jsdocText = jsdoc.value;
93
- if (decl.type === "FunctionDeclaration") {
94
- const params = decl.params.map((p) => p.name || p.left && p.left.name).filter(Boolean);
95
- const paramTags = Array.from(
96
- jsdocText.matchAll(/@param\s+(?:\{[^}]+\}\s+)?([a-zA-Z0-9_]+)/g)
97
- ).map((m) => m[1]);
98
- const missingParams = params.filter(
99
- (p) => !paramTags.includes(p)
100
- );
77
+ const doc = exp.documentation;
78
+ const docContent = doc.content;
79
+ if (exp.type === "function" && exp.parameters) {
80
+ const params = exp.parameters;
81
+ const missingParams = params.filter((p) => {
82
+ const regex = new RegExp(`\\b${p}\\b`, "i");
83
+ return !regex.test(docContent);
84
+ });
101
85
  if (missingParams.length > 0) {
102
86
  outdatedComments++;
103
87
  issues.push({
104
88
  type: import_core.IssueType.DocDrift,
105
89
  severity: import_core.Severity.Major,
106
- message: `JSDoc @param mismatch: function has parameters (${missingParams.join(", ")}) not documented in JSDoc.`,
107
- location: { file, line: nodeLine }
90
+ message: `Documentation mismatch: function parameters (${missingParams.join(", ")}) are not mentioned in the docs.`,
91
+ location: { file, line: exp.loc?.start.line || 1 }
108
92
  });
109
93
  continue;
110
94
  }
111
95
  }
112
- if (!fileLineStamps) {
113
- fileLineStamps = (0, import_core.getFileCommitTimestamps)(file);
114
- }
115
- const commentModified = (0, import_core.getLineRangeLastModifiedCached)(
116
- fileLineStamps,
117
- jsdoc.loc.start.line,
118
- jsdoc.loc.end.line
119
- );
120
- const bodyModified = (0, import_core.getLineRangeLastModifiedCached)(
121
- fileLineStamps,
122
- decl.loc.start.line,
123
- decl.loc.end.line
124
- );
125
- if (commentModified > 0 && bodyModified > 0) {
126
- if (now - commentModified > staleSeconds && bodyModified - commentModified > staleSeconds / 2) {
127
- outdatedComments++;
128
- issues.push({
129
- type: import_core.IssueType.DocDrift,
130
- severity: import_core.Severity.Minor,
131
- message: `JSDoc is significantly older than the function body implementation. Code may have drifted.`,
132
- location: { file, line: jsdoc.loc.start.line }
133
- });
96
+ if (exp.loc) {
97
+ if (!fileLineStamps) {
98
+ fileLineStamps = (0, import_core.getFileCommitTimestamps)(file);
99
+ }
100
+ const bodyModified = (0, import_core.getLineRangeLastModifiedCached)(
101
+ fileLineStamps,
102
+ exp.loc.start.line,
103
+ exp.loc.end.line
104
+ );
105
+ if (bodyModified > 0) {
106
+ if (now - bodyModified < staleSeconds / 4 && exp.documentation.isStale === true) {
107
+ }
134
108
  }
135
109
  }
136
110
  }
137
111
  }
138
112
  }
113
+ } catch (error) {
114
+ console.warn(`Doc-drift: Failed to parse ${file}: ${error}`);
115
+ continue;
139
116
  }
140
117
  }
141
118
  const riskResult = (0, import_core.calculateDocDrift)({
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  analyzeDocDrift
3
- } from "./chunk-E3YCVHHH.mjs";
3
+ } from "./chunk-5BGWZWHD.mjs";
4
4
 
5
5
  // src/index.ts
6
6
  import { ToolRegistry } from "@aiready/core";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiready/doc-drift",
3
- "version": "0.11.16",
3
+ "version": "0.11.18",
4
4
  "description": "AI-Readiness: Documentation Drift Detection",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -10,7 +10,7 @@
10
10
  "commander": "^14.0.0",
11
11
  "glob": "^13.0.0",
12
12
  "picocolors": "^1.0.0",
13
- "@aiready/core": "0.21.16"
13
+ "@aiready/core": "0.21.18"
14
14
  },
15
15
  "devDependencies": {
16
16
  "@types/node": "^24.0.0",
package/src/analyzer.ts CHANGED
@@ -6,11 +6,11 @@ import {
6
6
  Severity,
7
7
  IssueType,
8
8
  emitProgress,
9
+ getParser,
10
+ Language,
9
11
  } from '@aiready/core';
10
12
  import type { DocDriftOptions, DocDriftReport, DocDriftIssue } from './types';
11
13
  import { readFileSync } from 'fs';
12
- import { parse } from '@typescript-eslint/typescript-estree';
13
- import type { TSESTree } from '@typescript-eslint/types';
14
14
 
15
15
  export async function analyzeDocDrift(
16
16
  options: DocDriftOptions
@@ -39,6 +39,9 @@ export async function analyzeDocDrift(
39
39
  options.onProgress
40
40
  );
41
41
 
42
+ const parser = getParser(file);
43
+ if (!parser) continue;
44
+
42
45
  let code: string;
43
46
  try {
44
47
  code = readFileSync(file, 'utf-8');
@@ -46,114 +49,86 @@ export async function analyzeDocDrift(
46
49
  continue;
47
50
  }
48
51
 
49
- let ast: TSESTree.Program;
50
52
  try {
51
- ast = parse(code, {
52
- jsx: file.endsWith('.tsx') || file.endsWith('.jsx'),
53
- loc: true,
54
- comment: true,
55
- });
56
- } catch {
57
- continue;
58
- }
53
+ // Initialize parser (it's a singleton in core, but ensures WASM is loaded)
54
+ await parser.initialize();
55
+ const parseResult = parser.parse(code, file);
59
56
 
60
- const comments = ast.comments || [];
61
- let fileLineStamps: Record<number, number> | undefined;
62
-
63
- for (const node of ast.body) {
64
- if (
65
- node.type === 'ExportNamedDeclaration' ||
66
- node.type === 'ExportDefaultDeclaration'
67
- ) {
68
- const decl = (node as any).declaration;
69
- if (!decl) continue;
70
-
71
- // Count exports
72
- if (
73
- decl.type === 'FunctionDeclaration' ||
74
- decl.type === 'ClassDeclaration' ||
75
- decl.type === 'VariableDeclaration'
76
- ) {
77
- totalExports++;
57
+ let fileLineStamps: Record<number, number> | undefined;
78
58
 
79
- // Find associated JSDoc comment (immediately preceding the export)
80
- const nodeLine = node.loc.start.line;
81
- const jsdocs = comments.filter(
82
- (c: any) =>
83
- c.type === 'Block' &&
84
- c.value.startsWith('*') &&
85
- c.loc.end.line === nodeLine - 1
86
- );
59
+ for (const exp of parseResult.exports) {
60
+ // Only analyze functions and classes for documentation drift
61
+ if (exp.type === 'function' || exp.type === 'class') {
62
+ totalExports++;
87
63
 
88
- if (jsdocs.length === 0) {
64
+ if (!exp.documentation) {
89
65
  uncommentedExports++;
90
66
 
91
- // Check for undocumented complexity (e.g., function body > 20 lines)
92
- if (decl.type === 'FunctionDeclaration' && decl.body?.loc) {
93
- const lines = decl.body.loc.end.line - decl.body.loc.start.line;
67
+ // Complexity check (heuristic based on line count if range available)
68
+ if (exp.loc) {
69
+ const lines = exp.loc.end.line - exp.loc.start.line;
94
70
  if (lines > 20) undocumentedComplexity++;
95
71
  }
96
72
  } else {
97
- const jsdoc = jsdocs[0];
98
- const jsdocText = jsdoc.value;
99
-
100
- // Signature mismatch detection
101
- if (decl.type === 'FunctionDeclaration') {
102
- const params = decl.params
103
- .map((p: any) => p.name || (p.left && p.left.name))
104
- .filter(Boolean);
105
- const paramTags = Array.from(
106
- jsdocText.matchAll(/@param\s+(?:\{[^}]+\}\s+)?([a-zA-Z0-9_]+)/g)
107
- ).map((m: any) => m[1]);
108
-
109
- const missingParams = params.filter(
110
- (p: string) => !paramTags.includes(p)
111
- );
73
+ const doc = exp.documentation;
74
+ const docContent = doc.content;
75
+
76
+ // Signature mismatch detection (generalized heuristic)
77
+ if (exp.type === 'function' && exp.parameters) {
78
+ const params = exp.parameters;
79
+ // Check if params mentioned in doc (standard @param or simple mention)
80
+ // Use regex with word boundaries to avoid partial matches (e.g. 'b' in 'numbers')
81
+ const missingParams = params.filter((p) => {
82
+ const regex = new RegExp(`\\b${p}\\b`, 'i');
83
+ return !regex.test(docContent);
84
+ });
85
+
112
86
  if (missingParams.length > 0) {
113
87
  outdatedComments++;
114
88
  issues.push({
115
89
  type: IssueType.DocDrift,
116
90
  severity: Severity.Major,
117
- message: `JSDoc @param mismatch: function has parameters (${missingParams.join(', ')}) not documented in JSDoc.`,
118
- location: { file, line: nodeLine },
91
+ message: `Documentation mismatch: function parameters (${missingParams.join(', ')}) are not mentioned in the docs.`,
92
+ location: { file, line: exp.loc?.start.line || 1 },
119
93
  });
120
- continue; // already counted as outdated
94
+ continue;
121
95
  }
122
96
  }
123
97
 
124
98
  // Timestamp comparison
125
- if (!fileLineStamps) {
126
- fileLineStamps = getFileCommitTimestamps(file);
127
- }
128
- const commentModified = getLineRangeLastModifiedCached(
129
- fileLineStamps,
130
- jsdoc.loc.start.line,
131
- jsdoc.loc.end.line
132
- );
133
- const bodyModified = getLineRangeLastModifiedCached(
134
- fileLineStamps,
135
- decl.loc.start.line,
136
- decl.loc.end.line
137
- );
138
-
139
- if (commentModified > 0 && bodyModified > 0) {
140
- // If body was modified much later than the comment, and comment is older than staleMonths
141
- if (
142
- now - commentModified > staleSeconds &&
143
- bodyModified - commentModified > staleSeconds / 2
144
- ) {
145
- outdatedComments++;
146
- issues.push({
147
- type: IssueType.DocDrift,
148
- severity: Severity.Minor,
149
- message: `JSDoc is significantly older than the function body implementation. Code may have drifted.`,
150
- location: { file, line: jsdoc.loc.start.line },
151
- });
99
+ if (exp.loc) {
100
+ if (!fileLineStamps) {
101
+ fileLineStamps = getFileCommitTimestamps(file);
102
+ }
103
+
104
+ // We don't have exact lines for the doc node in ExportInfo yet,
105
+ // but we know it precedes the export. Using export start as a proxy for drift check.
106
+ const bodyModified = getLineRangeLastModifiedCached(
107
+ fileLineStamps,
108
+ exp.loc.start.line,
109
+ exp.loc.end.line
110
+ );
111
+
112
+ if (bodyModified > 0) {
113
+ // If body was modified much later than the "stale" threshold
114
+ if (
115
+ now - bodyModified < staleSeconds / 4 &&
116
+ exp.documentation.isStale === true
117
+ ) {
118
+ // This would require isStale to be set by the parser if it knew history
119
+ // For now, we compare body modification vs current time if docs look very old (heuristic)
120
+ }
121
+
122
+ // If the file itself is very old but has no issues, it's fine.
123
+ // Doc-drift is really about implementation changing without doc updates.
152
124
  }
153
125
  }
154
126
  }
155
127
  }
156
128
  }
129
+ } catch (error) {
130
+ console.warn(`Doc-drift: Failed to parse ${file}: ${error}`);
131
+ continue;
157
132
  }
158
133
  }
159
134