@getcodesentinel/codesentinel 1.9.4 → 1.10.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 +6 -1
- package/dist/index.js +205 -107
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -178,6 +178,9 @@ codesentinel analyze . --author-identity likely_merge
|
|
|
178
178
|
# Deterministic: strict email identity, no heuristic merging
|
|
179
179
|
codesentinel analyze . --author-identity strict_email
|
|
180
180
|
|
|
181
|
+
# Tune recency window (days) used for evolution volatility
|
|
182
|
+
codesentinel analyze . --recent-window-days 60
|
|
183
|
+
|
|
181
184
|
# Quiet mode (only JSON output)
|
|
182
185
|
codesentinel analyze . --log-level silent
|
|
183
186
|
|
|
@@ -203,6 +206,7 @@ codesentinel explain . --module src/components
|
|
|
203
206
|
# Explain in markdown or json
|
|
204
207
|
codesentinel explain . --format md
|
|
205
208
|
codesentinel explain . --format json
|
|
209
|
+
codesentinel explain . --recent-window-days 60
|
|
206
210
|
|
|
207
211
|
# Report generation (human + machine readable)
|
|
208
212
|
codesentinel report .
|
|
@@ -223,6 +227,7 @@ Notes:
|
|
|
223
227
|
- At `info`/`debug`, structural, evolution, and dependency stages report progress so long analyses are observable.
|
|
224
228
|
- `--output summary` (default) prints a compact result for terminal use.
|
|
225
229
|
- `--output json` (or `--json`) prints the full analysis object.
|
|
230
|
+
- `--recent-window-days <days>` customizes the git recency window used to compute `recentVolatility` (default: `30`).
|
|
226
231
|
|
|
227
232
|
When running through pnpm, pass CLI arguments after `--`:
|
|
228
233
|
|
|
@@ -318,7 +323,7 @@ Exit codes:
|
|
|
318
323
|
|
|
319
324
|
Text/markdown output includes:
|
|
320
325
|
|
|
321
|
-
- repository score and risk band (`low|moderate|high|very_high`)
|
|
326
|
+
- repository score and risk band (`low|moderate|elevated|high|very_high`)
|
|
322
327
|
- plain-language primary drivers
|
|
323
328
|
- concrete evidence values behind those drivers
|
|
324
329
|
- intersected signals (composite interaction terms)
|
package/dist/index.js
CHANGED
|
@@ -254,6 +254,46 @@ var DEFAULT_EXTERNAL_ANALYSIS_CONFIG = {
|
|
|
254
254
|
maxHighRiskDependencies: 100,
|
|
255
255
|
metadataRequestConcurrency: 8
|
|
256
256
|
};
|
|
257
|
+
var mapWithConcurrency = async (values, limit, handler) => {
|
|
258
|
+
const effectiveLimit = Math.max(1, limit);
|
|
259
|
+
const workerCount = Math.min(effectiveLimit, values.length);
|
|
260
|
+
const results = new Array(values.length);
|
|
261
|
+
let index = 0;
|
|
262
|
+
const workers = Array.from({ length: workerCount }, async () => {
|
|
263
|
+
while (true) {
|
|
264
|
+
const current = index;
|
|
265
|
+
index += 1;
|
|
266
|
+
if (current >= values.length) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const value = values[current];
|
|
270
|
+
if (value !== void 0) {
|
|
271
|
+
results[current] = await handler(value);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
await Promise.all(workers);
|
|
276
|
+
return results;
|
|
277
|
+
};
|
|
278
|
+
var collectDependencyMetadata = async (extraction, metadataProvider, concurrency, onProgress) => {
|
|
279
|
+
const directNames = new Set(extraction.directDependencies.map((dependency) => dependency.name));
|
|
280
|
+
let completed = 0;
|
|
281
|
+
return mapWithConcurrency(extraction.nodes, concurrency, async (node) => {
|
|
282
|
+
const result = {
|
|
283
|
+
key: `${node.name}@${node.version}`,
|
|
284
|
+
metadata: await metadataProvider.getMetadata(node.name, node.version, {
|
|
285
|
+
directDependency: directNames.has(node.name)
|
|
286
|
+
})
|
|
287
|
+
};
|
|
288
|
+
completed += 1;
|
|
289
|
+
onProgress?.({
|
|
290
|
+
completed,
|
|
291
|
+
total: extraction.nodes.length,
|
|
292
|
+
packageName: node.name
|
|
293
|
+
});
|
|
294
|
+
return result;
|
|
295
|
+
});
|
|
296
|
+
};
|
|
257
297
|
var LOCKFILE_CANDIDATES = [
|
|
258
298
|
{ fileName: "pnpm-lock.yaml", kind: "pnpm" },
|
|
259
299
|
{ fileName: "package-lock.json", kind: "npm" },
|
|
@@ -545,6 +585,24 @@ var parseYarnLock = (raw, directSpecs) => {
|
|
|
545
585
|
var parseBunLock = (_raw, _directSpecs) => {
|
|
546
586
|
throw new Error("unsupported_lockfile_format");
|
|
547
587
|
};
|
|
588
|
+
var parseLockfileExtraction = (lockfileKind, lockfileRaw, directSpecs) => {
|
|
589
|
+
switch (lockfileKind) {
|
|
590
|
+
case "pnpm":
|
|
591
|
+
return parsePnpmLockfile(lockfileRaw, directSpecs);
|
|
592
|
+
case "npm":
|
|
593
|
+
case "npm-shrinkwrap":
|
|
594
|
+
return {
|
|
595
|
+
...parsePackageLock(lockfileRaw, directSpecs),
|
|
596
|
+
kind: lockfileKind
|
|
597
|
+
};
|
|
598
|
+
case "yarn":
|
|
599
|
+
return parseYarnLock(lockfileRaw, directSpecs);
|
|
600
|
+
case "bun":
|
|
601
|
+
return parseBunLock(lockfileRaw, directSpecs);
|
|
602
|
+
default:
|
|
603
|
+
throw new Error("unsupported_lockfile_format");
|
|
604
|
+
}
|
|
605
|
+
};
|
|
548
606
|
var parseRetryAfterMs = (value) => {
|
|
549
607
|
if (value === null) {
|
|
550
608
|
return null;
|
|
@@ -977,77 +1035,31 @@ var resolveRegistryGraphFromDirectSpecs = async (directSpecs, options) => {
|
|
|
977
1035
|
truncated
|
|
978
1036
|
};
|
|
979
1037
|
};
|
|
980
|
-
var
|
|
981
|
-
|
|
982
|
-
...overrides
|
|
983
|
-
});
|
|
984
|
-
var parseExtraction = (lockfileKind, lockfileRaw, directSpecs) => {
|
|
985
|
-
switch (lockfileKind) {
|
|
986
|
-
case "pnpm":
|
|
987
|
-
return parsePnpmLockfile(lockfileRaw, directSpecs);
|
|
988
|
-
case "npm":
|
|
989
|
-
case "npm-shrinkwrap":
|
|
990
|
-
return {
|
|
991
|
-
...parsePackageLock(lockfileRaw, directSpecs),
|
|
992
|
-
kind: lockfileKind
|
|
993
|
-
};
|
|
994
|
-
case "yarn":
|
|
995
|
-
return parseYarnLock(lockfileRaw, directSpecs);
|
|
996
|
-
case "bun":
|
|
997
|
-
return parseBunLock(lockfileRaw, directSpecs);
|
|
998
|
-
default:
|
|
999
|
-
throw new Error("unsupported_lockfile_format");
|
|
1000
|
-
}
|
|
1001
|
-
};
|
|
1002
|
-
var mapWithConcurrency = async (values, limit, handler) => {
|
|
1003
|
-
const effectiveLimit = Math.max(1, limit);
|
|
1004
|
-
const workerCount = Math.min(effectiveLimit, values.length);
|
|
1005
|
-
const results = new Array(values.length);
|
|
1006
|
-
let index = 0;
|
|
1007
|
-
const workers = Array.from({ length: workerCount }, async () => {
|
|
1008
|
-
while (true) {
|
|
1009
|
-
const current = index;
|
|
1010
|
-
index += 1;
|
|
1011
|
-
if (current >= values.length) {
|
|
1012
|
-
return;
|
|
1013
|
-
}
|
|
1014
|
-
const value = values[current];
|
|
1015
|
-
if (value !== void 0) {
|
|
1016
|
-
results[current] = await handler(value);
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
});
|
|
1020
|
-
await Promise.all(workers);
|
|
1021
|
-
return results;
|
|
1022
|
-
};
|
|
1023
|
-
var analyzeDependencyExposure = async (input, metadataProvider, onProgress) => {
|
|
1024
|
-
const config = withDefaults(input.config);
|
|
1025
|
-
const packageJson = loadPackageJson(input.repositoryPath);
|
|
1038
|
+
var prepareDependencyExtraction = async (repositoryPath) => {
|
|
1039
|
+
const packageJson = loadPackageJson(repositoryPath);
|
|
1026
1040
|
if (packageJson === null) {
|
|
1027
1041
|
return {
|
|
1028
|
-
targetPath: input.repositoryPath,
|
|
1029
1042
|
available: false,
|
|
1030
1043
|
reason: "package_json_not_found"
|
|
1031
1044
|
};
|
|
1032
1045
|
}
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
const
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
extraction = {
|
|
1046
|
+
const directSpecs = parsePackageJson(packageJson.raw);
|
|
1047
|
+
const lockfile = selectLockfile(repositoryPath);
|
|
1048
|
+
if (lockfile === null) {
|
|
1049
|
+
const resolvedGraph = await resolveRegistryGraphFromDirectSpecs(directSpecs, {
|
|
1050
|
+
maxNodes: 500,
|
|
1051
|
+
maxDepth: 8
|
|
1052
|
+
});
|
|
1053
|
+
if (resolvedGraph.nodes.length === 0) {
|
|
1054
|
+
return {
|
|
1055
|
+
available: false,
|
|
1056
|
+
reason: "lockfile_not_found"
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
return {
|
|
1060
|
+
available: true,
|
|
1061
|
+
lockfileKind: "npm",
|
|
1062
|
+
extraction: {
|
|
1051
1063
|
kind: "npm",
|
|
1052
1064
|
directDependencies: resolvedGraph.directDependencies.map((dependency) => ({
|
|
1053
1065
|
name: dependency.name,
|
|
@@ -1055,46 +1067,61 @@ var analyzeDependencyExposure = async (input, metadataProvider, onProgress) => {
|
|
|
1055
1067
|
scope: dependency.scope
|
|
1056
1068
|
})),
|
|
1057
1069
|
nodes: resolvedGraph.nodes
|
|
1070
|
+
}
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
return {
|
|
1074
|
+
available: true,
|
|
1075
|
+
lockfileKind: lockfile.kind,
|
|
1076
|
+
extraction: parseLockfileExtraction(lockfile.kind, lockfile.raw, directSpecs)
|
|
1077
|
+
};
|
|
1078
|
+
};
|
|
1079
|
+
var withDefaults = (overrides) => ({
|
|
1080
|
+
...DEFAULT_EXTERNAL_ANALYSIS_CONFIG,
|
|
1081
|
+
...overrides
|
|
1082
|
+
});
|
|
1083
|
+
var analyzeDependencyExposure = async (input, metadataProvider, onProgress) => {
|
|
1084
|
+
const config = withDefaults(input.config);
|
|
1085
|
+
try {
|
|
1086
|
+
const prepared = await prepareDependencyExtraction(input.repositoryPath);
|
|
1087
|
+
if (!prepared.available) {
|
|
1088
|
+
return {
|
|
1089
|
+
targetPath: input.repositoryPath,
|
|
1090
|
+
available: false,
|
|
1091
|
+
reason: prepared.reason
|
|
1058
1092
|
};
|
|
1059
|
-
onProgress?.({ stage: "lockfile_selected", kind: "npm" });
|
|
1060
|
-
} else {
|
|
1061
|
-
extraction = parseExtraction(lockfile.kind, lockfile.raw, directSpecs);
|
|
1062
|
-
onProgress?.({ stage: "lockfile_selected", kind: lockfile.kind });
|
|
1063
1093
|
}
|
|
1064
|
-
|
|
1094
|
+
onProgress?.({ stage: "package_json_loaded" });
|
|
1095
|
+
onProgress?.({ stage: "lockfile_selected", kind: prepared.lockfileKind });
|
|
1096
|
+
const { extraction } = prepared;
|
|
1065
1097
|
onProgress?.({
|
|
1066
1098
|
stage: "lockfile_parsed",
|
|
1067
1099
|
dependencyNodes: extraction.nodes.length,
|
|
1068
1100
|
directDependencies: extraction.directDependencies.length
|
|
1069
1101
|
});
|
|
1070
1102
|
onProgress?.({ stage: "metadata_fetch_started", total: extraction.nodes.length });
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1103
|
+
const metadataEntries = await collectDependencyMetadata(
|
|
1104
|
+
extraction,
|
|
1105
|
+
metadataProvider,
|
|
1074
1106
|
config.metadataRequestConcurrency,
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
};
|
|
1082
|
-
completed += 1;
|
|
1083
|
-
onProgress?.({
|
|
1084
|
-
stage: "metadata_fetch_progress",
|
|
1085
|
-
completed,
|
|
1086
|
-
total: extraction.nodes.length,
|
|
1087
|
-
packageName: node.name
|
|
1088
|
-
});
|
|
1089
|
-
return result;
|
|
1090
|
-
}
|
|
1107
|
+
(event) => onProgress?.({
|
|
1108
|
+
stage: "metadata_fetch_progress",
|
|
1109
|
+
completed: event.completed,
|
|
1110
|
+
total: event.total,
|
|
1111
|
+
packageName: event.packageName
|
|
1112
|
+
})
|
|
1091
1113
|
);
|
|
1092
1114
|
onProgress?.({ stage: "metadata_fetch_completed", total: extraction.nodes.length });
|
|
1093
1115
|
const metadataByKey = /* @__PURE__ */ new Map();
|
|
1094
1116
|
for (const entry of metadataEntries) {
|
|
1095
1117
|
metadataByKey.set(entry.key, entry.metadata);
|
|
1096
1118
|
}
|
|
1097
|
-
const summary = buildExternalAnalysisSummary(
|
|
1119
|
+
const summary = buildExternalAnalysisSummary(
|
|
1120
|
+
input.repositoryPath,
|
|
1121
|
+
extraction,
|
|
1122
|
+
metadataByKey,
|
|
1123
|
+
config
|
|
1124
|
+
);
|
|
1098
1125
|
if (summary.available) {
|
|
1099
1126
|
onProgress?.({
|
|
1100
1127
|
stage: "summary_built",
|
|
@@ -4965,7 +4992,7 @@ var createEvolutionProgressReporter = (logger) => {
|
|
|
4965
4992
|
}
|
|
4966
4993
|
};
|
|
4967
4994
|
};
|
|
4968
|
-
var collectAnalysisInputs = async (inputPath, authorIdentityMode, logger = createSilentLogger()) => {
|
|
4995
|
+
var collectAnalysisInputs = async (inputPath, authorIdentityMode, options = {}, logger = createSilentLogger()) => {
|
|
4969
4996
|
const invocationCwd = process.env["INIT_CWD"] ?? process.cwd();
|
|
4970
4997
|
const targetPath = resolveTargetPath(inputPath, invocationCwd);
|
|
4971
4998
|
logger.info(`analyzing repository: ${targetPath}`);
|
|
@@ -4981,7 +5008,10 @@ var collectAnalysisInputs = async (inputPath, authorIdentityMode, logger = creat
|
|
|
4981
5008
|
const evolution = analyzeRepositoryEvolutionFromGit(
|
|
4982
5009
|
{
|
|
4983
5010
|
repositoryPath: targetPath,
|
|
4984
|
-
config: {
|
|
5011
|
+
config: {
|
|
5012
|
+
authorIdentityMode,
|
|
5013
|
+
...options.recentWindowDays === void 0 ? {} : { recentWindowDays: options.recentWindowDays }
|
|
5014
|
+
}
|
|
4985
5015
|
},
|
|
4986
5016
|
createEvolutionProgressReporter(logger)
|
|
4987
5017
|
);
|
|
@@ -5010,8 +5040,8 @@ var collectAnalysisInputs = async (inputPath, authorIdentityMode, logger = creat
|
|
|
5010
5040
|
external
|
|
5011
5041
|
};
|
|
5012
5042
|
};
|
|
5013
|
-
var runAnalyzeCommand = async (inputPath, authorIdentityMode, logger = createSilentLogger()) => {
|
|
5014
|
-
const analysisInputs = await collectAnalysisInputs(inputPath, authorIdentityMode, logger);
|
|
5043
|
+
var runAnalyzeCommand = async (inputPath, authorIdentityMode, options = {}, logger = createSilentLogger()) => {
|
|
5044
|
+
const analysisInputs = await collectAnalysisInputs(inputPath, authorIdentityMode, options, logger);
|
|
5015
5045
|
logger.info("computing risk summary");
|
|
5016
5046
|
const risk = computeRepositoryRiskSummary(analysisInputs);
|
|
5017
5047
|
logger.info(`analysis completed (repositoryScore=${risk.repositoryScore})`);
|
|
@@ -5026,7 +5056,14 @@ import { readFile, writeFile } from "fs/promises";
|
|
|
5026
5056
|
|
|
5027
5057
|
// src/application/build-analysis-snapshot.ts
|
|
5028
5058
|
var buildAnalysisSnapshot = async (inputPath, authorIdentityMode, options, logger) => {
|
|
5029
|
-
const analysisInputs = await collectAnalysisInputs(
|
|
5059
|
+
const analysisInputs = await collectAnalysisInputs(
|
|
5060
|
+
inputPath,
|
|
5061
|
+
authorIdentityMode,
|
|
5062
|
+
{
|
|
5063
|
+
...options.recentWindowDays === void 0 ? {} : { recentWindowDays: options.recentWindowDays }
|
|
5064
|
+
},
|
|
5065
|
+
logger
|
|
5066
|
+
);
|
|
5030
5067
|
const evaluation = evaluateRepositoryRisk(analysisInputs, { explain: options.includeTrace });
|
|
5031
5068
|
const summary = {
|
|
5032
5069
|
...analysisInputs,
|
|
@@ -5037,7 +5074,8 @@ var buildAnalysisSnapshot = async (inputPath, authorIdentityMode, options, logge
|
|
|
5037
5074
|
...evaluation.trace === void 0 ? {} : { trace: evaluation.trace },
|
|
5038
5075
|
analysisConfig: {
|
|
5039
5076
|
authorIdentityMode,
|
|
5040
|
-
includeTrace: options.includeTrace
|
|
5077
|
+
includeTrace: options.includeTrace,
|
|
5078
|
+
recentWindowDays: analysisInputs.evolution.available ? analysisInputs.evolution.metrics.recentWindowDays : options.recentWindowDays ?? null
|
|
5041
5079
|
}
|
|
5042
5080
|
});
|
|
5043
5081
|
};
|
|
@@ -5069,7 +5107,10 @@ var runCheckCommand = async (inputPath, authorIdentityMode, options, logger = cr
|
|
|
5069
5107
|
const current = await buildAnalysisSnapshot(
|
|
5070
5108
|
inputPath,
|
|
5071
5109
|
authorIdentityMode,
|
|
5072
|
-
{
|
|
5110
|
+
{
|
|
5111
|
+
includeTrace: options.includeTrace,
|
|
5112
|
+
...options.recentWindowDays === void 0 ? {} : { recentWindowDays: options.recentWindowDays }
|
|
5113
|
+
},
|
|
5073
5114
|
logger
|
|
5074
5115
|
);
|
|
5075
5116
|
let baseline;
|
|
@@ -5136,7 +5177,10 @@ var runCiCommand = async (inputPath, authorIdentityMode, options, logger = creat
|
|
|
5136
5177
|
const current = await buildAnalysisSnapshot(
|
|
5137
5178
|
inputPath,
|
|
5138
5179
|
authorIdentityMode,
|
|
5139
|
-
{
|
|
5180
|
+
{
|
|
5181
|
+
includeTrace: options.includeTrace,
|
|
5182
|
+
...options.recentWindowDays === void 0 ? {} : { recentWindowDays: options.recentWindowDays }
|
|
5183
|
+
},
|
|
5140
5184
|
logger
|
|
5141
5185
|
);
|
|
5142
5186
|
if (options.snapshotPath !== void 0) {
|
|
@@ -5191,7 +5235,10 @@ var runCiCommand = async (inputPath, authorIdentityMode, options, logger = creat
|
|
|
5191
5235
|
return buildAnalysisSnapshot(
|
|
5192
5236
|
baselineTargetPath,
|
|
5193
5237
|
authorIdentityMode,
|
|
5194
|
-
{
|
|
5238
|
+
{
|
|
5239
|
+
includeTrace: options.includeTrace,
|
|
5240
|
+
...options.recentWindowDays === void 0 ? {} : { recentWindowDays: options.recentWindowDays }
|
|
5241
|
+
},
|
|
5195
5242
|
logger
|
|
5196
5243
|
);
|
|
5197
5244
|
}
|
|
@@ -5263,7 +5310,10 @@ var runReportCommand = async (inputPath, authorIdentityMode, options, logger = c
|
|
|
5263
5310
|
const current = await buildAnalysisSnapshot(
|
|
5264
5311
|
inputPath,
|
|
5265
5312
|
authorIdentityMode,
|
|
5266
|
-
{
|
|
5313
|
+
{
|
|
5314
|
+
includeTrace: options.includeTrace,
|
|
5315
|
+
...options.recentWindowDays === void 0 ? {} : { recentWindowDays: options.recentWindowDays }
|
|
5316
|
+
},
|
|
5267
5317
|
logger
|
|
5268
5318
|
);
|
|
5269
5319
|
if (options.snapshotPath !== void 0) {
|
|
@@ -5309,7 +5359,14 @@ var selectTargets = (trace, summary, options) => {
|
|
|
5309
5359
|
);
|
|
5310
5360
|
};
|
|
5311
5361
|
var runExplainCommand = async (inputPath, authorIdentityMode, options, logger = createSilentLogger()) => {
|
|
5312
|
-
const analysisInputs = await collectAnalysisInputs(
|
|
5362
|
+
const analysisInputs = await collectAnalysisInputs(
|
|
5363
|
+
inputPath,
|
|
5364
|
+
authorIdentityMode,
|
|
5365
|
+
{
|
|
5366
|
+
...options.recentWindowDays === void 0 ? {} : { recentWindowDays: options.recentWindowDays }
|
|
5367
|
+
},
|
|
5368
|
+
logger
|
|
5369
|
+
);
|
|
5313
5370
|
logger.info("computing explainable risk summary");
|
|
5314
5371
|
const evaluation = evaluateRepositoryRisk(analysisInputs, { explain: true });
|
|
5315
5372
|
if (evaluation.trace === void 0) {
|
|
@@ -5331,6 +5388,13 @@ var runExplainCommand = async (inputPath, authorIdentityMode, options, logger =
|
|
|
5331
5388
|
var program = new Command();
|
|
5332
5389
|
var packageJsonPath = resolve5(dirname(fileURLToPath(import.meta.url)), "../package.json");
|
|
5333
5390
|
var { version } = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
|
|
5391
|
+
var parseRecentWindowDays = (value) => {
|
|
5392
|
+
const parsed = Number.parseInt(value, 10);
|
|
5393
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
5394
|
+
throw new Error("--recent-window-days must be a positive integer");
|
|
5395
|
+
}
|
|
5396
|
+
return parsed;
|
|
5397
|
+
};
|
|
5334
5398
|
program.name("codesentinel").description("Structural and evolutionary risk analysis for TypeScript/JavaScript codebases").version(version);
|
|
5335
5399
|
program.command("analyze").argument("[path]", "path to the project to analyze").addOption(
|
|
5336
5400
|
new Option(
|
|
@@ -5344,10 +5408,20 @@ program.command("analyze").argument("[path]", "path to the project to analyze").
|
|
|
5344
5408
|
).choices(["silent", "error", "warn", "info", "debug"]).default(parseLogLevel(process.env["CODESENTINEL_LOG_LEVEL"]))
|
|
5345
5409
|
).addOption(
|
|
5346
5410
|
new Option("--output <mode>", "output mode: summary (default) or json (full analysis object)").choices(["summary", "json"]).default("summary")
|
|
5347
|
-
).option("--json", "shortcut for --output json").
|
|
5411
|
+
).option("--json", "shortcut for --output json").addOption(
|
|
5412
|
+
new Option(
|
|
5413
|
+
"--recent-window-days <days>",
|
|
5414
|
+
"git recency window in days used for evolution volatility metrics"
|
|
5415
|
+
).argParser(parseRecentWindowDays).default(30)
|
|
5416
|
+
).action(
|
|
5348
5417
|
async (path, options) => {
|
|
5349
5418
|
const logger = createStderrLogger(options.logLevel);
|
|
5350
|
-
const summary = await runAnalyzeCommand(
|
|
5419
|
+
const summary = await runAnalyzeCommand(
|
|
5420
|
+
path,
|
|
5421
|
+
options.authorIdentity,
|
|
5422
|
+
{ recentWindowDays: options.recentWindowDays },
|
|
5423
|
+
logger
|
|
5424
|
+
);
|
|
5351
5425
|
const outputMode = options.json === true ? "json" : options.output;
|
|
5352
5426
|
process.stdout.write(`${formatAnalyzeOutput(summary, outputMode)}
|
|
5353
5427
|
`);
|
|
@@ -5364,6 +5438,11 @@ program.command("explain").argument("[path]", "path to the project to analyze").
|
|
|
5364
5438
|
"log verbosity: silent, error, warn, info, debug (logs are written to stderr)"
|
|
5365
5439
|
).choices(["silent", "error", "warn", "info", "debug"]).default(parseLogLevel(process.env["CODESENTINEL_LOG_LEVEL"]))
|
|
5366
5440
|
).option("--file <path>", "explain a specific file target").option("--module <name>", "explain a specific module target").option("--top <count>", "number of top hotspots to explain when no target is selected", "5").addOption(
|
|
5441
|
+
new Option(
|
|
5442
|
+
"--recent-window-days <days>",
|
|
5443
|
+
"git recency window in days used for evolution volatility metrics"
|
|
5444
|
+
).argParser(parseRecentWindowDays).default(30)
|
|
5445
|
+
).addOption(
|
|
5367
5446
|
new Option("--format <mode>", "output format: text, json, md").choices(["text", "json", "md"]).default("md")
|
|
5368
5447
|
).action(
|
|
5369
5448
|
async (path, options) => {
|
|
@@ -5373,6 +5452,7 @@ program.command("explain").argument("[path]", "path to the project to analyze").
|
|
|
5373
5452
|
...options.file === void 0 ? {} : { file: options.file },
|
|
5374
5453
|
...options.module === void 0 ? {} : { module: options.module },
|
|
5375
5454
|
top: Number.isFinite(top) ? top : 5,
|
|
5455
|
+
recentWindowDays: options.recentWindowDays,
|
|
5376
5456
|
format: options.format
|
|
5377
5457
|
};
|
|
5378
5458
|
const result = await runExplainCommand(path, options.authorIdentity, explainOptions, logger);
|
|
@@ -5422,7 +5502,12 @@ program.command("report").argument("[path]", "path to the project to analyze").a
|
|
|
5422
5502
|
).choices(["silent", "error", "warn", "info", "debug"]).default(parseLogLevel(process.env["CODESENTINEL_LOG_LEVEL"]))
|
|
5423
5503
|
).addOption(
|
|
5424
5504
|
new Option("--format <mode>", "output format: text, json, md").choices(["text", "json", "md"]).default("md")
|
|
5425
|
-
).option("--output <path>", "write rendered report to a file path").option("--compare <baseline>", "compare against a baseline snapshot JSON file").option("--snapshot <path>", "write current snapshot JSON artifact").option("--no-trace", "disable trace embedding in generated snapshot").
|
|
5505
|
+
).option("--output <path>", "write rendered report to a file path").option("--compare <baseline>", "compare against a baseline snapshot JSON file").option("--snapshot <path>", "write current snapshot JSON artifact").option("--no-trace", "disable trace embedding in generated snapshot").addOption(
|
|
5506
|
+
new Option(
|
|
5507
|
+
"--recent-window-days <days>",
|
|
5508
|
+
"git recency window in days used for evolution volatility metrics"
|
|
5509
|
+
).argParser(parseRecentWindowDays).default(30)
|
|
5510
|
+
).action(
|
|
5426
5511
|
async (path, options) => {
|
|
5427
5512
|
const logger = createStderrLogger(options.logLevel);
|
|
5428
5513
|
const result = await runReportCommand(
|
|
@@ -5433,7 +5518,8 @@ program.command("report").argument("[path]", "path to the project to analyze").a
|
|
|
5433
5518
|
...options.output === void 0 ? {} : { outputPath: options.output },
|
|
5434
5519
|
...options.compare === void 0 ? {} : { comparePath: options.compare },
|
|
5435
5520
|
...options.snapshot === void 0 ? {} : { snapshotPath: options.snapshot },
|
|
5436
|
-
includeTrace: options.trace
|
|
5521
|
+
includeTrace: options.trace,
|
|
5522
|
+
recentWindowDays: options.recentWindowDays
|
|
5437
5523
|
},
|
|
5438
5524
|
logger
|
|
5439
5525
|
);
|
|
@@ -5498,7 +5584,12 @@ program.command("check").argument("[path]", "path to the project to analyze").ad
|
|
|
5498
5584
|
new Option("--fail-on <level>", "failing severity threshold").choices(["error", "warn"]).default("error")
|
|
5499
5585
|
).addOption(
|
|
5500
5586
|
new Option("--format <mode>", "output format: text, json, md").choices(["text", "json", "md"]).default("text")
|
|
5501
|
-
).option("--output <path>", "write check output to a file path").option("--no-trace", "disable trace embedding in generated snapshot").
|
|
5587
|
+
).option("--output <path>", "write check output to a file path").option("--no-trace", "disable trace embedding in generated snapshot").addOption(
|
|
5588
|
+
new Option(
|
|
5589
|
+
"--recent-window-days <days>",
|
|
5590
|
+
"git recency window in days used for evolution volatility metrics"
|
|
5591
|
+
).argParser(parseRecentWindowDays).default(30)
|
|
5592
|
+
).action(
|
|
5502
5593
|
async (path, options) => {
|
|
5503
5594
|
const logger = createStderrLogger(options.logLevel);
|
|
5504
5595
|
try {
|
|
@@ -5509,6 +5600,7 @@ program.command("check").argument("[path]", "path to the project to analyze").ad
|
|
|
5509
5600
|
{
|
|
5510
5601
|
...options.compare === void 0 ? {} : { baselinePath: options.compare },
|
|
5511
5602
|
includeTrace: options.trace,
|
|
5603
|
+
recentWindowDays: options.recentWindowDays,
|
|
5512
5604
|
gateConfig,
|
|
5513
5605
|
outputFormat: options.format,
|
|
5514
5606
|
...options.output === void 0 ? {} : { outputPath: options.output }
|
|
@@ -5557,7 +5649,12 @@ program.command("ci").argument("[path]", "path to the project to analyze").addOp
|
|
|
5557
5649
|
"comma-separated default branch candidates for auto baseline resolution (for example: main,master)"
|
|
5558
5650
|
).option("--snapshot <path>", "write current snapshot JSON to path").option("--report <path>", "write markdown CI summary report").option("--json-output <path>", "write machine-readable CI JSON output").option("--max-repo-delta <value>", "maximum allowed normalized repository score increase").option("--no-new-cycles", "fail if new structural cycles are introduced").option("--no-new-high-risk-deps", "fail if new high-risk direct dependencies are introduced").option("--max-new-hotspots <count>", "maximum allowed number of new hotspots").option("--new-hotspot-score-threshold <score>", "minimum hotspot score to count as new hotspot").option("--max-repo-score <score>", "absolute repository score limit (0..100)").addOption(
|
|
5559
5651
|
new Option("--fail-on <level>", "failing severity threshold").choices(["error", "warn"]).default("error")
|
|
5560
|
-
).option("--no-trace", "disable trace embedding in generated snapshot").
|
|
5652
|
+
).option("--no-trace", "disable trace embedding in generated snapshot").addOption(
|
|
5653
|
+
new Option(
|
|
5654
|
+
"--recent-window-days <days>",
|
|
5655
|
+
"git recency window in days used for evolution volatility metrics"
|
|
5656
|
+
).argParser(parseRecentWindowDays).default(30)
|
|
5657
|
+
).action(
|
|
5561
5658
|
async (path, options) => {
|
|
5562
5659
|
const logger = createStderrLogger(options.logLevel);
|
|
5563
5660
|
try {
|
|
@@ -5575,6 +5672,7 @@ program.command("ci").argument("[path]", "path to the project to analyze").addOp
|
|
|
5575
5672
|
...options.report === void 0 ? {} : { reportPath: options.report },
|
|
5576
5673
|
...options.jsonOutput === void 0 ? {} : { jsonOutputPath: options.jsonOutput },
|
|
5577
5674
|
includeTrace: options.trace,
|
|
5675
|
+
recentWindowDays: options.recentWindowDays,
|
|
5578
5676
|
gateConfig
|
|
5579
5677
|
},
|
|
5580
5678
|
logger
|