@getcodesentinel/codesentinel 1.19.0 → 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 +186 -48
- 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
|
@@ -3702,6 +3702,7 @@ var ANSI2 = {
|
|
|
3702
3702
|
cyan: "\x1B[36m",
|
|
3703
3703
|
green: "\x1B[32m"
|
|
3704
3704
|
};
|
|
3705
|
+
var PROMPT_PADDING = " ";
|
|
3705
3706
|
var renderMenu = (currentVersion, actions, selectedIndex) => {
|
|
3706
3707
|
const optionLabels = actions.map((action, index) => `${index + 1}. ${action.label}`);
|
|
3707
3708
|
const labelWidth = optionLabels.reduce((max, label) => Math.max(max, label.length), 0);
|
|
@@ -3732,6 +3733,46 @@ var hideCursor2 = () => {
|
|
|
3732
3733
|
var showCursor2 = () => {
|
|
3733
3734
|
stderr2.write("\x1B[?25h");
|
|
3734
3735
|
};
|
|
3736
|
+
var pipeWithPadding = (stream, target, padding) => {
|
|
3737
|
+
if (stream === null) {
|
|
3738
|
+
return;
|
|
3739
|
+
}
|
|
3740
|
+
stream.setEncoding("utf8");
|
|
3741
|
+
let buffer = "";
|
|
3742
|
+
let needsPrefix = true;
|
|
3743
|
+
const writeChunk = (chunk) => {
|
|
3744
|
+
let start = 0;
|
|
3745
|
+
while (start < chunk.length) {
|
|
3746
|
+
if (needsPrefix) {
|
|
3747
|
+
target.write(padding);
|
|
3748
|
+
needsPrefix = false;
|
|
3749
|
+
}
|
|
3750
|
+
const newlineIndex = chunk.indexOf("\n", start);
|
|
3751
|
+
if (newlineIndex === -1) {
|
|
3752
|
+
target.write(chunk.slice(start));
|
|
3753
|
+
return;
|
|
3754
|
+
}
|
|
3755
|
+
target.write(chunk.slice(start, newlineIndex + 1));
|
|
3756
|
+
needsPrefix = true;
|
|
3757
|
+
start = newlineIndex + 1;
|
|
3758
|
+
}
|
|
3759
|
+
};
|
|
3760
|
+
stream.on("data", (chunk) => {
|
|
3761
|
+
buffer += chunk;
|
|
3762
|
+
const lastNewlineIndex = buffer.lastIndexOf("\n");
|
|
3763
|
+
if (lastNewlineIndex === -1) {
|
|
3764
|
+
return;
|
|
3765
|
+
}
|
|
3766
|
+
writeChunk(buffer.slice(0, lastNewlineIndex + 1));
|
|
3767
|
+
buffer = buffer.slice(lastNewlineIndex + 1);
|
|
3768
|
+
});
|
|
3769
|
+
stream.on("end", () => {
|
|
3770
|
+
if (buffer.length > 0) {
|
|
3771
|
+
writeChunk(buffer);
|
|
3772
|
+
buffer = "";
|
|
3773
|
+
}
|
|
3774
|
+
});
|
|
3775
|
+
};
|
|
3735
3776
|
var promptSelection = async (currentVersion, actions) => {
|
|
3736
3777
|
if (!stdin2.isTTY || !stderr2.isTTY || typeof stdin2.setRawMode !== "function") {
|
|
3737
3778
|
return "exit";
|
|
@@ -3788,46 +3829,99 @@ var createPrompt = () => createPromisesInterface({
|
|
|
3788
3829
|
output: stderr2
|
|
3789
3830
|
});
|
|
3790
3831
|
var promptText = async (prompt, label, defaultValue) => {
|
|
3791
|
-
const suffix = defaultValue === void 0 ? "" : ` [${defaultValue}]`;
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3832
|
+
const suffix = defaultValue === void 0 || defaultValue.length === 0 ? "" : ` [${defaultValue}]`;
|
|
3833
|
+
let answer;
|
|
3834
|
+
try {
|
|
3835
|
+
answer = await prompt.question(
|
|
3836
|
+
`${PROMPT_PADDING}${label}${suffix}: `
|
|
3837
|
+
);
|
|
3838
|
+
} catch (error) {
|
|
3839
|
+
if (error instanceof Error && "code" in error && error.code === "ABORT_ERR") {
|
|
3840
|
+
stderr2.write("\n");
|
|
3841
|
+
return null;
|
|
3842
|
+
}
|
|
3843
|
+
throw error;
|
|
3844
|
+
}
|
|
3795
3845
|
const trimmed = answer.trim();
|
|
3796
3846
|
return trimmed.length > 0 ? trimmed : defaultValue ?? "";
|
|
3797
3847
|
};
|
|
3848
|
+
var renderDependencyRiskPrompt = (errorMessage) => {
|
|
3849
|
+
clearTerminal();
|
|
3850
|
+
stderr2.write(`${PROMPT_PADDING}${ANSI2.bold}Scan dependency risk${ANSI2.reset}
|
|
3851
|
+
`);
|
|
3852
|
+
if (errorMessage !== void 0) {
|
|
3853
|
+
stderr2.write(`
|
|
3854
|
+
${PROMPT_PADDING}${errorMessage}
|
|
3855
|
+
`);
|
|
3856
|
+
}
|
|
3857
|
+
stderr2.write("\n");
|
|
3858
|
+
};
|
|
3798
3859
|
var buildDependencyRiskArgs = async () => {
|
|
3799
3860
|
const prompt = createPrompt();
|
|
3800
3861
|
try {
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3862
|
+
let errorMessage;
|
|
3863
|
+
while (true) {
|
|
3864
|
+
renderDependencyRiskPrompt(errorMessage);
|
|
3865
|
+
const dependency = await promptText(prompt, "Package name");
|
|
3866
|
+
if (dependency === null) {
|
|
3867
|
+
return { kind: "cancel" };
|
|
3868
|
+
}
|
|
3869
|
+
if (dependency.length === 0) {
|
|
3870
|
+
errorMessage = "A package name is required.";
|
|
3871
|
+
continue;
|
|
3872
|
+
}
|
|
3873
|
+
return {
|
|
3874
|
+
kind: "run",
|
|
3875
|
+
args: ["dependency-risk", dependency]
|
|
3876
|
+
};
|
|
3805
3877
|
}
|
|
3806
|
-
return ["dependency-risk", dependency];
|
|
3807
3878
|
} finally {
|
|
3808
3879
|
prompt.close();
|
|
3809
3880
|
}
|
|
3810
3881
|
};
|
|
3811
3882
|
var waitForReturnToMenu = async () => {
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
output: stderr2
|
|
3815
|
-
});
|
|
3816
|
-
try {
|
|
3817
|
-
await prompt.question("Press enter to return to the menu...");
|
|
3818
|
-
} finally {
|
|
3819
|
-
prompt.close();
|
|
3883
|
+
if (!stdin2.isTTY || !stderr2.isTTY || typeof stdin2.setRawMode !== "function") {
|
|
3884
|
+
return;
|
|
3820
3885
|
}
|
|
3886
|
+
stderr2.write(`
|
|
3887
|
+
${PROMPT_PADDING}Press enter to return to the menu...`);
|
|
3888
|
+
await new Promise((resolve6) => {
|
|
3889
|
+
emitKeypressEvents2(stdin2);
|
|
3890
|
+
const previousRawMode = stdin2.isRaw;
|
|
3891
|
+
const cleanup = () => {
|
|
3892
|
+
stdin2.off("keypress", onKeypress);
|
|
3893
|
+
stdin2.pause();
|
|
3894
|
+
stdin2.setRawMode(previousRawMode);
|
|
3895
|
+
showCursor2();
|
|
3896
|
+
stderr2.write("\n");
|
|
3897
|
+
resolve6();
|
|
3898
|
+
};
|
|
3899
|
+
const onKeypress = (_str, key) => {
|
|
3900
|
+
if (key.ctrl === true && key.name === "c") {
|
|
3901
|
+
cleanup();
|
|
3902
|
+
return;
|
|
3903
|
+
}
|
|
3904
|
+
if (key.name === "return" || key.name === "enter") {
|
|
3905
|
+
cleanup();
|
|
3906
|
+
}
|
|
3907
|
+
};
|
|
3908
|
+
hideCursor2();
|
|
3909
|
+
stdin2.on("keypress", onKeypress);
|
|
3910
|
+
stdin2.setRawMode(true);
|
|
3911
|
+
stdin2.resume();
|
|
3912
|
+
});
|
|
3821
3913
|
};
|
|
3822
3914
|
var runCliCommand = async (scriptPath2, args) => {
|
|
3823
3915
|
return await new Promise((resolve6, reject) => {
|
|
3824
3916
|
const child = spawn2(process.execPath, [...process.execArgv, scriptPath2, ...args], {
|
|
3825
|
-
stdio: "inherit",
|
|
3917
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
3826
3918
|
env: {
|
|
3827
3919
|
...process.env,
|
|
3828
3920
|
CODESENTINEL_NO_UPDATE_NOTIFIER: "1"
|
|
3829
3921
|
}
|
|
3830
3922
|
});
|
|
3923
|
+
pipeWithPadding(child.stdout, stdout, PROMPT_PADDING);
|
|
3924
|
+
pipeWithPadding(child.stderr, stderr2, PROMPT_PADDING);
|
|
3831
3925
|
child.on("error", (error) => {
|
|
3832
3926
|
reject(error);
|
|
3833
3927
|
});
|
|
@@ -3838,34 +3932,35 @@ var runCliCommand = async (scriptPath2, args) => {
|
|
|
3838
3932
|
};
|
|
3839
3933
|
var runInteractiveCliMenu = async (input) => {
|
|
3840
3934
|
if (!stdin2.isTTY || !stderr2.isTTY || !stdout.isTTY) {
|
|
3841
|
-
stderr2.write(
|
|
3935
|
+
stderr2.write(`${PROMPT_PADDING}Interactive menu requires a TTY.
|
|
3936
|
+
`);
|
|
3842
3937
|
return 1;
|
|
3843
3938
|
}
|
|
3844
3939
|
const actions = [
|
|
3845
3940
|
{
|
|
3846
3941
|
label: "Run overview",
|
|
3847
3942
|
description: "combined analyze + explain + report",
|
|
3848
|
-
commandBuilder: () => ["run"]
|
|
3943
|
+
commandBuilder: () => ({ kind: "run", args: ["run"] })
|
|
3849
3944
|
},
|
|
3850
3945
|
{
|
|
3851
3946
|
label: "Analyze repository",
|
|
3852
3947
|
description: "structural and health scoring summary",
|
|
3853
|
-
commandBuilder: () => ["analyze"]
|
|
3948
|
+
commandBuilder: () => ({ kind: "run", args: ["analyze"] })
|
|
3854
3949
|
},
|
|
3855
3950
|
{
|
|
3856
3951
|
label: "Explain hotspots",
|
|
3857
3952
|
description: "top findings in markdown by default",
|
|
3858
|
-
commandBuilder: () => ["explain", "--format", "md"]
|
|
3953
|
+
commandBuilder: () => ({ kind: "run", args: ["explain", "--format", "md"] })
|
|
3859
3954
|
},
|
|
3860
3955
|
{
|
|
3861
3956
|
label: "Generate report",
|
|
3862
3957
|
description: "create a full report for a repository",
|
|
3863
|
-
commandBuilder: () => ["report", "--format", "md"]
|
|
3958
|
+
commandBuilder: () => ({ kind: "run", args: ["report", "--format", "md"] })
|
|
3864
3959
|
},
|
|
3865
3960
|
{
|
|
3866
3961
|
label: "Run policy check",
|
|
3867
3962
|
description: "execute governance gates",
|
|
3868
|
-
commandBuilder: () => ["check"]
|
|
3963
|
+
commandBuilder: () => ({ kind: "run", args: ["check"] })
|
|
3869
3964
|
},
|
|
3870
3965
|
{
|
|
3871
3966
|
label: "Scan dependency risk",
|
|
@@ -3881,18 +3976,18 @@ var runInteractiveCliMenu = async (input) => {
|
|
|
3881
3976
|
}
|
|
3882
3977
|
const selectedAction = actions[selectedIndex];
|
|
3883
3978
|
if (selectedAction === void 0) {
|
|
3884
|
-
stderr2.write(
|
|
3979
|
+
stderr2.write(`
|
|
3980
|
+
${PROMPT_PADDING}`);
|
|
3885
3981
|
return 1;
|
|
3886
3982
|
}
|
|
3887
|
-
const
|
|
3888
|
-
if (
|
|
3889
|
-
await waitForReturnToMenu();
|
|
3983
|
+
const actionResult = await selectedAction.commandBuilder();
|
|
3984
|
+
if (actionResult.kind === "cancel") {
|
|
3890
3985
|
continue;
|
|
3891
3986
|
}
|
|
3892
|
-
const exitCode = await runCliCommand(input.scriptPath, args);
|
|
3987
|
+
const exitCode = await runCliCommand(input.scriptPath, actionResult.args);
|
|
3893
3988
|
if (exitCode !== 0) {
|
|
3894
3989
|
stderr2.write(`
|
|
3895
|
-
Command exited with code ${exitCode}.
|
|
3990
|
+
${PROMPT_PADDING}Command exited with code ${exitCode}.
|
|
3896
3991
|
`);
|
|
3897
3992
|
} else {
|
|
3898
3993
|
stderr2.write("\n");
|
|
@@ -4588,6 +4683,26 @@ var finalizeAuthorDistribution = (authorCommits) => {
|
|
|
4588
4683
|
share: round44(commits / totalCommits)
|
|
4589
4684
|
})).sort((a, b) => b.commits - a.commits || a.authorId.localeCompare(b.authorId));
|
|
4590
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
|
+
};
|
|
4591
4706
|
var buildCouplingMatrix = (coChangeByPair, fileCommitCount, consideredCommits, skippedLargeCommits, maxCouplingPairs) => {
|
|
4592
4707
|
const allPairs = [];
|
|
4593
4708
|
for (const [key, coChangeCommits] of coChangeByPair.entries()) {
|
|
@@ -4656,10 +4771,19 @@ var computeRepositoryEvolutionSummary = (targetPath, commits, config) => {
|
|
|
4656
4771
|
recentCommitCount: 0,
|
|
4657
4772
|
churnAdded: 0,
|
|
4658
4773
|
churnDeleted: 0,
|
|
4659
|
-
|
|
4774
|
+
authorsByCommits: /* @__PURE__ */ new Map(),
|
|
4775
|
+
authorsByChurn: /* @__PURE__ */ new Map()
|
|
4660
4776
|
};
|
|
4661
4777
|
current.churnAdded += fileChange.additions;
|
|
4662
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);
|
|
4663
4787
|
fileStats.set(fileChange.filePath, current);
|
|
4664
4788
|
}
|
|
4665
4789
|
for (const filePath of uniqueFiles) {
|
|
@@ -4672,7 +4796,10 @@ var computeRepositoryEvolutionSummary = (targetPath, commits, config) => {
|
|
|
4672
4796
|
current.recentCommitCount += 1;
|
|
4673
4797
|
}
|
|
4674
4798
|
const effectiveAuthorId = authorAliasById.get(commit.authorId) ?? commit.authorId;
|
|
4675
|
-
current.
|
|
4799
|
+
current.authorsByCommits.set(
|
|
4800
|
+
effectiveAuthorId,
|
|
4801
|
+
(current.authorsByCommits.get(effectiveAuthorId) ?? 0) + 1
|
|
4802
|
+
);
|
|
4676
4803
|
}
|
|
4677
4804
|
const orderedFiles = [...uniqueFiles].sort((a, b) => a.localeCompare(b));
|
|
4678
4805
|
if (orderedFiles.length > 1) {
|
|
@@ -4695,8 +4822,10 @@ var computeRepositoryEvolutionSummary = (targetPath, commits, config) => {
|
|
|
4695
4822
|
}
|
|
4696
4823
|
}
|
|
4697
4824
|
const files = [...fileStats.entries()].map(([filePath, stats]) => {
|
|
4698
|
-
const
|
|
4699
|
-
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;
|
|
4700
4829
|
return {
|
|
4701
4830
|
filePath,
|
|
4702
4831
|
commitCount: stats.commitCount,
|
|
@@ -4706,9 +4835,18 @@ var computeRepositoryEvolutionSummary = (targetPath, commits, config) => {
|
|
|
4706
4835
|
churnTotal: stats.churnAdded + stats.churnDeleted,
|
|
4707
4836
|
recentCommitCount: stats.recentCommitCount,
|
|
4708
4837
|
recentVolatility: stats.commitCount === 0 ? 0 : round44(stats.recentCommitCount / stats.commitCount),
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
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
|
|
4712
4850
|
};
|
|
4713
4851
|
}).sort((a, b) => a.filePath.localeCompare(b.filePath));
|
|
4714
4852
|
const fileCommitCount = new Map(files.map((file) => [file.filePath, file.commitCount]));
|
|
@@ -5463,15 +5601,15 @@ var computeRepositoryHealthSummary = (input) => {
|
|
|
5463
5601
|
let singleContributorFiles = 0;
|
|
5464
5602
|
let trackedFiles = 0;
|
|
5465
5603
|
for (const file of evolutionSourceFiles) {
|
|
5466
|
-
if (file.commitCount <= 0 || file.
|
|
5604
|
+
if (file.commitCount <= 0 || file.authorDistributionByCommits.length === 0) {
|
|
5467
5605
|
continue;
|
|
5468
5606
|
}
|
|
5469
5607
|
trackedFiles += 1;
|
|
5470
|
-
const dominantShare = clamp01(file.
|
|
5471
|
-
if (file.
|
|
5608
|
+
const dominantShare = clamp01(file.authorDistributionByCommits[0]?.share ?? 0);
|
|
5609
|
+
if (file.authorDistributionByCommits.length === 1 || dominantShare >= 0.9) {
|
|
5472
5610
|
singleContributorFiles += 1;
|
|
5473
5611
|
}
|
|
5474
|
-
for (const author of file.
|
|
5612
|
+
for (const author of file.authorDistributionByCommits) {
|
|
5475
5613
|
const commits = Math.max(0, author.commits);
|
|
5476
5614
|
if (commits <= 0) {
|
|
5477
5615
|
continue;
|
|
@@ -6234,7 +6372,7 @@ var computeEvolutionScales = (evolutionByFile, config) => {
|
|
|
6234
6372
|
config.quantileClamp.upper
|
|
6235
6373
|
),
|
|
6236
6374
|
busFactor: buildQuantileScale(
|
|
6237
|
-
evolutionFiles.map((metrics) => metrics.
|
|
6375
|
+
evolutionFiles.map((metrics) => metrics.busFactorByCommits),
|
|
6238
6376
|
config.quantileClamp.lower,
|
|
6239
6377
|
config.quantileClamp.upper
|
|
6240
6378
|
)
|
|
@@ -6423,9 +6561,9 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
|
|
|
6423
6561
|
evolutionScales.churnTotal
|
|
6424
6562
|
);
|
|
6425
6563
|
volatilityRisk = toUnitInterval(evolutionMetrics.recentVolatility);
|
|
6426
|
-
ownershipConcentrationRisk = toUnitInterval(evolutionMetrics.
|
|
6564
|
+
ownershipConcentrationRisk = toUnitInterval(evolutionMetrics.topAuthorShareByCommits);
|
|
6427
6565
|
busFactorRisk = toUnitInterval(
|
|
6428
|
-
1 - normalizeWithScale(evolutionMetrics.
|
|
6566
|
+
1 - normalizeWithScale(evolutionMetrics.busFactorByCommits, evolutionScales.busFactor)
|
|
6429
6567
|
);
|
|
6430
6568
|
const evolutionWeights = config.evolutionFactorWeights;
|
|
6431
6569
|
evolutionFactor = toUnitInterval(
|
|
@@ -6482,8 +6620,8 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
|
|
|
6482
6620
|
commitCount: evolutionMetrics?.commitCount ?? null,
|
|
6483
6621
|
churnTotal: evolutionMetrics?.churnTotal ?? null,
|
|
6484
6622
|
recentVolatility: evolutionMetrics?.recentVolatility ?? null,
|
|
6485
|
-
|
|
6486
|
-
|
|
6623
|
+
topAuthorShareByCommits: evolutionMetrics?.topAuthorShareByCommits ?? null,
|
|
6624
|
+
busFactorByCommits: evolutionMetrics?.busFactorByCommits ?? null,
|
|
6487
6625
|
dependencyAffinity: round46(dependencyAffinity),
|
|
6488
6626
|
repositoryExternalPressure: round46(dependencyComputation.repositoryExternalPressure),
|
|
6489
6627
|
structuralAttenuation: round46(structuralAttenuation)
|
|
@@ -6551,8 +6689,8 @@ var computeRiskSummary = (structural, evolution, external, config, traceCollecto
|
|
|
6551
6689
|
commitCount: context.rawMetrics.commitCount,
|
|
6552
6690
|
churnTotal: context.rawMetrics.churnTotal,
|
|
6553
6691
|
recentVolatility: context.rawMetrics.recentVolatility,
|
|
6554
|
-
|
|
6555
|
-
|
|
6692
|
+
topAuthorShareByCommits: context.rawMetrics.topAuthorShareByCommits,
|
|
6693
|
+
busFactorByCommits: context.rawMetrics.busFactorByCommits
|
|
6556
6694
|
},
|
|
6557
6695
|
normalizedMetrics: {
|
|
6558
6696
|
frequencyRisk: context.normalizedMetrics.frequencyRisk,
|