@getcodesentinel/codesentinel 1.17.1 → 1.17.3

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
@@ -416,6 +416,7 @@ Score direction:
416
416
 
417
417
  - `risk.riskScore`: higher means higher risk (worse).
418
418
  - `health.healthScore`: higher means better health posture.
419
+ - Report views also include derived tiers: `riskTier` and `healthTier`.
419
420
  - `health.trace`: per-dimension factor traces with normalized metrics and evidence.
420
421
 
421
422
  Health v2 dimensions and weights:
package/dist/index.js CHANGED
@@ -703,6 +703,14 @@ var DEFAULT_OPTIONS = {
703
703
  maxEntryBytes: 4 * 1024 * 1024,
704
704
  sweepIntervalWrites: 25
705
705
  };
706
+ var DEFAULT_BUCKET = "default";
707
+ var normalizeBucket = (value) => {
708
+ const trimmed = value.trim().toLowerCase();
709
+ if (trimmed.length === 0) {
710
+ return DEFAULT_BUCKET;
711
+ }
712
+ return trimmed.replace(/[^a-z0-9_-]/g, "_");
713
+ };
706
714
  var FileCacheStore = class {
707
715
  constructor(directoryPath, options = DEFAULT_OPTIONS) {
708
716
  this.directoryPath = directoryPath;
@@ -712,13 +720,22 @@ var FileCacheStore = class {
712
720
  inFlightWrites = /* @__PURE__ */ new Map();
713
721
  writesSinceSweep = 0;
714
722
  sweepPromise = Promise.resolve();
723
+ bucketForKey(key) {
724
+ const configured = this.options.bucketForKey?.(key);
725
+ if (configured === void 0) {
726
+ return DEFAULT_BUCKET;
727
+ }
728
+ return normalizeBucket(configured);
729
+ }
715
730
  toEntryPath(key) {
731
+ const bucket = this.bucketForKey(key);
716
732
  const digest = createHash("sha256").update(key).digest("hex");
717
- return join3(this.directoryPath, `${digest}.json`);
733
+ return join3(this.directoryPath, bucket, `${digest}.json`);
718
734
  }
719
735
  async writeEntry(key, entry) {
720
736
  const filePath = this.toEntryPath(key);
721
- await mkdir(this.directoryPath, { recursive: true });
737
+ const bucketPath = join3(this.directoryPath, this.bucketForKey(key));
738
+ await mkdir(bucketPath, { recursive: true });
722
739
  const tempPath = `${filePath}.tmp`;
723
740
  const payload = {
724
741
  key,
@@ -745,34 +762,74 @@ var FileCacheStore = class {
745
762
  await this.sweepPromise;
746
763
  }
747
764
  async evictToSizeLimit() {
765
+ const nowMs = Date.now();
748
766
  let entries;
749
767
  try {
750
- const dirEntries = await readdir(this.directoryPath, { withFileTypes: true });
768
+ const bucketEntries = await readdir(this.directoryPath, { withFileTypes: true });
769
+ const bucketNames = bucketEntries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
751
770
  entries = (await Promise.all(
752
- dirEntries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map(async (entry) => {
753
- const path = join3(this.directoryPath, entry.name);
754
- const info = await stat(path);
755
- return { path, size: info.size, mtimeMs: info.mtimeMs };
771
+ bucketNames.map(async (bucket) => {
772
+ const bucketPath = join3(this.directoryPath, bucket);
773
+ const files = await readdir(bucketPath, { withFileTypes: true });
774
+ return await Promise.all(
775
+ files.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map(async (entry) => {
776
+ const path = join3(bucketPath, entry.name);
777
+ const info = await stat(path);
778
+ return {
779
+ path,
780
+ size: info.size,
781
+ mtimeMs: info.mtimeMs,
782
+ bucket
783
+ };
784
+ })
785
+ );
756
786
  })
757
- )).filter((entry) => Number.isFinite(entry.size) && entry.size > 0);
787
+ )).flat().filter((entry) => Number.isFinite(entry.size) && entry.size > 0);
758
788
  } catch {
759
789
  return;
760
790
  }
791
+ if (this.options.maxAgeMsByBucket !== void 0) {
792
+ const retained = [];
793
+ let deletedAny2 = false;
794
+ for (const entry of entries) {
795
+ const maxAgeMs = this.options.maxAgeMsByBucket[entry.bucket];
796
+ const expired = typeof maxAgeMs === "number" && Number.isFinite(maxAgeMs) && nowMs - entry.mtimeMs > maxAgeMs;
797
+ if (!expired) {
798
+ retained.push(entry);
799
+ continue;
800
+ }
801
+ try {
802
+ await unlink(entry.path);
803
+ deletedAny2 = true;
804
+ } catch {
805
+ retained.push(entry);
806
+ }
807
+ }
808
+ entries = retained;
809
+ if (deletedAny2) {
810
+ this.byKey.clear();
811
+ }
812
+ }
761
813
  let totalBytes = entries.reduce((sum, entry) => sum + entry.size, 0);
762
814
  if (totalBytes <= this.options.maxBytes) {
763
815
  return;
764
816
  }
765
817
  entries.sort((a, b) => a.mtimeMs - b.mtimeMs);
818
+ let deletedAny = false;
766
819
  for (const entry of entries) {
767
820
  if (totalBytes <= this.options.maxBytes) {
768
821
  break;
769
822
  }
770
823
  try {
771
824
  await unlink(entry.path);
825
+ deletedAny = true;
772
826
  totalBytes -= entry.size;
773
827
  } catch {
774
828
  }
775
829
  }
830
+ if (deletedAny) {
831
+ this.byKey.clear();
832
+ }
776
833
  }
777
834
  async get(key) {
778
835
  if (this.byKey.has(key)) {
@@ -808,7 +865,7 @@ var FileCacheStore = class {
808
865
  };
809
866
  var SIX_HOURS_MS = 6 * 60 * 60 * 1e3;
810
867
  var ONE_DAY_MS = 24 * 60 * 60 * 1e3;
811
- var DEFAULT_MAX_BYTES = 100 * 1024 * 1024;
868
+ var DEFAULT_MAX_BYTES = 20 * 1024 * 1024;
812
869
  var DEFAULT_MAX_ENTRY_BYTES = 4 * 1024 * 1024;
813
870
  var DEFAULT_SWEEP_INTERVAL_WRITES = 25;
814
871
  var cacheStoreSingleton;
@@ -835,6 +892,8 @@ var getNpmMetadataCacheStore = () => {
835
892
  return cacheStoreSingleton;
836
893
  }
837
894
  const path = join4(resolveCodesentinelCacheDir(process.env), "npm-metadata-v2");
895
+ const packumentTtlMs = getPackumentCacheTtlMs();
896
+ const downloadsTtlMs = getWeeklyDownloadsCacheTtlMs();
838
897
  cacheStoreSingleton = new FileCacheStore(path, {
839
898
  maxBytes: parsePositiveIntegerFromEnv(
840
899
  process.env["CODESENTINEL_CACHE_MAX_BYTES"],
@@ -847,7 +906,20 @@ var getNpmMetadataCacheStore = () => {
847
906
  sweepIntervalWrites: parsePositiveIntegerFromEnv(
848
907
  process.env["CODESENTINEL_CACHE_SWEEP_INTERVAL_WRITES"],
849
908
  DEFAULT_SWEEP_INTERVAL_WRITES
850
- )
909
+ ),
910
+ bucketForKey: (key) => {
911
+ if (key.startsWith("npm:downloads:last-week:")) {
912
+ return "downloads";
913
+ }
914
+ if (key.startsWith("npm:packument:")) {
915
+ return "packument";
916
+ }
917
+ return "default";
918
+ },
919
+ maxAgeMsByBucket: {
920
+ downloads: downloadsTtlMs,
921
+ packument: packumentTtlMs
922
+ }
851
923
  });
852
924
  return cacheStoreSingleton;
853
925
  };
@@ -1700,6 +1772,21 @@ var toRiskTier = (score) => {
1700
1772
  }
1701
1773
  return "very_high";
1702
1774
  };
1775
+ var toHealthTier = (score) => {
1776
+ if (score < 20) {
1777
+ return "critical";
1778
+ }
1779
+ if (score < 40) {
1780
+ return "weak";
1781
+ }
1782
+ if (score < 60) {
1783
+ return "fair";
1784
+ }
1785
+ if (score < 80) {
1786
+ return "good";
1787
+ }
1788
+ return "excellent";
1789
+ };
1703
1790
  var factorLabelById = {
1704
1791
  "repository.structural": "Structural complexity",
1705
1792
  "repository.evolution": "Change volatility",
@@ -1936,6 +2023,7 @@ var createReport = (snapshot, diff) => {
1936
2023
  riskScore: snapshot.analysis.risk.riskScore,
1937
2024
  normalizedScore: snapshot.analysis.risk.normalizedScore,
1938
2025
  riskTier: toRiskTier(snapshot.analysis.risk.riskScore),
2026
+ healthTier: toHealthTier(snapshot.analysis.health.healthScore),
1939
2027
  confidence: repositoryConfidence(snapshot),
1940
2028
  dimensionScores: repositoryDimensionScores(snapshot)
1941
2029
  },
@@ -2003,6 +2091,7 @@ var renderTextReport = (report) => {
2003
2091
  lines.push(` riskScore: ${report.repository.riskScore}`);
2004
2092
  lines.push(` normalizedScore: ${report.repository.normalizedScore}`);
2005
2093
  lines.push(` riskTier: ${report.repository.riskTier}`);
2094
+ lines.push(` healthTier: ${report.repository.healthTier}`);
2006
2095
  lines.push(` confidence: ${report.repository.confidence ?? "n/a"}`);
2007
2096
  lines.push("");
2008
2097
  lines.push("Dimension Scores (0-100)");
@@ -2096,6 +2185,7 @@ var renderMarkdownReport = (report) => {
2096
2185
  lines.push(`- riskScore: \`${report.repository.riskScore}\``);
2097
2186
  lines.push(`- normalizedScore: \`${report.repository.normalizedScore}\``);
2098
2187
  lines.push(`- riskTier: \`${report.repository.riskTier}\``);
2188
+ lines.push(`- healthTier: \`${report.repository.healthTier}\``);
2099
2189
  lines.push(`- confidence: \`${report.repository.confidence ?? "n/a"}\``);
2100
2190
  lines.push("");
2101
2191
  lines.push("## Dimension Scores (0-100)");
@@ -2826,6 +2916,21 @@ import { dirname as dirname2, resolve as resolve5 } from "path";
2826
2916
  import { fileURLToPath } from "url";
2827
2917
 
2828
2918
  // src/application/format-analyze-output.ts
2919
+ var toHealthTier2 = (score) => {
2920
+ if (score < 20) {
2921
+ return "critical";
2922
+ }
2923
+ if (score < 40) {
2924
+ return "weak";
2925
+ }
2926
+ if (score < 60) {
2927
+ return "fair";
2928
+ }
2929
+ if (score < 80) {
2930
+ return "good";
2931
+ }
2932
+ return "excellent";
2933
+ };
2829
2934
  var createSummaryShape = (summary) => ({
2830
2935
  targetPath: summary.structural.targetPath,
2831
2936
  structural: summary.structural.metrics,
@@ -2865,6 +2970,7 @@ var createSummaryShape = (summary) => ({
2865
2970
  },
2866
2971
  health: {
2867
2972
  healthScore: summary.health.healthScore,
2973
+ healthTier: toHealthTier2(summary.health.healthScore),
2868
2974
  normalizedScore: summary.health.normalizedScore,
2869
2975
  dimensions: summary.health.dimensions,
2870
2976
  topIssues: summary.health.topIssues.slice(0, 5)
@@ -7295,6 +7401,7 @@ var renderReportHighlightsText = (report) => {
7295
7401
  lines.push(` healthScore: ${report.health.healthScore}`);
7296
7402
  lines.push(` normalizedScore: ${report.repository.normalizedScore}`);
7297
7403
  lines.push(` riskTier: ${report.repository.riskTier}`);
7404
+ lines.push(` healthTier: ${report.repository.healthTier}`);
7298
7405
  lines.push("");
7299
7406
  lines.push("Top Hotspots");
7300
7407
  for (const hotspot of report.hotspots.slice(0, 5)) {
@@ -7311,6 +7418,7 @@ var renderReportHighlightsMarkdown = (report) => {
7311
7418
  lines.push(`- healthScore: \`${report.health.healthScore}\``);
7312
7419
  lines.push(`- normalizedScore: \`${report.repository.normalizedScore}\``);
7313
7420
  lines.push(`- riskTier: \`${report.repository.riskTier}\``);
7421
+ lines.push(`- healthTier: \`${report.repository.healthTier}\``);
7314
7422
  lines.push("");
7315
7423
  lines.push("## Top Hotspots");
7316
7424
  for (const hotspot of report.hotspots.slice(0, 5)) {
@@ -7328,6 +7436,7 @@ var renderCompactText = (report, explainSummary) => {
7328
7436
  lines.push(` riskScore: ${report.repository.riskScore}`);
7329
7437
  lines.push(` healthScore: ${report.health.healthScore}`);
7330
7438
  lines.push(` riskTier: ${report.repository.riskTier}`);
7439
+ lines.push(` healthTier: ${report.repository.healthTier}`);
7331
7440
  lines.push(
7332
7441
  ` dimensions: structural=${report.repository.dimensionScores.structural ?? "n/a"}, evolution=${report.repository.dimensionScores.evolution ?? "n/a"}, external=${report.repository.dimensionScores.external ?? "n/a"}, interactions=${report.repository.dimensionScores.interactions ?? "n/a"}`
7333
7442
  );
@@ -7354,6 +7463,7 @@ var renderCompactMarkdown = (report, explainSummary) => {
7354
7463
  lines.push(`- riskScore: \`${report.repository.riskScore}\``);
7355
7464
  lines.push(`- healthScore: \`${report.health.healthScore}\``);
7356
7465
  lines.push(`- riskTier: \`${report.repository.riskTier}\``);
7466
+ lines.push(`- healthTier: \`${report.repository.healthTier}\``);
7357
7467
  lines.push(
7358
7468
  `- dimensions: structural=\`${report.repository.dimensionScores.structural ?? "n/a"}\`, evolution=\`${report.repository.dimensionScores.evolution ?? "n/a"}\`, external=\`${report.repository.dimensionScores.external ?? "n/a"}\`, interactions=\`${report.repository.dimensionScores.interactions ?? "n/a"}\``
7359
7469
  );