pumuki 6.3.135 → 6.3.136

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,14 @@ This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [6.3.136] - 2026-05-05
10
+
11
+ ### Fixed
12
+
13
+ - **PUMUKI-INC-059 all-severities blocking:** `PRE_WRITE`, `PRE_COMMIT`, `PRE_PUSH` y `CI` bloquean cualquier finding de reglas/skills AST Intelligence, incluyendo `WARN/MEDIUM` e `INFO/LOW`.
14
+ - **Políticas no relajables:** `skills.policy`, perfiles hard-mode y `PRE_WRITE=advisory` ya no pueden rebajar el threshold efectivo por debajo de `INFO`.
15
+ - **Replay RuralGo:** validado con binario local contra RuralGo: PRE_WRITE `115/115` findings bloqueantes y PRE_COMMIT `118/118` findings bloqueantes.
16
+
9
17
  ## [6.3.135] - 2026-05-03
10
18
 
11
19
  ### Fixed
@@ -37,6 +37,7 @@ import {
37
37
  hasSwiftOperationQueueUsage,
38
38
  hasSwiftContainsUserFilterUsage,
39
39
  hasSwiftPassedValueStateWrapperUsage,
40
+ hasSwiftStateWrapperWithoutPrivateUsage,
40
41
  hasSwiftPreconcurrencyUsage,
41
42
  hasSwiftSheetIsPresentedUsage,
42
43
  hasSwiftScrollViewShowsIndicatorsUsage,
@@ -425,6 +426,24 @@ struct DetailView: View {
425
426
  assert.equal(hasSwiftPassedValueStateWrapperUsage(validOwnership), false);
426
427
  });
427
428
 
