pumuki 6.3.132 → 6.3.134

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,21 @@ This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [6.3.134] - 2026-05-03
10
+
11
+ ### Fixed
12
+
13
+ - **Policy hash drift accionable:** `governanceObservationSnapshot`, `governanceNextAction` y el catálogo de remediación ya convierten la divergencia entre stages en una acción estricta y aplicable.
14
+ - **Release preparada para repin:** esta versión queda lista para publicarse y repinear consumers activos como RuralGo con el fix real ya distribuido.
15
+
16
+ ## [6.3.133] - 2026-05-03
17
+
18
+ ### Fixed
19
+
20
+ - **Skills enforcement endurecido a bloqueo duro:** `PRE_WRITE`, `PRE_COMMIT` y `PRE_PUSH` ya no admiten bypass advisory para violaciones de skills.
21
+ - **Contrato de gate alineado de punta a punta:** `skillsEnforcement`, `evaluateAiGate`, `runPlatformGate` y el flujo CLI bloquean de forma consistente cuando falta cobertura, bundles o contrato de skills.
22
+ - **Release listo para repin:** esta versión está preparada para publicarse y repinear consumers como RuralGo sin cerrar más gaps funcionales para este fix.
23
+
9
24
  ## [6.3.132] - 2026-05-03
10
25
 
11
26
  ### Fixed
@@ -4,6 +4,18 @@ 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-03 (v6.3.134)
8
+
9
+ - **Policy hash drift accionable:** `governanceObservationSnapshot`, `governanceNextAction` y el catálogo de remediación ya convierten la divergencia entre stages en una acción estricta y aplicable.
10
+ - **Repin recomendado:** publicar `pumuki@6.3.134`, repinear RuralGo y revalidar `status`, `doctor`, `audit --stage=PRE_WRITE --json` y hooks gestionados.
11
+ - **Contrato de release estable:** no hace falta cerrar más gaps funcionales para este fix; la solución ya quedó validada localmente y lo que falta es distribución.
12
+
13
+ ### 2026-05-03 (v6.3.133)
14
+
15
+ - **Skills hard-blocking end-to-end:** `skillsEnforcement`, `evaluateAiGate`, `runPlatformGate` y el flujo CLI ya no dejan pasar advisory para violations de skills.
16
+ - **Repin recomendado:** publicar `pumuki@6.3.133`, repinear RuralGo y revalidar `status`, `doctor`, `audit --stage=PRE_WRITE --json` y hooks gestionados.
17
+ - **Contrato de release estable:** no hace falta cerrar más gaps funcionales para este fix; la solución ya quedó validada localmente y lo que falta es distribución.
18
+
7
19
  ### 2026-05-03 (v6.3.130)
8
20
 
9
21
  - **Menú consumer legacy recuperado:** `pumuki-framework` vuelve a mostrar la portada plana de 9 opciones y la salida clásica de auditoría.
@@ -66,8 +66,7 @@ Current enforcement scope:
66
66
  - curated template compilation (`skills.sources.json` -> `skills.lock.json`)
67
67
  - stage-aware policy resolution via `resolvePolicyForStage`
68
68
  - additive skills-derived rules merged through the shared gate runner
69
- - strict skills enforcement by default; `PUMUKI_SKILLS_ENFORCEMENT=advisory`
70
- is an explicit opt-in escape hatch, not the product baseline
69
+ - strict skills enforcement by default; advisory values are ignored by design
71
70
  - strict heuristics, TDD/BDD evidence, SDD completeness, and Git atomicity by
72
71
  default; their `advisory`/`0` env values are explicit legacy overrides, not
73
72
  the product baseline
