@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 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 withDefaults = (overrides) => ({
981
- ...DEFAULT_EXTERNAL_ANALYSIS_CONFIG,
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
- onProgress?.({ stage: "package_json_loaded" });
1034
- try {
1035
- const directSpecs = parsePackageJson(packageJson.raw);
1036
- const lockfile = selectLockfile(input.repositoryPath);
1037
- let extraction;
1038
- if (lockfile === null) {
1039
- const resolvedGraph = await resolveRegistryGraphFromDirectSpecs(directSpecs, {
1040
- maxNodes: 500,
1041
- maxDepth: 8
1042
- });
1043
- if (resolvedGraph.nodes.length === 0) {
1044
- return {
1045
- targetPath: input.repositoryPath,
1046
- available: false,
1047
- reason: "lockfile_not_found"
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
- const directNames = new Set(extraction.directDependencies.map((dependency) => dependency.name));
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
- let completed = 0;
1072
- const metadataEntries = await mapWithConcurrency(
1073
- extraction.nodes,
1103
+ const metadataEntries = await collectDependencyMetadata(
1104
+ extraction,
1105
+ metadataProvider,
1074
1106
  config.metadataRequestConcurrency,
1075
- async (node) => {
1076
- const result = {
1077
- key: `${node.name}@${node.version}`,
1078
- metadata: await metadataProvider.getMetadata(node.name, node.version, {
1079
- directDependency: directNames.has(node.name)
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(input.repositoryPath, extraction, metadataByKey, config);
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: { authorIdentityMode }
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(inputPath, authorIdentityMode, logger);
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
- { includeTrace: options.includeTrace },
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
- { includeTrace: options.includeTrace },
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
- { includeTrace: options.includeTrace },
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
- { includeTrace: options.includeTrace },
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(inputPath, authorIdentityMode, logger);
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").action(
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(path, options.authorIdentity, logger);
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").action(
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").action(
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").action(
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