pumuki 6.3.147 → 6.3.149
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/core/facts/detectors/text/ios.test.ts +64 -0
- package/core/facts/detectors/text/ios.ts +97 -1
- package/core/facts/extractHeuristicFacts.ts +25 -5
- package/integrations/evidence/rulesCoverage.ts +70 -0
- package/integrations/evidence/schema.ts +9 -0
- package/integrations/gate/evaluateAiGate.ts +15 -0
- package/integrations/git/astIntelligenceDualValidation.ts +2 -2
- package/integrations/git/runPlatformGate.ts +131 -39
- package/package.json +1 -1
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
findSwiftOpenClosedSwitchMatch,
|
|
7
7
|
findSwiftConcreteDependencyDipMatch,
|
|
8
8
|
findSwiftPresentationSrpMatch,
|
|
9
|
+
findSwiftXCTestSrpMatch,
|
|
9
10
|
hasSwiftAnyViewUsage,
|
|
10
11
|
hasSwiftCallbackStyleSignature,
|
|
11
12
|
hasSwiftCornerRadiusUsage,
|
|
@@ -888,6 +889,69 @@ final class PumukiOcpIosCanaryUseCase {
|
|
|
888
889
|
assert.match(match.expected_fix, /estrategia|protocolo|registry/i);
|
|
889
890
|
});
|
|
890
891
|
|
|
892
|
+
test('findSwiftOpenClosedSwitchMatch detecta switch sobre outcome en Coordinator iOS', () => {
|
|
893
|
+
const source = `public final class LaunchFlowCoordinator {
|
|
894
|
+
public func bootstrap() async {
|
|
895
|
+
let outcome = await appConfigurationUseCase.execute()
|
|
896
|
+
switch outcome {
|
|
897
|
+
case .mandatoryUpdate:
|
|
898
|
+
route = .updateRequired
|
|
899
|
+
case .maintenance:
|
|
900
|
+
route = .maintenance
|
|
901
|
+
case .proceed:
|
|
902
|
+
route = .home
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
`;
|
|
907
|
+
|
|
908
|
+
const match = findSwiftOpenClosedSwitchMatch(source);
|
|
909
|
+
|
|
910
|
+
assert.ok(match);
|
|
911
|
+
assert.equal(match.primary_node.name, 'LaunchFlowCoordinator');
|
|
912
|
+
assert.deepEqual(match.related_nodes, [
|
|
913
|
+
{ kind: 'member', name: 'discriminator switch: outcome', lines: [4] },
|
|
914
|
+
{ kind: 'member', name: 'case .mandatoryUpdate', lines: [5] },
|
|
915
|
+
{ kind: 'member', name: 'case .maintenance', lines: [7] },
|
|
916
|
+
{ kind: 'member', name: 'case .proceed', lines: [9] },
|
|
917
|
+
]);
|
|
918
|
+
assert.match(match.why, /OCP/);
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
test('findSwiftXCTestSrpMatch detecta XCTestCase con responsabilidades mezcladas', () => {
|
|
922
|
+
const source = `import XCTest
|
|
923
|
+
|
|
924
|
+
final class LaunchFlowCoordinatorConfigTests: XCTestCase {
|
|
925
|
+
func test_bootstrap_whenMandatoryUpdate_routesToUpdateRequired() async {}
|
|
926
|
+
func test_bootstrap_whenSessionIsValid_routesHome() async {}
|
|
927
|
+
func test_completeOnboarding_marksProgressAndRoutesToLogin() async {}
|
|
928
|
+
}
|
|
929
|
+
`;
|
|
930
|
+
|
|
931
|
+
const match = findSwiftXCTestSrpMatch(source);
|
|
932
|
+
|
|
933
|
+
assert.ok(match);
|
|
934
|
+
assert.equal(match.primary_node.name, 'LaunchFlowCoordinatorConfigTests');
|
|
935
|
+
assert.deepEqual(match.related_nodes, [
|
|
936
|
+
{ kind: 'member', name: 'session routing tests', lines: [5] },
|
|
937
|
+
{ kind: 'member', name: 'onboarding progress tests', lines: [6] },
|
|
938
|
+
]);
|
|
939
|
+
assert.match(match.why, /XCTestCase|SRP/);
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
test('findSwiftXCTestSrpMatch permite XCTestCase enfocado en una responsabilidad', () => {
|
|
943
|
+
const source = `import XCTest
|
|
944
|
+
|
|
945
|
+
final class LaunchFlowCoordinatorNonBlockingConfigTests: XCTestCase {
|
|
946
|
+
func test_bootstrap_whenConfigFetchFailsWithCache_usesCachedConfigAndContinues() async {}
|
|
947
|
+
func test_bootstrap_whenOptionalUpdate_allowsAccessAndContinues() async {}
|
|
948
|
+
func test_bootstrap_whenProceed_continuesToSessionValidation() async {}
|
|
949
|
+
}
|
|
950
|
+
`;
|
|
951
|
+
|
|
952
|
+
assert.equal(findSwiftXCTestSrpMatch(source), undefined);
|
|
953
|
+
});
|
|
954
|
+
|
|
891
955
|
test('findSwiftInterfaceSegregationMatch devuelve payload semantico para ISP-iOS en application', () => {
|
|
892
956
|
const source = `
|
|
893
957
|
protocol PumukiIspIosCanarySessionManaging {
|
|
@@ -32,6 +32,8 @@ export type SwiftPresentationSrpMatch = {
|
|
|
32
32
|
lines: readonly number[];
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
+
export type SwiftXCTestSrpMatch = SwiftPresentationSrpMatch;
|
|
36
|
+
|
|
35
37
|
export type SwiftConcreteDependencyDipMatch = {
|
|
36
38
|
primary_node: SwiftSemanticNodeMatch;
|
|
37
39
|
related_nodes: readonly SwiftSemanticNodeMatch[];
|
|
@@ -1185,6 +1187,100 @@ export const findSwiftPresentationSrpMatch = (
|
|
|
1185
1187
|
};
|
|
1186
1188
|
};
|
|
1187
1189
|
|
|
1190
|
+
export const findSwiftXCTestSrpMatch = (source: string): SwiftXCTestSrpMatch | undefined => {
|
|
1191
|
+
if (!hasSwiftXCTestCaseSubclassUsage(source)) {
|
|
1192
|
+
return undefined;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
const classPattern = /\b(?:final\s+)?class\s+([A-Za-z0-9_]*Tests?)\s*:\s*XCTestCase\b/;
|
|
1196
|
+
const classLines = collectSwiftRegexLines(source, classPattern);
|
|
1197
|
+
if (classLines.length === 0) {
|
|
1198
|
+
return undefined;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
const classLine = source.split(/\r?\n/)[classLines[0] - 1] ?? '';
|
|
1202
|
+
const className = classLine.match(classPattern)?.[1];
|
|
1203
|
+
if (!className) {
|
|
1204
|
+
return undefined;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
const sourceLines = source.split(/\r?\n/);
|
|
1208
|
+
const familyPatterns: ReadonlyArray<{
|
|
1209
|
+
key: string;
|
|
1210
|
+
name: string;
|
|
1211
|
+
tokens: readonly string[];
|
|
1212
|
+
}> = [
|
|
1213
|
+
{ key: 'configuration', name: 'configuration outcome tests', tokens: ['config', 'configuration', 'update', 'cache'] },
|
|
1214
|
+
{ key: 'session', name: 'session routing tests', tokens: ['session', 'login', 'auth', 'valid', 'invalid'] },
|
|
1215
|
+
{ key: 'onboarding', name: 'onboarding progress tests', tokens: ['onboarding', 'progress', 'completeonboarding'] },
|
|
1216
|
+
{ key: 'permissions', name: 'permissions routing tests', tokens: ['permission', 'camera', 'location', 'notification'] },
|
|
1217
|
+
{ key: 'tutorial', name: 'tutorial or feature discovery tests', tokens: ['tutorial', 'featurediscovery'] },
|
|
1218
|
+
{ key: 'splash', name: 'splash delay tests', tokens: ['splash', 'delay', 'start'] },
|
|
1219
|
+
];
|
|
1220
|
+
const matchesFamily = (value: string, tokens: readonly string[]): boolean => {
|
|
1221
|
+
const normalizedValue = value.toLowerCase();
|
|
1222
|
+
return tokens.some((token) => normalizedValue.includes(token));
|
|
1223
|
+
};
|
|
1224
|
+
|
|
1225
|
+
const classFamilyKeys = new Set(
|
|
1226
|
+
familyPatterns
|
|
1227
|
+
.filter((entry) => matchesFamily(className, entry.tokens))
|
|
1228
|
+
.map((entry) => entry.key)
|
|
1229
|
+
);
|
|
1230
|
+
const matchedFamilies = new Map<string, SwiftSemanticNodeMatch>();
|
|
1231
|
+
|
|
1232
|
+
sourceLines.forEach((line, index) => {
|
|
1233
|
+
const sanitizedLine = stripSwiftLineForSemanticScan(line);
|
|
1234
|
+
const methodMatch = sanitizedLine.match(/\bfunc\s+(test[A-Za-z0-9_]+)\s*\(/);
|
|
1235
|
+
const methodName = methodMatch?.[1];
|
|
1236
|
+
if (!methodName) {
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
for (const family of familyPatterns) {
|
|
1241
|
+
if (!matchesFamily(methodName, family.tokens)) {
|
|
1242
|
+
continue;
|
|
1243
|
+
}
|
|
1244
|
+
if (classFamilyKeys.has(family.key)) {
|
|
1245
|
+
continue;
|
|
1246
|
+
}
|
|
1247
|
+
if (!matchedFamilies.has(family.key)) {
|
|
1248
|
+
matchedFamilies.set(family.key, {
|
|
1249
|
+
kind: 'member',
|
|
1250
|
+
name: family.name,
|
|
1251
|
+
lines: [index + 1],
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
});
|
|
1256
|
+
|
|
1257
|
+
const relatedNodes = [...matchedFamilies.values()];
|
|
1258
|
+
if (relatedNodes.length < 2) {
|
|
1259
|
+
return undefined;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
const relatedNodeNames = relatedNodes.map((node) => node.name).join(', ');
|
|
1263
|
+
const allLines = sortedUniqueLines([
|
|
1264
|
+
...classLines,
|
|
1265
|
+
...relatedNodes.flatMap((node) => [...node.lines]),
|
|
1266
|
+
]);
|
|
1267
|
+
|
|
1268
|
+
return {
|
|
1269
|
+
primary_node: {
|
|
1270
|
+
kind: 'class',
|
|
1271
|
+
name: className,
|
|
1272
|
+
lines: classLines,
|
|
1273
|
+
},
|
|
1274
|
+
related_nodes: relatedNodes,
|
|
1275
|
+
why: `${className} mezcla ${relatedNodeNames} dentro del mismo XCTestCase, rompiendo SRP en la suite de tests.`,
|
|
1276
|
+
impact:
|
|
1277
|
+
'La suite acumula múltiples razones de cambio, oculta regresiones por responsabilidad y hace más difícil aislar el baseline afectado antes de PRE_WRITE.',
|
|
1278
|
+
expected_fix:
|
|
1279
|
+
'Divide la suite por responsabilidad observable: configuración, sesión, onboarding, permisos, tutorial o splash deben vivir en XCTestCase separados.',
|
|
1280
|
+
lines: allLines,
|
|
1281
|
+
};
|
|
1282
|
+
};
|
|
1283
|
+
|
|
1188
1284
|
export const findSwiftConcreteDependencyDipMatch = (
|
|
1189
1285
|
source: string
|
|
1190
1286
|
): SwiftConcreteDependencyDipMatch | undefined => {
|
|
@@ -1275,7 +1371,7 @@ export const findSwiftOpenClosedSwitchMatch = (
|
|
|
1275
1371
|
}
|
|
1276
1372
|
|
|
1277
1373
|
const lines = source.split(/\r?\n/);
|
|
1278
|
-
const discriminatorPattern = /\b(?:kind|type|mode|channel|variant|provider|route|flow|source|experience)\b/i;
|
|
1374
|
+
const discriminatorPattern = /\b(?:kind|type|mode|channel|variant|provider|route|flow|source|experience|outcome|state|status|configuration|config)\b/i;
|
|
1279
1375
|
const switchPattern = /\bswitch\s+([A-Za-z_][A-Za-z0-9_\.]*)\s*\{/;
|
|
1280
1376
|
|
|
1281
1377
|
for (let index = 0; index < lines.length; index += 1) {
|
|
@@ -901,11 +901,30 @@ export const extractHeuristicFacts = (
|
|
|
901
901
|
}
|
|
902
902
|
}
|
|
903
903
|
|
|
904
|
-
if (
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
904
|
+
if (params.detectedPlatforms.ios?.detected && isIOSSwiftPath(fileFact.path)) {
|
|
905
|
+
if (isSwiftTestPath(fileFact.path)) {
|
|
906
|
+
const semanticTestSrpMatch = TextIOS.findSwiftXCTestSrpMatch(fileFact.content);
|
|
907
|
+
if (semanticTestSrpMatch) {
|
|
908
|
+
heuristicFacts.push(
|
|
909
|
+
createHeuristicFact({
|
|
910
|
+
ruleId: 'heuristics.ios.solid.srp.presentation-mixed-responsibilities.ast',
|
|
911
|
+
code: 'HEURISTICS_IOS_SOLID_SRP_XCTEST_MIXED_RESPONSIBILITIES_AST',
|
|
912
|
+
message:
|
|
913
|
+
'Semantic iOS SRP heuristic detected an XCTestCase suite mixing multiple responsibilities.',
|
|
914
|
+
filePath: fileFact.path,
|
|
915
|
+
lines: semanticTestSrpMatch.lines,
|
|
916
|
+
severity: 'CRITICAL',
|
|
917
|
+
primary_node: semanticTestSrpMatch.primary_node,
|
|
918
|
+
related_nodes: semanticTestSrpMatch.related_nodes,
|
|
919
|
+
why: semanticTestSrpMatch.why,
|
|
920
|
+
impact: semanticTestSrpMatch.impact,
|
|
921
|
+
expected_fix: semanticTestSrpMatch.expected_fix,
|
|
922
|
+
})
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
if (!isSwiftTestPath(fileFact.path)) {
|
|
909
928
|
if (isIOSApplicationOrPresentationPath(fileFact.path)) {
|
|
910
929
|
const semanticOcpMatch = TextIOS.findSwiftOpenClosedSwitchMatch(fileFact.content);
|
|
911
930
|
if (semanticOcpMatch) {
|
|
@@ -1027,6 +1046,7 @@ export const extractHeuristicFacts = (
|
|
|
1027
1046
|
})
|
|
1028
1047
|
);
|
|
1029
1048
|
}
|
|
1049
|
+
}
|
|
1030
1050
|
}
|
|
1031
1051
|
|
|
1032
1052
|
if (
|
|
@@ -30,6 +30,17 @@ const createCoverageRatio = (active: number, evaluated: number): number => {
|
|
|
30
30
|
return normalizeCoverageRatio(evaluated / active);
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
+
const createSemanticEnforcementRatio = (params: {
|
|
34
|
+
registryTotal: number;
|
|
35
|
+
unsupportedDetector: number;
|
|
36
|
+
}): number => {
|
|
37
|
+
if (params.registryTotal === 0) {
|
|
38
|
+
return 1;
|
|
39
|
+
}
|
|
40
|
+
const supported = Math.max(0, params.registryTotal - params.unsupportedDetector);
|
|
41
|
+
return normalizeCoverageRatio(supported / params.registryTotal);
|
|
42
|
+
};
|
|
43
|
+
|
|
33
44
|
export const createEmptySnapshotRulesCoverage = (
|
|
34
45
|
stage: GateStage
|
|
35
46
|
): SnapshotRulesCoverage => ({
|
|
@@ -48,6 +59,15 @@ export const createEmptySnapshotRulesCoverage = (
|
|
|
48
59
|
unevaluated: 0,
|
|
49
60
|
},
|
|
50
61
|
coverage_ratio: 1,
|
|
62
|
+
auto_runtime_coverage_ratio: 1,
|
|
63
|
+
semantic_enforcement_ratio: 1,
|
|
64
|
+
global_skills_enforcement: {
|
|
65
|
+
status: 'enforced',
|
|
66
|
+
registry_total: 0,
|
|
67
|
+
detector_supported: 0,
|
|
68
|
+
declarative_only: 0,
|
|
69
|
+
unsupported_detector: 0,
|
|
70
|
+
},
|
|
51
71
|
});
|
|
52
72
|
|
|
53
73
|
export const normalizeSnapshotRulesCoverage = (
|
|
@@ -118,6 +138,43 @@ export const normalizeSnapshotRulesCoverage = (
|
|
|
118
138
|
const coverageRatio = normalizeCoverageRatio(
|
|
119
139
|
Number.isFinite(value.coverage_ratio) ? value.coverage_ratio : ratioFromCounts
|
|
120
140
|
);
|
|
141
|
+
const registryTotal = normalizeCount(
|
|
142
|
+
value.global_skills_enforcement?.registry_total
|
|
143
|
+
?? value.registry_totals?.total
|
|
144
|
+
?? counts.registry_total
|
|
145
|
+
?? 0
|
|
146
|
+
);
|
|
147
|
+
const declarativeOnly = normalizeCount(
|
|
148
|
+
value.global_skills_enforcement?.declarative_only
|
|
149
|
+
?? value.registry_totals?.declarative
|
|
150
|
+
?? counts.registry_declarative
|
|
151
|
+
?? 0
|
|
152
|
+
);
|
|
153
|
+
const unsupportedDetector = normalizeCount(
|
|
154
|
+
value.global_skills_enforcement?.unsupported_detector
|
|
155
|
+
?? counts.unsupported_detector
|
|
156
|
+
?? unsupportedDetectorCount
|
|
157
|
+
);
|
|
158
|
+
const detectorSupported = normalizeCount(
|
|
159
|
+
value.global_skills_enforcement?.detector_supported
|
|
160
|
+
?? Math.max(0, registryTotal - unsupportedDetector)
|
|
161
|
+
);
|
|
162
|
+
const semanticEnforcementRatio = normalizeCoverageRatio(
|
|
163
|
+
Number.isFinite(value.semantic_enforcement_ratio)
|
|
164
|
+
? value.semantic_enforcement_ratio ?? 1
|
|
165
|
+
: createSemanticEnforcementRatio({
|
|
166
|
+
registryTotal,
|
|
167
|
+
unsupportedDetector,
|
|
168
|
+
})
|
|
169
|
+
);
|
|
170
|
+
const globalSkillsStatus: NonNullable<
|
|
171
|
+
SnapshotRulesCoverage['global_skills_enforcement']
|
|
172
|
+
>['status'] =
|
|
173
|
+
registryTotal === 0 || unsupportedDetector === 0
|
|
174
|
+
? 'enforced'
|
|
175
|
+
: detectorSupported > 0
|
|
176
|
+
? 'partially_enforced'
|
|
177
|
+
: 'unsupported';
|
|
121
178
|
|
|
122
179
|
const normalized: SnapshotRulesCoverage = {
|
|
123
180
|
stage,
|
|
@@ -131,6 +188,19 @@ export const normalizeSnapshotRulesCoverage = (
|
|
|
131
188
|
unevaluated_rule_ids: unevaluatedRuleIds,
|
|
132
189
|
counts,
|
|
133
190
|
coverage_ratio: coverageRatio,
|
|
191
|
+
auto_runtime_coverage_ratio: normalizeCoverageRatio(
|
|
192
|
+
Number.isFinite(value.auto_runtime_coverage_ratio)
|
|
193
|
+
? value.auto_runtime_coverage_ratio ?? coverageRatio
|
|
194
|
+
: coverageRatio
|
|
195
|
+
),
|
|
196
|
+
semantic_enforcement_ratio: semanticEnforcementRatio,
|
|
197
|
+
global_skills_enforcement: {
|
|
198
|
+
status: value.global_skills_enforcement?.status ?? globalSkillsStatus,
|
|
199
|
+
registry_total: registryTotal,
|
|
200
|
+
detector_supported: detectorSupported,
|
|
201
|
+
declarative_only: declarativeOnly,
|
|
202
|
+
unsupported_detector: unsupportedDetector,
|
|
203
|
+
},
|
|
134
204
|
};
|
|
135
205
|
|
|
136
206
|
if (registryTotals) {
|
|
@@ -76,6 +76,15 @@ export type SnapshotRulesCoverage = {
|
|
|
76
76
|
unsupported_detector?: number;
|
|
77
77
|
};
|
|
78
78
|
coverage_ratio: number;
|
|
79
|
+
auto_runtime_coverage_ratio?: number;
|
|
80
|
+
semantic_enforcement_ratio?: number;
|
|
81
|
+
global_skills_enforcement?: {
|
|
82
|
+
status: 'enforced' | 'partially_enforced' | 'unsupported';
|
|
83
|
+
registry_total: number;
|
|
84
|
+
detector_supported: number;
|
|
85
|
+
declarative_only: number;
|
|
86
|
+
unsupported_detector: number;
|
|
87
|
+
};
|
|
79
88
|
};
|
|
80
89
|
|
|
81
90
|
export type Snapshot = {
|
|
@@ -1300,6 +1300,15 @@ const toHighestTriggeredSeverity = (
|
|
|
1300
1300
|
return null;
|
|
1301
1301
|
};
|
|
1302
1302
|
|
|
1303
|
+
const hasAppliedGateWaiver = (evidenceResult: EvidenceReadResult): boolean => {
|
|
1304
|
+
if (evidenceResult.kind !== 'valid') {
|
|
1305
|
+
return false;
|
|
1306
|
+
}
|
|
1307
|
+
return evidenceResult.evidence.snapshot.findings.some(
|
|
1308
|
+
(finding) => finding.code === 'GATE_WAIVER_APPLIED'
|
|
1309
|
+
);
|
|
1310
|
+
};
|
|
1311
|
+
|
|
1303
1312
|
const collectEvidencePolicyThresholdViolations = (params: {
|
|
1304
1313
|
evidenceResult: EvidenceReadResult;
|
|
1305
1314
|
policy: ReturnType<typeof resolvePolicyForStage>['policy'];
|
|
@@ -1307,6 +1316,12 @@ const collectEvidencePolicyThresholdViolations = (params: {
|
|
|
1307
1316
|
if (params.evidenceResult.kind !== 'valid') {
|
|
1308
1317
|
return [];
|
|
1309
1318
|
}
|
|
1319
|
+
if (
|
|
1320
|
+
params.evidenceResult.evidence.ai_gate.status === 'ALLOWED' &&
|
|
1321
|
+
hasAppliedGateWaiver(params.evidenceResult)
|
|
1322
|
+
) {
|
|
1323
|
+
return [];
|
|
1324
|
+
}
|
|
1310
1325
|
|
|
1311
1326
|
const severityCounts = params.evidenceResult.evidence.severity_metrics.by_severity;
|
|
1312
1327
|
const blockSeverity = toHighestTriggeredSeverity(
|
|
@@ -162,7 +162,7 @@ export const resolveAstIntelligenceDualValidationMode = (
|
|
|
162
162
|
|
|
163
163
|
const toDualValidationFinding = (params: {
|
|
164
164
|
mode: Exclude<AstIntelligenceDualValidationMode, 'off'>;
|
|
165
|
-
stage: 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
|
|
165
|
+
stage: 'PRE_WRITE' | 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
|
|
166
166
|
summary: AstIntelligenceDualValidationSummary;
|
|
167
167
|
}): Finding | undefined => {
|
|
168
168
|
if (params.summary.divergences === 0) {
|
|
@@ -202,7 +202,7 @@ const toDualValidationFinding = (params: {
|
|
|
202
202
|
};
|
|
203
203
|
|
|
204
204
|
export const evaluateAstIntelligenceDualValidation = (params: {
|
|
205
|
-
stage: 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
|
|
205
|
+
stage: 'PRE_WRITE' | 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
|
|
206
206
|
skillsRules: RuleSet;
|
|
207
207
|
facts: ReadonlyArray<Fact>;
|
|
208
208
|
legacyFindings: ReadonlyArray<Finding>;
|
|
@@ -3,6 +3,7 @@ import type { Fact } from '../../core/facts/Fact';
|
|
|
3
3
|
import type { Finding } from '../../core/gate/Finding';
|
|
4
4
|
import type { GateOutcome } from '../../core/gate/GateOutcome';
|
|
5
5
|
import type { GatePolicy } from '../../core/gate/GatePolicy';
|
|
6
|
+
import type { GateStage } from '../../core/gate/GateStage';
|
|
6
7
|
import type { RuleSet } from '../../core/rules/RuleSet';
|
|
7
8
|
import type { SkillsRuleSetLoadResult } from '../config/skillsRuleSet';
|
|
8
9
|
import type { ResolvedStagePolicy } from '../gate/stagePolicies';
|
|
@@ -27,7 +28,7 @@ import { evaluateBrownfieldHotspotFindings } from './brownfieldHotspots';
|
|
|
27
28
|
import { emitPlatformGateEvidence } from './runPlatformGateEvidence';
|
|
28
29
|
import { printGateFindings } from './runPlatformGateOutput';
|
|
29
30
|
import { evaluateSddPolicy, type SddDecision } from '../sdd';
|
|
30
|
-
import type { SnapshotEvaluationMetrics } from '../evidence/schema';
|
|
31
|
+
import type { SnapshotEvaluationMetrics, SnapshotRulesCoverage } from '../evidence/schema';
|
|
31
32
|
import { createEmptyEvaluationMetrics } from '../evidence/evaluationMetrics';
|
|
32
33
|
import { createEmptySnapshotRulesCoverage } from '../evidence/rulesCoverage';
|
|
33
34
|
import { enforceTddBddPolicy } from '../tdd/enforcement';
|
|
@@ -212,8 +213,24 @@ const toSddBlockingFinding = (decision: Pick<SddDecision, 'code' | 'message'>):
|
|
|
212
213
|
source: 'sdd-policy',
|
|
213
214
|
});
|
|
214
215
|
|
|
216
|
+
const STRICT_ENFORCEMENT_STAGES = new Set<GateStage>([
|
|
217
|
+
'PRE_WRITE',
|
|
218
|
+
'PRE_COMMIT',
|
|
219
|
+
'PRE_PUSH',
|
|
220
|
+
'CI',
|
|
221
|
+
]);
|
|
222
|
+
|
|
223
|
+
const isStrictEnforcementStage = (
|
|
224
|
+
stage: GateStage
|
|
225
|
+
): stage is Exclude<GateStage, 'STAGED'> => STRICT_ENFORCEMENT_STAGES.has(stage);
|
|
226
|
+
|
|
227
|
+
const isLifecycleGateStage = (
|
|
228
|
+
stage: GateStage
|
|
229
|
+
): stage is 'PRE_COMMIT' | 'PRE_PUSH' | 'CI' =>
|
|
230
|
+
stage === 'PRE_COMMIT' || stage === 'PRE_PUSH' || stage === 'CI';
|
|
231
|
+
|
|
215
232
|
const toRulesCoverageBlockingFinding = (params: {
|
|
216
|
-
stage:
|
|
233
|
+
stage: Exclude<GateStage, 'STAGED'>;
|
|
217
234
|
activeRuleIds: ReadonlyArray<string>;
|
|
218
235
|
evaluatedRuleIds: ReadonlyArray<string>;
|
|
219
236
|
unevaluatedRuleIds: ReadonlyArray<string>;
|
|
@@ -243,7 +260,7 @@ const toRulesCoverageBlockingFinding = (params: {
|
|
|
243
260
|
};
|
|
244
261
|
|
|
245
262
|
const toSkillsUnsupportedAutoRulesBlockingFinding = (params: {
|
|
246
|
-
stage:
|
|
263
|
+
stage: Exclude<GateStage, 'STAGED'>;
|
|
247
264
|
filesScanned: number;
|
|
248
265
|
unsupportedAutoRuleIds: ReadonlyArray<string>;
|
|
249
266
|
unsupportedDetectorRuleIds?: ReadonlyArray<string>;
|
|
@@ -273,6 +290,44 @@ const toSkillsUnsupportedAutoRulesBlockingFinding = (params: {
|
|
|
273
290
|
};
|
|
274
291
|
};
|
|
275
292
|
|
|
293
|
+
const toSkillsUnsupportedDetectorRulesBlockingFinding = (params: {
|
|
294
|
+
stage: Exclude<GateStage, 'STAGED'>;
|
|
295
|
+
filesScanned: number;
|
|
296
|
+
unsupportedDetectorRuleIds: ReadonlyArray<string>;
|
|
297
|
+
registryTotal?: number;
|
|
298
|
+
registryDeclarative?: number;
|
|
299
|
+
}): Finding | undefined => {
|
|
300
|
+
if (params.filesScanned === 0) {
|
|
301
|
+
return undefined;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const unsupportedRuleIds = [...new Set(params.unsupportedDetectorRuleIds)].sort();
|
|
305
|
+
if (unsupportedRuleIds.length === 0) {
|
|
306
|
+
return undefined;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const unsupportedRuleIdsSample = unsupportedRuleIds
|
|
310
|
+
.slice(0, MAX_SCOPE_SAMPLE_PATHS)
|
|
311
|
+
.join(LIST_SEPARATOR);
|
|
312
|
+
const remainingRuleIds = Math.max(0, unsupportedRuleIds.length - MAX_SCOPE_SAMPLE_PATHS);
|
|
313
|
+
return {
|
|
314
|
+
ruleId: 'governance.skills.global-enforcement.incomplete',
|
|
315
|
+
severity: 'ERROR',
|
|
316
|
+
code: 'SKILLS_GLOBAL_ENFORCEMENT_INCOMPLETE_CRITICAL',
|
|
317
|
+
message:
|
|
318
|
+
`Global skills enforcement incomplete at ${params.stage}: ` +
|
|
319
|
+
`registry_total=${params.registryTotal ?? 'n/a'} ` +
|
|
320
|
+
`registry_declarative=${params.registryDeclarative ?? 'n/a'} ` +
|
|
321
|
+
`unsupported_detector=${unsupportedRuleIds.length} ` +
|
|
322
|
+
`unsupported_detector_rule_ids_sample=[${unsupportedRuleIdsSample}] ` +
|
|
323
|
+
`unsupported_detector_rule_ids_remaining=${remainingRuleIds}. ` +
|
|
324
|
+
'Every hard skill rule must be enforced by AST detector or fail closed before this stage can proceed.',
|
|
325
|
+
filePath: '.ai_evidence.json',
|
|
326
|
+
matchedBy: 'SkillsGlobalEnforcementGuard',
|
|
327
|
+
source: 'skills-global-enforcement',
|
|
328
|
+
};
|
|
329
|
+
};
|
|
330
|
+
|
|
276
331
|
const PLATFORM_SKILLS_RULE_PREFIXES: Record<
|
|
277
332
|
'ios' | 'android' | 'backend' | 'frontend',
|
|
278
333
|
string
|
|
@@ -452,7 +507,7 @@ const hasTrackForMemoryLeaksPattern = (content: string): boolean =>
|
|
|
452
507
|
/\btrackForMemoryLeaks\s*\(/.test(content);
|
|
453
508
|
|
|
454
509
|
const toIosTestsQualityBlockingFinding = (params: {
|
|
455
|
-
stage:
|
|
510
|
+
stage: Exclude<GateStage, 'STAGED'>;
|
|
456
511
|
facts: ReadonlyArray<Fact>;
|
|
457
512
|
}): Finding | undefined => {
|
|
458
513
|
const testFiles = collectIosTestFileContents(params.facts);
|
|
@@ -502,7 +557,7 @@ const toIosTestsQualityBlockingFinding = (params: {
|
|
|
502
557
|
};
|
|
503
558
|
|
|
504
559
|
const toActiveRulesEmptyForCodeChangesBlockingFinding = (params: {
|
|
505
|
-
stage:
|
|
560
|
+
stage: Exclude<GateStage, 'STAGED'>;
|
|
506
561
|
facts: ReadonlyArray<Fact>;
|
|
507
562
|
activeRuleIds: ReadonlyArray<string>;
|
|
508
563
|
}): Finding | undefined => {
|
|
@@ -570,7 +625,7 @@ const detectRequiredSkillsScopesFromPaths = (
|
|
|
570
625
|
};
|
|
571
626
|
|
|
572
627
|
const toSkillsScopeComplianceBlockingFinding = (params: {
|
|
573
|
-
stage:
|
|
628
|
+
stage: Exclude<GateStage, 'STAGED'>;
|
|
574
629
|
facts: ReadonlyArray<Fact>;
|
|
575
630
|
activeRuleIds: ReadonlyArray<string>;
|
|
576
631
|
evaluatedRuleIds: ReadonlyArray<string>;
|
|
@@ -619,7 +674,7 @@ const toSkillsScopeComplianceBlockingFinding = (params: {
|
|
|
619
674
|
};
|
|
620
675
|
|
|
621
676
|
const toPolicyAsCodeBlockingFinding = (params: {
|
|
622
|
-
stage:
|
|
677
|
+
stage: Exclude<GateStage, 'STAGED'>;
|
|
623
678
|
policyTrace?: ResolvedStagePolicy['trace'];
|
|
624
679
|
}): Finding | undefined => {
|
|
625
680
|
const validation = params.policyTrace?.validation;
|
|
@@ -642,7 +697,7 @@ const toPolicyAsCodeBlockingFinding = (params: {
|
|
|
642
697
|
};
|
|
643
698
|
|
|
644
699
|
const toDegradedModeFinding = (params: {
|
|
645
|
-
stage:
|
|
700
|
+
stage: Exclude<GateStage, 'STAGED'>;
|
|
646
701
|
policyTrace?: ResolvedStagePolicy['trace'];
|
|
647
702
|
}): Finding | undefined => {
|
|
648
703
|
const degraded = params.policyTrace?.degraded;
|
|
@@ -721,7 +776,7 @@ const toGateWaiverInvalidFinding = (params: {
|
|
|
721
776
|
});
|
|
722
777
|
|
|
723
778
|
const toPlatformSkillsCoverageBlockingFinding = (params: {
|
|
724
|
-
stage:
|
|
779
|
+
stage: Exclude<GateStage, 'STAGED'>;
|
|
725
780
|
detectedPlatforms: DetectedPlatforms;
|
|
726
781
|
activeBundles: ReadonlyArray<SkillsRuleSetLoadResult['activeBundles'][number]>;
|
|
727
782
|
activeRuleIds: ReadonlyArray<string>;
|
|
@@ -780,7 +835,7 @@ const toPlatformSkillsCoverageBlockingFinding = (params: {
|
|
|
780
835
|
};
|
|
781
836
|
|
|
782
837
|
const toCrossPlatformCriticalEnforcementBlockingFinding = (params: {
|
|
783
|
-
stage:
|
|
838
|
+
stage: Exclude<GateStage, 'STAGED'>;
|
|
784
839
|
detectedPlatforms: DetectedPlatforms;
|
|
785
840
|
skillsRules: SkillsRuleSetLoadResult['rules'];
|
|
786
841
|
evaluatedRuleIds: ReadonlyArray<string>;
|
|
@@ -990,9 +1045,7 @@ export async function runPlatformGate(params: {
|
|
|
990
1045
|
}
|
|
991
1046
|
: createEmptyEvaluationMetrics();
|
|
992
1047
|
const coverageBlockingFinding =
|
|
993
|
-
params.policy.stage
|
|
994
|
-
params.policy.stage === 'PRE_PUSH' ||
|
|
995
|
-
params.policy.stage === 'CI'
|
|
1048
|
+
isStrictEnforcementStage(params.policy.stage)
|
|
996
1049
|
? toRulesCoverageBlockingFinding({
|
|
997
1050
|
stage: params.policy.stage,
|
|
998
1051
|
activeRuleIds: coverage?.activeRuleIds ?? [],
|
|
@@ -1001,9 +1054,7 @@ export async function runPlatformGate(params: {
|
|
|
1001
1054
|
})
|
|
1002
1055
|
: undefined;
|
|
1003
1056
|
const unsupportedSkillsMappingFinding =
|
|
1004
|
-
params.policy.stage
|
|
1005
|
-
params.policy.stage === 'PRE_PUSH' ||
|
|
1006
|
-
params.policy.stage === 'CI'
|
|
1057
|
+
isStrictEnforcementStage(params.policy.stage)
|
|
1007
1058
|
? toSkillsUnsupportedAutoRulesBlockingFinding({
|
|
1008
1059
|
stage: params.policy.stage,
|
|
1009
1060
|
filesScanned,
|
|
@@ -1014,10 +1065,21 @@ export async function runPlatformGate(params: {
|
|
|
1014
1065
|
const effectiveUnsupportedSkillsMappingFinding = applySkillsFindingEnforcement(
|
|
1015
1066
|
unsupportedSkillsMappingFinding
|
|
1016
1067
|
);
|
|
1068
|
+
const unsupportedDetectorSkillsFinding =
|
|
1069
|
+
isStrictEnforcementStage(params.policy.stage)
|
|
1070
|
+
? toSkillsUnsupportedDetectorRulesBlockingFinding({
|
|
1071
|
+
stage: params.policy.stage,
|
|
1072
|
+
filesScanned,
|
|
1073
|
+
unsupportedDetectorRuleIds: skillsRuleSet.unsupportedDetectorRuleIds ?? [],
|
|
1074
|
+
registryTotal: skillsRuleSet.registryCoverage?.registryTotals.total,
|
|
1075
|
+
registryDeclarative: skillsRuleSet.registryCoverage?.registryTotals.declarative,
|
|
1076
|
+
})
|
|
1077
|
+
: undefined;
|
|
1078
|
+
const effectiveUnsupportedDetectorSkillsFinding = applySkillsFindingEnforcement(
|
|
1079
|
+
unsupportedDetectorSkillsFinding
|
|
1080
|
+
);
|
|
1017
1081
|
const platformSkillsCoverageFinding =
|
|
1018
|
-
params.policy.stage
|
|
1019
|
-
params.policy.stage === 'PRE_PUSH' ||
|
|
1020
|
-
params.policy.stage === 'CI'
|
|
1082
|
+
isStrictEnforcementStage(params.policy.stage)
|
|
1021
1083
|
? toPlatformSkillsCoverageBlockingFinding({
|
|
1022
1084
|
stage: params.policy.stage,
|
|
1023
1085
|
detectedPlatforms,
|
|
@@ -1030,9 +1092,7 @@ export async function runPlatformGate(params: {
|
|
|
1030
1092
|
platformSkillsCoverageFinding
|
|
1031
1093
|
);
|
|
1032
1094
|
const crossPlatformCriticalFinding =
|
|
1033
|
-
params.policy.stage
|
|
1034
|
-
params.policy.stage === 'PRE_PUSH' ||
|
|
1035
|
-
params.policy.stage === 'CI'
|
|
1095
|
+
isStrictEnforcementStage(params.policy.stage)
|
|
1036
1096
|
? toCrossPlatformCriticalEnforcementBlockingFinding({
|
|
1037
1097
|
stage: params.policy.stage,
|
|
1038
1098
|
detectedPlatforms,
|
|
@@ -1044,9 +1104,7 @@ export async function runPlatformGate(params: {
|
|
|
1044
1104
|
crossPlatformCriticalFinding
|
|
1045
1105
|
);
|
|
1046
1106
|
const skillsScopeComplianceFinding =
|
|
1047
|
-
params.policy.stage
|
|
1048
|
-
params.policy.stage === 'PRE_PUSH' ||
|
|
1049
|
-
params.policy.stage === 'CI'
|
|
1107
|
+
isStrictEnforcementStage(params.policy.stage)
|
|
1050
1108
|
? toSkillsScopeComplianceBlockingFinding({
|
|
1051
1109
|
stage: params.policy.stage,
|
|
1052
1110
|
facts,
|
|
@@ -1058,9 +1116,7 @@ export async function runPlatformGate(params: {
|
|
|
1058
1116
|
skillsScopeComplianceFinding
|
|
1059
1117
|
);
|
|
1060
1118
|
const activeRulesEmptyForCodeChangesFinding =
|
|
1061
|
-
params.policy.stage
|
|
1062
|
-
params.policy.stage === 'PRE_PUSH' ||
|
|
1063
|
-
params.policy.stage === 'CI'
|
|
1119
|
+
isStrictEnforcementStage(params.policy.stage)
|
|
1064
1120
|
? toActiveRulesEmptyForCodeChangesBlockingFinding({
|
|
1065
1121
|
stage: params.policy.stage,
|
|
1066
1122
|
facts,
|
|
@@ -1068,9 +1124,7 @@ export async function runPlatformGate(params: {
|
|
|
1068
1124
|
})
|
|
1069
1125
|
: undefined;
|
|
1070
1126
|
const iosTestsQualityFinding =
|
|
1071
|
-
params.policy.stage
|
|
1072
|
-
params.policy.stage === 'PRE_PUSH' ||
|
|
1073
|
-
params.policy.stage === 'CI'
|
|
1127
|
+
isStrictEnforcementStage(params.policy.stage)
|
|
1074
1128
|
? toIosTestsQualityBlockingFinding({
|
|
1075
1129
|
stage: params.policy.stage,
|
|
1076
1130
|
facts,
|
|
@@ -1080,15 +1134,14 @@ export async function runPlatformGate(params: {
|
|
|
1080
1134
|
iosTestsQualityFinding
|
|
1081
1135
|
);
|
|
1082
1136
|
const policyAsCodeBlockingFinding =
|
|
1083
|
-
params.policy.stage
|
|
1084
|
-
params.policy.stage === 'PRE_PUSH' ||
|
|
1085
|
-
params.policy.stage === 'CI'
|
|
1137
|
+
isStrictEnforcementStage(params.policy.stage)
|
|
1086
1138
|
? toPolicyAsCodeBlockingFinding({
|
|
1087
1139
|
stage: params.policy.stage,
|
|
1088
1140
|
policyTrace: params.policyTrace,
|
|
1089
1141
|
})
|
|
1090
1142
|
: undefined;
|
|
1091
|
-
const degradedModeFinding =
|
|
1143
|
+
const degradedModeFinding = isLifecycleGateStage(params.policy.stage)
|
|
1144
|
+
&& LIFECYCLE_GATE_STAGES.includes(params.policy.stage)
|
|
1092
1145
|
? toDegradedModeFinding({
|
|
1093
1146
|
stage: params.policy.stage,
|
|
1094
1147
|
policyTrace: params.policyTrace,
|
|
@@ -1097,9 +1150,7 @@ export async function runPlatformGate(params: {
|
|
|
1097
1150
|
const astIntelligenceDualValidation:
|
|
1098
1151
|
| AstIntelligenceDualValidationResult
|
|
1099
1152
|
| undefined =
|
|
1100
|
-
params.policy.stage
|
|
1101
|
-
|| params.policy.stage === 'PRE_PUSH'
|
|
1102
|
-
|| params.policy.stage === 'CI'
|
|
1153
|
+
isStrictEnforcementStage(params.policy.stage)
|
|
1103
1154
|
? dependencies.evaluateAstIntelligenceDualValidation({
|
|
1104
1155
|
stage: params.policy.stage,
|
|
1105
1156
|
skillsRules: skillsRuleSet.rules,
|
|
@@ -1125,7 +1176,7 @@ export async function runPlatformGate(params: {
|
|
|
1125
1176
|
}
|
|
1126
1177
|
const degradedModeBlocks =
|
|
1127
1178
|
params.policyTrace?.degraded?.action === DEGRADED_MODE_ACTION_BLOCK;
|
|
1128
|
-
const rulesCoverage = coverage
|
|
1179
|
+
const rulesCoverage: SnapshotRulesCoverage = coverage
|
|
1129
1180
|
? {
|
|
1130
1181
|
stage: params.policy.stage,
|
|
1131
1182
|
contract: skillsRuleSet.registryCoverage?.contract ?? 'AUTO_RUNTIME_RULES_FOR_STAGE',
|
|
@@ -1193,6 +1244,43 @@ export async function runPlatformGate(params: {
|
|
|
1193
1244
|
coverage.evaluatedRuleIds.length / coverage.activeRuleIds.length
|
|
1194
1245
|
).toFixed(DEFAULT_RULES_COVERAGE_RATIO_DECIMALS)
|
|
1195
1246
|
),
|
|
1247
|
+
auto_runtime_coverage_ratio:
|
|
1248
|
+
coverage.activeRuleIds.length === 0
|
|
1249
|
+
? 1
|
|
1250
|
+
: Number(
|
|
1251
|
+
(
|
|
1252
|
+
coverage.evaluatedRuleIds.length / coverage.activeRuleIds.length
|
|
1253
|
+
).toFixed(DEFAULT_RULES_COVERAGE_RATIO_DECIMALS)
|
|
1254
|
+
),
|
|
1255
|
+
semantic_enforcement_ratio: skillsRuleSet.registryCoverage
|
|
1256
|
+
? Number(
|
|
1257
|
+
(
|
|
1258
|
+
Math.max(
|
|
1259
|
+
0,
|
|
1260
|
+
skillsRuleSet.registryCoverage.registryTotals.total -
|
|
1261
|
+
(skillsRuleSet.unsupportedDetectorRuleIds ?? []).length
|
|
1262
|
+
) / Math.max(1, skillsRuleSet.registryCoverage.registryTotals.total)
|
|
1263
|
+
).toFixed(DEFAULT_RULES_COVERAGE_RATIO_DECIMALS)
|
|
1264
|
+
)
|
|
1265
|
+
: 1,
|
|
1266
|
+
global_skills_enforcement: {
|
|
1267
|
+
status:
|
|
1268
|
+
(skillsRuleSet.unsupportedDetectorRuleIds?.length ?? 0) === 0
|
|
1269
|
+
? 'enforced'
|
|
1270
|
+
: coverage.activeRuleIds.length > 0
|
|
1271
|
+
? 'partially_enforced'
|
|
1272
|
+
: 'unsupported',
|
|
1273
|
+
registry_total: skillsRuleSet.registryCoverage?.registryTotals.total ?? 0,
|
|
1274
|
+
detector_supported: Math.max(
|
|
1275
|
+
0,
|
|
1276
|
+
(skillsRuleSet.registryCoverage?.registryTotals.total ?? 0) -
|
|
1277
|
+
(skillsRuleSet.unsupportedDetectorRuleIds ?? []).length
|
|
1278
|
+
),
|
|
1279
|
+
declarative_only:
|
|
1280
|
+
skillsRuleSet.registryCoverage?.registryTotals.declarative ?? 0,
|
|
1281
|
+
unsupported_detector:
|
|
1282
|
+
(skillsRuleSet.unsupportedDetectorRuleIds ?? []).length,
|
|
1283
|
+
},
|
|
1196
1284
|
}
|
|
1197
1285
|
: createEmptySnapshotRulesCoverage(params.policy.stage);
|
|
1198
1286
|
const brownfieldHotspotFindings = dependencies.evaluateBrownfieldHotspotFindings({
|
|
@@ -1226,6 +1314,7 @@ export async function runPlatformGate(params: {
|
|
|
1226
1314
|
...(degradedModeFinding ? [degradedModeFinding] : []),
|
|
1227
1315
|
...(policyAsCodeBlockingFinding ? [policyAsCodeBlockingFinding] : []),
|
|
1228
1316
|
...(effectiveUnsupportedSkillsMappingFinding ? [effectiveUnsupportedSkillsMappingFinding] : []),
|
|
1317
|
+
...(effectiveUnsupportedDetectorSkillsFinding ? [effectiveUnsupportedDetectorSkillsFinding] : []),
|
|
1229
1318
|
...(effectivePlatformSkillsCoverageFinding ? [effectivePlatformSkillsCoverageFinding] : []),
|
|
1230
1319
|
...(effectiveCrossPlatformCriticalFinding ? [effectiveCrossPlatformCriticalFinding] : []),
|
|
1231
1320
|
...(effectiveSkillsScopeComplianceFinding ? [effectiveSkillsScopeComplianceFinding] : []),
|
|
@@ -1238,6 +1327,7 @@ export async function runPlatformGate(params: {
|
|
|
1238
1327
|
...findings,
|
|
1239
1328
|
]
|
|
1240
1329
|
: effectiveUnsupportedSkillsMappingFinding
|
|
1330
|
+
|| effectiveUnsupportedDetectorSkillsFinding
|
|
1241
1331
|
|| effectivePlatformSkillsCoverageFinding
|
|
1242
1332
|
|| effectiveCrossPlatformCriticalFinding
|
|
1243
1333
|
|| effectiveSkillsScopeComplianceFinding
|
|
@@ -1253,6 +1343,7 @@ export async function runPlatformGate(params: {
|
|
|
1253
1343
|
...(degradedModeFinding ? [degradedModeFinding] : []),
|
|
1254
1344
|
...(policyAsCodeBlockingFinding ? [policyAsCodeBlockingFinding] : []),
|
|
1255
1345
|
...(effectiveUnsupportedSkillsMappingFinding ? [effectiveUnsupportedSkillsMappingFinding] : []),
|
|
1346
|
+
...(effectiveUnsupportedDetectorSkillsFinding ? [effectiveUnsupportedDetectorSkillsFinding] : []),
|
|
1256
1347
|
...(effectivePlatformSkillsCoverageFinding ? [effectivePlatformSkillsCoverageFinding] : []),
|
|
1257
1348
|
...(effectiveCrossPlatformCriticalFinding ? [effectiveCrossPlatformCriticalFinding] : []),
|
|
1258
1349
|
...(effectiveSkillsScopeComplianceFinding ? [effectiveSkillsScopeComplianceFinding] : []),
|
|
@@ -1274,6 +1365,7 @@ export async function runPlatformGate(params: {
|
|
|
1274
1365
|
degradedModeBlocks ||
|
|
1275
1366
|
shouldBlockFromFinding(policyAsCodeBlockingFinding) ||
|
|
1276
1367
|
shouldBlockFromFinding(effectiveUnsupportedSkillsMappingFinding) ||
|
|
1368
|
+
shouldBlockFromFinding(effectiveUnsupportedDetectorSkillsFinding) ||
|
|
1277
1369
|
shouldBlockFromFinding(effectivePlatformSkillsCoverageFinding) ||
|
|
1278
1370
|
shouldBlockFromFinding(effectiveCrossPlatformCriticalFinding) ||
|
|
1279
1371
|
shouldBlockFromFinding(effectiveSkillsScopeComplianceFinding) ||
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.149",
|
|
4
4
|
"description": "Enterprise-grade AST Intelligence System with multi-platform support (iOS, Android, Backend, Frontend) and Feature-First + DDD + Clean Architecture enforcement. Includes dynamic violations API for intelligent querying.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|