pumuki 6.3.146 → 6.3.148
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 +86 -0
- package/core/facts/detectors/text/ios.ts +36 -0
- 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
|
@@ -375,11 +375,34 @@ final class SyncTests: XCTestCase {
|
|
|
375
375
|
}
|
|
376
376
|
}
|
|
377
377
|
}
|
|
378
|
+
`;
|
|
379
|
+
const brownfieldCompatibleUnitTest = `
|
|
380
|
+
import XCTest
|
|
381
|
+
|
|
382
|
+
final class LoginModelTests: XCTestCase {
|
|
383
|
+
func test_submit_validCredentials_storesSession() async throws {
|
|
384
|
+
let (sut, repository) = makeSUT()
|
|
385
|
+
try await sut.submit()
|
|
386
|
+
XCTAssertEqual(repository.receivedRequests.count, 1)
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
private func makeSUT(
|
|
390
|
+
file: StaticString = #filePath,
|
|
391
|
+
line: UInt = #line
|
|
392
|
+
) -> (LoginModel, AuthRepositorySpy) {
|
|
393
|
+
let repository = AuthRepositorySpy()
|
|
394
|
+
let sut = LoginModel(repository: repository)
|
|
395
|
+
trackForMemoryLeaks(sut, testCase: self, file: file, line: line)
|
|
396
|
+
trackForMemoryLeaks(repository, testCase: self, file: file, line: line)
|
|
397
|
+
return (sut, repository)
|
|
398
|
+
}
|
|
399
|
+
}
|
|
378
400
|
`;
|
|
379
401
|
|
|
380
402
|
assert.equal(hasSwiftLegacyXCTestImportUsage(unitTest), true);
|
|
381
403
|
assert.equal(hasSwiftLegacyXCTestImportUsage(uiTest), false);
|
|
382
404
|
assert.equal(hasSwiftLegacyXCTestImportUsage(performanceTest), false);
|
|
405
|
+
assert.equal(hasSwiftLegacyXCTestImportUsage(brownfieldCompatibleUnitTest), false);
|
|
383
406
|
});
|
|
384
407
|
|
|
385
408
|
test('hasSwiftLegacySwiftUiObservableWrapperUsage detecta @StateObject/@ObservedObject legacy', () => {
|
|
@@ -476,11 +499,34 @@ final class LoginUITests: XCTestCase {
|
|
|
476
499
|
app.launch()
|
|
477
500
|
}
|
|
478
501
|
}
|
|
502
|
+
`;
|
|
503
|
+
const brownfieldCompatibleSuite = `
|
|
504
|
+
import XCTest
|
|
505
|
+
|
|
506
|
+
final class LoginModelTests: XCTestCase {
|
|
507
|
+
func test_submit_validCredentials_storesSession() async throws {
|
|
508
|
+
let (sut, repository) = makeSUT()
|
|
509
|
+
try await sut.submit()
|
|
510
|
+
XCTAssertEqual(repository.receivedRequests.count, 1)
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
private func makeSUT(
|
|
514
|
+
file: StaticString = #filePath,
|
|
515
|
+
line: UInt = #line
|
|
516
|
+
) -> (LoginModel, AuthRepositorySpy) {
|
|
517
|
+
let repository = AuthRepositorySpy()
|
|
518
|
+
let sut = LoginModel(repository: repository)
|
|
519
|
+
trackForMemoryLeaks(sut, testCase: self, file: file, line: line)
|
|
520
|
+
trackForMemoryLeaks(repository, testCase: self, file: file, line: line)
|
|
521
|
+
return (sut, repository)
|
|
522
|
+
}
|
|
523
|
+
}
|
|
479
524
|
`;
|
|
480
525
|
|
|
481
526
|
assert.equal(hasSwiftModernizableXCTestSuiteUsage(legacySuite), true);
|
|
482
527
|
assert.equal(hasSwiftModernizableXCTestSuiteUsage(mixedSuite), false);
|
|
483
528
|
assert.equal(hasSwiftModernizableXCTestSuiteUsage(uiSuite), false);
|
|
529
|
+
assert.equal(hasSwiftModernizableXCTestSuiteUsage(brownfieldCompatibleSuite), false);
|
|
484
530
|
});
|
|
485
531
|
|
|
486
532
|
test('hasSwiftMixedTestingFrameworksUsage detecta mezcla XCTestCase y Testing/@Test', () => {
|
|
@@ -549,6 +595,46 @@ final class BuyerCommerceUISmokeTests: XCTestCase {
|
|
|
549
595
|
assert.equal(hasSwiftXCTUnwrapUsage(`${uiSource}\nlet value = try XCTUnwrap(optional)`), false);
|
|
550
596
|
});
|
|
551
597
|
|
|
598
|
+
test('hasSwiftXCTestAssertionUsage excluye XCTest brownfield compatible y bloquea suites sin contrato de calidad', () => {
|
|
599
|
+
const compatibleSource = `
|
|
600
|
+
import XCTest
|
|
601
|
+
|
|
602
|
+
final class LoginModelTests: XCTestCase {
|
|
603
|
+
func test_submit_validCredentials_storesSession() async throws {
|
|
604
|
+
let (sut, repository) = makeSUT()
|
|
605
|
+
try await sut.submit()
|
|
606
|
+
XCTAssertEqual(repository.receivedRequests.count, 1)
|
|
607
|
+
let session = try XCTUnwrap(repository.savedSession)
|
|
608
|
+
XCTAssertEqual(session.userId, "buyer-1")
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
private func makeSUT(
|
|
612
|
+
file: StaticString = #filePath,
|
|
613
|
+
line: UInt = #line
|
|
614
|
+
) -> (LoginModel, AuthRepositorySpy) {
|
|
615
|
+
let repository = AuthRepositorySpy()
|
|
616
|
+
let sut = LoginModel(repository: repository)
|
|
617
|
+
trackForMemoryLeaks(sut, testCase: self, file: file, line: line)
|
|
618
|
+
trackForMemoryLeaks(repository, testCase: self, file: file, line: line)
|
|
619
|
+
return (sut, repository)
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
`;
|
|
623
|
+
const missingQualityContract = `
|
|
624
|
+
import XCTest
|
|
625
|
+
|
|
626
|
+
final class LoginModelTests: XCTestCase {
|
|
627
|
+
func test_submit_validCredentials_storesSession() async throws {
|
|
628
|
+
XCTAssertEqual(repository.receivedRequests.count, 1)
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
`;
|
|
632
|
+
|
|
633
|
+
assert.equal(hasSwiftXCTestAssertionUsage(compatibleSource), false);
|
|
634
|
+
assert.equal(hasSwiftXCTUnwrapUsage(compatibleSource), false);
|
|
635
|
+
assert.equal(hasSwiftXCTestAssertionUsage(missingQualityContract), true);
|
|
636
|
+
});
|
|
637
|
+
|
|
552
638
|
test('hasSwiftXCTUnwrapUsage detecta XCTUnwrap real y evita strings', () => {
|
|
553
639
|
const source = `
|
|
554
640
|
let value = try XCTUnwrap(optionalValue)
|
|
@@ -730,6 +730,30 @@ const hasSwiftLegacyXCTestMethodUsage = (source: string): boolean => {
|
|
|
730
730
|
.length > 0;
|
|
731
731
|
};
|
|
732
732
|
|
|
733
|
+
const hasSwiftMakeSutUsage = (source: string): boolean => {
|
|
734
|
+
return hasSwiftSanitizedRegexMatch(source, /\bmakeSUT\s*\(/);
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
const hasSwiftMemoryLeakTrackingUsage = (source: string): boolean => {
|
|
738
|
+
return hasSwiftSanitizedRegexMatch(source, /\btrackForMemoryLeaks\s*\(/);
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
const hasSwiftBrownfieldCompatibleXCTestUsage = (source: string): boolean => {
|
|
742
|
+
if (!hasSwiftXCTestImportUsage(source) || !hasSwiftXCTestCaseSubclassUsage(source)) {
|
|
743
|
+
return false;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
if (!hasSwiftLegacyXCTestMethodUsage(source)) {
|
|
747
|
+
return false;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
if (hasSwiftTestingImportUsage(source) || hasSwiftTestingSuiteAttributeUsage(source)) {
|
|
751
|
+
return false;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
return hasSwiftMakeSutUsage(source) && hasSwiftMemoryLeakTrackingUsage(source);
|
|
755
|
+
};
|
|
756
|
+
|
|
733
757
|
export const hasSwiftLegacyXCTestImportUsage = (source: string): boolean => {
|
|
734
758
|
if (!hasSwiftXCTestImportUsage(source)) {
|
|
735
759
|
return false;
|
|
@@ -739,6 +763,10 @@ export const hasSwiftLegacyXCTestImportUsage = (source: string): boolean => {
|
|
|
739
763
|
return false;
|
|
740
764
|
}
|
|
741
765
|
|
|
766
|
+
if (hasSwiftBrownfieldCompatibleXCTestUsage(source)) {
|
|
767
|
+
return false;
|
|
768
|
+
}
|
|
769
|
+
|
|
742
770
|
return true;
|
|
743
771
|
};
|
|
744
772
|
|
|
@@ -771,6 +799,10 @@ export const hasSwiftXCTestAssertionUsage = (source: string): boolean => {
|
|
|
771
799
|
return false;
|
|
772
800
|
}
|
|
773
801
|
|
|
802
|
+
if (hasSwiftBrownfieldCompatibleXCTestUsage(source)) {
|
|
803
|
+
return false;
|
|
804
|
+
}
|
|
805
|
+
|
|
774
806
|
return (
|
|
775
807
|
collectSwiftRegexLines(source, /\bXCTAssert[A-Za-z0-9_]*\s*\(/).length > 0 ||
|
|
776
808
|
collectSwiftRegexLines(source, /\bXCTFail\s*\(/).length > 0
|
|
@@ -782,6 +814,10 @@ export const hasSwiftXCTUnwrapUsage = (source: string): boolean => {
|
|
|
782
814
|
return false;
|
|
783
815
|
}
|
|
784
816
|
|
|
817
|
+
if (hasSwiftBrownfieldCompatibleXCTestUsage(source)) {
|
|
818
|
+
return false;
|
|
819
|
+
}
|
|
820
|
+
|
|
785
821
|
return collectSwiftRegexLines(source, /\bXCTUnwrap\s*\(/).length > 0;
|
|
786
822
|
};
|
|
787
823
|
|
|
@@ -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.148",
|
|
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": {
|