arkgate 4.8.24 → 4.8.26
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/CHANGELOG.md +95 -0
- package/README.md +2 -2
- package/bin/ark-check-runtime.mjs +17 -14
- package/bin/ark-layer-match.mjs +115 -13
- package/bin/ark-mcp-runtime.mjs +36 -31
- package/bin/ark-shared.mjs +4 -23
- package/bin/lib/adoption-stance.mjs +2 -2
- package/bin/lib/adr-path.mjs +3 -3
- package/bin/lib/agent-gates.mjs +0 -61
- package/bin/lib/agent-homes.mjs +12 -21
- package/bin/lib/agent-projection-command.mjs +4 -4
- package/bin/lib/ambient-state.mjs +9 -30
- package/bin/lib/analysis-engine.mjs +14 -14
- package/bin/lib/architecture-scan.mjs +11 -9
- package/bin/lib/ark-order-doctor.mjs +3 -28
- package/bin/lib/ark-order-facts.mjs +6 -99
- package/bin/lib/ark-order-types.mjs +1 -2
- package/bin/lib/ark-run-doctor.mjs +3 -28
- package/bin/lib/ark-run-facts.mjs +30 -40
- package/bin/lib/ark-run-sensors.mjs +1 -1
- package/bin/lib/arkrule-file-hints.mjs +2 -47
- package/bin/lib/arkrules-contract.mjs +124 -133
- package/bin/lib/arkrules-sensors.mjs +7 -5
- package/bin/lib/check-args.mjs +63 -1
- package/bin/lib/ci-and-commands.mjs +8 -72
- package/bin/lib/ci-merge-boundary.mjs +1 -1
- package/bin/lib/class-source-scan.mjs +5 -5
- package/bin/lib/clone-detection.mjs +474 -0
- package/bin/lib/codex-home.mjs +1 -1
- package/bin/lib/config-contract-slices.mjs +44 -6
- package/bin/lib/config-contract.mjs +40 -117
- package/bin/lib/config-extras.mjs +16 -22
- package/bin/lib/contract-smells.mjs +33 -15
- package/bin/lib/core-ratchet.mjs +0 -2
- package/bin/lib/dashboard-cli.mjs +2 -2
- package/bin/lib/deep-module-coach.mjs +2 -4
- package/bin/lib/deploy-path.mjs +13 -38
- package/bin/lib/design-smells.mjs +2 -2
- package/bin/lib/diagnostic-catalog.mjs +8 -1
- package/bin/lib/doctor-advisories.mjs +55 -9
- package/bin/lib/doctor-green-cite.mjs +1 -1
- package/bin/lib/doctor-human.mjs +1 -1
- package/bin/lib/doctor-next-actions.mjs +2 -2
- package/bin/lib/doctor-plan.mjs +4 -6
- package/bin/lib/duplication-io.mjs +572 -0
- package/bin/lib/effective-contract-load.mjs +181 -44
- package/bin/lib/entry-points-io.mjs +424 -0
- package/bin/lib/extra-merge-teeth.mjs +36 -0
- package/bin/lib/field-install.mjs +38 -14
- package/bin/lib/first-run-help.mjs +7 -3
- package/bin/lib/flat-parent-importers.mjs +5 -3
- package/bin/lib/gate-files.mjs +30 -89
- package/bin/lib/github-enforcement.mjs +165 -28
- package/bin/lib/golden-pattern.mjs +1 -1
- package/bin/lib/hook-templates.mjs +16 -16
- package/bin/lib/html-report-advisories.mjs +4 -0
- package/bin/lib/html-report-depth.mjs +2 -2
- package/bin/lib/html-report.mjs +2 -2
- package/bin/lib/import-graph-projection.mjs +112 -0
- package/bin/lib/improvement-compass-doctor.mjs +1 -24
- package/bin/lib/invariant-coverage-io.mjs +4 -3
- package/bin/lib/invariant-coverage.mjs +26 -0
- package/bin/lib/invariant-probe-cli.mjs +347 -0
- package/bin/lib/invariant-probe-io.mjs +165 -0
- package/bin/lib/invariant-probe-runner.mjs +268 -0
- package/bin/lib/invariant-probe-sites.mjs +174 -0
- package/bin/lib/invariant-probe-workspace.mjs +183 -0
- package/bin/lib/invariant-probe.mjs +461 -0
- package/bin/lib/invariant-tests-path.mjs +16 -22
- package/bin/lib/literal-path-drift.mjs +4 -2
- package/bin/lib/managed-upgrade.mjs +2 -8
- package/bin/lib/mcp-adoption.mjs +4 -5
- package/bin/lib/mcp-hook-payload.mjs +4 -4
- package/bin/lib/orphan-modules-io.mjs +458 -0
- package/bin/lib/orphan-modules.mjs +251 -0
- package/bin/lib/outside-importers.mjs +197 -0
- package/bin/lib/package-manager.mjs +3 -3
- package/bin/lib/parse-health.mjs +1 -1
- package/bin/lib/persistence-write-hint.mjs +33 -8
- package/bin/lib/pin-dependent-gates.mjs +2 -2
- package/bin/lib/policy-delta-io.mjs +60 -12
- package/bin/lib/post-green-path.mjs +1 -1
- package/bin/lib/prepare-change.mjs +3 -9
- package/bin/lib/prepare-write.mjs +1 -2
- package/bin/lib/presets.mjs +56 -68
- package/bin/lib/product-copy.mjs +1 -6
- package/bin/lib/project-root.mjs +43 -85
- package/bin/lib/resolved-candidate-facts.mjs +90 -62
- package/bin/lib/rules-under-contract.mjs +72 -8
- package/bin/lib/scan-files.mjs +4 -4
- package/bin/lib/schema-validation.mjs +130 -0
- package/bin/lib/sensor-promote-cli.mjs +2 -11
- package/bin/lib/sensor-promote-io.mjs +33 -22
- package/bin/lib/sensor-promotion.mjs +1 -1
- package/bin/lib/skill-catalog-apply.mjs +1 -1
- package/bin/lib/skill-install.mjs +42 -38
- package/bin/lib/snippet-analysis.mjs +1 -1
- package/bin/lib/start-install-recovery.mjs +1 -1
- package/bin/lib/start-preview.mjs +1 -1
- package/bin/lib/states-transitions-presence.mjs +3 -3
- package/bin/lib/status-command.mjs +2 -18
- package/bin/lib/status-manifest.mjs +18 -26
- package/bin/lib/status-transition-catalog.mjs +0 -6
- package/bin/lib/suggestions.mjs +3 -3
- package/bin/lib/team-parliament-io.mjs +3 -17
- package/bin/lib/team-parliament.mjs +15 -9
- package/bin/lib/upgrade-behavior-changes.mjs +2 -2
- package/bin/lib/upgrade-command.mjs +2 -24
- package/bin/lib/upgrade-outcome.mjs +1 -1
- package/bin/lib/upstream-report.mjs +21 -10
- package/bin/lib/violations.mjs +10 -9
- package/bin/lib/weakest-link.mjs +13 -0
- package/bin/lib/write-path-capabilities.mjs +2 -2
- package/dist/chunk-G3JRBJGH.js +16 -0
- package/dist/chunk-R3AJ5RSZ.cjs +16 -0
- package/dist/{configTypes-BJbaeoj4.d.cts → configTypes-GPvvdleM.d.cts} +12 -1
- package/dist/{configTypes-BJbaeoj4.d.ts → configTypes-GPvvdleM.d.ts} +12 -1
- package/dist/eslint/index.cjs +11 -11
- package/dist/eslint/index.d.cts +10 -1
- package/dist/eslint/index.d.ts +10 -1
- package/dist/eslint/index.js +11 -11
- package/dist/index.cjs +34 -34
- package/dist/index.d.cts +587 -515
- package/dist/index.d.ts +587 -515
- package/dist/index.js +34 -34
- package/dist/nestjs/index.cjs +1 -1
- package/dist/nestjs/index.d.cts +1 -1
- package/dist/nestjs/index.d.ts +1 -1
- package/dist/nestjs/index.js +1 -1
- package/dist/order/index.cjs +1 -1
- package/dist/order/index.js +1 -1
- package/dist/runtime/index.cjs +12 -12
- package/dist/runtime/index.d.cts +174 -136
- package/dist/runtime/index.d.ts +174 -136
- package/dist/runtime/index.js +12 -12
- package/dist/{types-DRinG-vT.d.cts → types-CnauMQQL.d.cts} +33 -25
- package/dist/{types-DRinG-vT.d.ts → types-CnauMQQL.d.ts} +33 -25
- package/docs/README.md +3 -1
- package/docs/agent-guide.md +140 -6
- package/docs/ai-gates.md +15 -1
- package/docs/configuration.md +40 -11
- package/docs/diagnostics.md +119 -3
- package/docs/package-surface.md +4 -1
- package/docs/product-voice.md +7 -1
- package/package.json +11 -2
- package/schemas/ark.config.schema.json +31 -3
- package/schemas/ark.invariant-probe.schema.json +368 -0
- package/server.json +2 -2
- package/templates/agent-skills/README.md +1 -1
- package/templates/agent-skills/ark-adopt/SKILL.md +1 -0
- package/templates/agent-skills/ark-contract/SKILL.md +17 -0
- package/templates/agent-skills/ark-coverage/SKILL.md +2 -0
- package/templates/agent-skills/ark-explore/SKILL.md +7 -0
- package/templates/agent-skills/ark-fix/SKILL.md +20 -0
- package/templates/agent-skills/ark-place/SKILL.md +8 -0
- package/templates/skills/ark-adopt.md +1 -0
- package/templates/skills/ark-contract.md +17 -0
- package/templates/skills/ark-coverage.md +2 -0
- package/templates/skills/ark-explore.md +7 -0
- package/templates/skills/ark-fix.md +20 -0
- package/templates/skills/ark-place.md +8 -0
- package/bin/lib/ark-order-error.mjs +0 -18
- package/bin/lib/ark-order-invariants.mjs +0 -388
- package/bin/lib/stable-hash.mjs +0 -90
- package/dist/chunk-MBXCFLO4.js +0 -16
- package/dist/chunk-RNQHRDCQ.cjs +0 -16
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
normalize,
|
|
59
59
|
} from './scan-files.mjs';
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
const RESOLVED_FACTS_RESOLVER_IDENTITY = 'arkgate-typescript-resolver@1';
|
|
62
62
|
|
|
63
63
|
const IN_MEMORY_STORES = new Set([
|
|
64
64
|
'InMemoryAuditStore',
|
|
@@ -995,7 +995,8 @@ function objectLiteralHasProperty(ts, object, name) {
|
|
|
995
995
|
);
|
|
996
996
|
}
|
|
997
997
|
|
|
998
|
-
|
|
998
|
+
/** Start offsets of `@ts-ignore` directives and `@ts-nocheck` pragmas in a parsed file. */
|
|
999
|
+
export function tsSuppressionPositions(sourceFile, source) {
|
|
999
1000
|
const positions = new Set();
|
|
1000
1001
|
for (const directive of sourceFile.commentDirectives ?? []) {
|
|
1001
1002
|
const start = directive.range?.pos;
|
|
@@ -1012,28 +1013,21 @@ function tsSuppressionPositions(sourceFile, source) {
|
|
|
1012
1013
|
return [...positions];
|
|
1013
1014
|
}
|
|
1014
1015
|
|
|
1015
|
-
|
|
1016
|
-
const facts = tsSuppressionPositions(sourceFile, source).map((position) => ({
|
|
1017
|
-
file: relativePath,
|
|
1018
|
-
line: lineOf(sourceFile, position),
|
|
1019
|
-
kind: 'ts-suppression',
|
|
1020
|
-
}));
|
|
1021
|
-
for (const dependency of dependencies) {
|
|
1022
|
-
if (!dependency.unresolved) continue;
|
|
1023
|
-
facts.push({
|
|
1024
|
-
file: relativePath,
|
|
1025
|
-
line: dependency.line,
|
|
1026
|
-
kind: dependency.kind === 'require' ? 'dynamic-require' : 'dynamic-import',
|
|
1027
|
-
});
|
|
1028
|
-
}
|
|
1016
|
+
const ARKGATE_RUNTIME_SPECIFIER = /^arkgate(?:\/runtime)?$/;
|
|
1029
1017
|
|
|
1018
|
+
/**
|
|
1019
|
+
* Local bindings of the arkgate kernel factories whose omitted options fall back
|
|
1020
|
+
* to in-memory stores: named imports (local name → factory) plus namespace
|
|
1021
|
+
* imports of `arkgate` / `arkgate/runtime`.
|
|
1022
|
+
*/
|
|
1023
|
+
export function arkInMemoryFactoryBindings(ts, sourceFile) {
|
|
1030
1024
|
const importedFactories = new Map();
|
|
1031
1025
|
const arkNamespaces = new Set();
|
|
1032
1026
|
for (const statement of sourceFile.statements) {
|
|
1033
1027
|
if (!ts.isImportDeclaration(statement) || !ts.isStringLiteralLike(statement.moduleSpecifier)) {
|
|
1034
1028
|
continue;
|
|
1035
1029
|
}
|
|
1036
|
-
if (
|
|
1030
|
+
if (!ARKGATE_RUNTIME_SPECIFIER.test(statement.moduleSpecifier.text)) continue;
|
|
1037
1031
|
const bindings = statement.importClause?.namedBindings;
|
|
1038
1032
|
if (bindings && ts.isNamespaceImport(bindings)) arkNamespaces.add(bindings.name.text);
|
|
1039
1033
|
if (!bindings || !ts.isNamedImports(bindings)) continue;
|
|
@@ -1043,6 +1037,71 @@ function collectSafetyUses(ts, sourceFile, relativePath, source, dependencies) {
|
|
|
1043
1037
|
if (requirements) importedFactories.set(element.name.text, { imported, requirements });
|
|
1044
1038
|
}
|
|
1045
1039
|
}
|
|
1040
|
+
return { importedFactories, arkNamespaces };
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
/** Named `InMemory*Store` imports of an arkgate import declaration. */
|
|
1044
|
+
export function arkInMemoryStoreImports(ts, node) {
|
|
1045
|
+
const specifier = node.moduleSpecifier;
|
|
1046
|
+
if (!ts.isStringLiteralLike(specifier) || !ARKGATE_RUNTIME_SPECIFIER.test(specifier.text)) {
|
|
1047
|
+
return [];
|
|
1048
|
+
}
|
|
1049
|
+
const elements =
|
|
1050
|
+
node.importClause?.namedBindings && ts.isNamedImports(node.importClause.namedBindings)
|
|
1051
|
+
? node.importClause.namedBindings.elements
|
|
1052
|
+
: [];
|
|
1053
|
+
const out = [];
|
|
1054
|
+
for (const element of elements) {
|
|
1055
|
+
const imported = element.propertyName?.text ?? element.name.text;
|
|
1056
|
+
if (IN_MEMORY_STORES.has(imported)) out.push({ element, imported });
|
|
1057
|
+
}
|
|
1058
|
+
return out;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
/**
|
|
1062
|
+
* The factory a call expression invokes when that call definitely takes the
|
|
1063
|
+
* in-memory defaults (no options, `undefined`, or an object literal missing a
|
|
1064
|
+
* required store); otherwise undefined.
|
|
1065
|
+
*/
|
|
1066
|
+
export function inMemoryDefaultsFactoryCall(ts, node, { importedFactories, arkNamespaces }) {
|
|
1067
|
+
let factory;
|
|
1068
|
+
if (ts.isIdentifier(node.expression)) {
|
|
1069
|
+
factory = importedFactories.get(node.expression.text);
|
|
1070
|
+
} else if (
|
|
1071
|
+
ts.isPropertyAccessExpression(node.expression) &&
|
|
1072
|
+
ts.isIdentifier(node.expression.expression) &&
|
|
1073
|
+
arkNamespaces.has(node.expression.expression.text)
|
|
1074
|
+
) {
|
|
1075
|
+
const imported = node.expression.name.text;
|
|
1076
|
+
const requirements = IN_MEMORY_DEFAULT_FACTORIES.get(imported);
|
|
1077
|
+
if (requirements) factory = { imported, requirements };
|
|
1078
|
+
}
|
|
1079
|
+
if (!factory) return undefined;
|
|
1080
|
+
const options = node.arguments[0];
|
|
1081
|
+
const definitelyDefaults =
|
|
1082
|
+
!options ||
|
|
1083
|
+
(ts.isIdentifier(options) && options.text === 'undefined') ||
|
|
1084
|
+
(ts.isObjectLiteralExpression(options) &&
|
|
1085
|
+
factory.requirements.some((name) => !objectLiteralHasProperty(ts, options, name)));
|
|
1086
|
+
return definitelyDefaults ? factory : undefined;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
function collectSafetyUses(ts, sourceFile, relativePath, source, dependencies) {
|
|
1090
|
+
const facts = tsSuppressionPositions(sourceFile, source).map((position) => ({
|
|
1091
|
+
file: relativePath,
|
|
1092
|
+
line: lineOf(sourceFile, position),
|
|
1093
|
+
kind: 'ts-suppression',
|
|
1094
|
+
}));
|
|
1095
|
+
for (const dependency of dependencies) {
|
|
1096
|
+
if (!dependency.unresolved) continue;
|
|
1097
|
+
facts.push({
|
|
1098
|
+
file: relativePath,
|
|
1099
|
+
line: dependency.line,
|
|
1100
|
+
kind: dependency.kind === 'require' ? 'dynamic-require' : 'dynamic-import',
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
const factoryBindings = arkInMemoryFactoryBindings(ts, sourceFile);
|
|
1046
1105
|
|
|
1047
1106
|
const visit = (node) => {
|
|
1048
1107
|
if (
|
|
@@ -1057,56 +1116,25 @@ function collectSafetyUses(ts, sourceFile, relativePath, source, dependencies) {
|
|
|
1057
1116
|
}
|
|
1058
1117
|
|
|
1059
1118
|
if (ts.isImportDeclaration(node)) {
|
|
1060
|
-
const
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
: [];
|
|
1068
|
-
for (const element of elements) {
|
|
1069
|
-
const imported = element.propertyName?.text ?? element.name.text;
|
|
1070
|
-
if (IN_MEMORY_STORES.has(imported)) {
|
|
1071
|
-
facts.push({
|
|
1072
|
-
file: relativePath,
|
|
1073
|
-
line: lineOf(sourceFile, element.getStart(sourceFile)),
|
|
1074
|
-
kind: 'in-memory-store',
|
|
1075
|
-
symbol: imported,
|
|
1076
|
-
});
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1119
|
+
for (const { element, imported } of arkInMemoryStoreImports(ts, node)) {
|
|
1120
|
+
facts.push({
|
|
1121
|
+
file: relativePath,
|
|
1122
|
+
line: lineOf(sourceFile, element.getStart(sourceFile)),
|
|
1123
|
+
kind: 'in-memory-store',
|
|
1124
|
+
symbol: imported,
|
|
1125
|
+
});
|
|
1079
1126
|
}
|
|
1080
1127
|
}
|
|
1081
1128
|
|
|
1082
1129
|
if (ts.isCallExpression(node)) {
|
|
1083
|
-
|
|
1084
|
-
if (ts.isIdentifier(node.expression)) {
|
|
1085
|
-
factory = importedFactories.get(node.expression.text);
|
|
1086
|
-
} else if (
|
|
1087
|
-
ts.isPropertyAccessExpression(node.expression) &&
|
|
1088
|
-
ts.isIdentifier(node.expression.expression) &&
|
|
1089
|
-
arkNamespaces.has(node.expression.expression.text)
|
|
1090
|
-
) {
|
|
1091
|
-
const imported = node.expression.name.text;
|
|
1092
|
-
const requirements = IN_MEMORY_DEFAULT_FACTORIES.get(imported);
|
|
1093
|
-
if (requirements) factory = { imported, requirements };
|
|
1094
|
-
}
|
|
1130
|
+
const factory = inMemoryDefaultsFactoryCall(ts, node, factoryBindings);
|
|
1095
1131
|
if (factory) {
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
if (definitelyDefaults) {
|
|
1103
|
-
facts.push({
|
|
1104
|
-
file: relativePath,
|
|
1105
|
-
line: lineOf(sourceFile, node.getStart(sourceFile)),
|
|
1106
|
-
kind: 'in-memory-store',
|
|
1107
|
-
symbol: `${factory.imported} defaults`,
|
|
1108
|
-
});
|
|
1109
|
-
}
|
|
1132
|
+
facts.push({
|
|
1133
|
+
file: relativePath,
|
|
1134
|
+
line: lineOf(sourceFile, node.getStart(sourceFile)),
|
|
1135
|
+
kind: 'in-memory-store',
|
|
1136
|
+
symbol: `${factory.imported} defaults`,
|
|
1137
|
+
});
|
|
1110
1138
|
}
|
|
1111
1139
|
}
|
|
1112
1140
|
ts.forEachChild(node, visit);
|
|
@@ -16,8 +16,8 @@ import {
|
|
|
16
16
|
invariantIdsFromCatalog,
|
|
17
17
|
loadInvariantCoverageInputs,
|
|
18
18
|
} from './invariant-coverage-io.mjs';
|
|
19
|
+
import { formatProbeStatusLines, probeStatusSection } from './invariant-probe-io.mjs';
|
|
19
20
|
import {
|
|
20
|
-
EXTRA_MERGE_TEETH_GOVERNED_FLOOR,
|
|
21
21
|
composeMergePlanesHonesty,
|
|
22
22
|
demoteExtraPlaneTeethUnderClassificationFloor,
|
|
23
23
|
} from './extra-merge-teeth.mjs';
|
|
@@ -39,8 +39,6 @@ const COVERED_SAMPLE_MAX = 24;
|
|
|
39
39
|
const STRUCTURE_CATALOG_MAX = 40;
|
|
40
40
|
const UNCOVERED_CATALOG_MAX = 30;
|
|
41
41
|
|
|
42
|
-
export { EXTRA_MERGE_TEETH_GOVERNED_FLOOR };
|
|
43
|
-
|
|
44
42
|
/** Doctor walk often hands absolute paths; layer globs are project-relative. */
|
|
45
43
|
function projectRelativePath(root, filePath) {
|
|
46
44
|
const posix = String(filePath).replace(/\\/g, '/');
|
|
@@ -100,6 +98,39 @@ function arkOrderMergeInput(config, residualCount = 0) {
|
|
|
100
98
|
};
|
|
101
99
|
}
|
|
102
100
|
|
|
101
|
+
function rulesBySlice(arkRules) {
|
|
102
|
+
const groups = new Map();
|
|
103
|
+
const rows = [...(arkRules?.structure ?? []), ...(arkRules?.invariants ?? [])];
|
|
104
|
+
for (const entry of rows) {
|
|
105
|
+
const childId = entry?.provenance?.childId;
|
|
106
|
+
const sourceFile = entry?.provenance?.sourceFile;
|
|
107
|
+
if (typeof childId !== 'string' || typeof sourceFile !== 'string') continue;
|
|
108
|
+
const key = `${childId}\0${sourceFile}`;
|
|
109
|
+
let group = groups.get(key);
|
|
110
|
+
if (!group) {
|
|
111
|
+
group = {
|
|
112
|
+
slice: childId,
|
|
113
|
+
layer: entry.provenance?.layer ?? null,
|
|
114
|
+
sourceFile,
|
|
115
|
+
ids: [],
|
|
116
|
+
appliesTo: [],
|
|
117
|
+
};
|
|
118
|
+
groups.set(key, group);
|
|
119
|
+
}
|
|
120
|
+
if (typeof entry.id === 'string' && !group.ids.includes(entry.id)) group.ids.push(entry.id);
|
|
121
|
+
for (const pattern of entry.appliesTo ?? []) {
|
|
122
|
+
if (typeof pattern === 'string' && !group.appliesTo.includes(pattern)) group.appliesTo.push(pattern);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return [...groups.values()]
|
|
126
|
+
.map((group) => ({
|
|
127
|
+
...group,
|
|
128
|
+
ids: [...group.ids].sort(),
|
|
129
|
+
appliesTo: [...group.appliesTo].sort(),
|
|
130
|
+
}))
|
|
131
|
+
.sort((left, right) => left.slice.localeCompare(right.slice) || left.sourceFile.localeCompare(right.sourceFile));
|
|
132
|
+
}
|
|
133
|
+
|
|
103
134
|
/** ADR 0012 D2 drift: arkrules/*.json no arkRules entry references (advisory). */
|
|
104
135
|
function unreferencedArkRulesFiles(loaded) {
|
|
105
136
|
const files = (loaded?.warnings ?? []).map((w) => w.path).filter(Boolean);
|
|
@@ -110,11 +141,32 @@ function unreferencedArkRulesFiles(loaded) {
|
|
|
110
141
|
* @param {{ rulesMigration?: boolean }} [options] rulesMigration:false skips the
|
|
111
142
|
* migration counts (the inventory payload computes them itself).
|
|
112
143
|
*/
|
|
144
|
+
function governedPathsFromFacts(root, facts) {
|
|
145
|
+
const rows = facts?.files;
|
|
146
|
+
if (!Array.isArray(rows) || rows.length === 0) return undefined;
|
|
147
|
+
const base = String(root).replace(/\\/g, '/').replace(/\/+$/, '');
|
|
148
|
+
const prefix = `${base}/`;
|
|
149
|
+
const out = [];
|
|
150
|
+
for (const entry of rows) {
|
|
151
|
+
const raw = typeof entry === 'string' ? entry : entry?.path;
|
|
152
|
+
if (typeof raw !== 'string' || raw.length === 0) continue;
|
|
153
|
+
const norm = raw.replace(/\\/g, '/');
|
|
154
|
+
const rel = norm.startsWith(prefix)
|
|
155
|
+
? norm.slice(prefix.length)
|
|
156
|
+
: norm.replace(/^\.\//, '');
|
|
157
|
+
if (!rel || rel.startsWith('..') || rel.startsWith('/')) continue;
|
|
158
|
+
out.push(rel);
|
|
159
|
+
}
|
|
160
|
+
return out.length > 0 ? out : undefined;
|
|
161
|
+
}
|
|
162
|
+
|
|
113
163
|
export function summarizeRulesUnderContract(root, config, facts, classification, options = {}) {
|
|
164
|
+
const governedFiles = governedPathsFromFacts(root, facts);
|
|
165
|
+
const loadOpts = governedFiles ? { files: governedFiles } : {};
|
|
114
166
|
if (!config?.arkRules || Object.keys(config.arkRules).length === 0) {
|
|
115
167
|
let drift = {};
|
|
116
168
|
try {
|
|
117
|
-
drift = unreferencedArkRulesFiles(loadEffectiveArkRulesFromDisk(root, config));
|
|
169
|
+
drift = unreferencedArkRulesFiles(loadEffectiveArkRulesFromDisk(root, config, loadOpts));
|
|
118
170
|
} catch {
|
|
119
171
|
drift = {};
|
|
120
172
|
}
|
|
@@ -136,7 +188,7 @@ export function summarizeRulesUnderContract(root, config, facts, classification,
|
|
|
136
188
|
};
|
|
137
189
|
}
|
|
138
190
|
try {
|
|
139
|
-
const loaded = loadEffectiveArkRulesFromDisk(root, config);
|
|
191
|
+
const loaded = loadEffectiveArkRulesFromDisk(root, config, loadOpts);
|
|
140
192
|
if (loaded.errors?.length) {
|
|
141
193
|
return {
|
|
142
194
|
active: true,
|
|
@@ -167,6 +219,7 @@ export function summarizeRulesUnderContract(root, config, facts, classification,
|
|
|
167
219
|
const covById = new Map(
|
|
168
220
|
(coverage.coverage ?? []).map((row) => [row.invariantId, row])
|
|
169
221
|
);
|
|
222
|
+
const probeSection = probeStatusSection(root, loaded.arkRules.invariants);
|
|
170
223
|
const byLayer = loaded.arkRules.byLayer ?? {};
|
|
171
224
|
const layers = Object.keys(byLayer)
|
|
172
225
|
.sort((a, b) => a.localeCompare(b))
|
|
@@ -180,6 +233,7 @@ export function summarizeRulesUnderContract(root, config, facts, classification,
|
|
|
180
233
|
return {
|
|
181
234
|
name,
|
|
182
235
|
sourceFile: part.sourceFile ?? null,
|
|
236
|
+
...(Array.isArray(part.sourceFiles) ? { sourceFiles: [...part.sourceFiles] } : {}),
|
|
183
237
|
structureRules: (part.structure ?? []).length,
|
|
184
238
|
invariants: layerInvariants.length,
|
|
185
239
|
coveredInvariants: covered,
|
|
@@ -280,6 +334,7 @@ export function summarizeRulesUnderContract(root, config, facts, classification,
|
|
|
280
334
|
partialCoverage: coverage.partial,
|
|
281
335
|
testFilesScanned: coverageInputs.testFiles.length,
|
|
282
336
|
layers,
|
|
337
|
+
...(rulesBySlice(loaded.arkRules).length > 0 ? { bySlice: rulesBySlice(loaded.arkRules) } : {}),
|
|
283
338
|
structure,
|
|
284
339
|
structureTruncated,
|
|
285
340
|
uncovered,
|
|
@@ -296,6 +351,8 @@ export function summarizeRulesUnderContract(root, config, facts, classification,
|
|
|
296
351
|
.rulesMigration,
|
|
297
352
|
}),
|
|
298
353
|
...(coverageInputs.stats ? { coverageStats: coverageInputs.stats } : {}),
|
|
354
|
+
// ADR 0039: only when a probe artifact exists — absence stays silent.
|
|
355
|
+
...(probeSection ? { probe: probeSection } : {}),
|
|
299
356
|
mergePlanes,
|
|
300
357
|
notAScore: true,
|
|
301
358
|
note: 'ArkRules plane (intra-layer) — counts and catalog, never a score. Green with uncovered residual must say so. Structure sensors are heuristics; invariants are catalog+coverage evidence, not a business runtime.',
|
|
@@ -335,6 +392,7 @@ export function formatArkRulesEvidenceLines(section) {
|
|
|
335
392
|
}
|
|
336
393
|
const discard = formatCoverageDiscards(section.coverageStats);
|
|
337
394
|
if (discard) lines.push(`ArkRules:${discard}`);
|
|
395
|
+
lines.push(...formatProbeStatusLines(section.probe));
|
|
338
396
|
return lines;
|
|
339
397
|
}
|
|
340
398
|
|
|
@@ -621,7 +679,7 @@ export function formatRulesUnderContractHtml(section, esc) {
|
|
|
621
679
|
</section>`;
|
|
622
680
|
}
|
|
623
681
|
|
|
624
|
-
|
|
682
|
+
const RULES_INVENTORY_MAX_FILES = 400;
|
|
625
683
|
|
|
626
684
|
/**
|
|
627
685
|
* Brownfield rules migration (AR13/AR15): one code path for `--rules-inventory`,
|
|
@@ -633,7 +691,7 @@ export const RULES_INVENTORY_MAX_FILES = 400;
|
|
|
633
691
|
* @param {{ root: string, config: Record<string, any>, files?: string[], arkRules?: any }} input
|
|
634
692
|
* `files` defaults to the governed walk; `arkRules` to the catalog on disk.
|
|
635
693
|
*/
|
|
636
|
-
|
|
694
|
+
function buildRulesMigration({ root, config, files, arkRules }) {
|
|
637
695
|
const governed = Array.isArray(files) ? files : collectGovernedFiles(root, config);
|
|
638
696
|
const fileContents = {};
|
|
639
697
|
const fileLayers = {};
|
|
@@ -651,7 +709,12 @@ export function buildRulesMigration({ root, config, files, arkRules }) {
|
|
|
651
709
|
let catalog = arkRules;
|
|
652
710
|
if (catalog === undefined && config?.arkRules) {
|
|
653
711
|
try {
|
|
654
|
-
const loaded = loadEffectiveArkRulesFromDisk(root, config
|
|
712
|
+
const loaded = loadEffectiveArkRulesFromDisk(root, config, {
|
|
713
|
+
files: governed.map((file) => {
|
|
714
|
+
const absolute = path.isAbsolute(file) ? file : path.resolve(root, file);
|
|
715
|
+
return path.relative(root, absolute).split(path.sep).join('/');
|
|
716
|
+
}),
|
|
717
|
+
});
|
|
655
718
|
catalog = loaded.errors.length > 0 ? null : loaded.arkRules;
|
|
656
719
|
} catch {
|
|
657
720
|
catalog = null; // the advisory inventory is still useful
|
|
@@ -730,6 +793,7 @@ export function buildRulesInventoryPayload(root, config, files) {
|
|
|
730
793
|
coverageEvidence = {
|
|
731
794
|
symbolEvidence: section.symbolEvidence ?? [],
|
|
732
795
|
discarded: section.coverageStats?.discarded ?? null,
|
|
796
|
+
...(section.probe ? { probe: section.probe } : {}),
|
|
733
797
|
};
|
|
734
798
|
}
|
|
735
799
|
} catch {
|
package/bin/lib/scan-files.mjs
CHANGED
|
@@ -8,12 +8,12 @@ import { isScanExcludedRelative, isSkippedSourceDir } from '../ark-shared.mjs';
|
|
|
8
8
|
|
|
9
9
|
export { isSkippedSourceDir };
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
const SOURCE_FILE_NAME = /\.[cm]?[tj]sx?$/;
|
|
12
12
|
|
|
13
13
|
/** Unit/e2e test files are not architecture surface — agents and Nest put them next
|
|
14
14
|
* to production code (*.spec.ts). Counting them as ungoverned forces false
|
|
15
15
|
* CONFIG_UNCLASSIFIED_FILES under --strict-config on every starter. */
|
|
16
|
-
|
|
16
|
+
const TEST_FILE_NAME =
|
|
17
17
|
/(^test(?:[-_.]).*|\.(spec|test)(?:-d)?\.)(tsx?|jsx?|mjsx?|cjsx?|mts|cts)$/i;
|
|
18
18
|
|
|
19
19
|
export function isGovernableSourceFile(name) {
|
|
@@ -123,7 +123,7 @@ export function walk(dir, files = [], options = {}) {
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
/** Walk include roots then drop codegen / config.exclude (universal scan filter). */
|
|
126
|
-
|
|
126
|
+
function isIncludeMatch(relativePath, include) {
|
|
127
127
|
return (include ?? []).some((entry) => {
|
|
128
128
|
const includeRoot = String(entry)
|
|
129
129
|
.replace(/\\/g, '/')
|
|
@@ -192,7 +192,7 @@ export function normalize(value) {
|
|
|
192
192
|
export const UNGOVERNED_PROBE_CAP = 200;
|
|
193
193
|
|
|
194
194
|
/** Tooling configs (vite.config.ts, eslint.config.js …) are not the product source a contract governs. */
|
|
195
|
-
|
|
195
|
+
const TOOLING_CONFIG_FILE_NAME = /\.config\.[cm]?[jt]sx?$/i;
|
|
196
196
|
|
|
197
197
|
/**
|
|
198
198
|
* Count governable source files under `root` that the contract's own scope cannot hide.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GENERATED FILE — do not edit by hand.
|
|
3
|
+
*
|
|
4
|
+
* Canonical algorithm: src/domain/schemaValidation.ts
|
|
5
|
+
* Regenerate: node scripts/generate-cli-pure.mjs
|
|
6
|
+
* Drift check: node scripts/generate-cli-pure.mjs --check
|
|
7
|
+
*
|
|
8
|
+
* Pure CLI helper (bin/lib/schema-validation.mjs). Zero Node I/O.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export function isObject(value) {
|
|
12
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
export function propertyPath(parent, key) {
|
|
15
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key)
|
|
16
|
+
? `${parent}.${key}`
|
|
17
|
+
: `${parent}[${JSON.stringify(key)}]`;
|
|
18
|
+
}
|
|
19
|
+
export function valueType(value) {
|
|
20
|
+
if (value === null)
|
|
21
|
+
return 'null';
|
|
22
|
+
if (Array.isArray(value))
|
|
23
|
+
return 'array';
|
|
24
|
+
return typeof value;
|
|
25
|
+
}
|
|
26
|
+
function resolveSchemaRef(ref, root) {
|
|
27
|
+
const prefix = '#/$defs/';
|
|
28
|
+
if (!ref.startsWith(prefix))
|
|
29
|
+
return undefined;
|
|
30
|
+
return root.$defs[ref.slice(prefix.length)];
|
|
31
|
+
}
|
|
32
|
+
/** Walk `value` against `schema`, appending every finding to `issues`. */
|
|
33
|
+
export function validateSchemaNode(value, schema, path, root, issues) {
|
|
34
|
+
if (schema.$ref) {
|
|
35
|
+
const referenced = resolveSchemaRef(schema.$ref, root);
|
|
36
|
+
if (!referenced) {
|
|
37
|
+
issues.push({ path, message: `schema reference ${schema.$ref} cannot be resolved` });
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
validateSchemaNode(value, referenced, path, root, issues);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (schema.const !== undefined && !Object.is(value, schema.const)) {
|
|
44
|
+
issues.push({ path, message: `must equal ${JSON.stringify(schema.const)}` });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (schema.enum && !schema.enum.some((candidate) => Object.is(candidate, value))) {
|
|
48
|
+
issues.push({ path, message: `must be one of ${schema.enum.map(String).join(', ')}` });
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (schema.type === 'object') {
|
|
52
|
+
if (!isObject(value)) {
|
|
53
|
+
issues.push({ path, message: `must be an object; received ${valueType(value)}` });
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const properties = schema.properties ?? {};
|
|
57
|
+
for (const key of schema.required ?? []) {
|
|
58
|
+
if (value[key] === undefined) {
|
|
59
|
+
issues.push({ path: propertyPath(path, key), message: 'is required' });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (schema.additionalProperties === false) {
|
|
63
|
+
for (const key of Object.keys(value)) {
|
|
64
|
+
if (!(key in properties)) {
|
|
65
|
+
issues.push({ path: propertyPath(path, key), message: 'unknown field' });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else if (schema.additionalProperties !== undefined &&
|
|
70
|
+
schema.additionalProperties !== true &&
|
|
71
|
+
typeof schema.additionalProperties === 'object') {
|
|
72
|
+
const additional = schema.additionalProperties;
|
|
73
|
+
for (const key of Object.keys(value)) {
|
|
74
|
+
if (!(key in properties)) {
|
|
75
|
+
validateSchemaNode(value[key], additional, propertyPath(path, key), root, issues);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
for (const [key, childSchema] of Object.entries(properties)) {
|
|
80
|
+
if (value[key] !== undefined) {
|
|
81
|
+
validateSchemaNode(value[key], childSchema, propertyPath(path, key), root, issues);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (schema.type === 'array') {
|
|
87
|
+
if (!Array.isArray(value)) {
|
|
88
|
+
issues.push({ path, message: `must be an array; received ${valueType(value)}` });
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (schema.minItems !== undefined && value.length < schema.minItems) {
|
|
92
|
+
issues.push({ path, message: `must contain at least ${schema.minItems} item(s)` });
|
|
93
|
+
}
|
|
94
|
+
if (schema.uniqueItems) {
|
|
95
|
+
const serialized = value.map((entry) => JSON.stringify(entry));
|
|
96
|
+
if (new Set(serialized).size !== serialized.length) {
|
|
97
|
+
issues.push({ path, message: 'must not contain duplicate items' });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (schema.items) {
|
|
101
|
+
value.forEach((entry, index) => validateSchemaNode(entry, schema.items, `${path}[${index}]`, root, issues));
|
|
102
|
+
}
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (schema.type === 'string') {
|
|
106
|
+
if (typeof value !== 'string') {
|
|
107
|
+
issues.push({ path, message: `must be a string; received ${valueType(value)}` });
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (schema.minLength !== undefined && value.length < schema.minLength) {
|
|
111
|
+
issues.push({ path, message: `must contain at least ${schema.minLength} character(s)` });
|
|
112
|
+
}
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (schema.type === 'boolean') {
|
|
116
|
+
if (typeof value !== 'boolean') {
|
|
117
|
+
issues.push({ path, message: `must be a boolean; received ${valueType(value)}` });
|
|
118
|
+
}
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (schema.type === 'integer') {
|
|
122
|
+
if (!Number.isInteger(value)) {
|
|
123
|
+
issues.push({ path, message: `must be an integer; received ${valueType(value)}` });
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (schema.minimum !== undefined && value < schema.minimum) {
|
|
127
|
+
issues.push({ path, message: `must be at least ${schema.minimum}` });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -11,18 +11,9 @@ import path from 'node:path';
|
|
|
11
11
|
|
|
12
12
|
import { arkCommand } from '../ark-shared.mjs';
|
|
13
13
|
import { collectGovernedFiles, normalize } from './scan-files.mjs';
|
|
14
|
+
// Same palette and TTY test as the entry's stderr output.
|
|
15
|
+
import { color } from './violations.mjs';
|
|
14
16
|
|
|
15
|
-
// Same shape and the same TTY test as the entry's own `color`: this module is
|
|
16
|
-
// the only other writer to that terminal, and a second copy is cheaper than
|
|
17
|
-
// exporting the entry's internals into a cycle.
|
|
18
|
-
const useColor = process.stderr.isTTY && !process.env.NO_COLOR;
|
|
19
|
-
const color = {
|
|
20
|
-
red: (s) => (useColor ? `\x1b[31m${s}\x1b[0m` : s),
|
|
21
|
-
yellow: (s) => (useColor ? `\x1b[33m${s}\x1b[0m` : s),
|
|
22
|
-
green: (s) => (useColor ? `\x1b[32m${s}\x1b[0m` : s),
|
|
23
|
-
dim: (s) => (useColor ? `\x1b[2m${s}\x1b[0m` : s),
|
|
24
|
-
bold: (s) => (useColor ? `\x1b[1m${s}\x1b[0m` : s),
|
|
25
|
-
};
|
|
26
17
|
|
|
27
18
|
/**
|
|
28
19
|
* A rule id or a source path comes out of the project's own ArkRules JSON and
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
invariantIdsFromCatalog,
|
|
17
17
|
loadInvariantCoverageInputs,
|
|
18
18
|
} from './invariant-coverage-io.mjs';
|
|
19
|
+
import { attachProbeEvidence } from './invariant-probe-io.mjs';
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* The root as the filesystem sees it. Comparing a realpath against a lexical
|
|
@@ -79,28 +80,11 @@ export function loadSensorMap(root, config, facts) {
|
|
|
79
80
|
}));
|
|
80
81
|
|
|
81
82
|
const catalogued = loaded.arkRules.invariants ?? [];
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
...coverageOptionsFromConfig(config),
|
|
88
|
-
});
|
|
89
|
-
const coverage = evaluateInvariantCoverage({
|
|
90
|
-
arkRules: loaded.arkRules,
|
|
91
|
-
fileContents: inputs.fileContents,
|
|
92
|
-
testFiles: inputs.testFiles,
|
|
93
|
-
testGlobsMissing: inputs.testGlobsMissing,
|
|
94
|
-
coverageBudgetExhausted: inputs.coverageBudgetExhausted === true,
|
|
95
|
-
...(inputs.stats ? { coverageStats: inputs.stats } : {}),
|
|
96
|
-
// The declared roots decide `outsideDeclaredRoots`, which is one of the
|
|
97
|
-
// four reasons canPromoteInvariant refuses. Dropping them here would make
|
|
98
|
-
// this surface promise a promotion the gate then denies.
|
|
99
|
-
...(inputs.coverageRoots ? { coverageRoots: inputs.coverageRoots } : {}),
|
|
100
|
-
});
|
|
101
|
-
coverageRows = coverage.coverage ?? [];
|
|
102
|
-
partial = coverage.partial === true;
|
|
103
|
-
}
|
|
83
|
+
const evaluated = evaluateCatalogCoverage(root, config, facts, loaded.arkRules);
|
|
84
|
+
// ADR 0039: a committed probe artifact can only subtract promotability.
|
|
85
|
+
// Read-only here — this path never runs a test.
|
|
86
|
+
const coverageRows = attachProbeEvidence(root, evaluated.rows, catalogued).rows;
|
|
87
|
+
const partial = evaluated.partial;
|
|
104
88
|
const evidenceById = new Map(coverageRows.map((row) => [row.invariantId, row]));
|
|
105
89
|
|
|
106
90
|
const invariants = catalogued.map((entry) => ({
|
|
@@ -120,6 +104,33 @@ export function loadSensorMap(root, config, facts) {
|
|
|
120
104
|
};
|
|
121
105
|
}
|
|
122
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Invariant coverage rows for a loaded catalog, evaluated the way the
|
|
109
|
+
* promotion judge needs them (declared `coverageRoots` included). `inputs` is
|
|
110
|
+
* the loaded file set, reused by the probe to pick covering tests.
|
|
111
|
+
*/
|
|
112
|
+
export function evaluateCatalogCoverage(root, config, facts, arkRules) {
|
|
113
|
+
const catalogued = arkRules?.invariants ?? [];
|
|
114
|
+
if (catalogued.length === 0) return { rows: [], partial: false, inputs: null };
|
|
115
|
+
const inputs = loadInvariantCoverageInputs(root, facts ?? { files: [] }, {
|
|
116
|
+
invariantIds: invariantIdsFromCatalog(arkRules),
|
|
117
|
+
...coverageOptionsFromConfig(config),
|
|
118
|
+
});
|
|
119
|
+
const coverage = evaluateInvariantCoverage({
|
|
120
|
+
arkRules,
|
|
121
|
+
fileContents: inputs.fileContents,
|
|
122
|
+
testFiles: inputs.testFiles,
|
|
123
|
+
testGlobsMissing: inputs.testGlobsMissing,
|
|
124
|
+
coverageBudgetExhausted: inputs.coverageBudgetExhausted === true,
|
|
125
|
+
...(inputs.stats ? { coverageStats: inputs.stats } : {}),
|
|
126
|
+
// The declared roots decide `outsideDeclaredRoots`, which is one of the
|
|
127
|
+
// four reasons canPromoteInvariant refuses. Dropping them here would make
|
|
128
|
+
// this surface promise a promotion the gate then denies.
|
|
129
|
+
...(inputs.coverageRoots ? { coverageRoots: inputs.coverageRoots } : {}),
|
|
130
|
+
});
|
|
131
|
+
return { rows: coverage.coverage ?? [], partial: coverage.partial === true, inputs };
|
|
132
|
+
}
|
|
133
|
+
|
|
123
134
|
/**
|
|
124
135
|
* Count the findings each declared rule produced in an analysis that already
|
|
125
136
|
* ran. One run answers for every rule: the loop this replaces was one full
|
|
@@ -188,7 +188,7 @@ export function buildSensorMap(input) {
|
|
|
188
188
|
sourceFile,
|
|
189
189
|
description: nullable(rule.description),
|
|
190
190
|
promotable: verdict.ok,
|
|
191
|
-
blocker: verdict.ok ? null : 'no-coverage-evidence',
|
|
191
|
+
blocker: verdict.ok ? null : verdict.blocker ?? 'no-coverage-evidence',
|
|
192
192
|
reason: `${locate(rule.id, sourceFile)}: ${verdict.reason}`,
|
|
193
193
|
coverageEvaluated: rule.coverage != null,
|
|
194
194
|
...ambiguity(rule.id),
|
|
@@ -19,7 +19,7 @@ function skillName(skill) {
|
|
|
19
19
|
return Array.isArray(skill) ? skill[0] : skill?.name || skill;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
function projectCatalogReady(root, skillNames) {
|
|
23
23
|
return skillNames.some((name) => fs.existsSync(path.join(root, canonicalSkillPath(name))));
|
|
24
24
|
}
|
|
25
25
|
|