@aiready/doc-drift 0.13.4 → 0.13.5
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/.turbo/turbo-build.log +10 -10
- package/dist/{chunk-5BGWZWHD.mjs → chunk-P74XAVQ3.mjs} +21 -6
- package/dist/cli.js +21 -6
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +12 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +39 -17
- package/dist/index.mjs +19 -12
- package/package.json +2 -2
- package/src/__tests__/analyzer.test.ts +1 -0
- package/src/__tests__/provider.test.ts +1 -0
- package/src/__tests__/scoring.test.ts +1 -0
- package/src/analyzer.ts +32 -17
- package/src/scoring.ts +24 -11
- package/src/types.ts +2 -0
- package/dist/chunk-5EFFNN6L.mjs +0 -145
- package/dist/chunk-BBGJNBVI.mjs +0 -189
- package/dist/chunk-CGSYYULO.mjs +0 -145
- package/dist/chunk-E3YCVHHH.mjs +0 -152
- package/dist/chunk-FMK4O4O7.mjs +0 -143
- package/dist/chunk-NWJ6PSNT.mjs +0 -129
- package/dist/chunk-TSLAGWBV.mjs +0 -165
- package/dist/chunk-VLBPAYS3.mjs +0 -209
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
> @aiready/doc-drift@0.13.
|
|
3
|
+
> @aiready/doc-drift@0.13.5 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
|
[34mCLI[39m Building entry: src/cli.ts, src/index.ts
|
|
@@ -9,16 +9,16 @@
|
|
|
9
9
|
[34mCLI[39m Target: es2020
|
|
10
10
|
[34mCJS[39m Build start
|
|
11
11
|
[34mESM[39m Build start
|
|
12
|
-
[
|
|
13
|
-
[
|
|
12
|
+
[32mCJS[39m [1mdist/cli.js [22m[32m7.25 KB[39m
|
|
13
|
+
[32mCJS[39m [1mdist/index.js [22m[32m7.85 KB[39m
|
|
14
|
+
[32mCJS[39m ⚡️ Build success in 122ms
|
|
15
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m2.59 KB[39m
|
|
14
16
|
[32mESM[39m [1mdist/cli.mjs [22m[32m1.39 KB[39m
|
|
15
|
-
[32mESM[39m
|
|
16
|
-
[
|
|
17
|
-
[32mCJS[39m [1mdist/cli.js [22m[32m6.48 KB[39m
|
|
18
|
-
[32mCJS[39m ⚡️ Build success in 200ms
|
|
17
|
+
[32mESM[39m [1mdist/chunk-P74XAVQ3.mjs [22m[32m4.54 KB[39m
|
|
18
|
+
[32mESM[39m ⚡️ Build success in 124ms
|
|
19
19
|
DTS Build start
|
|
20
|
-
DTS ⚡️ Build success in
|
|
20
|
+
DTS ⚡️ Build success in 5302ms
|
|
21
21
|
DTS dist/cli.d.ts 108.00 B
|
|
22
|
-
DTS dist/index.d.ts 2.
|
|
22
|
+
DTS dist/index.d.ts 2.66 KB
|
|
23
23
|
DTS dist/cli.d.mts 108.00 B
|
|
24
|
-
DTS dist/index.d.mts 2.
|
|
24
|
+
DTS dist/index.d.mts 2.66 KB
|
|
@@ -26,6 +26,7 @@ async function analyzeDocDrift(options) {
|
|
|
26
26
|
let totalExports = 0;
|
|
27
27
|
let outdatedComments = 0;
|
|
28
28
|
let undocumentedComplexity = 0;
|
|
29
|
+
let actualDrift = 0;
|
|
29
30
|
const now = Math.floor(Date.now() / 1e3);
|
|
30
31
|
let processed = 0;
|
|
31
32
|
for (const file of files) {
|
|
@@ -75,10 +76,9 @@ async function analyzeDocDrift(options) {
|
|
|
75
76
|
message: `Documentation mismatch: function parameters (${missingParams.join(", ")}) are not mentioned in the docs.`,
|
|
76
77
|
location: { file, line: exp.loc?.start.line || 1 }
|
|
77
78
|
});
|
|
78
|
-
continue;
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
|
-
if (exp.loc) {
|
|
81
|
+
if (exp.loc && doc.loc) {
|
|
82
82
|
if (!fileLineStamps) {
|
|
83
83
|
fileLineStamps = getFileCommitTimestamps(file);
|
|
84
84
|
}
|
|
@@ -87,8 +87,21 @@ async function analyzeDocDrift(options) {
|
|
|
87
87
|
exp.loc.start.line,
|
|
88
88
|
exp.loc.end.line
|
|
89
89
|
);
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
const docModified = getLineRangeLastModifiedCached(
|
|
91
|
+
fileLineStamps,
|
|
92
|
+
doc.loc.start.line,
|
|
93
|
+
doc.loc.end.line
|
|
94
|
+
);
|
|
95
|
+
if (bodyModified > 0 && docModified > 0) {
|
|
96
|
+
const DRIFT_THRESHOLD_SECONDS = 24 * 60 * 60;
|
|
97
|
+
if (bodyModified - docModified > DRIFT_THRESHOLD_SECONDS) {
|
|
98
|
+
actualDrift++;
|
|
99
|
+
issues.push({
|
|
100
|
+
type: IssueType.DocDrift,
|
|
101
|
+
severity: Severity.Major,
|
|
102
|
+
message: `Documentation drift: logic was modified on ${new Date(bodyModified * 1e3).toLocaleDateString()} but documentation was last updated on ${new Date(docModified * 1e3).toLocaleDateString()}.`,
|
|
103
|
+
location: { file, line: doc.loc.start.line }
|
|
104
|
+
});
|
|
92
105
|
}
|
|
93
106
|
}
|
|
94
107
|
}
|
|
@@ -104,7 +117,8 @@ async function analyzeDocDrift(options) {
|
|
|
104
117
|
uncommentedExports,
|
|
105
118
|
totalExports,
|
|
106
119
|
outdatedComments,
|
|
107
|
-
undocumentedComplexity
|
|
120
|
+
undocumentedComplexity,
|
|
121
|
+
actualDrift
|
|
108
122
|
});
|
|
109
123
|
return {
|
|
110
124
|
summary: {
|
|
@@ -118,7 +132,8 @@ async function analyzeDocDrift(options) {
|
|
|
118
132
|
uncommentedExports,
|
|
119
133
|
totalExports,
|
|
120
134
|
outdatedComments,
|
|
121
|
-
undocumentedComplexity
|
|
135
|
+
undocumentedComplexity,
|
|
136
|
+
actualDrift
|
|
122
137
|
},
|
|
123
138
|
recommendations: riskResult.recommendations
|
|
124
139
|
};
|
package/dist/cli.js
CHANGED
|
@@ -47,6 +47,7 @@ async function analyzeDocDrift(options) {
|
|
|
47
47
|
let totalExports = 0;
|
|
48
48
|
let outdatedComments = 0;
|
|
49
49
|
let undocumentedComplexity = 0;
|
|
50
|
+
let actualDrift = 0;
|
|
50
51
|
const now = Math.floor(Date.now() / 1e3);
|
|
51
52
|
let processed = 0;
|
|
52
53
|
for (const file of files) {
|
|
@@ -96,10 +97,9 @@ async function analyzeDocDrift(options) {
|
|
|
96
97
|
message: `Documentation mismatch: function parameters (${missingParams.join(", ")}) are not mentioned in the docs.`,
|
|
97
98
|
location: { file, line: exp.loc?.start.line || 1 }
|
|
98
99
|
});
|
|
99
|
-
continue;
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
|
-
if (exp.loc) {
|
|
102
|
+
if (exp.loc && doc.loc) {
|
|
103
103
|
if (!fileLineStamps) {
|
|
104
104
|
fileLineStamps = (0, import_core.getFileCommitTimestamps)(file);
|
|
105
105
|
}
|
|
@@ -108,8 +108,21 @@ async function analyzeDocDrift(options) {
|
|
|
108
108
|
exp.loc.start.line,
|
|
109
109
|
exp.loc.end.line
|
|
110
110
|
);
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
const docModified = (0, import_core.getLineRangeLastModifiedCached)(
|
|
112
|
+
fileLineStamps,
|
|
113
|
+
doc.loc.start.line,
|
|
114
|
+
doc.loc.end.line
|
|
115
|
+
);
|
|
116
|
+
if (bodyModified > 0 && docModified > 0) {
|
|
117
|
+
const DRIFT_THRESHOLD_SECONDS = 24 * 60 * 60;
|
|
118
|
+
if (bodyModified - docModified > DRIFT_THRESHOLD_SECONDS) {
|
|
119
|
+
actualDrift++;
|
|
120
|
+
issues.push({
|
|
121
|
+
type: import_core.IssueType.DocDrift,
|
|
122
|
+
severity: import_core.Severity.Major,
|
|
123
|
+
message: `Documentation drift: logic was modified on ${new Date(bodyModified * 1e3).toLocaleDateString()} but documentation was last updated on ${new Date(docModified * 1e3).toLocaleDateString()}.`,
|
|
124
|
+
location: { file, line: doc.loc.start.line }
|
|
125
|
+
});
|
|
113
126
|
}
|
|
114
127
|
}
|
|
115
128
|
}
|
|
@@ -125,7 +138,8 @@ async function analyzeDocDrift(options) {
|
|
|
125
138
|
uncommentedExports,
|
|
126
139
|
totalExports,
|
|
127
140
|
outdatedComments,
|
|
128
|
-
undocumentedComplexity
|
|
141
|
+
undocumentedComplexity,
|
|
142
|
+
actualDrift
|
|
129
143
|
});
|
|
130
144
|
return {
|
|
131
145
|
summary: {
|
|
@@ -139,7 +153,8 @@ async function analyzeDocDrift(options) {
|
|
|
139
153
|
uncommentedExports,
|
|
140
154
|
totalExports,
|
|
141
155
|
outdatedComments,
|
|
142
|
-
undocumentedComplexity
|
|
156
|
+
undocumentedComplexity,
|
|
157
|
+
actualDrift
|
|
143
158
|
},
|
|
144
159
|
recommendations: riskResult.recommendations
|
|
145
160
|
};
|
package/dist/cli.mjs
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -49,11 +49,23 @@ interface DocDriftReport {
|
|
|
49
49
|
outdatedComments: number;
|
|
50
50
|
/** Count of complex functions without sufficient documentation */
|
|
51
51
|
undocumentedComplexity: number;
|
|
52
|
+
/** Number of functions where code changed after docs */
|
|
53
|
+
actualDrift: number;
|
|
52
54
|
};
|
|
53
55
|
/** AI-generated remediation advice */
|
|
54
56
|
recommendations: string[];
|
|
55
57
|
}
|
|
56
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Analyzes documentation drift across a set of files.
|
|
61
|
+
* This tool detects:
|
|
62
|
+
* 1. Missing documentation for complex functions/classes.
|
|
63
|
+
* 2. Signature mismatches (parameters not mentioned in docs).
|
|
64
|
+
* 3. Temporal drift (logic changed after documentation was last updated).
|
|
65
|
+
*
|
|
66
|
+
* @param options - Analysis configuration including include/exclude patterns and drift thresholds.
|
|
67
|
+
* @returns A comprehensive report with drift scores and specific issues.
|
|
68
|
+
*/
|
|
57
69
|
declare function analyzeDocDrift(options: DocDriftOptions): Promise<DocDriftReport>;
|
|
58
70
|
|
|
59
71
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -49,11 +49,23 @@ interface DocDriftReport {
|
|
|
49
49
|
outdatedComments: number;
|
|
50
50
|
/** Count of complex functions without sufficient documentation */
|
|
51
51
|
undocumentedComplexity: number;
|
|
52
|
+
/** Number of functions where code changed after docs */
|
|
53
|
+
actualDrift: number;
|
|
52
54
|
};
|
|
53
55
|
/** AI-generated remediation advice */
|
|
54
56
|
recommendations: string[];
|
|
55
57
|
}
|
|
56
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Analyzes documentation drift across a set of files.
|
|
61
|
+
* This tool detects:
|
|
62
|
+
* 1. Missing documentation for complex functions/classes.
|
|
63
|
+
* 2. Signature mismatches (parameters not mentioned in docs).
|
|
64
|
+
* 3. Temporal drift (logic changed after documentation was last updated).
|
|
65
|
+
*
|
|
66
|
+
* @param options - Analysis configuration including include/exclude patterns and drift thresholds.
|
|
67
|
+
* @returns A comprehensive report with drift scores and specific issues.
|
|
68
|
+
*/
|
|
57
69
|
declare function analyzeDocDrift(options: DocDriftOptions): Promise<DocDriftReport>;
|
|
58
70
|
|
|
59
71
|
/**
|
package/dist/index.js
CHANGED
|
@@ -42,6 +42,7 @@ async function analyzeDocDrift(options) {
|
|
|
42
42
|
let totalExports = 0;
|
|
43
43
|
let outdatedComments = 0;
|
|
44
44
|
let undocumentedComplexity = 0;
|
|
45
|
+
let actualDrift = 0;
|
|
45
46
|
const now = Math.floor(Date.now() / 1e3);
|
|
46
47
|
let processed = 0;
|
|
47
48
|
for (const file of files) {
|
|
@@ -91,10 +92,9 @@ async function analyzeDocDrift(options) {
|
|
|
91
92
|
message: `Documentation mismatch: function parameters (${missingParams.join(", ")}) are not mentioned in the docs.`,
|
|
92
93
|
location: { file, line: exp.loc?.start.line || 1 }
|
|
93
94
|
});
|
|
94
|
-
continue;
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
|
-
if (exp.loc) {
|
|
97
|
+
if (exp.loc && doc.loc) {
|
|
98
98
|
if (!fileLineStamps) {
|
|
99
99
|
fileLineStamps = (0, import_core.getFileCommitTimestamps)(file);
|
|
100
100
|
}
|
|
@@ -103,8 +103,21 @@ async function analyzeDocDrift(options) {
|
|
|
103
103
|
exp.loc.start.line,
|
|
104
104
|
exp.loc.end.line
|
|
105
105
|
);
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
const docModified = (0, import_core.getLineRangeLastModifiedCached)(
|
|
107
|
+
fileLineStamps,
|
|
108
|
+
doc.loc.start.line,
|
|
109
|
+
doc.loc.end.line
|
|
110
|
+
);
|
|
111
|
+
if (bodyModified > 0 && docModified > 0) {
|
|
112
|
+
const DRIFT_THRESHOLD_SECONDS = 24 * 60 * 60;
|
|
113
|
+
if (bodyModified - docModified > DRIFT_THRESHOLD_SECONDS) {
|
|
114
|
+
actualDrift++;
|
|
115
|
+
issues.push({
|
|
116
|
+
type: import_core.IssueType.DocDrift,
|
|
117
|
+
severity: import_core.Severity.Major,
|
|
118
|
+
message: `Documentation drift: logic was modified on ${new Date(bodyModified * 1e3).toLocaleDateString()} but documentation was last updated on ${new Date(docModified * 1e3).toLocaleDateString()}.`,
|
|
119
|
+
location: { file, line: doc.loc.start.line }
|
|
120
|
+
});
|
|
108
121
|
}
|
|
109
122
|
}
|
|
110
123
|
}
|
|
@@ -120,7 +133,8 @@ async function analyzeDocDrift(options) {
|
|
|
120
133
|
uncommentedExports,
|
|
121
134
|
totalExports,
|
|
122
135
|
outdatedComments,
|
|
123
|
-
undocumentedComplexity
|
|
136
|
+
undocumentedComplexity,
|
|
137
|
+
actualDrift
|
|
124
138
|
});
|
|
125
139
|
return {
|
|
126
140
|
summary: {
|
|
@@ -134,7 +148,8 @@ async function analyzeDocDrift(options) {
|
|
|
134
148
|
uncommentedExports,
|
|
135
149
|
totalExports,
|
|
136
150
|
outdatedComments,
|
|
137
|
-
undocumentedComplexity
|
|
151
|
+
undocumentedComplexity,
|
|
152
|
+
actualDrift
|
|
138
153
|
},
|
|
139
154
|
recommendations: riskResult.recommendations
|
|
140
155
|
};
|
|
@@ -173,26 +188,33 @@ function calculateDocDriftScore(report) {
|
|
|
173
188
|
uncommentedExports: rawData.uncommentedExports,
|
|
174
189
|
totalExports: rawData.totalExports,
|
|
175
190
|
outdatedComments: rawData.outdatedComments,
|
|
176
|
-
undocumentedComplexity: rawData.undocumentedComplexity
|
|
191
|
+
undocumentedComplexity: rawData.undocumentedComplexity,
|
|
192
|
+
actualDrift: rawData.actualDrift
|
|
177
193
|
});
|
|
178
194
|
const factors = [
|
|
179
195
|
{
|
|
180
|
-
name: "
|
|
196
|
+
name: "Undocumented Complexity",
|
|
181
197
|
impact: -Math.min(
|
|
182
|
-
|
|
183
|
-
rawData.
|
|
198
|
+
50,
|
|
199
|
+
rawData.undocumentedComplexity / Math.max(1, rawData.totalExports) / 0.2 * 100 * 0.5
|
|
184
200
|
),
|
|
185
|
-
description: `${rawData.
|
|
201
|
+
description: `${rawData.undocumentedComplexity} complex functions lack docs (high risk)`
|
|
186
202
|
},
|
|
187
203
|
{
|
|
188
|
-
name: "Outdated Comments",
|
|
189
|
-
impact: -Math.min(
|
|
190
|
-
|
|
204
|
+
name: "Outdated/Incomplete Comments",
|
|
205
|
+
impact: -Math.min(
|
|
206
|
+
30,
|
|
207
|
+
rawData.outdatedComments / Math.max(1, rawData.totalExports) / 0.4 * 100 * 0.3
|
|
208
|
+
),
|
|
209
|
+
description: `${rawData.outdatedComments} functions with parameter-mismatch in docs`
|
|
191
210
|
},
|
|
192
211
|
{
|
|
193
|
-
name: "
|
|
194
|
-
impact: -Math.min(
|
|
195
|
-
|
|
212
|
+
name: "Uncommented Exports",
|
|
213
|
+
impact: -Math.min(
|
|
214
|
+
20,
|
|
215
|
+
rawData.uncommentedExports / Math.max(1, rawData.totalExports) / 0.8 * 100 * 0.2
|
|
216
|
+
),
|
|
217
|
+
description: `${rawData.uncommentedExports} uncommented exports`
|
|
196
218
|
}
|
|
197
219
|
];
|
|
198
220
|
const recommendations = riskResult.recommendations.map((rec) => ({
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
analyzeDocDrift
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-P74XAVQ3.mjs";
|
|
4
4
|
|
|
5
5
|
// src/index.ts
|
|
6
6
|
import { ToolRegistry } from "@aiready/core";
|
|
@@ -44,26 +44,33 @@ function calculateDocDriftScore(report) {
|
|
|
44
44
|
uncommentedExports: rawData.uncommentedExports,
|
|
45
45
|
totalExports: rawData.totalExports,
|
|
46
46
|
outdatedComments: rawData.outdatedComments,
|
|
47
|
-
undocumentedComplexity: rawData.undocumentedComplexity
|
|
47
|
+
undocumentedComplexity: rawData.undocumentedComplexity,
|
|
48
|
+
actualDrift: rawData.actualDrift
|
|
48
49
|
});
|
|
49
50
|
const factors = [
|
|
50
51
|
{
|
|
51
|
-
name: "
|
|
52
|
+
name: "Undocumented Complexity",
|
|
52
53
|
impact: -Math.min(
|
|
53
|
-
|
|
54
|
-
rawData.
|
|
54
|
+
50,
|
|
55
|
+
rawData.undocumentedComplexity / Math.max(1, rawData.totalExports) / 0.2 * 100 * 0.5
|
|
55
56
|
),
|
|
56
|
-
description: `${rawData.
|
|
57
|
+
description: `${rawData.undocumentedComplexity} complex functions lack docs (high risk)`
|
|
57
58
|
},
|
|
58
59
|
{
|
|
59
|
-
name: "Outdated Comments",
|
|
60
|
-
impact: -Math.min(
|
|
61
|
-
|
|
60
|
+
name: "Outdated/Incomplete Comments",
|
|
61
|
+
impact: -Math.min(
|
|
62
|
+
30,
|
|
63
|
+
rawData.outdatedComments / Math.max(1, rawData.totalExports) / 0.4 * 100 * 0.3
|
|
64
|
+
),
|
|
65
|
+
description: `${rawData.outdatedComments} functions with parameter-mismatch in docs`
|
|
62
66
|
},
|
|
63
67
|
{
|
|
64
|
-
name: "
|
|
65
|
-
impact: -Math.min(
|
|
66
|
-
|
|
68
|
+
name: "Uncommented Exports",
|
|
69
|
+
impact: -Math.min(
|
|
70
|
+
20,
|
|
71
|
+
rawData.uncommentedExports / Math.max(1, rawData.totalExports) / 0.8 * 100 * 0.2
|
|
72
|
+
),
|
|
73
|
+
description: `${rawData.uncommentedExports} uncommented exports`
|
|
67
74
|
}
|
|
68
75
|
];
|
|
69
76
|
const recommendations = riskResult.recommendations.map((rec) => ({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiready/doc-drift",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.5",
|
|
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.23.
|
|
13
|
+
"@aiready/core": "0.23.6"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
16
|
"@types/node": "^24.0.0",
|
|
@@ -76,6 +76,7 @@ export function complexFunction(data: any) {
|
|
|
76
76
|
expect(report.rawData.uncommentedExports).toBe(1);
|
|
77
77
|
expect(report.rawData.outdatedComments).toBe(1);
|
|
78
78
|
expect(report.rawData.undocumentedComplexity).toBe(1);
|
|
79
|
+
expect(report.rawData.actualDrift).toBe(0);
|
|
79
80
|
|
|
80
81
|
expect(report.issues.length).toBeGreaterThan(0);
|
|
81
82
|
expect(report.issues.some((i) => i.message.includes('b'))).toBe(true);
|
package/src/analyzer.ts
CHANGED
|
@@ -11,6 +11,16 @@ import {
|
|
|
11
11
|
import type { DocDriftOptions, DocDriftReport, DocDriftIssue } from './types';
|
|
12
12
|
import { readFileSync } from 'fs';
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Analyzes documentation drift across a set of files.
|
|
16
|
+
* This tool detects:
|
|
17
|
+
* 1. Missing documentation for complex functions/classes.
|
|
18
|
+
* 2. Signature mismatches (parameters not mentioned in docs).
|
|
19
|
+
* 3. Temporal drift (logic changed after documentation was last updated).
|
|
20
|
+
*
|
|
21
|
+
* @param options - Analysis configuration including include/exclude patterns and drift thresholds.
|
|
22
|
+
* @returns A comprehensive report with drift scores and specific issues.
|
|
23
|
+
*/
|
|
14
24
|
export async function analyzeDocDrift(
|
|
15
25
|
options: DocDriftOptions
|
|
16
26
|
): Promise<DocDriftReport> {
|
|
@@ -24,6 +34,7 @@ export async function analyzeDocDrift(
|
|
|
24
34
|
let totalExports = 0;
|
|
25
35
|
let outdatedComments = 0;
|
|
26
36
|
let undocumentedComplexity = 0;
|
|
37
|
+
let actualDrift = 0;
|
|
27
38
|
|
|
28
39
|
const now = Math.floor(Date.now() / 1000);
|
|
29
40
|
|
|
@@ -76,7 +87,6 @@ export async function analyzeDocDrift(
|
|
|
76
87
|
if (exp.type === 'function' && exp.parameters) {
|
|
77
88
|
const params = exp.parameters;
|
|
78
89
|
// Check if params mentioned in doc (standard @param or simple mention)
|
|
79
|
-
// Use regex with word boundaries to avoid partial matches (e.g. 'b' in 'numbers')
|
|
80
90
|
const missingParams = params.filter((p) => {
|
|
81
91
|
const regex = new RegExp(`\\b${p}\\b`, 'i');
|
|
82
92
|
return !regex.test(docContent);
|
|
@@ -90,36 +100,39 @@ export async function analyzeDocDrift(
|
|
|
90
100
|
message: `Documentation mismatch: function parameters (${missingParams.join(', ')}) are not mentioned in the docs.`,
|
|
91
101
|
location: { file, line: exp.loc?.start.line || 1 },
|
|
92
102
|
});
|
|
93
|
-
continue;
|
|
94
103
|
}
|
|
95
104
|
}
|
|
96
105
|
|
|
97
|
-
// Timestamp comparison
|
|
98
|
-
if (exp.loc) {
|
|
106
|
+
// Timestamp comparison for temporal drift
|
|
107
|
+
if (exp.loc && doc.loc) {
|
|
99
108
|
if (!fileLineStamps) {
|
|
100
109
|
fileLineStamps = getFileCommitTimestamps(file);
|
|
101
110
|
}
|
|
102
111
|
|
|
103
|
-
// We don't have exact lines for the doc node in ExportInfo yet,
|
|
104
|
-
// but we know it precedes the export. Using export start as a proxy for drift check.
|
|
105
112
|
const bodyModified = getLineRangeLastModifiedCached(
|
|
106
113
|
fileLineStamps,
|
|
107
114
|
exp.loc.start.line,
|
|
108
115
|
exp.loc.end.line
|
|
109
116
|
);
|
|
110
117
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
) {
|
|
117
|
-
// This would require isStale to be set by the parser if it knew history
|
|
118
|
-
// For now, we compare body modification vs current time if docs look very old (heuristic)
|
|
119
|
-
}
|
|
118
|
+
const docModified = getLineRangeLastModifiedCached(
|
|
119
|
+
fileLineStamps,
|
|
120
|
+
doc.loc.start.line,
|
|
121
|
+
doc.loc.end.line
|
|
122
|
+
);
|
|
120
123
|
|
|
121
|
-
|
|
122
|
-
//
|
|
124
|
+
if (bodyModified > 0 && docModified > 0) {
|
|
125
|
+
// If body was modified more than 1 day AFTER the documentation
|
|
126
|
+
const DRIFT_THRESHOLD_SECONDS = 24 * 60 * 60;
|
|
127
|
+
if (bodyModified - docModified > DRIFT_THRESHOLD_SECONDS) {
|
|
128
|
+
actualDrift++;
|
|
129
|
+
issues.push({
|
|
130
|
+
type: IssueType.DocDrift,
|
|
131
|
+
severity: Severity.Major,
|
|
132
|
+
message: `Documentation drift: logic was modified on ${new Date(bodyModified * 1000).toLocaleDateString()} but documentation was last updated on ${new Date(docModified * 1000).toLocaleDateString()}.`,
|
|
133
|
+
location: { file, line: doc.loc.start.line },
|
|
134
|
+
});
|
|
135
|
+
}
|
|
123
136
|
}
|
|
124
137
|
}
|
|
125
138
|
}
|
|
@@ -136,6 +149,7 @@ export async function analyzeDocDrift(
|
|
|
136
149
|
totalExports,
|
|
137
150
|
outdatedComments,
|
|
138
151
|
undocumentedComplexity,
|
|
152
|
+
actualDrift,
|
|
139
153
|
});
|
|
140
154
|
|
|
141
155
|
return {
|
|
@@ -151,6 +165,7 @@ export async function analyzeDocDrift(
|
|
|
151
165
|
totalExports,
|
|
152
166
|
outdatedComments,
|
|
153
167
|
undocumentedComplexity,
|
|
168
|
+
actualDrift,
|
|
154
169
|
},
|
|
155
170
|
recommendations: riskResult.recommendations,
|
|
156
171
|
};
|
package/src/scoring.ts
CHANGED
|
@@ -16,28 +16,41 @@ export function calculateDocDriftScore(
|
|
|
16
16
|
totalExports: rawData.totalExports,
|
|
17
17
|
outdatedComments: rawData.outdatedComments,
|
|
18
18
|
undocumentedComplexity: rawData.undocumentedComplexity,
|
|
19
|
+
actualDrift: rawData.actualDrift,
|
|
19
20
|
});
|
|
20
21
|
|
|
21
22
|
const factors: ToolScoringOutput['factors'] = [
|
|
22
23
|
{
|
|
23
|
-
name: '
|
|
24
|
+
name: 'Undocumented Complexity',
|
|
24
25
|
impact: -Math.min(
|
|
25
|
-
|
|
26
|
-
(rawData.
|
|
26
|
+
50,
|
|
27
|
+
(rawData.undocumentedComplexity /
|
|
28
|
+
Math.max(1, rawData.totalExports) /
|
|
29
|
+
0.2) *
|
|
27
30
|
100 *
|
|
28
|
-
0.
|
|
31
|
+
0.5
|
|
29
32
|
),
|
|
30
|
-
description: `${rawData.
|
|
33
|
+
description: `${rawData.undocumentedComplexity} complex functions lack docs (high risk)`,
|
|
31
34
|
},
|
|
32
35
|
{
|
|
33
|
-
name: 'Outdated Comments',
|
|
34
|
-
impact: -Math.min(
|
|
35
|
-
|
|
36
|
+
name: 'Outdated/Incomplete Comments',
|
|
37
|
+
impact: -Math.min(
|
|
38
|
+
30,
|
|
39
|
+
(rawData.outdatedComments / Math.max(1, rawData.totalExports) / 0.4) *
|
|
40
|
+
100 *
|
|
41
|
+
0.3
|
|
42
|
+
),
|
|
43
|
+
description: `${rawData.outdatedComments} functions with parameter-mismatch in docs`,
|
|
36
44
|
},
|
|
37
45
|
{
|
|
38
|
-
name: '
|
|
39
|
-
impact: -Math.min(
|
|
40
|
-
|
|
46
|
+
name: 'Uncommented Exports',
|
|
47
|
+
impact: -Math.min(
|
|
48
|
+
20,
|
|
49
|
+
(rawData.uncommentedExports / Math.max(1, rawData.totalExports) / 0.8) *
|
|
50
|
+
100 *
|
|
51
|
+
0.2
|
|
52
|
+
),
|
|
53
|
+
description: `${rawData.uncommentedExports} uncommented exports`,
|
|
41
54
|
},
|
|
42
55
|
];
|
|
43
56
|
|
package/src/types.ts
CHANGED
|
@@ -50,6 +50,8 @@ export interface DocDriftReport {
|
|
|
50
50
|
outdatedComments: number;
|
|
51
51
|
/** Count of complex functions without sufficient documentation */
|
|
52
52
|
undocumentedComplexity: number;
|
|
53
|
+
/** Number of functions where code changed after docs */
|
|
54
|
+
actualDrift: number;
|
|
53
55
|
};
|
|
54
56
|
/** AI-generated remediation advice */
|
|
55
57
|
recommendations: string[];
|