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.
@@ -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: 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
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: 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
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: 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
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: 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
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: 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
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: 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
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: 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
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: 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
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: 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
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 === 'PRE_COMMIT' ||
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 === 'PRE_COMMIT' ||
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 === 'PRE_COMMIT' ||
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 === 'PRE_COMMIT' ||
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 === 'PRE_COMMIT' ||
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 === 'PRE_COMMIT' ||
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 === 'PRE_COMMIT' ||
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 === 'PRE_COMMIT' ||
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 = LIFECYCLE_GATE_STAGES.includes(params.policy.stage)
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 === 'PRE_COMMIT'
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.146",
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": {