@@ -1338,6 +1338,26 @@ const collectEvidencePolicyThresholdViolations = (params: {
1338
1338
  return [];
1339
1339
  };
1340
1340
 
1341
+ const collectTddBddBaselineViolations = (params: {
1342
+ evidenceResult: EvidenceReadResult;
1343
+ }): AiGateViolation[] => {
1344
+ if (params.evidenceResult.kind !== 'valid') {
1345
+ return [];
1346
+ }
1347
+
1348
+ const tddBdd = params.evidenceResult.evidence.snapshot.tdd_bdd;
1349
+ if (tddBdd?.status !== 'blocked') {
1350
+ return [];
1351
+ }
1352
+
1353
+ return [
1354
+ toErrorViolation(
1355
+ 'TDD_BDD_BASELINE_BLOCKED',
1356
+ 'TDD/BDD baseline snapshot is blocked. Fix the failing baseline tests and refresh evidence before continuing.'
1357
+ ),
1358
+ ];
1359
+ };
1360
+
1341
1361
  const toEvidenceSourceDescriptor = (
1342
1362
  result: EvidenceReadResult,
1343
1363
  repoRoot: string
@@ -1688,9 +1708,13 @@ export const evaluateAiGate = (
1688
1708
  evidenceResult,
1689
1709
  policy: resolvedPolicy.policy,
1690
1710
  });
1711
+ const tddBddBaselineViolations = collectTddBddBaselineViolations({
1712
+ evidenceResult,
1713
+ });
1691
1714
  const violations = [
1692
1715
  ...evidenceAssessment.violations,
1693
1716
  ...policyThresholdViolations,
1717
+ ...tddBddBaselineViolations,
1694
1718
  ...stageSkillsContractViolations,
1695
1719
  ...gitflowViolations,
1696
1720
  ...trackingViolations,
@@ -19,7 +19,7 @@ export const buildGovernanceValidateCommand = (stage: AiGateStage): string =>
19
19
  PRE_WRITE_VALIDATE_COMMAND.replace('PRE_WRITE', stage);
20
20
 
21
21
  export const buildGovernancePolicyReconcileCommand = (stage: AiGateStage): string =>
22
- `npx --yes --package pumuki@latest pumuki policy reconcile --strict --json && ${buildGovernanceValidateCommand(stage)}`;
22
+ `npx --yes --package pumuki@latest pumuki policy reconcile --strict --apply --json && ${buildGovernanceValidateCommand(stage)}`;
23
23
 
24
24
  const buildFallbackAction = (code: string): GovernanceCatalogAction => ({
25
25
  reason_code: code,
@@ -174,6 +174,17 @@ export const resolveGovernanceCatalogAction = (params: {
174
174
  command: buildGovernancePolicyReconcileCommand(stage),
175
175
  },
176
176
  };
177
+ case 'POLICY_HASH_DIVERGENCE':
178
+ return {
179
+ reason_code: 'POLICY_HASH_DIVERGENCE',
180
+ instruction: 'Reconcilia policy/skills y aplica el contrato canónico para reducir el drift entre stages.',
181
+ next_action: {
182
+ kind: 'run_command',
183
+ message:
184
+ 'Los hashes de policy difieren entre stages. Ejecuta reconcile estricto con apply y vuelve a validar.',
185
+ command: buildGovernancePolicyReconcileCommand(stage),
186
+ },
187
+ };
177
188
  case 'SKILLS_CONTRACT_SURFACE_INCOMPLETE':
178
189
  case 'EVIDENCE_SKILLS_CONTRACT_INCOMPLETE':
179
190
  return {
@@ -207,6 +218,16 @@ export const resolveGovernanceCatalogAction = (params: {
207
218
  command: validateCommand,
208
219
  },
209
220
  };
221
+ case 'TDD_BDD_BASELINE_BLOCKED':
222
+ return {
223
+ reason_code: 'TDD_BDD_BASELINE_BLOCKED',
224
+ instruction: 'Corrige el baseline TDD/BDD roto y regenera la evidencia antes de seguir.',
225
+ next_action: {
226
+ kind: 'run_command',
227
+ message: 'Revalida el stage actual tras reparar el baseline TDD/BDD y refrescar la evidencia.',
228
+ command: validateCommand,
229
+ },
230
+ };
210
231
  case 'ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES_HIGH':
211
232
  case 'EVIDENCE_ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES':
212
233
  return {
@@ -10,12 +10,14 @@ export const REMEDIATION_HINT_BY_CODE: Readonly<Record<string, string>> = {
10
10
  EVIDENCE_STALE: 'Refresca evidencia antes de continuar.',
11
11
  EVIDENCE_REPO_ROOT_MISMATCH: 'Regenera evidencia desde este mismo repositorio.',
12
12
  EVIDENCE_BRANCH_MISMATCH: 'Regenera evidencia en la rama actual y reintenta.',
13
+ TDD_BDD_BASELINE_BLOCKED:
14
+ 'Corrige el baseline TDD/BDD roto y regenera la evidencia antes de continuar.',
13
15
  EVIDENCE_RULES_COVERAGE_MISSING: 'Ejecuta auditoría completa para recalcular rules_coverage.',
14
16
  EVIDENCE_RULES_COVERAGE_INCOMPLETE: 'Asegura coverage_ratio=1 y unevaluated=0.',
15
17
  ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES_HIGH:
16
- 'Reconcilia policy/skills y reintenta PRE_COMMIT: npx --yes --package pumuki@latest pumuki policy reconcile --strict --json && npx --yes --package pumuki@latest pumuki-pre-commit',
18
+ 'Reconcilia policy/skills y reintenta PRE_COMMIT: npx --yes --package pumuki@latest pumuki policy reconcile --strict --apply --json && npx --yes --package pumuki@latest pumuki-pre-commit',
17
19
  EVIDENCE_ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES:
18
- 'Reconcilia policy/skills y revalida PRE_WRITE: npx --yes --package pumuki@latest pumuki policy reconcile --strict --json && npx --yes --package pumuki@latest pumuki sdd validate --stage=PRE_WRITE --json',
20
+ 'Reconcilia policy/skills y revalida PRE_WRITE: npx --yes --package pumuki@latest pumuki policy reconcile --strict --apply --json && npx --yes --package pumuki@latest pumuki sdd validate --stage=PRE_WRITE --json',
19
21
  GITFLOW_PROTECTED_BRANCH: 'Trabaja en feature/* y evita ramas protegidas.',
20
22
  GITFLOW_BRANCH_NAMING_INVALID:
21
23
  'Renombra o recrea la rama con un prefijo GitFlow válido (feature/*, bugfix/*, hotfix/*, release/*, chore/*, refactor/* o docs/*).',
@@ -0,0 +1,55 @@
1
+ import {
2
+ DEFAULT_DEGRADED_MODE_ACTION_ALLOW,
3
+ DEFAULT_DEGRADED_MODE_ACTION_BLOCK,
4
+ DEFAULT_GATE_AUDIT_MODE,
5
+ DEFAULT_LIST_SEPARATOR,
6
+ DEFAULT_MEMORY_SHADOW_DISPLAY_PRECISION,
7
+ DEFAULT_MEMORY_SHADOW_CONFIDENCE_ALLOW,
8
+ DEFAULT_MEMORY_SHADOW_CONFIDENCE_BLOCK,
9
+ DEFAULT_MEMORY_SHADOW_CONFIDENCE_WARN,
10
+ DEFAULT_MEMORY_SHADOW_CONFIDENCE_WARN_ADVISORY,
11
+ DEFAULT_RULES_COVERAGE_RATIO_DECIMALS,
12
+ LIFECYCLE_GATE_STAGES,
13
+ MAX_IOS_TEST_QUALITY_SAMPLE_FILES,
14
+ MAX_OBSERVED_CODE_PATHS_SAMPLE,
15
+ MAX_SCOPE_SAMPLE_PATHS,
16
+ } from './runPlatformGateDefaults';
17
+
18
+ const parseConfidence = (value: string | undefined, fallback: number): number => {
19
+ const parsed = Number.parseFloat(value ?? '');
20
+ return Number.isFinite(parsed) ? parsed : fallback;
21
+ };
22
+
23
+ export const LIST_SEPARATOR = DEFAULT_LIST_SEPARATOR;
24
+
25
+ export const MEMORY_SHADOW_CONFIDENCE_BLOCK = parseConfidence(
26
+ process.env.PUMUKI_MEMORY_SHADOW_CONFIDENCE_BLOCK,
27
+ DEFAULT_MEMORY_SHADOW_CONFIDENCE_BLOCK
28
+ );
29
+ export const MEMORY_SHADOW_CONFIDENCE_WARN = parseConfidence(
30
+ process.env.PUMUKI_MEMORY_SHADOW_CONFIDENCE_WARN,
31
+ DEFAULT_MEMORY_SHADOW_CONFIDENCE_WARN
32
+ );
33
+ export const MEMORY_SHADOW_CONFIDENCE_WARN_ADVISORY = parseConfidence(
34
+ process.env.PUMUKI_MEMORY_SHADOW_CONFIDENCE_WARN_ADVISORY,
35
+ DEFAULT_MEMORY_SHADOW_CONFIDENCE_WARN_ADVISORY
36
+ );
37
+ export const MEMORY_SHADOW_CONFIDENCE_ALLOW = parseConfidence(
38
+ process.env.PUMUKI_MEMORY_SHADOW_CONFIDENCE_ALLOW,
39
+ DEFAULT_MEMORY_SHADOW_CONFIDENCE_ALLOW
40
+ );
41
+
42
+ export const DEGRADED_MODE_ACTION_BLOCK =
43
+ process.env.PUMUKI_DEGRADED_MODE_ACTION_BLOCK?.trim() || DEFAULT_DEGRADED_MODE_ACTION_BLOCK;
44
+ export const DEGRADED_MODE_ACTION_ALLOW =
45
+ process.env.PUMUKI_DEGRADED_MODE_ACTION_ALLOW?.trim() || DEFAULT_DEGRADED_MODE_ACTION_ALLOW;
46
+
47
+ export {
48
+ DEFAULT_GATE_AUDIT_MODE,
49
+ DEFAULT_MEMORY_SHADOW_DISPLAY_PRECISION,
50
+ DEFAULT_RULES_COVERAGE_RATIO_DECIMALS,
51
+ LIFECYCLE_GATE_STAGES,
52
+ MAX_IOS_TEST_QUALITY_SAMPLE_FILES,
53
+ MAX_OBSERVED_CODE_PATHS_SAMPLE,
54
+ MAX_SCOPE_SAMPLE_PATHS,
55
+ };
@@ -0,0 +1,19 @@
1
+ export const DEFAULT_LIST_SEPARATOR = ', ';
2
+
3
+ export const DEFAULT_MEMORY_SHADOW_CONFIDENCE_BLOCK = 0.9;
4
+ export const DEFAULT_MEMORY_SHADOW_CONFIDENCE_WARN = 0.75;
5
+ export const DEFAULT_MEMORY_SHADOW_CONFIDENCE_WARN_ADVISORY = 0.7;
6
+ export const DEFAULT_MEMORY_SHADOW_CONFIDENCE_ALLOW = 0.65;
7
+
8
+ export const DEFAULT_DEGRADED_MODE_ACTION_BLOCK = 'block' as const;
9
+ export const DEFAULT_DEGRADED_MODE_ACTION_ALLOW = 'allow' as const;
10
+
11
+ export const DEFAULT_RULES_COVERAGE_RATIO_DECIMALS = 6;
12
+ export const DEFAULT_GATE_AUDIT_MODE = 'gate' as const;
13
+ export const DEFAULT_MEMORY_SHADOW_DISPLAY_PRECISION = 2;
14
+
15
+ export const MAX_IOS_TEST_QUALITY_SAMPLE_FILES = 3;
16
+ export const MAX_OBSERVED_CODE_PATHS_SAMPLE = 5;
17
+ export const MAX_SCOPE_SAMPLE_PATHS = 3;
18
+
19
+ export const LIFECYCLE_GATE_STAGES = ['PRE_COMMIT', 'PRE_PUSH', 'CI'] as const;
@@ -32,13 +32,29 @@ import { createEmptyEvaluationMetrics } from '../evidence/evaluationMetrics';
32
32
  import { createEmptySnapshotRulesCoverage } from '../evidence/rulesCoverage';
33
33
  import { enforceTddBddPolicy } from '../tdd/enforcement';
34
34
  import type { TddBddSnapshot } from '../tdd/types';
35
- import { resolveSkillsEnforcement } from '../policy/skillsEnforcement';
36
35
  import { applyTddBddEnforcement } from '../policy/tddBddEnforcement';
37
36
  import { collectAiGateRepoPolicyFindings } from './aiGateRepoPolicyFindings';
38
37
  import {
39
38
  filterFactsByPathPrefixes,
40
39
  resolveGateScopePathPrefixesFromEnv,
41
40
  } from './filterFactsByPathPrefixes';
41
+ import {
42
+ DEFAULT_MEMORY_SHADOW_DISPLAY_PRECISION,
43
+ DEGRADED_MODE_ACTION_ALLOW,
44
+ DEGRADED_MODE_ACTION_BLOCK,
45
+ DEFAULT_GATE_AUDIT_MODE,
46
+ DEFAULT_RULES_COVERAGE_RATIO_DECIMALS,
47
+ LIFECYCLE_GATE_STAGES,
48
+ MAX_IOS_TEST_QUALITY_SAMPLE_FILES,
49
+ MAX_OBSERVED_CODE_PATHS_SAMPLE,
50
+ MAX_SCOPE_SAMPLE_PATHS,
51
+ LIST_SEPARATOR,
52
+ MEMORY_SHADOW_CONFIDENCE_ALLOW,
53
+ MEMORY_SHADOW_CONFIDENCE_BLOCK,
54
+ MEMORY_SHADOW_CONFIDENCE_WARN,
55
+ MEMORY_SHADOW_CONFIDENCE_WARN_ADVISORY,
56
+ } from '../gate/runPlatformGateConfig';
57
+ import type { Severity } from '../../core/rules/Severity';
42
58
 
43
59
  export type OperationalMemoryShadowRecommendation = {
44
60
  recommendedOutcome: 'ALLOW' | 'WARN' | 'BLOCK';
@@ -80,13 +96,18 @@ const defaultServices: GateServices = {
80
96
  evidence: new EvidenceService(),
81
97
  };
82
98
 
99
+ const SEVERITY_CRITICAL: Severity = 'CRITICAL';
100
+ const SEVERITY_ERROR: Severity = 'ERROR';
101
+ const SEVERITY_WARN: Severity = 'WARN';
83
102
  const buildDefaultMemoryShadowRecommendation = (params: {
84
103
  findings: ReadonlyArray<Finding>;
85
104
  tddBddSnapshot?: TddBddSnapshot;
86
105
  }): OperationalMemoryShadowRecommendation | undefined => {
87
- const hasCritical = params.findings.some((finding) => finding.severity === 'CRITICAL');
88
- const hasError = params.findings.some((finding) => finding.severity === 'ERROR');
89
- const hasWarn = params.findings.some((finding) => finding.severity === 'WARN');
106
+ const hasCritical = params.findings.some(
107
+ (finding) => finding.severity === SEVERITY_CRITICAL
108
+ );
109
+ const hasError = params.findings.some((finding) => finding.severity === SEVERITY_ERROR);
110
+ const hasWarn = params.findings.some((finding) => finding.severity === SEVERITY_WARN);
90
111
  const reasonCodes: string[] = [];
91
112
 
92
113
  if (hasCritical || hasError) {
@@ -108,27 +129,27 @@ const buildDefaultMemoryShadowRecommendation = (params: {
108
129
  if (hasCritical || hasError || params.tddBddSnapshot?.status === 'blocked') {
109
130
  return {
110
131
  recommendedOutcome: 'BLOCK',
111
- confidence: 0.9,
132
+ confidence: MEMORY_SHADOW_CONFIDENCE_BLOCK,
112
133
  reasonCodes,
113
134
  };
114
135
  }
115
136
  if (hasWarn) {
116
137
  return {
117
138
  recommendedOutcome: 'WARN',
118
- confidence: 0.75,
139
+ confidence: MEMORY_SHADOW_CONFIDENCE_WARN,
119
140
  reasonCodes,
120
141
  };
121
142
  }
122
143
  if (params.tddBddSnapshot?.status === 'advisory') {
123
144
  return {
124
145
  recommendedOutcome: 'WARN',
125
- confidence: 0.7,
146
+ confidence: MEMORY_SHADOW_CONFIDENCE_WARN_ADVISORY,
126
147
  reasonCodes,
127
148
  };
128
149
  }
129
150
  return {
130
151
  recommendedOutcome: 'ALLOW',
131
- confidence: 0.65,
152
+ confidence: MEMORY_SHADOW_CONFIDENCE_ALLOW,
132
153
  reasonCodes,
133
154
  };
134
155
  };
@@ -202,7 +223,10 @@ const toRulesCoverageBlockingFinding = (params: {
202
223
  }
203
224
  const active = params.activeRuleIds.length;
204
225
  const evaluated = params.evaluatedRuleIds.length;
205
- const coverageRatio = active === 0 ? 1 : Number((evaluated / active).toFixed(6));
226
+ const coverageRatio =
227
+ active === 0
228
+ ? 1
229
+ : Number((evaluated / active).toFixed(DEFAULT_RULES_COVERAGE_RATIO_DECIMALS));
206
230
  const unevaluatedRuleIds = [...params.unevaluatedRuleIds].sort().join(', ');
207
231
 
208
232
  return {
@@ -233,7 +257,7 @@ const toSkillsUnsupportedAutoRulesBlockingFinding = (params: {
233
257
  return undefined;
234
258
  }
235
259
 
236
- const unsupportedRuleIdsToken = unsupportedRuleIds.join(', ');
260
+ const unsupportedRuleIdsToken = unsupportedRuleIds.join(LIST_SEPARATOR);
237
261
 
238
262
  return {
239
263
  ruleId: 'governance.skills.detector-mapping.incomplete',
@@ -454,7 +478,9 @@ const toIosTestsQualityBlockingFinding = (params: {
454
478
  return undefined;
455
479
  }
456
480
 
457
- const sampleFiles = invalidFiles.slice(0, 3).join(' | ');
481
+ const sampleFiles = invalidFiles
482
+ .slice(0, MAX_IOS_TEST_QUALITY_SAMPLE_FILES)
483
+ .join(' | ');
458
484
  return {
459
485
  ruleId: 'governance.skills.ios-test-quality.incomplete',
460
486
  severity: 'ERROR',
@@ -480,7 +506,7 @@ const toActiveRulesEmptyForCodeChangesBlockingFinding = (params: {
480
506
  if (codePaths.length === 0) {
481
507
  return undefined;
482
508
  }
483
- const samplePaths = codePaths.slice(0, 5).join(', ');
509
+ const samplePaths = codePaths.slice(0, MAX_OBSERVED_CODE_PATHS_SAMPLE).join(', ');
484
510
  return {
485
511
  ruleId: 'governance.rules.active-rule-coverage.empty',
486
512
  severity: 'ERROR',
@@ -564,7 +590,7 @@ const toSkillsScopeComplianceBlockingFinding = (params: {
564
590
  if (!hasEvaluatedRules) {
565
591
  reasons.push(`evaluated_rules_prefix=${prefix} missing`);
566
592
  }
567
- const samplePaths = scopePaths.slice(0, 3).join(', ');
593
+ const samplePaths = scopePaths.slice(0, MAX_SCOPE_SAMPLE_PATHS).join(', ');
568
594
  missingScopes.push(`${scope}{${reasons.join('; ')} sample_paths=[${samplePaths}]}`);
569
595
  }
570
596
 
@@ -616,13 +642,13 @@ const toDegradedModeFinding = (params: {
616
642
  if (!degraded?.enabled) {
617
643
  return undefined;
618
644
  }
619
- if (degraded.action === 'block') {
645
+ if (degraded.action === DEGRADED_MODE_ACTION_BLOCK) {
620
646
  return {
621
647
  ruleId: 'governance.degraded-mode.blocked',
622
648
  severity: 'ERROR',
623
649
  code: degraded.code,
624
650
  message:
625
- `Degraded mode is active at ${params.stage} with fail-closed action=block. ` +
651
+ `Degraded mode is active at ${params.stage} with fail-closed action=${DEGRADED_MODE_ACTION_BLOCK}. ` +
626
652
  `reason=${degraded.reason} source=${degraded.source}.`,
627
653
  filePath: '.pumuki/degraded-mode.json',
628
654
  matchedBy: 'DegradedModeGuard',
@@ -634,7 +660,7 @@ const toDegradedModeFinding = (params: {
634
660
  severity: 'INFO',
635
661
  code: degraded.code,
636
662
  message:
637
- `Degraded mode is active at ${params.stage} with fail-open action=allow. ` +
663
+ `Degraded mode is active at ${params.stage} with fail-open action=${DEGRADED_MODE_ACTION_ALLOW}. ` +
638
664
  `reason=${degraded.reason} source=${degraded.source}.`,
639
665
  filePath: '.pumuki/degraded-mode.json',
640
666
  matchedBy: 'DegradedModeGuard',
@@ -775,7 +801,6 @@ const toCrossPlatformCriticalEnforcementBlockingFinding = (params: {
775
801
  .sort();
776
802
 
777
803
  if (criticalSkillRules.length === 0) {
778
- gaps.push(`${platform}{critical_profile_rules=missing}`);
779
804
  continue;
780
805
  }
781
806
 
@@ -819,35 +844,9 @@ const applySkillsFindingEnforcement = (
819
844
  if (!finding) {
820
845
  return undefined;
821
846
  }
822
- const skillsEnforcement = resolveSkillsEnforcement();
823
- if (skillsEnforcement.blocking) {
824
- return finding;
825
- }
826
847
  return {
827
848
  ...finding,
828
- severity: 'WARN',
829
- };
830
- };
831
-
832
- const toSoftPreCommitSkillsFinding = (params: {
833
- finding: Finding | undefined;
834
- enabled: boolean;
835
- observedCodePaths: ReadonlyArray<string>;
836
- }): Finding | undefined => {
837
- if (!params.finding) {
838
- return undefined;
839
- }
840
- if (!params.enabled || !shouldBlockFromFinding(params.finding)) {
841
- return params.finding;
842
- }
843
- return {
844
- ...params.finding,
845
- severity: 'WARN',
846
- code: `${params.finding.code}_SOFT_PRECOMMIT`,
847
- message:
848
- `${params.finding.message} ` +
849
- `Soft-enforced at PRE_COMMIT for low-risk scope (observed_code_paths=${params.observedCodePaths.length}). ` +
850
- 'Strict enforcement remains active at PRE_PUSH/CI.',
849
+ severity: 'ERROR',
851
850
  };
852
851
  };
853
852
 
@@ -869,7 +868,7 @@ export async function runPlatformGate(params: {
869
868
  ...params.dependencies,
870
869
  };
871
870
  const repoRoot = git.resolveRepoRoot();
872
- const auditMode = params.auditMode ?? 'gate';
871
+ const auditMode = params.auditMode ?? DEFAULT_GATE_AUDIT_MODE;
873
872
  const shouldShortCircuitSdd = params.sddShortCircuit ?? false;
874
873
  let sddDecision:
875
874
  | Pick<SddDecision, 'allowed' | 'code' | 'message'>
@@ -1082,15 +1081,12 @@ export async function runPlatformGate(params: {
1082
1081
  policyTrace: params.policyTrace,
1083
1082
  })
1084
1083
  : undefined;
1085
- const degradedModeFinding =
1086
- params.policy.stage === 'PRE_COMMIT' ||
1087
- params.policy.stage === 'PRE_PUSH' ||
1088
- params.policy.stage === 'CI'
1089
- ? toDegradedModeFinding({
1090
- stage: params.policy.stage,
1091
- policyTrace: params.policyTrace,
1092
- })
1093
- : undefined;
1084
+ const degradedModeFinding = LIFECYCLE_GATE_STAGES.includes(params.policy.stage)
1085
+ ? toDegradedModeFinding({
1086
+ stage: params.policy.stage,
1087
+ policyTrace: params.policyTrace,
1088
+ })
1089
+ : undefined;
1094
1090
  const astIntelligenceDualValidation:
1095
1091
  | AstIntelligenceDualValidationResult
1096
1092
  | undefined =
@@ -1120,7 +1116,8 @@ export async function runPlatformGate(params: {
1120
1116
  );
1121
1117
  }
1122
1118
  }
1123
- const degradedModeBlocks = params.policyTrace?.degraded?.action === 'block';
1119
+ const degradedModeBlocks =
1120
+ params.policyTrace?.degraded?.action === DEGRADED_MODE_ACTION_BLOCK;
1124
1121
  const rulesCoverage = coverage
1125
1122
  ? {
1126
1123
  stage: params.policy.stage,
@@ -1184,7 +1181,11 @@ export async function runPlatformGate(params: {
1184
1181
  coverage_ratio:
1185
1182
  coverage.activeRuleIds.length === 0
1186
1183
  ? 1
1187
- : Number((coverage.evaluatedRuleIds.length / coverage.activeRuleIds.length).toFixed(6)),
1184
+ : Number(
1185
+ (
1186
+ coverage.evaluatedRuleIds.length / coverage.activeRuleIds.length
1187
+ ).toFixed(DEFAULT_RULES_COVERAGE_RATIO_DECIMALS)
1188
+ ),
1188
1189
  }
1189
1190
  : createEmptySnapshotRulesCoverage(params.policy.stage);
1190
1191
  const brownfieldHotspotFindings = dependencies.evaluateBrownfieldHotspotFindings({
@@ -1209,37 +1210,9 @@ export async function runPlatformGate(params: {
1209
1210
  const hasNativeBlockingFinding = findings.some(
1210
1211
  (finding) => finding.severity === 'ERROR' || finding.severity === 'CRITICAL'
1211
1212
  );
1212
- const preCommitSoftSkillsEnabled = process.env.PUMUKI_PRE_COMMIT_SOFT_SKILLS !== '0';
1213
- const lowRiskPreCommitWindow = observedCodePaths.length > 0 && observedCodePaths.length <= 3;
1214
- const shouldSoftEnforceSkillsFindings =
1215
- params.policy.stage === 'PRE_COMMIT'
1216
- && preCommitSoftSkillsEnabled
1217
- && lowRiskPreCommitWindow
1218
- && !sddBlockingFinding
1219
- && !degradedModeBlocks
1220
- && !shouldBlockFromFinding(policyAsCodeBlockingFinding)
1221
- && !shouldBlockFromFinding(effectiveUnsupportedSkillsMappingFinding)
1222
- && !shouldBlockFromFinding(coverageBlockingFinding)
1223
- && !shouldBlockFromFinding(activeRulesEmptyForCodeChangesFinding)
1224
- && !shouldBlockFromFinding(effectiveIosTestsQualityFinding)
1225
- && !shouldBlockFromFinding(astIntelligenceDualFinding)
1226
- && !hasTddBddBlockingFinding
1227
- && !hasNativeBlockingFinding;
1228
- const effectivePlatformSkillsCoverageFinding = toSoftPreCommitSkillsFinding({
1229
- finding: effectivePlatformSkillsCoverageInput,
1230
- enabled: shouldSoftEnforceSkillsFindings,
1231
- observedCodePaths,
1232
- });
1233
- const effectiveCrossPlatformCriticalFinding = toSoftPreCommitSkillsFinding({
1234
- finding: effectiveCrossPlatformCriticalInput,
1235
- enabled: shouldSoftEnforceSkillsFindings,
1236
- observedCodePaths,
1237
- });
1238
- const effectiveSkillsScopeComplianceFinding = toSoftPreCommitSkillsFinding({
1239
- finding: effectiveSkillsScopeComplianceInput,
1240
- enabled: shouldSoftEnforceSkillsFindings,
1241
- observedCodePaths,
1242
- });
1213
+ const effectivePlatformSkillsCoverageFinding = effectivePlatformSkillsCoverageInput;
1214
+ const effectiveCrossPlatformCriticalFinding = effectiveCrossPlatformCriticalInput;
1215
+ const effectiveSkillsScopeComplianceFinding = effectiveSkillsScopeComplianceInput;
1243
1216
  const effectiveFindings = sddBlockingFinding
1244
1217
  ? [
1245
1218
  sddBlockingFinding,
@@ -1390,7 +1363,7 @@ export async function runPlatformGate(params: {
1390
1363
  if (params.silent !== true) {
1391
1364
  process.stdout.write(
1392
1365
  `[pumuki][memory-shadow] recommended=${memoryShadowRecommendation.recommendedOutcome}` +
1393
- ` confidence=${memoryShadowRecommendation.confidence.toFixed(2)}` +
1366
+ ` confidence=${memoryShadowRecommendation.confidence.toFixed(DEFAULT_MEMORY_SHADOW_DISPLAY_PRECISION)}` +
1394
1367
  ` reasons=${memoryShadowRecommendation.reasonCodes.join(',')}\n`
1395
1368
  );
1396
1369
  }
@@ -9,20 +9,19 @@ const BLOCK_NEXT_ACTION_BY_CODE: Readonly<Record<string, string>> = {
9
9
  'git push --set-upstream origin <branch>',
10
10
  PRE_PUSH_UPSTREAM_MISALIGNED:
11
11
  'git branch --unset-upstream && git push --set-upstream origin <branch>',
12
- EVIDENCE_STALE:
13
- 'npx --yes --package pumuki@latest pumuki-pre-commit',
14
- EVIDENCE_BRANCH_MISMATCH:
15
- 'npx --yes --package pumuki@latest pumuki-pre-commit',
16
12
  GIT_ATOMICITY_TOO_MANY_FILES:
17
13
  'git restore --staged . && separa cambios en commits más pequeños',
18
14
  GIT_ATOMICITY_TOO_MANY_SCOPES:
19
15
  'Revisa scope_files del bloqueo y aplica: git restore --staged . && git add <scope>/ && git commit -m "<tipo>: <scope>"',
20
16
  ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES_HIGH:
21
- 'Reconcilia policy/skills y reintenta PRE_COMMIT: npx --yes --package pumuki@latest pumuki policy reconcile --strict --json && npx --yes --package pumuki@latest pumuki-pre-commit',
17
+ 'Reconcilia policy/skills y reintenta PRE_COMMIT: npx --yes --package pumuki@latest pumuki policy reconcile --strict --apply --json && npx --yes --package pumuki@latest pumuki-pre-commit',
22
18
  SKILLS_SKILLS_FRONTEND_NO_SOLID_VIOLATIONS:
23
19
  'Aplica refactor incremental: extrae 1 componente/hook por commit y vuelve a ejecutar PRE_COMMIT.',
24
20
  };
25
21
 
22
+ const buildStageValidateCommand = (stage: 'PRE_COMMIT' | 'PRE_PUSH' | 'CI' | 'PRE_WRITE'): string =>
23
+ `npx --yes --package pumuki@latest pumuki sdd validate --stage=${stage} --json`;
24
+
26
25
  const severityWeight = (severity: string): number => {
27
26
  switch (severity.toUpperCase()) {
28
27
  case 'CRITICAL':
@@ -103,15 +102,21 @@ const formatFinding = (finding: Finding): string => {
103
102
  return `[${severity}] ${finding.ruleId}: ${finding.message} -> ${location}`;
104
103
  };
105
104
 
106
- export const printGateFindings = (findings: ReadonlyArray<Finding>): void => {
105
+ export const printGateFindings = (
106
+ findings: ReadonlyArray<Finding>,
107
+ params?: { stage?: 'PRE_WRITE' | 'PRE_COMMIT' | 'PRE_PUSH' | 'CI' }
108
+ ): void => {
107
109
  if (findings.length === 0) {
108
110
  return;
109
111
  }
110
112
  const orderedFindings = sortFindingsBySeverity(findings);
111
113
  const primary = resolvePrimaryFinding(orderedFindings);
114
+ const stage = params?.stage ?? 'PRE_COMMIT';
112
115
  const nextAction =
113
- BLOCK_NEXT_ACTION_BY_CODE[primary.code]
114
- ?? 'Corrige el bloqueante primario y vuelve a ejecutar el mismo comando.';
116
+ primary.code === 'EVIDENCE_STALE' || primary.code === 'EVIDENCE_BRANCH_MISMATCH'
117
+ ? buildStageValidateCommand(stage)
118
+ : BLOCK_NEXT_ACTION_BY_CODE[primary.code]
119
+ ?? 'Corrige el bloqueante primario y vuelve a ejecutar el mismo comando.';
115
120
  process.stdout.write(
116
121
  `[pumuki][block-summary] primary=${primary.code} severity=${primary.severity.toUpperCase()} rule=${primary.ruleId}\n`
117
122
  );
@@ -55,7 +55,7 @@ type LifecycleAuditDependencies = {
55
55
  };
56
56
 
57
57
  const POLICY_RECONCILE_HINT =
58
- 'If .pumuki/policy-as-code.json signatures drift after a pumuki upgrade, run: pumuki policy reconcile --apply';
58
+ 'If .pumuki/policy-as-code.json signatures drift after a pumuki upgrade, run: pumuki policy reconcile --strict --apply --json';
59
59
 
60
60
  const countUntrackedMatchingExtensions = (
61
61
  git: Pick<IGitService, 'resolveRepoRoot' | 'runGit'>,
@@ -1650,7 +1650,7 @@ const buildPreWriteValidateCommand = (params: {
1650
1650
  const buildPreWritePolicyReconcileCommand = (repoRoot?: string): string =>
1651
1651
  `${buildPinnedPumukiNpxCommand({
1652
1652
  repoRoot,
1653
- executableAndArgs: 'pumuki policy reconcile --strict --json',
1653
+ executableAndArgs: 'pumuki policy reconcile --strict --apply --json',
1654
1654
  })} && ${buildPreWriteValidateCommand({ repoRoot, stage: 'PRE_WRITE' })}`;
1655
1655
 
1656
1656
  type PreWriteValidationEnvelope = {
@@ -1838,11 +1838,13 @@ const PRE_WRITE_HINTS_BY_CODE: Readonly<Record<string, string>> = {
1838
1838
  EVIDENCE_RULES_COVERAGE_MISSING: 'Ejecuta auditoría completa para recalcular rules_coverage.',
1839
1839
  EVIDENCE_RULES_COVERAGE_INCOMPLETE: 'Asegura unevaluated=0 y coverage_ratio=1.',
1840
1840
  EVIDENCE_ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES:
1841
- 'No hay active_rule_ids para plataforma de código detectada. Reconcilia policy/skills en modo estricto y revalida PRE_WRITE.',
1841
+ 'No hay active_rule_ids para plataforma de código detectada. Reconcilia policy/skills en modo estricto con apply y revalida PRE_WRITE.',
1842
1842
  EVIDENCE_PLATFORM_CRITICAL_SKILLS_RULES_MISSING:
1843
- 'Reconcilia policy/skills en modo estricto y materializa reglas críticas (p.ej. skills.ios.critical-test-quality).',
1843
+ 'Reconcilia policy/skills en modo estricto con apply y materializa reglas críticas (p.ej. skills.ios.critical-test-quality).',
1844
1844
  EVIDENCE_CROSS_PLATFORM_CRITICAL_ENFORCEMENT_INCOMPLETE:
1845
- 'Reconcilia policy/skills en modo estricto para completar enforcement crítico transversal.',
1845
+ 'Reconcilia policy/skills en modo estricto con apply para completar enforcement crítico transversal.',
1846
+ TDD_BDD_BASELINE_BLOCKED:
1847
+ 'Corrige el baseline TDD/BDD roto y regenera la evidencia antes de continuar.',
1846
1848
  EVIDENCE_SKILLS_CONTRACT_INCOMPLETE:
1847
1849
  'Completa contrato skills/policy para el stage actual y vuelve a validar.',
1848
1850
  EVIDENCE_PREWRITE_WORKTREE_OVER_LIMIT:
@@ -834,7 +834,7 @@ const buildPolicySignatureRemediation = (
834
834
  const mismatch = Object.values(policyValidation.stages).some(
835
835
  (stage) => stage.validationCode === 'POLICY_AS_CODE_SIGNATURE_MISMATCH'
836
836
  );
837
- return mismatch ? 'pumuki policy reconcile --apply' : undefined;
837
+ return mismatch ? 'pumuki policy reconcile --strict --apply --json' : undefined;
838
838
  };
839
839
 
840
840
  export const runLifecycleDoctor = (params?: {
@@ -91,6 +91,16 @@ const resolveBlockedAction = (
91
91
  message: 'La política efectiva todavía no es estricta en todos los stages requeridos.',
92
92
  };
93
93
  }
94
+ if (snapshot.attention_codes.includes('POLICY_HASH_DIVERGENCE')) {
95
+ return {
96
+ stage,
97
+ phase: 'RED',
98
+ action: 'ask',
99
+ confidence_pct: 58,
100
+ ...resolveGovernanceCatalogAction({ code: 'POLICY_HASH_DIVERGENCE', stage }),
101
+ message: 'Los hashes de policy difieren entre stages; reconcilia y aplica el contrato canónico.',
102
+ };
103
+ }
94
104
  if (!snapshot.contract_surface.skills_lock_json || !snapshot.contract_surface.skills_sources_json) {
95
105
  return {
96
106
  stage,
@@ -263,6 +263,12 @@ export const readGovernanceObservationSnapshot = (params: {
263
263
  if (!policyValidation.stages.CI.strict) {
264
264
  attention.push('POLICY_CI_NOT_STRICT');
265
265
  }
266
+ const policyHashes = new Set(
267
+ Object.values(policyValidation.stages).map((stage) => stage.hash)
268
+ );
269
+ if (policyHashes.size > 1) {
270
+ attention.push('POLICY_HASH_DIVERGENCE');
271
+ }
266
272
  if (onProtected) {
267
273
  attention.push('GITFLOW_PROTECTED_BRANCH_CONTEXT');
268
274
  }
@@ -94,7 +94,7 @@ const nextActionFromViolation = (
94
94
  command:
95
95
  `${buildPinnedPumukiNpxCommand({
96
96
  repoRoot,
97
- executableAndArgs: 'pumuki policy reconcile --strict --json',
97
+ executableAndArgs: 'pumuki policy reconcile --strict --apply --json',
98
98
  })} && ${buildPinnedPumukiNpxCommand({
99
99
  repoRoot,
100
100
  executableAndArgs: 'pumuki sdd validate --stage=PRE_WRITE --json',
@@ -121,7 +121,7 @@ const nextActionFromViolation = (
121
121
  command:
122
122
  `${buildPinnedPumukiNpxCommand({
123
123
  repoRoot,
124
- executableAndArgs: 'pumuki policy reconcile --strict --json',
124
+ executableAndArgs: 'pumuki policy reconcile --strict --apply --json',
125
125
  })} && ${buildPinnedPumukiNpxCommand({
126
126
  repoRoot,
127
127
  executableAndArgs: 'pumuki sdd validate --stage=PRE_WRITE --json',
@@ -3,19 +3,30 @@ import {
3
3
  type GovernanceCatalogNextAction,
4
4
  } from '../gate/governanceActionCatalog';
5
5
  import { evaluateAiGate, type AiGateStage, type AiGateViolation } from '../gate/evaluateAiGate';
6
+ import { readEvidenceResult } from '../evidence/readEvidence';
6
7
  import { collectWorktreeAtomicSlices } from '../git/worktreeAtomicSlices';
7
8
  import { readLifecyclePolicyValidationSnapshot } from '../lifecycle/policyValidationSnapshot';
8
9
  import { resolveLearningContextExperimentalFeature } from '../policy/experimentalFeatures';
9
10
  import { resolvePreWriteEnforcement } from '../policy/preWriteEnforcement';
10
11
  import { readSddLearningContext, type SddLearningContext } from '../sdd/learningInsights';
11
12
 
13
+ const resolveTddStatus = (
14
+ repoRoot: string
15
+ ): 'skipped' | 'passed' | 'advisory' | 'blocked' | 'waived' | null => {
16
+ const evidenceResult = readEvidenceResult(repoRoot);
17
+ if (evidenceResult.kind !== 'valid') {
18
+ return null;
19
+ }
20
+ return evidenceResult.evidence.snapshot.tdd_bdd?.status ?? null;
21
+ };
22
+
12
23
  const ACTIONABLE_HINTS_BY_CODE: Readonly<Record<string, string>> = {
13
24
  EVIDENCE_MISSING:
14
25
  'Ejecuta una auditoría (1/2/3/4 u opciones de motor 11–14) para regenerar .ai_evidence.json.',
15
26
  EVIDENCE_INVALID: 'Regenera .ai_evidence.json desde una opción de auditoría.',
16
27
  EVIDENCE_INTEGRITY_MISSING: 'Refresca evidencia para regenerar metadatos de integridad.',
17
28
  EVIDENCE_ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES:
18
- 'No hay active_rule_ids para plataforma de código detectada. Ejecuta reconcile --strict y revalida PRE_WRITE.',
29
+ 'No hay active_rule_ids para plataforma de código detectada. Ejecuta reconcile --strict --apply y revalida PRE_WRITE.',
19
30
  EVIDENCE_STALE: 'Refresca evidencia antes de continuar con commit/push.',
20
31
  EVIDENCE_TIMESTAMP_INVALID: 'Regenera evidencia para obtener un timestamp válido.',
21
32
  EVIDENCE_GATE_BLOCKED: 'Corrige primero las violaciones bloqueantes y vuelve a auditar.',
@@ -32,9 +43,11 @@ const ACTIONABLE_HINTS_BY_CODE: Readonly<Record<string, string>> = {
32
43
  EVIDENCE_PLATFORM_SKILLS_BUNDLES_MISSING:
33
44
  'Carga los bundles de skills requeridos por plataforma detectada y regenera evidencia.',
34
45
  EVIDENCE_PLATFORM_CRITICAL_SKILLS_RULES_MISSING:
35
- 'Ejecuta `pumuki policy reconcile --strict --json`, materializa reglas críticas (p.ej. skills.ios.critical-test-quality) y revalida PRE_WRITE.',
46
+ 'Ejecuta `pumuki policy reconcile --strict --apply --json`, materializa reglas críticas (p.ej. skills.ios.critical-test-quality) y revalida PRE_WRITE.',
36
47
  EVIDENCE_CROSS_PLATFORM_CRITICAL_ENFORCEMENT_INCOMPLETE:
37
- 'Reconcilia policy/skills en modo estricto para enforcement crítico transversal y vuelve a validar PRE_WRITE.',
48
+ 'Reconcilia policy/skills en modo estricto con apply para enforcement crítico transversal y vuelve a validar PRE_WRITE.',
49
+ TDD_BDD_BASELINE_BLOCKED:
50
+ 'Corrige el baseline TDD/BDD roto y regenera la evidencia antes de continuar.',
38
51
  EVIDENCE_SKILLS_CONTRACT_INCOMPLETE:
39
52
  'Completa el contrato skills/policy para el stage solicitado y vuelve a validar.',
40
53
  EVIDENCE_PREWRITE_WORKTREE_OVER_LIMIT:
@@ -161,7 +174,7 @@ export type EnterprisePreFlightCheckResult = {
161
174
  hints: ReadonlyArray<string>;
162
175
  learning_context: SddLearningContext | null;
163
176
  ast_analysis: null;
164
- tdd_status: null;
177
+ tdd_status: 'skipped' | 'passed' | 'advisory' | 'blocked' | 'waived' | null;
165
178
  };
166
179
  };
167
180
 
@@ -246,7 +259,7 @@ export const runEnterprisePreFlightCheck = (params: {
246
259
  hints,
247
260
  learning_context: learningContext,
248
261
  ast_analysis: null,
249
- tdd_status: null,
262
+ tdd_status: resolveTddStatus(params.repoRoot),
250
263
  },
251
264
  };
252
265
  };
@@ -1,55 +1,9 @@
1
- export type SkillsEnforcementMode = 'advisory' | 'strict';
2
-
3
1
  export type SkillsEnforcementResolution = {
4
- mode: SkillsEnforcementMode;
5
- source: 'default' | 'env';
6
2
  blocking: boolean;
7
3
  };
8
4
 
9
- const SKILLS_ENFORCEMENT_ENV = 'PUMUKI_SKILLS_ENFORCEMENT';
10
-
11
- const toSkillsEnforcementMode = (
12
- value: string | undefined
13
- ): SkillsEnforcementMode | null => {
14
- if (typeof value !== 'string') {
15
- return null;
16
- }
17
- const normalized = value.trim().toLowerCase();
18
- if (
19
- normalized === 'strict'
20
- || normalized === '1'
21
- || normalized === 'true'
22
- || normalized === 'yes'
23
- || normalized === 'on'
24
- ) {
25
- return 'strict';
26
- }
27
- if (
28
- normalized === 'advisory'
29
- || normalized === 'warn'
30
- || normalized === 'warning'
31
- || normalized === '0'
32
- || normalized === 'false'
33
- || normalized === 'no'
34
- || normalized === 'off'
35
- ) {
36
- return 'advisory';
37
- }
38
- return null;
39
- };
40
-
41
5
  export const resolveSkillsEnforcement = (): SkillsEnforcementResolution => {
42
- const modeFromEnv = toSkillsEnforcementMode(process.env[SKILLS_ENFORCEMENT_ENV]);
43
- if (modeFromEnv) {
44
- return {
45
- mode: modeFromEnv,
46
- source: 'env',
47
- blocking: modeFromEnv === 'strict',
48
- };
49
- }
50
6
  return {
51
- mode: 'strict',
52
- source: 'default',
53
7
  blocking: true,
54
8
  };
55
9
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.132",
3
+ "version": "6.3.134",
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": {