@hublo/sentinel 1.5.0-alpha.21 → 1.5.0-alpha.22
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/dist/bin/sentinel.js
CHANGED
|
@@ -5283,14 +5283,25 @@ function scanGraph(cwd, config) {
|
|
|
5283
5283
|
import { mkdtempSync as mkdtempSync2, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
5284
5284
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
5285
5285
|
import { join as join20 } from "node:path";
|
|
5286
|
-
var
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5286
|
+
var GAUGE_THRESHOLDS = {
|
|
5287
|
+
// McCabe 1976 says 10, our p90 says 11. The only indicator where two independent candidates agree
|
|
5288
|
+
// within one unit, and 94.8% of our functions already sit in the 1-10 band SEI calls low risk.
|
|
5289
|
+
complexity: 10,
|
|
5290
|
+
// IfSQ SP-2, "few people can understand nesting to more than 3 levels". Our p90 agrees at 3.
|
|
5291
|
+
"max-depth": 3,
|
|
5292
|
+
// No research anywhere for a FILE's length: IfSQ's three citations (500, 100-150, 143) are all
|
|
5293
|
+
// about routines. So our p90 of 157, rounded down.
|
|
5294
|
+
"max-lines": 150,
|
|
5295
|
+
// Our p90. IfSQ SP-1 says 150 and is discarded for being more permissive than our own code.
|
|
5296
|
+
"max-lines-per-function": 109,
|
|
5297
|
+
// No research. Our p90 is 15, the rule's default is 10, so the default stays as the stricter one.
|
|
5291
5298
|
"max-statements": 10,
|
|
5299
|
+
// No research, and none needed: beyond three parameters the fix is an object or currying, so this
|
|
5300
|
+
// is a design boundary and not a dial. Our p90 of 4 is the looser candidate and loses.
|
|
5292
5301
|
"max-params": 3,
|
|
5293
|
-
|
|
5302
|
+
// Same nesting family as `max-depth`, no citation of its own. Our p90 agrees at 3.
|
|
5303
|
+
"max-nested-callbacks": 3,
|
|
5304
|
+
// 1 is the rule's floor, there is no stricter value to pick.
|
|
5294
5305
|
"max-classes-per-file": 1
|
|
5295
5306
|
};
|
|
5296
5307
|
var CATEGORIES = [
|
|
@@ -5312,7 +5323,7 @@ function sizeRulesConfig() {
|
|
|
5312
5323
|
// `warn` exits 0, and both report the same 18 diagnostics. So the tool measures, this role
|
|
5313
5324
|
// judges, and a non-zero exit means the tool failed.
|
|
5314
5325
|
rules: Object.fromEntries(
|
|
5315
|
-
Object.entries(
|
|
5326
|
+
Object.entries(GAUGE_THRESHOLDS).map(([rule, threshold]) => [rule, ["warn", threshold]])
|
|
5316
5327
|
)
|
|
5317
5328
|
};
|
|
5318
5329
|
}
|
|
@@ -5341,7 +5352,8 @@ function ruleOf(code) {
|
|
|
5341
5352
|
return /\(([^)]+)\)\s*$/.exec(code)?.[1];
|
|
5342
5353
|
}
|
|
5343
5354
|
function valueOf(message) {
|
|
5344
|
-
|
|
5355
|
+
const match = /\((\d+)\)/.exec(message) ?? /complexity of (\d+)/.exec(message);
|
|
5356
|
+
return Number(match?.[1] ?? 0);
|
|
5345
5357
|
}
|
|
5346
5358
|
function breachesOf(raw) {
|
|
5347
5359
|
let parsed;
|
|
@@ -5355,7 +5367,7 @@ function breachesOf(raw) {
|
|
|
5355
5367
|
for (const diagnostic of diagnostics) {
|
|
5356
5368
|
const rule = ruleOf(diagnostic.code ?? "");
|
|
5357
5369
|
const file = diagnostic.filename;
|
|
5358
|
-
if (!rule || !file || !(rule in
|
|
5370
|
+
if (!rule || !file || !(rule in GAUGE_THRESHOLDS)) continue;
|
|
5359
5371
|
const value = valueOf(diagnostic.message ?? "");
|
|
5360
5372
|
const line = diagnostic.labels?.[0]?.span?.line ?? 0;
|
|
5361
5373
|
const key = `${file}\0${rule}`;
|
|
@@ -5524,59 +5536,59 @@ var metricsHelp = {
|
|
|
5524
5536
|
},
|
|
5525
5537
|
complexity: {
|
|
5526
5538
|
label: "Files with a complex function",
|
|
5527
|
-
what: "Files holding at least one function whose cyclomatic complexity passes
|
|
5539
|
+
what: "Files holding at least one function whose cyclomatic complexity passes 10.",
|
|
5528
5540
|
read: "Count of files, so it is comparable between modules only against their file count, which the unit carries. It is the same measure as the band rows above, seen per file rather than per function. A file is counted once per indicator, however many times the rule fires inside it. Measured on `libs/nest/auth`, counting diagnostics instead would have inflated the total by 1.8x, and by 2.8x at lower thresholds, because `max-nested-callbacks` reports once per level.",
|
|
5529
|
-
caveat: "It does not say which function, nor whether branching is wrong there: a parser legitimately branches. \u26D4 This is a GAUGE and not the line your lint refuses at. It runs oxlint with its own config in a temporary directory, at
|
|
5530
|
-
source: "oxlint complexity, at
|
|
5541
|
+
caveat: "It does not say which function, nor whether branching is wrong there: a parser legitimately branches. \u26D4 This is a GAUGE and not the line your lint refuses at. It runs oxlint with its own config in a temporary directory, at a threshold fixed once for every module, and never reads the module's `.oxlintrc.json`. So it answers the same question the same way in every module, including the 146 of 198 that have not adopted lint, and it WILL disagree with a green build: the `nest` preset refuses `complexity` at 30 while this reports above 10.",
|
|
5542
|
+
source: "oxlint complexity, above 10. The threshold comes from McCabe 1976, which puts the limit at 10, and from our own p90 at 11: the one indicator where two independent references agree within a unit. 94.8% of our functions already sit in the 1-10 band. The rule for every one of the eight is `min(research, our p90, the rule's default)`, and a vendor default alone is not a reference: at 50, max-lines-per-function flagged 21.3% of our files and carried 77% of the union of all eight. The exclusions are the same list the duplication product pass is given, passed as `--ignore-pattern`: the `ignorePatterns` key in oxlint's config is accepted and silently ignored, which had this gauge measuring the test suite until the file counts were compared, 137 against 111 on `libs/nest/auth`."
|
|
5531
5543
|
},
|
|
5532
5544
|
"max-depth": {
|
|
5533
5545
|
label: "Files with deep nesting",
|
|
5534
|
-
what: "Files holding a block nested more than
|
|
5546
|
+
what: "Files holding a block nested more than three levels deep.",
|
|
5535
5547
|
read: "The rarest of the indicators, 0.6 firings per 100 files across the repository, so one occurrence is worth opening rather than filtering. A file is counted once per indicator, however many times the rule fires inside it. Measured on `libs/nest/auth`, counting diagnostics instead would have inflated the total by 1.8x, and by 2.8x at lower thresholds, because `max-nested-callbacks` reports once per level.",
|
|
5536
5548
|
caveat: "Depth is not complexity: four flat `if`s in a row never trip it, and one `if` inside three loops does.",
|
|
5537
|
-
source: "oxlint max-depth, at
|
|
5549
|
+
source: "oxlint max-depth, above 3. The threshold is IfSQ SP-2, which says few people can understand nesting of conditional statements beyond 3 levels, and our p90 agrees at 3. The rule for every one of the eight is `min(research, our p90, the rule's default)`, and a vendor default alone is not a reference: at 50, max-lines-per-function flagged 21.3% of our files and carried 77% of the union of all eight. The exclusions are the same list the duplication product pass is given, passed as `--ignore-pattern`: the `ignorePatterns` key in oxlint's config is accepted and silently ignored, which had this gauge measuring the test suite until the file counts were compared, 137 against 111 on `libs/nest/auth`."
|
|
5538
5550
|
},
|
|
5539
5551
|
"max-lines": {
|
|
5540
|
-
label: "
|
|
5541
|
-
what: "Files longer than
|
|
5552
|
+
label: "Long files",
|
|
5553
|
+
what: "Files longer than 150 lines, blank lines and comments included.",
|
|
5542
5554
|
read: "A size signal and nothing else. Read it beside the duplication figure: a long file that duplicates nothing is a different problem from a long file that repeats itself. A file is counted once per indicator, however many times the rule fires inside it. Measured on `libs/nest/auth`, counting diagnostics instead would have inflated the total by 1.8x, and by 2.8x at lower thresholds, because `max-nested-callbacks` reports once per level.",
|
|
5543
5555
|
caveat: "Length is not a defect. A generated file, a long constant table or a barrel is long by nature, which is why this is reported and never failed on.",
|
|
5544
|
-
source: "oxlint max-lines,
|
|
5556
|
+
source: "oxlint max-lines, above 150. The threshold is our p90 of 157 rounded down, because no research anywhere sets a FILE's length: IfSQ's three citations (500, 100-150, 143) are all about routines. The rule for every one of the eight is `min(research, our p90, the rule's default)`, and a vendor default alone is not a reference: at 50, max-lines-per-function flagged 21.3% of our files and carried 77% of the union of all eight. The exclusions are the same list the duplication product pass is given, passed as `--ignore-pattern`: the `ignorePatterns` key in oxlint's config is accepted and silently ignored, which had this gauge measuring the test suite until the file counts were compared, 137 against 111 on `libs/nest/auth`."
|
|
5545
5557
|
},
|
|
5546
5558
|
"max-lines-per-function": {
|
|
5547
5559
|
label: "Files with a long function",
|
|
5548
|
-
what: "Files holding at least one function longer than
|
|
5560
|
+
what: "Files holding at least one function longer than 109 lines.",
|
|
5549
5561
|
read: "\u26A0\uFE0F A DESCRIPTOR, not part of the maintainability figure. It fires on 46.9 files per 100 across the repository, so it would fire on nearly every pull request. A file is counted once per indicator, however many times the rule fires inside it. Measured on `libs/nest/auth`, counting diagnostics instead would have inflated the total by 1.8x, and by 2.8x at lower thresholds, because `max-nested-callbacks` reports once per level.",
|
|
5550
|
-
caveat: "
|
|
5551
|
-
source: "oxlint max-lines-per-function,
|
|
5562
|
+
caveat: "Length is a signal and not a defect: a long function can be a flat list of fields. Read it beside the statement count, which is the other half of the same question, and which it does NOT duplicate: measured here, 255 long functions breach no statement count and 699 dense ones are not long.",
|
|
5563
|
+
source: "oxlint max-lines-per-function, above 109. The threshold is our p90. IfSQ SP-1 says 150, citing IBM on routines over 500 lines, and is discarded here for being more permissive than our own code. The rule for every one of the eight is `min(research, our p90, the rule's default)`, and a vendor default alone is not a reference: at 50, max-lines-per-function flagged 21.3% of our files and carried 77% of the union of all eight. The exclusions are the same list the duplication product pass is given, passed as `--ignore-pattern`: the `ignorePatterns` key in oxlint's config is accepted and silently ignored, which had this gauge measuring the test suite until the file counts were compared, 137 against 111 on `libs/nest/auth`."
|
|
5552
5564
|
},
|
|
5553
5565
|
"max-statements": {
|
|
5554
5566
|
label: "Files with a long function body",
|
|
5555
5567
|
what: "Files holding at least one function of more than ten statements.",
|
|
5556
5568
|
read: "\u26A0\uFE0F A DESCRIPTOR, not part of the maintainability figure, for the same reason as the row above: 42.9 firings per 100 files. A file is counted once per indicator, however many times the rule fires inside it. Measured on `libs/nest/auth`, counting diagnostics instead would have inflated the total by 1.8x, and by 2.8x at lower thresholds, because `max-nested-callbacks` reports once per level.",
|
|
5557
5569
|
caveat: "It counts statements and not work: ten calls in a row trip it where one dense expression does not.",
|
|
5558
|
-
source: "oxlint max-statements,
|
|
5570
|
+
source: "oxlint max-statements, above 10. The threshold is the rule's own default, kept because our p90 of 15 is the looser of the two; it is not redundant with the line count, 699 functions breach it without being long and 255 are long without breaching it. The rule for every one of the eight is `min(research, our p90, the rule's default)`, and a vendor default alone is not a reference: at 50, max-lines-per-function flagged 21.3% of our files and carried 77% of the union of all eight. The exclusions are the same list the duplication product pass is given, passed as `--ignore-pattern`: the `ignorePatterns` key in oxlint's config is accepted and silently ignored, which had this gauge measuring the test suite until the file counts were compared, 137 against 111 on `libs/nest/auth`."
|
|
5559
5571
|
},
|
|
5560
5572
|
"max-params": {
|
|
5561
5573
|
label: "Files with a wide signature",
|
|
5562
5574
|
what: "Files holding at least one function taking more than three parameters.",
|
|
5563
5575
|
read: "The most frequent of the scored indicators, 9.6 per 100 files. A wide signature is usually an object waiting to be named. A file is counted once per indicator, however many times the rule fires inside it. Measured on `libs/nest/auth`, counting diagnostics instead would have inflated the total by 1.8x, and by 2.8x at lower thresholds, because `max-nested-callbacks` reports once per level.",
|
|
5564
5576
|
caveat: "A destructured object parameter counts as one, so the figure does not see a function taking fifteen fields in one bag.",
|
|
5565
|
-
source: "oxlint max-params,
|
|
5577
|
+
source: "oxlint max-params, above 3. The threshold is the rule\u2019s own default, kept because our p90 of 4 is looser; beyond three parameters the fix is an object or currying, so this is a design boundary rather than a dial. The rule for every one of the eight is `min(research, our p90, the rule's default)`, and a vendor default alone is not a reference: at 50, max-lines-per-function flagged 21.3% of our files and carried 77% of the union of all eight. The exclusions are the same list the duplication product pass is given, passed as `--ignore-pattern`: the `ignorePatterns` key in oxlint's config is accepted and silently ignored, which had this gauge measuring the test suite until the file counts were compared, 137 against 111 on `libs/nest/auth`."
|
|
5566
5578
|
},
|
|
5567
5579
|
"max-nested-callbacks": {
|
|
5568
5580
|
label: "Files with nested callbacks",
|
|
5569
|
-
what: "Files holding callbacks nested more than
|
|
5581
|
+
what: "Files holding callbacks nested more than three deep.",
|
|
5570
5582
|
read: "Zero everywhere measured so far. Kept because a non-zero value is then unambiguous. A file is counted once per indicator, however many times the rule fires inside it. Measured on `libs/nest/auth`, counting diagnostics instead would have inflated the total by 1.8x, and by 2.8x at lower thresholds, because `max-nested-callbacks` reports once per level.",
|
|
5571
5583
|
caveat: "It counts callbacks and not promise chains or awaits, so modern asynchronous code can be deeply sequential without ever tripping it.",
|
|
5572
|
-
source: "oxlint max-nested-callbacks, at
|
|
5584
|
+
source: "oxlint max-nested-callbacks, above 3. The threshold is IfSQ SP-2 again, same nesting family, and our p90 agrees at 3. It sat at 10 while the deepest chain in the repository is 4, so it reported 0 files for as long as it shipped. The rule for every one of the eight is `min(research, our p90, the rule's default)`, and a vendor default alone is not a reference: at 50, max-lines-per-function flagged 21.3% of our files and carried 77% of the union of all eight. The exclusions are the same list the duplication product pass is given, passed as `--ignore-pattern`: the `ignorePatterns` key in oxlint's config is accepted and silently ignored, which had this gauge measuring the test suite until the file counts were compared, 137 against 111 on `libs/nest/auth`."
|
|
5573
5585
|
},
|
|
5574
5586
|
"max-classes-per-file": {
|
|
5575
5587
|
label: "Files with several classes",
|
|
5576
5588
|
what: "Files declaring more than one class.",
|
|
5577
5589
|
read: "1.8 per 100 files. On a nest service it usually means a DTO file, which is a convention rather than a defect. A file is counted once per indicator, however many times the rule fires inside it. Measured on `libs/nest/auth`, counting diagnostics instead would have inflated the total by 1.8x, and by 2.8x at lower thresholds, because `max-nested-callbacks` reports once per level.",
|
|
5578
5590
|
caveat: "It says nothing about the size of the classes, and a file with one 800-line class never appears here.",
|
|
5579
|
-
source: "oxlint max-classes-per-file,
|
|
5591
|
+
source: "oxlint max-classes-per-file, above 1. The threshold is the rule's floor, there is no stricter value to pick. The rule for every one of the eight is `min(research, our p90, the rule's default)`, and a vendor default alone is not a reference: at 50, max-lines-per-function flagged 21.3% of our files and carried 77% of the union of all eight. The exclusions are the same list the duplication product pass is given, passed as `--ignore-pattern`: the `ignorePatterns` key in oxlint's config is accepted and silently ignored, which had this gauge measuring the test suite until the file counts were compared, 137 against 111 on `libs/nest/auth`."
|
|
5580
5592
|
},
|
|
5581
5593
|
usedOnlyByTests: {
|
|
5582
5594
|
label: "Used only by tests",
|
|
@@ -5584,6 +5596,13 @@ var metricsHelp = {
|
|
|
5584
5596
|
read: "A third state between alive and dead, and the only one of the three that names a design question rather than a defect: a production module shipping something only the suites consume. Zero is the ordinary value.",
|
|
5585
5597
|
caveat: "It says nothing about whether that is wrong. A test factory deliberately published by a library belongs here and is doing its job. What it refuses to do is call those files dead, which is what a walk that ignores tests does: measured on `libs/nest/auth`, 11 live files are imported only from a test, `auth.module.ts` among them, and 47 projects depend on that module. \u26A0\uFE0F It sees FILES, so an export used only by specs inside a file that production also imports is invisible here.",
|
|
5586
5598
|
source: "The workspace index in `workspace/consumers.ts`: one scan of the tracked files, specifiers resolved against the tsconfig aliases, an importer classified as a test by the same conventions the duplication pass uses. Measured on this repository: 278 such files in all, 39 of them in `libs/front/components`."
|
|
5599
|
+
},
|
|
5600
|
+
measuredFiles: {
|
|
5601
|
+
label: "Files the gauge read",
|
|
5602
|
+
what: "How many product files the size indicators were measured over.",
|
|
5603
|
+
read: "The denominator of every size row above. None of them compares between modules without it: three breaches over 50 files and three over 3 000 are not the same module.",
|
|
5604
|
+
caveat: "It is the gauge\u2019s population and not the module\u2019s: `src` only, tests, mocks and generated files excluded, which is the same list every other pass is given. So it is smaller than what `ls` would count, deliberately, and it will differ from the duplication pass\u2019s own file count, which drops files shorter than the detection window.",
|
|
5605
|
+
source: "oxlint's `number_of_files` for the run this role drives, with the role's product exclusions passed as `--ignore-pattern`. Verified against the graph pass on `libs/nest/auth`: both read the same 99 files."
|
|
5587
5606
|
}
|
|
5588
5607
|
};
|
|
5589
5608
|
function helpFor(key) {
|
|
@@ -6000,7 +6019,7 @@ import { existsSync as existsSync15, mkdirSync, readFileSync as readFileSync16,
|
|
|
6000
6019
|
import { dirname as dirname2, join as join21, relative as relative4, resolve as resolve3, sep } from "node:path";
|
|
6001
6020
|
var CACHE_DIR = join21("node_modules", ".cache", "sentinel");
|
|
6002
6021
|
var CACHE_FILE = "consumer-index.json";
|
|
6003
|
-
var SHAPE =
|
|
6022
|
+
var SHAPE = 4;
|
|
6004
6023
|
var EXTENSIONS2 = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
6005
6024
|
function workspaceRootFrom(cwd) {
|
|
6006
6025
|
let dir = resolve3(cwd);
|
|
@@ -6070,6 +6089,24 @@ function resolveSpecifier2(specifier, table, workspaceRoot) {
|
|
|
6070
6089
|
}
|
|
6071
6090
|
return void 0;
|
|
6072
6091
|
}
|
|
6092
|
+
function resolveRelative(importer, specifier, workspaceRoot) {
|
|
6093
|
+
const base = join21(dirname2(importer), specifier).split(sep).join("/");
|
|
6094
|
+
if (base.startsWith("..")) return void 0;
|
|
6095
|
+
const isFile = (candidate) => {
|
|
6096
|
+
try {
|
|
6097
|
+
return statSync4(join21(workspaceRoot, candidate)).isFile();
|
|
6098
|
+
} catch {
|
|
6099
|
+
return false;
|
|
6100
|
+
}
|
|
6101
|
+
};
|
|
6102
|
+
if (isFile(base)) return base;
|
|
6103
|
+
for (const extension of EXTENSIONS2) if (isFile(base + extension)) return base + extension;
|
|
6104
|
+
for (const extension of EXTENSIONS2) {
|
|
6105
|
+
const index = `${base}/index${extension}`;
|
|
6106
|
+
if (isFile(index)) return index;
|
|
6107
|
+
}
|
|
6108
|
+
return void 0;
|
|
6109
|
+
}
|
|
6073
6110
|
function isTestFile(path2) {
|
|
6074
6111
|
return /\.(test|spec|steps|e2e-spec|integration-spec|stories)\.[cm]?[jt]sx?$|(^|\/)(tests|__tests__|test|testing|stubs|__mocks__|mocks)\//.test(
|
|
6075
6112
|
path2
|
|
@@ -6091,8 +6128,18 @@ function buildConsumerIndex(workspaceRoot, moduleRoots) {
|
|
|
6091
6128
|
if (table.size === 0) return { imported, testOnly, builtFrom: "no alias table" };
|
|
6092
6129
|
const result = spawnSync5(
|
|
6093
6130
|
"git",
|
|
6094
|
-
[
|
|
6095
|
-
|
|
6131
|
+
[
|
|
6132
|
+
"grep",
|
|
6133
|
+
"-I",
|
|
6134
|
+
"-n",
|
|
6135
|
+
"-o",
|
|
6136
|
+
"-E",
|
|
6137
|
+
"@[A-Za-z0-9_-]+/[A-Za-z0-9_./-]+|from '(\\.\\./)+[^']+'",
|
|
6138
|
+
"--",
|
|
6139
|
+
"*.ts",
|
|
6140
|
+
"*.tsx"
|
|
6141
|
+
],
|
|
6142
|
+
{ cwd: workspaceRoot, encoding: "utf8", maxBuffer: 1024 * 1024 * 1024 }
|
|
6096
6143
|
);
|
|
6097
6144
|
if (result.error || result.status !== 0 && result.status !== 1) {
|
|
6098
6145
|
return { imported, testOnly, builtFrom: "git grep failed" };
|
|
@@ -6106,8 +6153,16 @@ function buildConsumerIndex(workspaceRoot, moduleRoots) {
|
|
|
6106
6153
|
if (first < 0 || second < 0) continue;
|
|
6107
6154
|
const importer = line.slice(0, first).split(sep).join("/");
|
|
6108
6155
|
const specifier = line.slice(second + 1);
|
|
6109
|
-
|
|
6110
|
-
const
|
|
6156
|
+
let target;
|
|
6157
|
+
const relative5 = /^from '((?:\.\.\/)+[^']+)'$/.exec(specifier)?.[1];
|
|
6158
|
+
if (relative5) {
|
|
6159
|
+
target = resolveRelative(importer, relative5, workspaceRoot);
|
|
6160
|
+
} else {
|
|
6161
|
+
if (!seen.has(specifier)) {
|
|
6162
|
+
seen.set(specifier, resolveSpecifier2(specifier, table, workspaceRoot));
|
|
6163
|
+
}
|
|
6164
|
+
target = seen.get(specifier);
|
|
6165
|
+
}
|
|
6111
6166
|
if (!target) continue;
|
|
6112
6167
|
if (moduleOf(importer, moduleRoots) === moduleOf(target, moduleRoots)) continue;
|
|
6113
6168
|
imported.add(target);
|
|
@@ -6478,7 +6533,7 @@ function measuredWith(config) {
|
|
|
6478
6533
|
// never appear as `(module)`: a gauge whose scale moves per module compares nothing.
|
|
6479
6534
|
row(
|
|
6480
6535
|
"size indicators",
|
|
6481
|
-
`${Object.entries(
|
|
6536
|
+
`${Object.entries(GAUGE_THRESHOLDS).map(([rule, threshold]) => `${rule} ${threshold}`).join(", ")} (min(research, our p90, the rule's default), oxlint run outside the module)`
|
|
6482
6537
|
),
|
|
6483
6538
|
// ⚠️ The graph pass's one knob, and it has to be here: it changes both the community count and
|
|
6484
6539
|
// `Q`, so the two figures are meaningless without it. The seed is NOT printed beside it,
|
|
@@ -6773,10 +6828,13 @@ function assembleReport(ctx, config, product, tests, dead, complexity, graph, co
|
|
|
6773
6828
|
} else if (!unreadable) {
|
|
6774
6829
|
notes.push("fewer than two files, so no graph figure is reported");
|
|
6775
6830
|
}
|
|
6831
|
+
if (size && size.files > 0) {
|
|
6832
|
+
rows.push({ metric: "measuredFiles", value: size.files, unit: "files the gauge read" });
|
|
6833
|
+
}
|
|
6776
6834
|
if (size) {
|
|
6777
6835
|
const counts = /* @__PURE__ */ new Map();
|
|
6778
6836
|
for (const breach of size.breaches) counts.set(breach.rule, (counts.get(breach.rule) ?? 0) + 1);
|
|
6779
|
-
for (const [rule, threshold] of Object.entries(
|
|
6837
|
+
for (const [rule, threshold] of Object.entries(GAUGE_THRESHOLDS)) {
|
|
6780
6838
|
const count = counts.get(rule) ?? 0;
|
|
6781
6839
|
if (count === 0) continue;
|
|
6782
6840
|
rows.push({
|
package/dist/index.js
CHANGED
|
@@ -548,6 +548,13 @@ The related work carries two results that challenge the Complexity category: Her
|
|
|
548
548
|
that most code complexity metrics correlate highly with LOC, and Moser et al. that process
|
|
549
549
|
metrics outperform code metrics as fault predictors.
|
|
550
550
|
|
|
551
|
+
### IfSQ
|
|
552
|
+
|
|
553
|
+
The one standard found that attaches a citation to a numeric limit, and the only external source
|
|
554
|
+
used to fix this role’s own thresholds. It is a human-inspection standard rather than a tool, so its
|
|
555
|
+
table sits with the decision it serves, in **11.4**: three of its fifteen indicators carry a number,
|
|
556
|
+
and none of them is about a file’s length.
|
|
557
|
+
|
|
551
558
|
---
|
|
552
559
|
|
|
553
560
|
## 6. The tool, measured rather than read
|
|
@@ -866,6 +873,8 @@ each one fires per 100 files, which is measured rather than judged:
|
|
|
866
873
|
| `max-params` | 3 | 9.6 |
|
|
867
874
|
| `max-nested-callbacks` | 10 | 0 |
|
|
868
875
|
|
|
876
|
+
⛔ **Superseded by 10.7, and closed by 11.6.** The two figures below, and the break they rest on, were measured on four modules before two defects in the gauge were fixed: diagnostics counted as breaches, and a population that included the test suite. Over 177 modules the same two rules measure 10.4 and 6.9 per 100 files against the next at 4.6, a ratio of 1.5 rather than 4.5. Section 11 then changed the thresholds themselves, and at those the most frequent rule reaches 11.0% of files against 10.7% for the next: there is no break left to split on, so `DESCRIPTORS`, `scored` and `described` are removed and every indicator counts.
|
|
877
|
+
|
|
869
878
|
**Which are out, and why.** There is a break in the data, a factor of 4.5, not a line we drew:
|
|
870
879
|
|
|
871
880
|
| kept as a descriptor | per 100 files |
|
|
@@ -968,17 +977,18 @@ touch no production file at all and the median touches **4**.
|
|
|
968
977
|
|
|
969
978
|
### 9.7 What is still a decision
|
|
970
979
|
|
|
971
|
-
| open
|
|
972
|
-
|
|
|
973
|
-
| the `/10` in the `MI`
|
|
974
|
-
| the criticality order of the
|
|
975
|
-
| the overhead line of the SQL study
|
|
976
|
-
| the workspace index for libraries
|
|
977
|
-
| a feedback channel on findings
|
|
980
|
+
| open | what it needs |
|
|
981
|
+
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
|
982
|
+
| the `/10` in the `MI` | the distribution over the 198 modules. It changes the spread, never the order |
|
|
983
|
+
| the criticality order of the **eight** | one argument per indicator. **Rarity no longer rules any out:** see 11.6, where the split is removed. Ranking the eight is still open |
|
|
984
|
+
| the overhead line of the SQL study | the harness rebuilt. #90 corrects the attribution and withdraws the 71%, which was never a ratio |
|
|
985
|
+
| the workspace index for libraries | built and measured in 10.4 and 10.5. Nothing open here any more |
|
|
986
|
+
| a feedback channel on findings | unchanged from section 8, and it is what would let any of this be proven wrong |
|
|
978
987
|
|
|
979
988
|
The one measurement that unblocks the first two is the same: **the eight indicators over all 198
|
|
980
|
-
modules, once.**
|
|
981
|
-
|
|
989
|
+
modules, once.** That measurement now exists, in 10.7 over 177 modules and in 11 over a five-module
|
|
990
|
+
bench, and it is what fixed the thresholds. The figures in THIS section still come from four modules
|
|
991
|
+
and are quoted as such.
|
|
982
992
|
|
|
983
993
|
---
|
|
984
994
|
|
|
@@ -1173,7 +1183,245 @@ the binary failing. Measured: same 18 diagnostics either way, exit 1 against exi
|
|
|
1173
1183
|
|
|
1174
1184
|
---
|
|
1175
1185
|
|
|
1176
|
-
|
|
1186
|
+
### 10.7 The whole repository, measured, and what it does to 9.3
|
|
1187
|
+
|
|
1188
|
+
Section 9.3 chose which indicators enter the maintainability figure by how often each fires per 100
|
|
1189
|
+
files, and excluded two of them on a **break of a factor 4.5** in that data. Those figures came from
|
|
1190
|
+
four modules, measured before two defects found while building the gauge were fixed. Here is the
|
|
1191
|
+
same question over **177 modules and 29 770 files**, with the gauge reading `src` only and counting
|
|
1192
|
+
a file once per indicator.
|
|
1193
|
+
|
|
1194
|
+
| indicator | per 100 files | modules above zero |
|
|
1195
|
+
| ------------------------ | ------------: | -----------------: |
|
|
1196
|
+
| `max-lines-per-function` | **10.4** | 75 |
|
|
1197
|
+
| `max-statements` | **6.9** | 82 |
|
|
1198
|
+
| `max-params` | 4.6 | 69 |
|
|
1199
|
+
| `max-classes-per-file` | 3.5 | 53 |
|
|
1200
|
+
| `max-lines` | 2.1 | 59 |
|
|
1201
|
+
| `complexity` | 0.8 | 33 |
|
|
1202
|
+
| `max-depth` | 0.0 | 12 |
|
|
1203
|
+
| `max-nested-callbacks` | 0.0 | 0 |
|
|
1204
|
+
|
|
1205
|
+
⛔ **The 4.5 break is gone, and it was an artefact of the measurement rather than a fact about the
|
|
1206
|
+
code.** Section 9.3 reported 46.9 and 42.9 for the two descriptors; the **same three modules**
|
|
1207
|
+
measured now give **14.4 and 7.7**. Two defects fixed tonight explain the direction and the order of
|
|
1208
|
+
magnitude:
|
|
1209
|
+
|
|
1210
|
+
- diagnostics were counted where a breach is a (file, rule) pair. `max-nested-callbacks` fires once
|
|
1211
|
+
per nesting level; measured, counting diagnostics inflates by **1.8x at the published thresholds
|
|
1212
|
+
and 2.8x at lower ones**.
|
|
1213
|
+
- the population included the test suite, because `ignorePatterns` in oxlint's config is accepted
|
|
1214
|
+
and silently ignored. Measured on `libs/front/components`: `max-lines-per-function` falls from
|
|
1215
|
+
**33.6 to 23.9** per 100 files once the specs leave.
|
|
1216
|
+
|
|
1217
|
+
What the clean data shows instead is a **smooth gradient**, consecutive ratios of 1.5, 1.5, 1.3,
|
|
1218
|
+
1.7, then 2.6 and finally 17.1 between `complexity` and `max-depth`:
|
|
1219
|
+
|
|
1220
|
+
max-lines-per-function 10.4 -> max-statements 6.9 x1.5
|
|
1221
|
+
max-statements 6.9 -> max-params 4.6 x1.5
|
|
1222
|
+
max-params 4.6 -> max-classes 3.5 x1.3
|
|
1223
|
+
max-classes 3.5 -> max-lines 2.1 x1.7
|
|
1224
|
+
max-lines 2.1 -> complexity 0.8 x2.6
|
|
1225
|
+
complexity 0.8 -> max-depth 0.0 x17.1
|
|
1226
|
+
|
|
1227
|
+
⚠️ **So the descriptor split now rests on nothing measured.** The two rules excluded from the figure
|
|
1228
|
+
are still the two most frequent, which is a reason to treat them with care, but 1.5x is not the 4.5x
|
|
1229
|
+
the decision was taken on, and inventing a cut here would be exactly the kind of threshold this
|
|
1230
|
+
document refuses elsewhere. The relative ORDER did survive: `complexity` below `max-lines` below
|
|
1231
|
+
`max-params` as before, with only `max-classes-per-file` moving up, from below `complexity` to above
|
|
1232
|
+
it.
|
|
1233
|
+
|
|
1234
|
+
**This is left open rather than re-decided.** What the data supports today is one clean statement:
|
|
1235
|
+
`max-nested-callbacks` fires **nowhere** in the repository, and `max-depth` in 12 modules for 14
|
|
1236
|
+
files in all, so those two carry almost no information here whatever the figure does with them.
|
|
1237
|
+
|
|
1238
|
+
---
|
|
1239
|
+
|
|
1240
|
+
## 11. How each threshold was chosen
|
|
1241
|
+
|
|
1242
|
+
Sections 9.3 and 10.7 left one question open: the gauge ran at the thresholds ESLint ships, which is
|
|
1243
|
+
not a choice, and 10.7 showed the one decision taken on those figures rested on a measurement
|
|
1244
|
+
defect. This section closes it. Every number in `GAUGE_THRESHOLDS` comes from one rule applied eight
|
|
1245
|
+
times, and the rule is stated before any figure so it cannot be fitted to a result afterwards.
|
|
1246
|
+
|
|
1247
|
+
### 11.1 The rule: `min(research, our p90, the rule's default)`
|
|
1248
|
+
|
|
1249
|
+
Three candidates per indicator, the strictest wins, and there is no fourth. The minimum is what
|
|
1250
|
+
makes it safe in **both** directions of failure, and both directions were observed here:
|
|
1251
|
+
|
|
1252
|
+
- ⛔ **A threshold above what the code reaches detects nothing.** `max-nested-callbacks` shipped at
|
|
1253
|
+
10 while the deepest chain in the repository is 4. It reported 0 files in 177 modules, for as long
|
|
1254
|
+
as it existed. 10.7 recorded that as "carries almost no information"; the cause is simpler, the
|
|
1255
|
+
scale was wrong.
|
|
1256
|
+
- ⛔ **Research can be more permissive than our own code, and then our code wins.** IfSQ's SP-1 puts
|
|
1257
|
+
a routine at 150 lines. Our p90 is 109. Adopting 150 because a published standard says so would
|
|
1258
|
+
relax a control to match a document, which is the opposite of measuring.
|
|
1259
|
+
|
|
1260
|
+
A vendor default is a candidate, never a reference: it is shipped to fit every codebase, so it was
|
|
1261
|
+
calibrated on none. What it cost here is in 11.6.
|
|
1262
|
+
|
|
1263
|
+
### 11.2 Three traps in deriving a threshold from our own distribution
|
|
1264
|
+
|
|
1265
|
+
⚠️ **The p90 over the whole population collapses to 0 for four of the eight.** Ninety per cent of
|
|
1266
|
+
files never nest a block, never nest a callback, declare at most one class and take at most one
|
|
1267
|
+
parameter, so the percentile sits at the floor and a threshold of 0 flags everything. So "our p90"
|
|
1268
|
+
means **the p90 over the files the rule touches**, which is the only convention that does not
|
|
1269
|
+
collapse:
|
|
1270
|
+
|
|
1271
|
+
| indicator | p90 over all 8 083 files | p90 over the files it touches |
|
|
1272
|
+
| ---------------------- | -----------------------: | ----------------------------: |
|
|
1273
|
+
| `max-depth` | 0 | 3 |
|
|
1274
|
+
| `max-nested-callbacks` | 0 | 3 |
|
|
1275
|
+
| `max-classes-per-file` | 0 | 5 |
|
|
1276
|
+
| `max-params` | 3 | 4 |
|
|
1277
|
+
|
|
1278
|
+
⚠️ **Rounding goes downward only.** `max-lines` derives a p90 of 157 and publishes 150. Rounding up
|
|
1279
|
+
would move a threshold away from the evidence that produced it, in the one direction the rule above
|
|
1280
|
+
exists to forbid.
|
|
1281
|
+
|
|
1282
|
+
⛔ **The harness read seven of the eight message shapes.** oxlint parenthesises the measured value,
|
|
1283
|
+
`too many statements (20)`, except for `complexity`, which writes `has a complexity of 15`. A reader
|
|
1284
|
+
of the parenthesised form alone returns 0 for every complexity breach and says nothing about it: the
|
|
1285
|
+
first run of this bench reported **0 files above complexity 10, 15 and 20 alike**, which reads as
|
|
1286
|
+
"nothing to see" rather than as a broken instrument. The same defect sat in the role's own
|
|
1287
|
+
`valueOf`, where it was latent because no report row consumed the value yet. Both are fixed, and the
|
|
1288
|
+
regression test now uses the messages oxlint really emits rather than a paraphrase.
|
|
1289
|
+
|
|
1290
|
+
### 11.3 The bench
|
|
1291
|
+
|
|
1292
|
+
**8 083 product files over five modules**: `console` and `host-admin` (react), `front-components`
|
|
1293
|
+
(react library), `recruitment` and `network` (nest). The exclusion list is the role's own, so no
|
|
1294
|
+
spec, fixture, mock, story, generated or declaration file is in any figure below. Distributions come
|
|
1295
|
+
from one oxlint run per rule set with every maximum at 1, so each occurrence reports its real value
|
|
1296
|
+
instead of only its breach.
|
|
1297
|
+
|
|
1298
|
+
### 11.4 What IfSQ gives, and what it does not
|
|
1299
|
+
|
|
1300
|
+
IfSQ (ifsq.org) is the one standard found that states numeric limits with citations attached. It is
|
|
1301
|
+
a standard for **human inspection**: the level pages say what to look for, and `assessment.html`
|
|
1302
|
+
says who inspects, from self-assessment to third-party certification. There is no defect-density
|
|
1303
|
+
rule and no grade boundary anywhere on the site, checked page by page.
|
|
1304
|
+
|
|
1305
|
+
**Three of its fifteen indicators carry a number:**
|
|
1306
|
+
|
|
1307
|
+
| code | indicator | threshold | citations given |
|
|
1308
|
+
| -------- | ------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
1309
|
+
| **SP-1** | Routine Too Long | > 150 lines, comments and blank lines excluded | IBM, "the most error-prone routines were those larger than 500 lines"; "when routines averaged 100 to 150 lines each, code was more stable"; "routines with 143 lines or less are 2.4 times less expensive to fix" |
|
|
1310
|
+
| **SP-2** | Nesting Too Deep | > 3 levels of conditional nesting | "few people can understand more than 3 levels of nested if statements". ⚠️ One finding page says "more than four" instead |
|
|
1311
|
+
| **SP-3** | Routine Too Complex | > 10 binary terms in conditionals | McCabe 1976 |
|
|
1312
|
+
|
|
1313
|
+
⛔ **IfSQ sources nothing about a FILE's length.** All three citations above are about routines. So
|
|
1314
|
+
`max-lines` has no research candidate at all, and its threshold is our p90 and nothing else.
|
|
1315
|
+
|
|
1316
|
+
The remaining twelve state a defect to look for with no measurement: WIP-1 Vague "To Do", WIP-2
|
|
1317
|
+
Disabled Code, WIP-3 Empty Statement Block, SPM-1 Magic Numbers, SPM-2 Magic Strings, SPM-3
|
|
1318
|
+
Copy/Paste Programming, DP-1 Parameter Not Checked, DP-2 Status Ignored After Call, DP-3 Unexpected
|
|
1319
|
+
State Not Trapped, and the three Causes for Concern, which are an inspector's judgement. Of those,
|
|
1320
|
+
SPM-3 is already covered by the duplication pass and WIP-2 partly by the dead-code pass; WIP-1,
|
|
1321
|
+
WIP-3, SPM-1, SPM-2, DP-1 and DP-2 are candidates for indicators this role does not have yet.
|
|
1322
|
+
|
|
1323
|
+
### 11.5 The eight thresholds, with their origin
|
|
1324
|
+
|
|
1325
|
+
| indicator | was | **is** | research | our p90 | what decided it |
|
|
1326
|
+
| ------------------------ | --: | ------: | -------- | ------: | --------------------------------------------------------------------------------------------------------- |
|
|
1327
|
+
| `complexity` | 20 | **10** | 10 | 11 | the only indicator where two independent references agree within a unit |
|
|
1328
|
+
| `max-lines-per-function` | 50 | **109** | 150 | 109 | research is more permissive than our code, so our code wins |
|
|
1329
|
+
| `max-lines` | 300 | **150** | none | 157 | our p90, rounded down. No research sets a file's length |
|
|
1330
|
+
| `max-depth` | 4 | **3** | 3 | 3 | research and our code agree |
|
|
1331
|
+
| `max-nested-callbacks` | 10 | **3** | 3 (SP-2) | 3 | same nesting family as SP-2, no citation of its own. Was above the repository's maximum of 4 |
|
|
1332
|
+
| `max-statements` | 10 | **10** | none | 15 | the default is the stricter candidate and stays |
|
|
1333
|
+
| `max-params` | 3 | **3** | none | 4 | the default stays. Beyond three parameters the fix is an object or currying, so this is a design boundary |
|
|
1334
|
+
| `max-classes-per-file` | 1 | **1** | none | 5 | 1 is the rule's floor, there is no stricter value |
|
|
1335
|
+
|
|
1336
|
+
Five move, and `complexity` is worth one line on its own: 94.8% of our 11 050 measured functions
|
|
1337
|
+
already sit in the 1-10 band the SEI calls low risk, so 10 is a ratchet rather than a demand.
|
|
1338
|
+
|
|
1339
|
+
| SEI band | functions | share |
|
|
1340
|
+
| ------------------- | --------: | ---------: |
|
|
1341
|
+
| 1-10, low risk | 10 475 | **94.8 %** |
|
|
1342
|
+
| 11-15 | 319 | 2.9 % |
|
|
1343
|
+
| 16-20 | 120 | 1.1 % |
|
|
1344
|
+
| 21-50, high risk | 128 | 1.2 % |
|
|
1345
|
+
| over 50, untestable | 8 | 0.1 % |
|
|
1346
|
+
|
|
1347
|
+
### 11.6 What the eight say together, and the end of the descriptor split
|
|
1348
|
+
|
|
1349
|
+
A threshold is only half a decision: what matters is what the **set** reports.
|
|
1350
|
+
|
|
1351
|
+
| | at the vendor defaults | at the thresholds above |
|
|
1352
|
+
| ---------------------- | ------------------------------: | ----------------------: |
|
|
1353
|
+
| union of the eight | 2 236 files, 27.7 % | **1 708 files, 21.1 %** |
|
|
1354
|
+
| largest single rule | `max-lines-per-function`, 1 724 | `max-statements`, 891 |
|
|
1355
|
+
| its share of the union | **77 %** | **52 %** |
|
|
1356
|
+
|
|
1357
|
+
⛔ **At the defaults one rule was the report.** `max-lines-per-function` at 50 flagged 21.3% of all
|
|
1358
|
+
product files and carried 77% of the union, so the other seven indicators were inaudible: the
|
|
1359
|
+
report said "your functions are longer than 50 lines" and nothing else could be heard beside it.
|
|
1360
|
+
|
|
1361
|
+
And this closes what 10.7 left open. The descriptor split held `max-statements` and
|
|
1362
|
+
`max-lines-per-function` out of the figure on a **break of a factor 4.5** between 42.9/46.9 and 9.6
|
|
1363
|
+
per 100 files. 10.7 already showed those numbers were an artefact of two defects. At the thresholds
|
|
1364
|
+
above the two most frequent rules are 11.0% and 10.7%, and the next is 7.8%:
|
|
1365
|
+
|
|
1366
|
+
| indicator | files | per 100 files | ratio to the next |
|
|
1367
|
+
| ------------------------ | ----: | ------------: | ----------------: |
|
|
1368
|
+
| `max-statements` | 891 | 11.0 | 1.03 |
|
|
1369
|
+
| `max-lines` | 864 | 10.7 | 1.37 |
|
|
1370
|
+
| `max-lines-per-function` | 630 | 7.8 | 1.26 |
|
|
1371
|
+
| `complexity` | 499 | 6.2 | 1.34 |
|
|
1372
|
+
| `max-params` | 373 | 4.6 | 2.32 |
|
|
1373
|
+
| `max-classes-per-file` | 161 | 2.0 | 17.89 |
|
|
1374
|
+
| `max-nested-callbacks` | 9 | 0.1 | 1.13 |
|
|
1375
|
+
| `max-depth` | 8 | 0.1 | - |
|
|
1376
|
+
|
|
1377
|
+
**The top five are a smooth gradient, 1.03 to 1.37, so there is no population to exclude.** The one
|
|
1378
|
+
large break, 17.9, sits at the BOTTOM, between `max-classes-per-file` and the two nesting rules: it
|
|
1379
|
+
says those two are rare here, not that the frequent ones are noise. `DESCRIPTORS`, `scored` and
|
|
1380
|
+
`described` are removed: every indicator counts, and the asymmetry that needed explaining in two
|
|
1381
|
+
places no longer exists. Note also that the two nesting rules moved from reporting **nothing at all**
|
|
1382
|
+
to reporting 9 and 8 files: lowering them from 10 and 4 to 3 is what gave them any information.
|
|
1383
|
+
|
|
1384
|
+
### 11.7 Why `max-statements` is not a second reading of `max-lines-per-function`
|
|
1385
|
+
|
|
1386
|
+
The obvious objection to keeping both is that a long function has many statements. Measured by
|
|
1387
|
+
joining the two diagnostics on the same function span, over 25 658 functions of which 10 441 carry
|
|
1388
|
+
both values:
|
|
1389
|
+
|
|
1390
|
+
| | |
|
|
1391
|
+
| --------------------------------------- | -------------------: |
|
|
1392
|
+
| Pearson, statements against lines | 0.726 |
|
|
1393
|
+
| Spearman | 0.600 |
|
|
1394
|
+
| lines per statement, p25 / median / p90 | 2.5 / **4.4** / 14.0 |
|
|
1395
|
+
|
|
1396
|
+
Correlated, so not independent. But the density varies by a factor of **5.6** across functions, and
|
|
1397
|
+
that is what forbids giving them one value. At the chosen thresholds:
|
|
1398
|
+
|
|
1399
|
+
| | functions | what it is |
|
|
1400
|
+
| ----------------------------- | --------: | ------------------------ |
|
|
1401
|
+
| over both | 399 | long and dense |
|
|
1402
|
+
| `max-statements` only | **699** | dense logic in few lines |
|
|
1403
|
+
| `max-lines-per-function` only | **255** | long with little logic |
|
|
1404
|
+
| overlap | **29 %** | |
|
|
1405
|
+
|
|
1406
|
+
Both shapes are real, and in this repository they are two different kinds of file:
|
|
1407
|
+
|
|
1408
|
+
| file | lines | statements | shape |
|
|
1409
|
+
| --------------------------------------- | ----: | ---------: | ---------------------- |
|
|
1410
|
+
| `add-to-network/JobsEvaluationList.tsx` | 259 | 2 | one large JSX return |
|
|
1411
|
+
| `entities/job-offer.entity.ts` | 48 | 46 | one statement per line |
|
|
1412
|
+
|
|
1413
|
+
So `max-lines-per-function` measures volume including markup, and `max-statements` measures
|
|
1414
|
+
imperative density, and neither sees what the other sees. Giving `max-statements` the same 109 would
|
|
1415
|
+
kill it: at the median density that is a function of roughly 480 lines, so the line count would
|
|
1416
|
+
always fire first and the statement count would never say anything again.
|
|
1417
|
+
|
|
1418
|
+
---
|
|
1419
|
+
|
|
1420
|
+
## 12. Sources
|
|
1421
|
+
|
|
1422
|
+
- IfSQ. _Level 1, Level 2 and Level 3 Standards for Code Quality._ <https://www.ifsq.org/level-1.html>,
|
|
1423
|
+
<https://www.ifsq.org/level-2.html>, <https://www.ifsq.org/structured-programming.html>,
|
|
1424
|
+
<https://www.ifsq.org/research.html>, <https://www.ifsq.org/assessment.html>
|
|
1177
1425
|
|
|
1178
1426
|
- Ben Khalfallah, H. _Code Health Meter: A Quantitative and Graph-Theoretic Foundation for
|
|
1179
1427
|
Automated Code Quality and Architecture Assessment._ ACM TOSEM 35(4), article 114, March 2026.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hublo/sentinel",
|
|
3
|
-
"version": "1.5.0-alpha.
|
|
3
|
+
"version": "1.5.0-alpha.22",
|
|
4
4
|
"description": "One CLI that guards code health across Hublo repos: shared lint/typescript/build/test presets, static & dynamic analysis, and architecture checks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|