429
+ test('hasSwiftStateWrapperWithoutPrivateUsage detecta @State y @StateObject sin private en SwiftUI Views', () => {
430
+ const positive = `
431
+ struct DetailView: View {
432
+ @State var filter: String = ""
433
+ @StateObject var viewModel = DetailViewModel()
434
+ }
435
+ `;
436
+ const negative = `
437
+ struct DetailView: View {
438
+ @State private var filter: String = ""
439
+ @StateObject private var viewModel = DetailViewModel()
440
+ }
441
+ `;
442
+
443
+ assert.equal(hasSwiftStateWrapperWithoutPrivateUsage(positive), true);
444
+ assert.equal(hasSwiftStateWrapperWithoutPrivateUsage(negative), false);
445
+ });
446
+
428
447
  test('hasSwiftModernizableXCTestSuiteUsage detecta suites legacy y excluye mixed/UI', () => {
429
448
  const legacySuite = `
430
449
  import XCTest
@@ -527,6 +527,36 @@ export const hasSwiftLegacySwiftUiObservableWrapperUsage = (source: string): boo
527
527
  return hasSwiftSanitizedRegexMatch(source, /@\s*(?:StateObject|ObservedObject)\b/);
528
528
  };
529
529
 
530
+ export const hasSwiftStateWrapperWithoutPrivateUsage = (source: string): boolean => {
531
+ const typeDeclarations = parseSwiftTypeDeclarations(source);
532
+ if (typeDeclarations.length === 0) {
533
+ return false;
534
+ }
535
+
536
+ const sourceLines = source.split(/\r?\n/);
537
+ const stateWrapperPattern = /@\s*(?:State|StateObject)\b/;
538
+
539
+ for (const typeDeclaration of typeDeclarations) {
540
+ if (!typeDeclaration.conformances.includes('View')) {
541
+ continue;
542
+ }
543
+
544
+ const hasViolation = visitSwiftTopLevelTypeBodyLines(sourceLines, typeDeclaration, ({ line }) => {
545
+ if (!stateWrapperPattern.test(line)) {
546
+ return false;
547
+ }
548
+
549
+ return !/\bprivate\b/.test(line);
550
+ });
551
+
552
+ if (hasViolation) {
553
+ return true;
554
+ }
555
+ }
556
+
557
+ return false;
558
+ };
559
+
530
560
  const hasSwiftPassedValueWrapperInitialization = (
531
561
  source: string,
532
562
  options: {
@@ -711,6 +711,7 @@ const textDetectorRegistry: ReadonlyArray<TextDetectorRegistryEntry> = [
711
711
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftObservableObjectUsage, ruleId: 'heuristics.ios.observable-object.ast', code: 'HEURISTICS_IOS_OBSERVABLE_OBJECT_AST', message: 'AST heuristic detected ObservableObject usage.' },
712
712
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftLegacySwiftUiObservableWrapperUsage, ruleId: 'heuristics.ios.legacy-swiftui-observable-wrapper.ast', code: 'HEURISTICS_IOS_LEGACY_SWIFTUI_OBSERVABLE_WRAPPER_AST', message: 'AST heuristic detected @StateObject/@ObservedObject usage in a modern SwiftUI path.' },
713
713
  { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftPassedValueStateWrapperUsage, ruleId: 'heuristics.ios.passed-value-state-wrapper.ast', code: 'HEURISTICS_IOS_PASSED_VALUE_STATE_WRAPPER_AST', message: 'AST heuristic detected a passed value stored as @State/@StateObject via init wrapper ownership.' },
714
+ { platform: 'ios', pathCheck: isIOSSwiftPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftStateWrapperWithoutPrivateUsage, ruleId: 'heuristics.ios.swiftui.state-wrapper-private.ast', code: 'HEURISTICS_IOS_SWIFTUI_STATE_WRAPPER_PRIVATE_AST', message: 'AST heuristic detected @State/@StateObject usage in a SwiftUI View without private visibility.' },
714
715
  { platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftForEachIndicesUsage, ruleId: 'heuristics.ios.foreach-indices.ast', code: 'HEURISTICS_IOS_FOREACH_INDICES_AST', message: 'AST heuristic detected ForEach(...indices...) usage where stable element identity may be preferred.' },
715
716
  { platform: 'ios', pathCheck: isIOSApplicationOrPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftContainsUserFilterUsage, ruleId: 'heuristics.ios.contains-user-filter.ast', code: 'HEURISTICS_IOS_CONTAINS_USER_FILTER_AST', message: 'AST heuristic detected contains() in a user-facing filter where localizedStandardContains() may be preferred.' },
716
717
  { platform: 'ios', pathCheck: isIOSPresentationPath, excludePaths: [isSwiftTestPath], detect: TextIOS.hasSwiftGeometryReaderUsage, ruleId: 'heuristics.ios.geometryreader.ast', code: 'HEURISTICS_IOS_GEOMETRYREADER_AST', message: 'AST heuristic detected GeometryReader usage that may be replaceable with modern layout APIs.' },
@@ -38,8 +38,15 @@ test('evaluateRules genera finding cuando la condicion coincide y usa code expli
38
38
  code: 'BACKEND_FILE_MODIFIED',
39
39
  message: 'Backend file modified.',
40
40
  filePath: 'apps/backend/src/main.ts',
41
+ lines: undefined,
41
42
  matchedBy: 'FileChange',
42
43
  source: 'git',
44
+ blocking: true,
45
+ primary_node: undefined,
46
+ related_nodes: undefined,
47
+ why: undefined,
48
+ impact: undefined,
49
+ expected_fix: undefined,
43
50
  });
44
51
  });
45
52
 
@@ -23,8 +23,7 @@ type FindingTarget = {
23
23
  expected_fix?: string;
24
24
  };
25
25
 
26
- const isBlockingSeverity = (severity: RuleDefinition['severity']): boolean =>
27
- severity === 'CRITICAL' || severity === 'ERROR';
26
+ const isBlockingSeverity = (_severity: RuleDefinition['severity']): boolean => true;
28
27
 
29
28
  export type EvaluateRulesCoverageResult = {
30
29
  findings: ReadonlyArray<Finding>;
@@ -4,6 +4,12 @@ This file tracks the active deterministic framework line used in this repository
4
4
  Canonical release chronology lives in `CHANGELOG.md`.
5
5
  This file keeps only the operational highlights and rollout notes that matter while running the framework.
6
6
 
7
+ ### 2026-05-05 (v6.3.136)
8
+
9
+ - **All-severities blocking para consumers:** `PRE_WRITE`, `PRE_COMMIT`, `PRE_PUSH` y `CI` bloquean por cualquier violación de skills/reglas AST Intelligence, sin permitir rebajas por `skills.policy`, hard-mode `critical-high` ni `PRE_WRITE=advisory`.
10
+ - **RuralGo primero:** replay local previo a publicación confirma PRE_WRITE `115/115` bloqueantes y PRE_COMMIT `118/118` bloqueantes, con iOS y Android visibles en la auditoría.
11
+ - **Rollout recomendado:** publicar `pumuki@6.3.136`, repinear RuralGo antes que cualquier otro consumer y revalidar `status`, `doctor`, `audit --stage=PRE_WRITE --json`, `audit --stage=PRE_COMMIT --json` y hooks gestionados.
12
+
7
13
  ### 2026-05-03 (v6.3.135)
8
14
 
9
15
  - **Bootstrap de pre-push por delta real:** en ramas sin upstream, `PRE_PUSH` elige la base con menor delta real entre `main` y `develop`, evitando que un rollout nacido de `main` se bloquee como si heredara todo `develop`.
@@ -20,7 +20,7 @@ Artefacto contractual de `phase10-avdlee-parity-closure`.
20
20
  | skill avdlee | bundle Pumuki | rule ids canónicos | tests contractuales | evidence runtime |
21
21
  | --- | --- | --- | --- | --- |
22
22
  | `swift-concurrency` | `ios-concurrency-guidelines` | `skills.ios.no-dispatchqueue`, `skills.ios.no-dispatchgroup`, `skills.ios.no-dispatchsemaphore`, `skills.ios.no-operation-queue`, `skills.ios.no-task-detached`, `skills.ios.no-unchecked-sendable`, `skills.ios.no-preconcurrency`, `skills.ios.no-nonisolated-unsafe`, `skills.ios.no-assume-isolated` | `integrations/config/__tests__/iosAvdleeParity.test.ts`, `core/facts/detectors/text/ios.test.ts`, `core/facts/__tests__/extractHeuristicFacts.test.ts`, `core/rules/presets/heuristics/ios.test.ts`, `integrations/config/__tests__/skillsDetectorRegistry.test.ts`, `integrations/config/__tests__/skillsMarkdownRules.test.ts` | `skills.sources.json`, `skills.lock.json`, `integrations/config/skillsCompilerTemplates.ts`, `integrations/config/skillsDetectorRegistry.ts`, `integrations/evidence/buildEvidence.ts` |
23
- | `swiftui-expert-skill` | `ios-swiftui-expert-guidelines` | `skills.ios.no-observable-object`, `skills.ios.no-legacy-swiftui-observable-wrapper`, `skills.ios.no-passed-value-state-wrapper`, `skills.ios.no-navigation-view`, `skills.ios.no-foreground-color`, `skills.ios.no-corner-radius`, `skills.ios.no-tab-item`, `skills.ios.no-on-tap-gesture`, `skills.ios.no-string-format`, `skills.ios.no-foreach-indices`, `skills.ios.no-contains-user-filter`, `skills.ios.no-geometryreader`, `skills.ios.no-font-weight-bold`, `skills.ios.no-scrollview-shows-indicators`, `skills.ios.no-sheet-is-presented`, `skills.ios.no-legacy-onchange`, `skills.ios.no-uiscreen-main-bounds` | `integrations/config/__tests__/iosAvdleeParity.test.ts`, `core/facts/detectors/text/ios.test.ts`, `core/facts/detectors/text/iosSwiftUiModernizationSnapshot.test.ts`, `core/facts/__tests__/extractHeuristicFacts.test.ts`, `core/rules/presets/heuristics/ios.test.ts`, `integrations/config/__tests__/skillsDetectorRegistry.test.ts`, `integrations/config/__tests__/skillsMarkdownRules.test.ts` | `skills.sources.json`, `skills.lock.json`, `assets/rule-packs/ios-swiftui-modernization-v1.json`, `assets/rule-packs/ios-swiftui-modernization-v2.json`, `integrations/config/skillsCompilerTemplates.ts`, `integrations/config/skillsDetectorRegistry.ts`, `integrations/evidence/buildEvidence.ts` |
23
+ | `swiftui-expert-skill` | `ios-swiftui-expert-guidelines` | `skills.ios.no-observable-object`, `skills.ios.no-legacy-swiftui-observable-wrapper`, `skills.ios.no-passed-value-state-wrapper`, `skills.ios.guideline.ios-swiftui-expert.always-mark-state-and-stateobject-as-private-makes-dependencies-clear`, `skills.ios.no-navigation-view`, `skills.ios.no-foreground-color`, `skills.ios.no-corner-radius`, `skills.ios.no-tab-item`, `skills.ios.no-on-tap-gesture`, `skills.ios.no-string-format`, `skills.ios.no-foreach-indices`, `skills.ios.no-contains-user-filter`, `skills.ios.no-geometryreader`, `skills.ios.no-font-weight-bold`, `skills.ios.no-scrollview-shows-indicators`, `skills.ios.no-sheet-is-presented`, `skills.ios.no-legacy-onchange`, `skills.ios.no-uiscreen-main-bounds` | `integrations/config/__tests__/iosAvdleeParity.test.ts`, `core/facts/detectors/text/ios.test.ts`, `core/facts/detectors/text/iosSwiftUiModernizationSnapshot.test.ts`, `core/facts/__tests__/extractHeuristicFacts.test.ts`, `core/rules/presets/heuristics/ios.test.ts`, `integrations/config/__tests__/skillsDetectorRegistry.test.ts`, `integrations/config/__tests__/skillsMarkdownRules.test.ts` | `skills.sources.json`, `skills.lock.json`, `assets/rule-packs/ios-swiftui-modernization-v1.json`, `assets/rule-packs/ios-swiftui-modernization-v2.json`, `integrations/config/skillsCompilerTemplates.ts`, `integrations/config/skillsDetectorRegistry.ts`, `integrations/evidence/buildEvidence.ts` |
24
24
  | `swift-testing-expert` | `ios-swift-testing-guidelines` | `skills.ios.prefer-swift-testing`, `skills.ios.no-xctassert`, `skills.ios.no-xctunwrap`, `skills.ios.no-wait-for-expectations`, `skills.ios.no-legacy-expectation-description`, `skills.ios.no-mixed-testing-frameworks` | `integrations/config/__tests__/iosAvdleeParity.test.ts`, `core/facts/detectors/text/ios.test.ts`, `core/facts/__tests__/extractHeuristicFacts.test.ts`, `core/rules/presets/heuristics/ios.test.ts`, `integrations/config/__tests__/skillsDetectorRegistry.test.ts`, `integrations/config/__tests__/skillsMarkdownRules.test.ts` | `skills.sources.json`, `skills.lock.json`, `integrations/config/skillsCompilerTemplates.ts`, `integrations/config/skillsDetectorRegistry.ts`, `integrations/evidence/buildEvidence.ts` |
25
25
  | `core-data-expert` | `ios-core-data-guidelines` | `skills.ios.no-nsmanagedobject-boundary`, `skills.ios.no-nsmanagedobject-async-boundary`, `skills.ios.no-core-data-layer-leak`, `skills.ios.no-nsmanagedobject-state-leak` | `integrations/config/__tests__/iosAvdleeParity.test.ts`, `core/facts/detectors/text/ios.test.ts`, `core/facts/__tests__/extractHeuristicFacts.test.ts`, `core/rules/presets/heuristics/ios.test.ts`, `integrations/config/__tests__/skillsDetectorRegistry.test.ts`, `integrations/config/__tests__/skillsMarkdownRules.test.ts` | `skills.sources.json`, `skills.lock.json`, `integrations/config/skillsCompilerTemplates.ts`, `integrations/config/skillsDetectorRegistry.ts`, `integrations/evidence/buildEvidence.ts` |
26
26
 
@@ -183,6 +183,16 @@ export const skillsCompilerTemplates: Record<string, SkillsCompilerTemplate> = {
183
183
  stage: 'PRE_PUSH',
184
184
  locked: true,
185
185
  },
186
+ {
187
+ id: 'skills.ios.guideline.ios-swiftui-expert.always-mark-state-and-stateobject-as-private-makes-dependencies-clear',
188
+ description:
189
+ 'Always mark @State and @StateObject as private (makes dependencies clear).',
190
+ severity: 'WARN',
191
+ platform: 'ios',
192
+ confidence: 'MEDIUM',
193
+ stage: 'PRE_PUSH',
194
+ locked: true,
195
+ },
186
196
  {
187
197
  id: 'skills.ios.no-navigation-view',
188
198
  description: 'Avoid NavigationView in modern iOS code; prefer NavigationStack.',
@@ -69,6 +69,10 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
69
69
  'skills.ios.no-passed-value-state-wrapper': heuristicDetector('ios.passed-value-state-wrapper', [
70
70
  'heuristics.ios.passed-value-state-wrapper.ast',
71
71
  ]),
72
+ 'skills.ios.guideline.ios-swiftui-expert.always-mark-state-and-stateobject-as-private-makes-dependencies-clear':
73
+ heuristicDetector('ios.swiftui.state-wrapper-private', [
74
+ 'heuristics.ios.swiftui.state-wrapper-private.ast',
75
+ ]),
72
76
  'skills.ios.no-navigation-view': heuristicDetector('ios.navigation-view', [
73
77
  'heuristics.ios.navigation-view.ast',
74
78
  ]),
@@ -346,6 +346,13 @@ const normalizeKnownRuleTarget = (
346
346
  ) {
347
347
  return 'skills.ios.no-passed-value-state-wrapper';
348
348
  }
349
+ if (
350
+ (includes('always mark') && includes('state') && includes('stateobject') && includes('private')) ||
351
+ (includes('state and stateobject as private') && includes('dependencies clear')) ||
352
+ (includes('mark state and stateobject as private') && includes('makes dependencies clear'))
353
+ ) {
354
+ return 'skills.ios.guideline.ios-swiftui-expert.always-mark-state-and-stateobject-as-private-makes-dependencies-clear';
355
+ }
349
356
  if (includes('navigationview') || includes('navigation view')) {
350
357
  return 'skills.ios.no-navigation-view';
351
358
  }
@@ -282,6 +282,10 @@ const equivalentRuleFamilies: ReadonlyArray<ReadonlyArray<string>> = [
282
282
  'skills.ios.no-passed-value-state-wrapper',
283
283
  'heuristics.ios.passed-value-state-wrapper.ast',
284
284
  ],
285
+ [
286
+ 'skills.ios.guideline.ios-swiftui-expert.always-mark-state-and-stateobject-as-private-makes-dependencies-clear',
287
+ 'heuristics.ios.swiftui.state-wrapper-private.ast',
288
+ ],
285
289
  ['skills.ios.no-navigation-view', 'heuristics.ios.navigation-view.ast'],
286
290
  ['skills.ios.no-foreground-color', 'heuristics.ios.foreground-color.ast'],
287
291
  ['skills.ios.no-corner-radius', 'heuristics.ios.corner-radius.ast'],
@@ -581,10 +585,7 @@ const normalizeAndDedupeFindings = (
581
585
  };
582
586
 
583
587
  const toGateOutcome = (findings: ReadonlyArray<SnapshotFinding>): GateOutcome => {
584
- if (findings.some((finding) => finding.severity === 'CRITICAL')) {
585
- return 'BLOCK';
586
- }
587
- return findings.length > 0 ? 'WARN' : 'PASS';
588
+ return findings.length > 0 ? 'BLOCK' : 'PASS';
588
589
  };
589
590
 
590
591
  const bySeverity = (findings: ReadonlyArray<SnapshotFinding>): Record<Severity, number> => {
@@ -93,15 +93,21 @@ type EnterpriseStageThresholds = {
93
93
  warnOnOrAbove: EnterpriseSeverity;
94
94
  };
95
95
 
96
+ const enforceAllSeverityBlockingPolicy = (policy: GatePolicy): GatePolicy => ({
97
+ ...policy,
98
+ blockOnOrAbove: 'INFO',
99
+ warnOnOrAbove: 'INFO',
100
+ });
101
+
96
102
  const toGatePolicyFromEnterpriseThresholds = (
97
103
  stage: SkillsStage,
98
104
  thresholds: EnterpriseStageThresholds
99
105
  ): GatePolicy => {
100
- return {
106
+ return enforceAllSeverityBlockingPolicy({
101
107
  stage,
102
108
  blockOnOrAbove: mapEnterpriseSeverityToGateSeverity(thresholds.blockOnOrAbove),
103
109
  warnOnOrAbove: mapEnterpriseSeverityToGateSeverity(thresholds.warnOnOrAbove),
104
- };
110
+ });
105
111
  };
106
112
 
107
113
  const toGatePolicyRecordFromEnterpriseThresholds = (
@@ -127,41 +133,41 @@ const toGatePolicyRecordFromEnterpriseThresholds = (
127
133
  const defaultPolicyByStage: Record<SkillsStage, GatePolicy> = {
128
134
  PRE_WRITE: {
129
135
  stage: 'PRE_WRITE',
130
- blockOnOrAbove: 'ERROR',
131
- warnOnOrAbove: 'WARN',
136
+ blockOnOrAbove: 'INFO',
137
+ warnOnOrAbove: 'INFO',
132
138
  },
133
139
  PRE_COMMIT: {
134
140
  stage: 'PRE_COMMIT',
135
- blockOnOrAbove: 'ERROR',
136
- warnOnOrAbove: 'WARN',
141
+ blockOnOrAbove: 'INFO',
142
+ warnOnOrAbove: 'INFO',
137
143
  },
138
144
  PRE_PUSH: {
139
145
  stage: 'PRE_PUSH',
140
- blockOnOrAbove: 'ERROR',
141
- warnOnOrAbove: 'WARN',
146
+ blockOnOrAbove: 'INFO',
147
+ warnOnOrAbove: 'INFO',
142
148
  },
143
149
  CI: {
144
150
  stage: 'CI',
145
- blockOnOrAbove: 'ERROR',
146
- warnOnOrAbove: 'WARN',
151
+ blockOnOrAbove: 'INFO',
152
+ warnOnOrAbove: 'INFO',
147
153
  },
148
154
  };
149
155
 
150
156
  const hardModeEnterpriseThresholdsByStage: Record<SkillsStage, EnterpriseStageThresholds> = {
151
157
  PRE_WRITE: {
152
- blockOnOrAbove: 'MEDIUM',
158
+ blockOnOrAbove: 'LOW',
153
159
  warnOnOrAbove: 'LOW',
154
160
  },
155
161
  PRE_COMMIT: {
156
- blockOnOrAbove: 'MEDIUM',
162
+ blockOnOrAbove: 'LOW',
157
163
  warnOnOrAbove: 'LOW',
158
164
  },
159
165
  PRE_PUSH: {
160
- blockOnOrAbove: 'MEDIUM',
166
+ blockOnOrAbove: 'LOW',
161
167
  warnOnOrAbove: 'LOW',
162
168
  },
163
169
  CI: {
164
- blockOnOrAbove: 'MEDIUM',
170
+ blockOnOrAbove: 'LOW',
165
171
  warnOnOrAbove: 'LOW',
166
172
  },
167
173
  };
@@ -624,7 +630,7 @@ export const resolvePolicyForStage = (
624
630
  const profilePolicy = profileName
625
631
  ? hardModePolicyProfileByStage[profileName][stage]
626
632
  : null;
627
- const hardModePolicy = profilePolicy ?? hardModePolicyByStage[stage];
633
+ const hardModePolicy = enforceAllSeverityBlockingPolicy(profilePolicy ?? hardModePolicyByStage[stage]);
628
634
  const bundle = profileName
629
635
  ? `gate-policy.hard-mode.${profileName}.${stage}`
630
636
  : `gate-policy.hard-mode.${stage}`;
@@ -657,7 +663,7 @@ export const resolvePolicyForStage = (
657
663
  };
658
664
  }
659
665
 
660
- const defaults = defaultPolicyByStage[stage];
666
+ const defaults = enforceAllSeverityBlockingPolicy(defaultPolicyByStage[stage]);
661
667
  const loadedPolicy = loadSkillsPolicy(repoRoot);
662
668
  const stageOverride = loadedPolicy?.stages[stage];
663
669
 
@@ -691,11 +697,11 @@ export const resolvePolicyForStage = (
691
697
  };
692
698
  }
693
699
 
694
- const resolvedPolicy: GatePolicy = {
700
+ const resolvedPolicy: GatePolicy = enforceAllSeverityBlockingPolicy({
695
701
  stage: defaults.stage,
696
702
  blockOnOrAbove: stageOverride.blockOnOrAbove,
697
703
  warnOnOrAbove: stageOverride.warnOnOrAbove,
698
- };
704
+ });
699
705
 
700
706
  const bundle = `gate-policy.skills.policy.${stage}`;
701
707
  const hash = createPolicyTraceHash({
@@ -530,6 +530,7 @@ const patchOperationalHintsAfterDocumentationOnlyEvidenceSync = (repoRoot: strin
530
530
  const syncTrackedEvidenceAfterSuccessfulPreCommit = (params: {
531
531
  dependencies: StageRunnerDependencies;
532
532
  repoRoot: string;
533
+ gateBlocked: boolean;
533
534
  }): boolean => {
534
535
  const evidenceAbsolutePath = join(params.repoRoot, EVIDENCE_FILE_PATH);
535
536
  if (!existsSync(evidenceAbsolutePath)) {
@@ -562,6 +563,9 @@ const syncTrackedEvidenceAfterSuccessfulPreCommit = (params: {
562
563
  process.stderr.write(
563
564
  `[pumuki][evidence-sync] unable to restage tracked ${EVIDENCE_FILE_PATH}: ${details}\n`
564
565
  );
566
+ if (params.gateBlocked) {
567
+ return false;
568
+ }
565
569
  params.dependencies.notifyGateBlocked({
566
570
  repoRoot: params.repoRoot,
567
571
  stage: 'PRE_COMMIT',
@@ -824,10 +828,10 @@ export async function runPreCommitStage(
824
828
  return 1;
825
829
  }
826
830
  if (
827
- result.exitCode === 0 &&
828
831
  syncTrackedEvidenceAfterSuccessfulPreCommit({
829
832
  dependencies: activeDependencies,
830
833
  repoRoot,
834
+ gateBlocked: result.exitCode !== 0,
831
835
  })
832
836
  ) {
833
837
  return 1;
@@ -71,10 +71,10 @@ const countUntrackedMatchingExtensions = (
71
71
  };
72
72
 
73
73
  const isFindingBlocking = (finding: SnapshotFinding): boolean => {
74
- if (typeof finding.blocking === 'boolean') {
75
- return finding.blocking;
76
- }
77
- return finding.severity === 'CRITICAL' || finding.severity === 'ERROR';
74
+ return finding.severity === 'CRITICAL' ||
75
+ finding.severity === 'ERROR' ||
76
+ finding.severity === 'WARN' ||
77
+ finding.severity === 'INFO';
78
78
  };
79
79
 
80
80
  const toLifecycleAuditFinding = (finding: SnapshotFinding): LifecycleAuditFinding => ({
@@ -220,8 +220,8 @@ Pumuki lifecycle commands:
220
220
 
221
221
  const LOOP_RUN_POLICY: GatePolicy = {
222
222
  stage: 'STAGED',
223
- blockOnOrAbove: 'ERROR',
224
- warnOnOrAbove: 'WARN',
223
+ blockOnOrAbove: 'INFO',
224
+ warnOnOrAbove: 'INFO',
225
225
  };
226
226
 
227
227
  const isLifecycleCommand = (value: string): value is LifecycleCommand =>
@@ -117,6 +117,9 @@ const tryApplyPolicyAutofix = (params: {
117
117
  }
118
118
 
119
119
  const preWriteStage = params.report.stages.PRE_WRITE;
120
+ const preCommitStage = params.report.stages.PRE_COMMIT;
121
+ const prePushStage = params.report.stages.PRE_PUSH;
122
+ const ciStage = params.report.stages.CI;
120
123
  const signatures = {
121
124
  PRE_WRITE: createPolicyAsCodeSignature({
122
125
  stage: 'PRE_COMMIT',
@@ -125,9 +128,27 @@ const tryApplyPolicyAutofix = (params: {
125
128
  hash: preWriteStage.hash,
126
129
  version: '1.0',
127
130
  }),
128
- PRE_COMMIT: params.report.stages.PRE_COMMIT.signature,
129
- PRE_PUSH: params.report.stages.PRE_PUSH.signature,
130
- CI: params.report.stages.CI.signature,
131
+ PRE_COMMIT: createPolicyAsCodeSignature({
132
+ stage: 'PRE_COMMIT',
133
+ source: toContractSource(preCommitStage.source),
134
+ bundle: preCommitStage.bundle,
135
+ hash: preCommitStage.hash,
136
+ version: '1.0',
137
+ }),
138
+ PRE_PUSH: createPolicyAsCodeSignature({
139
+ stage: 'PRE_PUSH',
140
+ source: toContractSource(prePushStage.source),
141
+ bundle: prePushStage.bundle,
142
+ hash: prePushStage.hash,
143
+ version: '1.0',
144
+ }),
145
+ CI: createPolicyAsCodeSignature({
146
+ stage: 'CI',
147
+ source: toContractSource(ciStage.source),
148
+ bundle: ciStage.bundle,
149
+ hash: ciStage.hash,
150
+ version: '1.0',
151
+ }),
131
152
  };
132
153
  if (!signatures.PRE_COMMIT || !signatures.PRE_PUSH || !signatures.CI) {
133
154
  return {
@@ -112,8 +112,8 @@ export const buildPreWriteAutomationTrace = async (params: {
112
112
  const gateExitCode = await params.runPlatformGate({
113
113
  policy: {
114
114
  stage: 'PRE_COMMIT',
115
- blockOnOrAbove: 'ERROR',
116
- warnOnOrAbove: 'WARN',
115
+ blockOnOrAbove: 'INFO',
116
+ warnOnOrAbove: 'INFO',
117
117
  },
118
118
  scope: {
119
119
  kind: 'workingTree',
@@ -148,8 +148,8 @@ export const buildPreWriteAutomationTrace = async (params: {
148
148
  const gateExitCode = await params.runPlatformGate({
149
149
  policy: {
150
150
  stage: 'PRE_PUSH',
151
- blockOnOrAbove: 'ERROR',
152
- warnOnOrAbove: 'WARN',
151
+ blockOnOrAbove: 'INFO',
152
+ warnOnOrAbove: 'INFO',
153
153
  },
154
154
  scope: {
155
155
  kind: 'workingTree',
@@ -32,6 +32,12 @@ type EnterpriseStageThresholds = {
32
32
  warnOnOrAbove: EnterpriseSeverity;
33
33
  };
34
34
 
35
+ const enforceAllSeverityBlockingPolicy = (policy: GatePolicy): GatePolicy => ({
36
+ ...policy,
37
+ blockOnOrAbove: 'INFO',
38
+ warnOnOrAbove: 'INFO',
39
+ });
40
+
35
41
  const mapEnterpriseSeverityToGateSeverity = (
36
42
  severity: EnterpriseSeverity
37
43
  ): GatePolicy['blockOnOrAbove'] => {
@@ -51,7 +57,7 @@ const toGatePolicyFromEnterpriseThresholds = (
51
57
  stage: SkillsStage,
52
58
  thresholds: EnterpriseStageThresholds
53
59
  ): GatePolicy => {
54
- return {
60
+ return enforceAllSeverityBlockingPolicy({
55
61
  stage,
56
62
  blockOnOrAbove: mapEnterpriseSeverityToGateSeverity(
57
63
  thresholds.blockOnOrAbove
@@ -59,7 +65,7 @@ const toGatePolicyFromEnterpriseThresholds = (
59
65
  warnOnOrAbove: mapEnterpriseSeverityToGateSeverity(
60
66
  thresholds.warnOnOrAbove
61
67
  ),
62
- };
68
+ });
63
69
  };
64
70
 
65
71
  const toGatePolicyRecordFromEnterpriseThresholds = (
@@ -88,23 +94,23 @@ const toGatePolicyRecordFromEnterpriseThresholds = (
88
94
  const defaultPolicyByStage: Record<SkillsStage, GatePolicy> = {
89
95
  PRE_WRITE: {
90
96
  stage: 'PRE_WRITE',
91
- blockOnOrAbove: 'ERROR',
92
- warnOnOrAbove: 'WARN',
97
+ blockOnOrAbove: 'INFO',
98
+ warnOnOrAbove: 'INFO',
93
99
  },
94
100
  PRE_COMMIT: {
95
101
  stage: 'PRE_COMMIT',
96
- blockOnOrAbove: 'ERROR',
97
- warnOnOrAbove: 'WARN',
102
+ blockOnOrAbove: 'INFO',
103
+ warnOnOrAbove: 'INFO',
98
104
  },
99
105
  PRE_PUSH: {
100
106
  stage: 'PRE_PUSH',
101
- blockOnOrAbove: 'ERROR',
102
- warnOnOrAbove: 'WARN',
107
+ blockOnOrAbove: 'INFO',
108
+ warnOnOrAbove: 'INFO',
103
109
  },
104
110
  CI: {
105
111
  stage: 'CI',
106
- blockOnOrAbove: 'ERROR',
107
- warnOnOrAbove: 'WARN',
112
+ blockOnOrAbove: 'INFO',
113
+ warnOnOrAbove: 'INFO',
108
114
  },
109
115
  };
110
116
 
@@ -113,19 +119,19 @@ const hardModeEnterpriseThresholdsByStage: Record<
113
119
  EnterpriseStageThresholds
114
120
  > = {
115
121
  PRE_WRITE: {
116
- blockOnOrAbove: 'MEDIUM',
122
+ blockOnOrAbove: 'LOW',
117
123
  warnOnOrAbove: 'LOW',
118
124
  },
119
125
  PRE_COMMIT: {
120
- blockOnOrAbove: 'MEDIUM',
126
+ blockOnOrAbove: 'LOW',
121
127
  warnOnOrAbove: 'LOW',
122
128
  },
123
129
  PRE_PUSH: {
124
- blockOnOrAbove: 'MEDIUM',
130
+ blockOnOrAbove: 'LOW',
125
131
  warnOnOrAbove: 'LOW',
126
132
  },
127
133
  CI: {
128
- blockOnOrAbove: 'MEDIUM',
134
+ blockOnOrAbove: 'LOW',
129
135
  warnOnOrAbove: 'LOW',
130
136
  },
131
137
  };
@@ -374,7 +380,7 @@ export const resolveExplicitPolicyProfileForStage = (
374
380
  const profilePolicy = profileName
375
381
  ? hardModePolicyProfileByStage[profileName][stage]
376
382
  : null;
377
- const hardModePolicy = profilePolicy ?? hardModePolicyByStage[stage];
383
+ const hardModePolicy = enforceAllSeverityBlockingPolicy(profilePolicy ?? hardModePolicyByStage[stage]);
378
384
  return {
379
385
  policy: hardModePolicy,
380
386
  source: 'hard-mode',
@@ -388,15 +394,15 @@ export const resolveExplicitPolicyProfileForStage = (
388
394
  };
389
395
  }
390
396
 
391
- const defaults = resolveCorePolicyForStage(stage);
397
+ const defaults = enforceAllSeverityBlockingPolicy(resolveCorePolicyForStage(stage));
392
398
  const stageOverride = explicitPack.policy.stages[stage];
393
399
 
394
400
  return {
395
- policy: {
401
+ policy: enforceAllSeverityBlockingPolicy({
396
402
  stage: defaults.stage,
397
403
  blockOnOrAbove: stageOverride.blockOnOrAbove,
398
404
  warnOnOrAbove: stageOverride.warnOnOrAbove,
399
- },
405
+ }),
400
406
  source: 'skills.policy',
401
407
  layer: 'policy-pack',
402
408
  activation: 'explicit',
@@ -21,7 +21,7 @@ export const resolvePreWriteEnforcement = (): PreWriteEnforcementResolution => {
21
21
  return {
22
22
  mode: experimentalFeature.mode,
23
23
  source: experimentalFeature.source,
24
- blocking: experimentalFeature.blocking,
24
+ blocking: experimentalFeature.mode !== 'off',
25
25
  layer: experimentalFeature.layer,
26
26
  activationVariable: experimentalFeature.activationVariable,
27
27
  legacyActivationVariable: experimentalFeature.legacyActivationVariable,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.135",
3
+ "version": "6.3.136",
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": {
package/skills.lock.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": "1.0",
3
3
  "compilerVersion": "1.0.0",
4
- "generatedAt": "2026-05-03T12:45:29.960Z",
4
+ "generatedAt": "2026-05-03T20:19:41.600Z",
5
5
  "bundles": [
6
6
  {
7
7
  "name": "android-guidelines",
@@ -8299,18 +8299,19 @@
8299
8299
  "name": "ios-swiftui-expert-guidelines",
8300
8300
  "version": "1.0.0",
8301
8301
  "source": "file:vendor/skills/swiftui-expert-skill/SKILL.md",
8302
- "hash": "33a19bd25ca8546d49f2070956784e4fe3f653a25365c8a9734af5cd46b8ec60",
8302
+ "hash": "59dcc140ff7423c1ec320af0389139183571ca2a61afc7272d773727432cba17",
8303
8303
  "rules": [
8304
8304
  {
8305
8305
  "id": "skills.ios.guideline.ios-swiftui-expert.always-mark-state-and-stateobject-as-private-makes-dependencies-clear",
8306
- "description": "Always mark @State and @StateObject as private (makes dependencies clear)",
8306
+ "description": "Always mark @State and @StateObject as private (makes dependencies clear).",
8307
8307
  "severity": "WARN",
8308
8308
  "platform": "ios",
8309
- "sourceSkill": "ios-swiftui-expert-guidelines",
8310
- "sourcePath": "vendor/skills/swiftui-expert-skill/SKILL.md",
8311
8309
  "confidence": "MEDIUM",
8310
+ "stage": "PRE_PUSH",
8312
8311
  "locked": true,
8313
- "evaluationMode": "DECLARATIVE",
8312
+ "sourceSkill": "ios-swiftui-expert-guidelines",
8313
+ "sourcePath": "vendor/skills/swiftui-expert-skill/SKILL.md",
8314
+ "evaluationMode": "AUTO",
8314
8315
  "origin": "core"
8315
8316
  },
8316
8317
  {