@getcodesentinel/codesentinel 1.19.1 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/index.js +61 -18
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -233,8 +233,9 @@ Notes:
|
|
|
233
233
|
|
|
234
234
|
- `likely_merge` (default) may merge multiple emails that likely belong to the same person based on repository history.
|
|
235
235
|
- `strict_email` treats each canonical email as a distinct author, which avoids false merges but can split the same person across multiple emails.
|
|
236
|
-
- Git mailmap is enabled (`git log --use-mailmap`). Put `.mailmap` in the repository being analyzed (the `codesentinel analyze [path]` target). Git will then deterministically unify known aliases before CodeSentinel computes
|
|
237
|
-
- `
|
|
236
|
+
- Git mailmap is enabled (`git log --use-mailmap`). Put `.mailmap` in the repository being analyzed (the `codesentinel analyze [path]` target). Git will then deterministically unify known aliases before CodeSentinel computes ownership distributions.
|
|
237
|
+
- Evolution output includes both `authorDistributionByCommits` and `authorDistributionByChurn`, plus their derived `topAuthorShare...` and `busFactor...` fields.
|
|
238
|
+
- Current scoring continues to use the commit-weighted ownership view (`...ByCommits`).
|
|
238
239
|
- Logs are emitted to `stderr` and JSON output is written to `stdout`, so CI redirection still works.
|
|
239
240
|
- You can set a default log level with `CODESENTINEL_LOG_LEVEL` (`silent|error|warn|info|debug`).
|
|
240
241
|
- At `info`/`debug`, structural, evolution, and dependency stages report progress so long analyses are observable.
|
package/dist/index.js
CHANGED
|
@@ -4683,6 +4683,26 @@ var finalizeAuthorDistribution = (authorCommits) => {
|
|
|
4683
4683
|
share: round44(commits / totalCommits)
|
|
4684
4684
|
})).sort((a, b) => b.commits - a.commits || a.authorId.localeCompare(b.authorId));
|
|
4685
4685
|
};
|
|
4686
|
+
var finalizeAuthorChurnDistribution = (authorChurn) => {
|
|
4687
|
+
const entries = [...authorChurn.entries()].map(([authorId, churn]) => {
|
|
4688
|
+
const churnAdded = churn.churnAdded;
|
|
4689
|
+
const churnDeleted = churn.churnDeleted;
|
|
4690
|
+
return {
|
|
4691
|
+
authorId,
|
|
4692
|
+
churnAdded,
|
|
4693
|
+
churnDeleted,
|
|
4694
|
+
churnTotal: churnAdded + churnDeleted
|
|
4695
|
+
};
|
|
4696
|
+
});
|
|
4697
|
+
const totalChurn = entries.reduce((sum, entry) => sum + entry.churnTotal, 0);
|
|
4698
|
+
if (totalChurn === 0) {
|
|
4699
|
+
return [];
|
|
4700
|
+
}
|
|
4701
|
+
return entries.map((entry) => ({
|
|
4702
|
+
...entry,
|
|
4703
|
+
share: round44(entry.churnTotal / totalChurn)
|
|
4704
|
+
})).sort((a, b) => b.churnTotal - a.churnTotal || a.authorId.localeCompare(b.authorId));
|
|
4705
|
+
};
|
|
4686
4706
|
var buildCouplingMatrix = (coChangeByPair, fileCommitCount, consideredCommits, skippedLargeCommits, maxCouplingPairs) => {
|
|
4687
4707
|
const allPairs = [];
|
|
4688
4708
|
for (const [key, coChangeCommits] of coChangeByPair.entries()) {
|
|
@@ -4751,10 +4771,19 @@ var computeRepositoryEvolutionSummary = (targetPath, commits, config) => {
|
|
|
4751
4771
|
recentCommitCount: 0,
|
|
4752
4772
|
churnAdded: 0,
|
|
4753
4773
|
churnDeleted: 0,
|
|
4754
|
-
|
|
4774
|
+
authorsByCommits: /* @__PURE__ */ new Map(),
|
|
4775
|
+
authorsByChurn: /* @__PURE__ */ new Map()
|
|
4755
4776
|
};
|
|
4756
4777
|
current.churnAdded += fileChange.additions;
|
|
4757
4778
|
current.churnDeleted += fileChange.deletions;
|
|
4779
|
+
const effectiveAuthorId = authorAliasById.get(commit.authorId) ?? commit.authorId;
|
|
4780
|
+
const authorChurn = current.authorsByChurn.get(effectiveAuthorId) ?? {
|
|
4781
|
+
churnAdded: 0,
|
|
4782
|
+
churnDeleted: 0
|
|
4783
|
+
};
|
|
4784
|
+
authorChurn.churnAdded += fileChange.additions;
|
|
4785
|
+
authorChurn.churnDeleted += fileChange.deletions;
|
|
4786
|
+
current.authorsByChurn.set(effectiveAuthorId, authorChurn);
|
|
4758
4787
|
fileStats.set(fileChange.filePath, current);
|
|
4759
4788
|
}
|
|
4760
4789
|
for (const filePath of uniqueFiles) {
|
|
@@ -4767,7 +4796,10 @@ var computeRepositoryEvolutionSummary = (targetPath, commits, config) => {
|
|
|
4767
4796
|
current.recentCommitCount += 1;
|
|
4768
4797
|
}
|
|
4769
4798
|
const effectiveAuthorId = authorAliasById.get(commit.authorId) ?? commit.authorId;
|
|
4770
|
-
current.
|
|
4799
|
+
current.authorsByCommits.set(
|
|
4800
|
+
effectiveAuthorId,
|
|
4801
|
+
(current.authorsByCommits.get(effectiveAuthorId) ?? 0) + 1
|
|
4802
|
+
);
|
|
4771
4803
|
}
|
|
4772
4804
|
const orderedFiles = [...uniqueFiles].sort((a, b) => a.localeCompare(b));
|
|
4773
4805
|
if (orderedFiles.length > 1) {
|
|
@@ -4790,8 +4822,10 @@ var computeRepositoryEvolutionSummary = (targetPath, commits, config) => {
|
|
|
4790
4822
|
}
|
|
4791
4823
|
}
|
|
4792
4824
|
const files = [...fileStats.entries()].map(([filePath, stats]) => {
|
|
4793
|
-
const
|
|
4794
|
-
const
|
|
4825
|
+
const authorDistributionByCommits = finalizeAuthorDistribution(stats.authorsByCommits);
|
|
4826
|
+
const authorDistributionByChurn = finalizeAuthorChurnDistribution(stats.authorsByChurn);
|
|
4827
|
+
const topAuthorShareByCommits = authorDistributionByCommits[0]?.share ?? 0;
|
|
4828
|
+
const topAuthorShareByChurn = authorDistributionByChurn[0]?.share ?? 0;
|
|
4795
4829
|
return {
|
|
4796
4830
|
filePath,
|
|
4797
4831
|
commitCount: stats.commitCount,
|
|
@@ -4801,9 +4835,18 @@ var computeRepositoryEvolutionSummary = (targetPath, commits, config) => {
|
|
|
4801
4835
|
churnTotal: stats.churnAdded + stats.churnDeleted,
|
|
4802
4836
|
recentCommitCount: stats.recentCommitCount,
|
|
4803
4837
|
recentVolatility: stats.commitCount === 0 ? 0 : round44(stats.recentCommitCount / stats.commitCount),
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4838
|
+
topAuthorShareByCommits,
|
|
4839
|
+
busFactorByCommits: computeBusFactor(
|
|
4840
|
+
authorDistributionByCommits,
|
|
4841
|
+
config.busFactorCoverageThreshold
|
|
4842
|
+
),
|
|
4843
|
+
authorDistributionByCommits,
|
|
4844
|
+
topAuthorShareByChurn,
|
|
4845
|
+
busFactorByChurn: computeBusFactor(
|
|
4846
|
+
authorDistributionByChurn,
|
|
4847
|
+
config.busFactorCoverageThreshold
|
|
4848
|
+
),
|
|
4849
|
+
authorDistributionByChurn
|
|
4807
4850
|
};
|
|
4808
4851
|
}).sort((a, b) => a.filePath.localeCompare(b.filePath));
|
|
4809
4852
|
const fileCommitCount = new Map(files.map((file) => [file.filePath, file.commitCount]));
|
|
@@ -5558,15 +5601,15 @@ var computeRepositoryHealthSummary = (input) => {
|
|
|
5558
5601
|
let singleContributorFiles = 0;
|
|
5559
5602
|
let trackedFiles = 0;
|
|
5560
5603
|
for (const file of evolutionSourceFiles) {
|
|
5561
|
-
if (file.commitCount <= 0 || file.
|
|
5604
|
+
if (file.commitCount <= 0 || file.authorDistributionByCommits.length === 0) {
|
|
5562
5605
|
continue;
|
|
5563
5606
|
}
|
|
5564
5607
|
trackedFiles += 1;
|
|
5565
|
-
const dominantShare = clamp01(file.
|
|
5566
|
-
if (file.
|
|
5608
|
+
const dominantShare = clamp01(file.authorDistributionByCommits[0]?.share ?? 0);
|
|
5609
|
+
if (file.authorDistributionByCommits.length === 1 || dominantShare >= 0.9) {
|
|
5567
5610
|
singleContributorFiles += 1;
|
|
5568
5611
|
}
|
|
5569
|
-
for (const author of file.
|
|
5612
|
+
for (const author of file.authorDistributionByCommits) {
|
|
5570
5613
|
const commits = Math.max(0, author.commits);
|
|
5571
5614
|
if (commits <= 0) {
|
|
5572
5615
|
continue;
|
|
@@ -6329,7 +6372,7 @@ var computeEvolutionScales = (evolutionByFile, config) => {
|
|
|
6329
6372
|
config.quantileClamp.upper
|
|
6330
6373
|
),
|
|
6331
6374
|
busFactor: buildQuantileScale(
|
|
6332
|
-
evolutionFiles.map((metrics) => metrics.
|
|
6375
|
+
evolutionFiles.map((metrics) => metrics.busFactorByCommits),
|
|
6333
6376
|
config.quantileClamp.lower,
|
|
6334
6377
|
config.quantileClamp.upper
|
|
6335
6378
|
)
|
|
@@ -6518,9 +6561,9 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
|
|
|
6518
6561
|
evolutionScales.churnTotal
|
|
6519
6562
|
);
|
|
6520
6563
|
volatilityRisk = toUnitInterval(evolutionMetrics.recentVolatility);
|
|
6521
|
-
ownershipConcentrationRisk = toUnitInterval(evolutionMetrics.
|
|
6564
|
+
ownershipConcentrationRisk = toUnitInterval(evolutionMetrics.topAuthorShareByCommits);
|
|
6522
6565
|
busFactorRisk = toUnitInterval(
|
|
6523
|
-
1 - normalizeWithScale(evolutionMetrics.
|
|
6566
|
+
1 - normalizeWithScale(evolutionMetrics.busFactorByCommits, evolutionScales.busFactor)
|
|
6524
6567
|
);
|
|
6525
6568
|
const evolutionWeights = config.evolutionFactorWeights;
|
|
6526
6569
|
evolutionFactor = toUnitInterval(
|
|
@@ -6577,8 +6620,8 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
|
|
|
6577
6620
|
commitCount: evolutionMetrics?.commitCount ?? null,
|
|
6578
6621
|
churnTotal: evolutionMetrics?.churnTotal ?? null,
|
|
6579
6622
|
recentVolatility: evolutionMetrics?.recentVolatility ?? null,
|
|
6580
|
-
|
|
6581
|
-
|
|
6623
|
+
topAuthorShareByCommits: evolutionMetrics?.topAuthorShareByCommits ?? null,
|
|
6624
|
+
busFactorByCommits: evolutionMetrics?.busFactorByCommits ?? null,
|
|
6582
6625
|
dependencyAffinity: round46(dependencyAffinity),
|
|
6583
6626
|
repositoryExternalPressure: round46(dependencyComputation.repositoryExternalPressure),
|
|
6584
6627
|
structuralAttenuation: round46(structuralAttenuation)
|
|
@@ -6646,8 +6689,8 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
|
|
|
6646
6689
|
commitCount: context.rawMetrics.commitCount,
|
|
6647
6690
|
churnTotal: context.rawMetrics.churnTotal,
|
|
6648
6691
|
recentVolatility: context.rawMetrics.recentVolatility,
|
|
6649
|
-
|
|
6650
|
-
|
|
6692
|
+
topAuthorShareByCommits: context.rawMetrics.topAuthorShareByCommits,
|
|
6693
|
+
busFactorByCommits: context.rawMetrics.busFactorByCommits
|
|
6651
6694
|
},
|
|
6652
6695
|
normalizedMetrics: {
|
|
6653
6696
|
frequencyRisk: context.normalizedMetrics.frequencyRisk,
|