pumuki 6.3.267 → 6.3.269

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.
Files changed (29) hide show
  1. package/VERSION +1 -1
  2. package/core/gate/evaluateGate.test.ts +13 -5
  3. package/core/gate/evaluateGate.ts +4 -4
  4. package/docs/governance/CONTRIBUTING.md +1 -1
  5. package/integrations/config/coreSkillsLock.ts +2 -1
  6. package/integrations/config/skillsDetectorRegistry.ts +18 -0
  7. package/integrations/config/skillsRuleClassification.ts +147 -0
  8. package/integrations/git/astIntelligenceDualValidation.ts +1 -0
  9. package/integrations/git/runPlatformGate.ts +125 -33
  10. package/integrations/git/stageRunners.ts +4 -10
  11. package/integrations/lifecycle/cli.ts +94 -4
  12. package/integrations/lifecycle/doctor.ts +33 -0
  13. package/integrations/lifecycle/install.ts +26 -4
  14. package/integrations/lifecycle/notificationStatus.ts +54 -0
  15. package/integrations/lifecycle/preWriteAutomation.ts +35 -5
  16. package/integrations/lifecycle/preWriteLease.ts +271 -0
  17. package/integrations/lifecycle/status.ts +40 -2
  18. package/integrations/lifecycle/watch.ts +2 -1
  19. package/integrations/mcp/evidenceStdioServer.cli.ts +2 -2
  20. package/integrations/policy/skillsEnforcement.ts +3 -14
  21. package/integrations/policy/tddBddEnforcement.ts +3 -2
  22. package/package.json +2 -1
  23. package/scripts/classify-skills-rules.ts +32 -0
  24. package/scripts/framework-menu-system-notifications-cause.ts +11 -1
  25. package/scripts/package-install-smoke-consumer-git-payload-lib.ts +2 -2
  26. package/scripts/package-install-smoke-consumer-git-repo-lib.ts +1 -1
  27. package/scripts/package-install-smoke-execution-lib.ts +3 -1
  28. package/scripts/package-install-smoke-gate-lib.ts +3 -0
  29. package/scripts/package-install-smoke-lifecycle-lib.ts +29 -0
package/VERSION CHANGED
@@ -1 +1 @@
1
- v6.3.238
1
+ v6.3.269
@@ -25,12 +25,14 @@ test('evaluateGate devuelve WARN cuando hay warnings sin bloqueantes', () => {
25
25
  severity: 'WARN',
26
26
  code: 'RULE_WARN',
27
27
  message: 'Warn finding',
28
+ blocking: false,
28
29
  },
29
30
  {
30
31
  ruleId: 'rule.info',
31
32
  severity: 'INFO',
32
33
  code: 'RULE_INFO',
33
34
  message: 'Info finding',
35
+ blocking: false,
34
36
  },
35
37
  ];
36
38
 
@@ -42,7 +44,7 @@ test('evaluateGate devuelve WARN cuando hay warnings sin bloqueantes', () => {
42
44
  assert.equal(result.warnings[0]?.ruleId, 'rule.warn');
43
45
  });
44
46
 
45
- test('evaluateGate devuelve BLOCK cuando existe al menos un finding bloqueante', () => {
47
+ test('evaluateGate devuelve BLOCK para cualquier finding runtime salvo blocking=false', () => {
46
48
  const findings: Finding[] = [
47
49
  {
48
50
  ruleId: 'rule.error',
@@ -62,18 +64,24 @@ test('evaluateGate devuelve BLOCK cuando existe al menos un finding bloqueante',
62
64
  code: 'RULE_WARN',
63
65
  message: 'Warn finding',
64
66
  },
67
+ {
68
+ ruleId: 'rule.info.advisory',
69
+ severity: 'INFO',
70
+ code: 'RULE_INFO_ADVISORY',
71
+ message: 'Info advisory finding',
72
+ blocking: false,
73
+ },
65
74
  ];
66
75
 
67
76
  const result = evaluateGate(findings, defaultPolicy);
68
77
 
69
78
  assert.equal(result.outcome, 'BLOCK');
70
- assert.equal(result.blocking.length, 2);
79
+ assert.equal(result.blocking.length, 3);
71
80
  assert.deepEqual(
72
81
  result.blocking.map((finding) => finding.ruleId),
73
- ['rule.error', 'rule.critical']
82
+ ['rule.error', 'rule.critical', 'rule.warn']
74
83
  );
75
- assert.equal(result.warnings.length, 1);
76
- assert.equal(result.warnings[0]?.ruleId, 'rule.warn');
84
+ assert.deepEqual(result.warnings, []);
77
85
  });
78
86
 
79
87
  test('evaluateGate bloquea cualquier severidad cuando la policy zero-violations usa INFO', () => {
@@ -3,16 +3,16 @@ import type { GateOutcome } from './GateOutcome';
3
3
  import type { GatePolicy } from './GatePolicy';
4
4
  import { isSeverityAtLeast } from '../rules/Severity';
5
5
 
6
+ const isBlockingFinding = (finding: Finding): boolean => finding.blocking !== false;
7
+
6
8
  export function evaluateGate(
7
9
  findings: Finding[],
8
10
  policy: GatePolicy
9
11
  ): { outcome: GateOutcome; blocking: Finding[]; warnings: Finding[] } {
10
- const blocking = findings.filter((finding) =>
11
- isSeverityAtLeast(finding.severity, policy.blockOnOrAbove)
12
- );
12
+ const blocking = findings.filter(isBlockingFinding);
13
13
  const warnings = findings.filter(
14
14
  (finding) =>
15
- !isSeverityAtLeast(finding.severity, policy.blockOnOrAbove) &&
15
+ !isBlockingFinding(finding) &&
16
16
  isSeverityAtLeast(finding.severity, policy.warnOnOrAbove)
17
17
  );
18
18
 
@@ -89,4 +89,4 @@ npm run test:heuristics
89
89
  ## Notes for automation contributors
90
90
 
91
91
  This repository still contains legacy hooks/scripts for compatibility.
92
- For framework refactor commits, maintainers may use `--no-verify` to avoid unrelated legacy hook failures.
92
+ Framework refactor commits must run through the managed hooks; bypassing verification is not an accepted maintainer workflow.
@@ -20,7 +20,8 @@ export const resolveCoreSkillsLockForPackageRoot = (
20
20
  if (compiledLock.bundles.length > 0) {
21
21
  return compiledLock;
22
22
  }
23
- } catch {
23
+ } catch (error) {
24
+ void error;
24
25
  }
25
26
 
26
27
  return loadSkillsLock(packageRoot);
@@ -427,6 +427,16 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
427
427
  'skills.frontend.no-god-classes': heuristicDetector('typescript.god-class', [
428
428
  'heuristics.ts.god-class-large-class.ast',
429
429
  ]),
430
+ 'skills.ios.guideline.ios.verificar-que-no-viole-solid-srp-ocp-lsp-isp-dip': heuristicDetector(
431
+ 'ios.solid',
432
+ [
433
+ 'heuristics.ios.solid.srp.presentation-mixed-responsibilities.ast',
434
+ 'heuristics.ios.solid.ocp.discriminator-switch.ast',
435
+ 'heuristics.ios.solid.dip.concrete-framework-dependency.ast',
436
+ 'heuristics.ios.solid.isp.fat-protocol-dependency.ast',
437
+ 'heuristics.ios.solid.lsp.narrowed-precondition.ast',
438
+ ]
439
+ ),
430
440
  'skills.android.no-thread-sleep': heuristicDetector('android.thread-sleep', [
431
441
  'heuristics.android.thread-sleep.ast',
432
442
  ]),
@@ -505,6 +515,14 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
505
515
  'heuristics.android.solid.isp.fat-interface-dependency.ast',
506
516
  'heuristics.android.solid.lsp.narrowed-precondition.ast',
507
517
  ]),
518
+ 'skills.android.guideline.android.verificar-que-no-viole-solid-srp-ocp-lsp-isp-dip':
519
+ heuristicDetector('android.solid', [
520
+ 'heuristics.android.solid.srp.presentation-mixed-responsibilities.ast',
521
+ 'heuristics.android.solid.ocp.discriminator-branching.ast',
522
+ 'heuristics.android.solid.dip.concrete-framework-dependency.ast',
523
+ 'heuristics.android.solid.isp.fat-interface-dependency.ast',
524
+ 'heuristics.android.solid.lsp.narrowed-precondition.ast',
525
+ ]),
508
526
  };
509
527
 
510
528
  export const listSkillsDetectorBindings = (): ReadonlyArray<{
@@ -0,0 +1,147 @@
1
+ import type { SkillsCompiledRule, SkillsLockV1 } from './skillsLock';
2
+ import { resolveMappedHeuristicRuleIdsForCompiledRule } from './skillsDetectorRegistry';
3
+
4
+ export type SkillsRuleClassificationStatus =
5
+ | 'AST_IMPLEMENTED'
6
+ | 'IMPLEMENTABLE_AHORA'
7
+ | 'REQUIERE_ESTUDIO'
8
+ | 'NO_ES_REGLA_DE_CODIGO';
9
+
10
+ export type ClassifiedSkillsRule = {
11
+ ruleId: string;
12
+ platform: SkillsCompiledRule['platform'];
13
+ sourceSkill: string;
14
+ sourcePath: string;
15
+ evaluationMode: 'AUTO' | 'DECLARATIVE';
16
+ severity: SkillsCompiledRule['severity'];
17
+ status: SkillsRuleClassificationStatus;
18
+ reason: string;
19
+ astNodeIds: ReadonlyArray<string>;
20
+ };
21
+
22
+ export type SkillsRuleClassificationSummary = {
23
+ total: number;
24
+ byStatus: Record<SkillsRuleClassificationStatus, number>;
25
+ byPlatform: Record<string, number>;
26
+ rules: ReadonlyArray<ClassifiedSkillsRule>;
27
+ };
28
+
29
+ const emptyStatusCounts = (): Record<SkillsRuleClassificationStatus, number> => ({
30
+ AST_IMPLEMENTED: 0,
31
+ IMPLEMENTABLE_AHORA: 0,
32
+ REQUIERE_ESTUDIO: 0,
33
+ NO_ES_REGLA_DE_CODIGO: 0,
34
+ });
35
+
36
+ const normalizeText = (value: string): string =>
37
+ value
38
+ .normalize('NFD')
39
+ .replace(/\p{Diacritic}/gu, '')
40
+ .toLowerCase();
41
+
42
+ const NON_CODE_RULE_PATTERNS: ReadonlyArray<RegExp> = [
43
+ /\bactua como\b/,
44
+ /\bsiempre responder\b/,
45
+ /\bflujo bdd\b/,
46
+ /\bfeature files\b/,
47
+ /\bspecs\b.*\bimplementacion\b/,
48
+ /\bcomprobar que compile\b/,
49
+ /\banalizar estructura existente\b/,
50
+ /\bandroid profiler\b/,
51
+ /\bxcode instruments\b/,
52
+ /\bdocumentation\b/,
53
+ /\bdocumentacion\b/,
54
+ /\breadme\b/,
55
+ /\bci\/cd\b/,
56
+ ];
57
+
58
+ const isNonCodeRule = (rule: SkillsCompiledRule): boolean => {
59
+ const haystack = normalizeText(`${rule.id} ${rule.description} ${rule.sourceSkill}`);
60
+ return NON_CODE_RULE_PATTERNS.some((pattern) => pattern.test(haystack));
61
+ };
62
+
63
+ const classifyRule = (rule: SkillsCompiledRule): ClassifiedSkillsRule => {
64
+ const evaluationMode = rule.evaluationMode ?? 'AUTO';
65
+ const astNodeIds = resolveMappedHeuristicRuleIdsForCompiledRule(rule);
66
+
67
+ if (astNodeIds.length > 0) {
68
+ return {
69
+ ruleId: rule.id,
70
+ platform: rule.platform,
71
+ sourceSkill: rule.sourceSkill,
72
+ sourcePath: rule.sourcePath,
73
+ evaluationMode,
74
+ severity: rule.severity,
75
+ status: 'AST_IMPLEMENTED',
76
+ reason: `Mapped to AST/nodal detector(s): ${astNodeIds.join(', ')}`,
77
+ astNodeIds,
78
+ };
79
+ }
80
+
81
+ if (evaluationMode === 'AUTO') {
82
+ return {
83
+ ruleId: rule.id,
84
+ platform: rule.platform,
85
+ sourceSkill: rule.sourceSkill,
86
+ sourcePath: rule.sourcePath,
87
+ evaluationMode,
88
+ severity: rule.severity,
89
+ status: 'IMPLEMENTABLE_AHORA',
90
+ reason: 'AUTO rule without AST/nodal detector binding; implement detector mapping.',
91
+ astNodeIds,
92
+ };
93
+ }
94
+
95
+ if (isNonCodeRule(rule)) {
96
+ return {
97
+ ruleId: rule.id,
98
+ platform: rule.platform,
99
+ sourceSkill: rule.sourceSkill,
100
+ sourcePath: rule.sourcePath,
101
+ evaluationMode,
102
+ severity: rule.severity,
103
+ status: 'NO_ES_REGLA_DE_CODIGO',
104
+ reason: 'Process, documentation, validation workflow, or operating rule; keep outside code AST runtime.',
105
+ astNodeIds,
106
+ };
107
+ }
108
+
109
+ return {
110
+ ruleId: rule.id,
111
+ platform: rule.platform,
112
+ sourceSkill: rule.sourceSkill,
113
+ sourcePath: rule.sourcePath,
114
+ evaluationMode,
115
+ severity: rule.severity,
116
+ status: 'REQUIERE_ESTUDIO',
117
+ reason: 'Declarative skill rule without AST/nodal detector. Study exact code pattern, AST node, and closure criterion.',
118
+ astNodeIds,
119
+ };
120
+ };
121
+
122
+ export const classifySkillsRules = (
123
+ lock: SkillsLockV1
124
+ ): SkillsRuleClassificationSummary => {
125
+ const rules = lock.bundles
126
+ .flatMap((bundle) => bundle.rules)
127
+ .filter((rule) => rule.id.startsWith('skills.'))
128
+ .map(classifyRule)
129
+ .sort((left, right) => left.ruleId.localeCompare(right.ruleId));
130
+
131
+ const byStatus = emptyStatusCounts();
132
+ const byPlatform: Record<string, number> = {};
133
+
134
+ for (const rule of rules) {
135
+ byStatus[rule.status] += 1;
136
+ byPlatform[rule.platform] = (byPlatform[rule.platform] ?? 0) + 1;
137
+ }
138
+
139
+ return {
140
+ total: rules.length,
141
+ byStatus,
142
+ byPlatform: Object.fromEntries(
143
+ Object.entries(byPlatform).sort(([left], [right]) => left.localeCompare(right))
144
+ ),
145
+ rules,
146
+ };
147
+ };
@@ -188,6 +188,7 @@ const toDualValidationFinding = (params: {
188
188
  return {
189
189
  ruleId: 'governance.ast-intelligence.dual-validation.shadow',
190
190
  severity: 'INFO',
191
+ blocking: false,
191
192
  code: 'AST_INTELLIGENCE_DUAL_VALIDATION_SHADOW',
192
193
  message:
193
194
  `AST Intelligence dual validation shadow at ${params.stage}: ` +
@@ -39,6 +39,12 @@ import {
39
39
  filterFactsByPathPrefixes,
40
40
  resolveGateScopePathPrefixesFromEnv,
41
41
  } from './filterFactsByPathPrefixes';
42
+ import {
43
+ readPreWriteLeaseStatus,
44
+ toPreWriteEnforcementGapFinding,
45
+ } from '../lifecycle/preWriteLease';
46
+ import { evaluateContextGate, type ContextGateResult } from '../context/contextGate';
47
+ import { emitGateBlockedNotification } from '../notifications/emitAuditSummaryNotification';
42
48
 
43
49
  export type OperationalMemoryShadowRecommendation = {
44
50
  recommendedOutcome: 'ALLOW' | 'WARN' | 'BLOCK';
@@ -73,6 +79,8 @@ export type GateDependencies = {
73
79
  stage: 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
74
80
  branch: string | null;
75
81
  }) => GateWaiverResult;
82
+ evaluateContextGate: typeof evaluateContextGate;
83
+ notifyGateBlocked: typeof emitGateBlockedNotification;
76
84
  };
77
85
 
78
86
  const defaultServices: GateServices = {
@@ -105,20 +113,8 @@ const normalizeScopedRuleEngineFindings = (params: {
105
113
  findings: ReadonlyArray<Finding>;
106
114
  scope: GateScope;
107
115
  }): ReadonlyArray<Finding> => {
108
- if (params.scope.kind !== 'staged' && params.scope.kind !== 'range') {
109
- return params.findings;
110
- }
111
-
112
- return params.findings.filter((finding) => {
113
- if (
114
- !finding.filePath ||
115
- hasActionableFindingLocation(finding) ||
116
- (!finding.ruleId.startsWith('skills.') && !finding.ruleId.startsWith('heuristics.'))
117
- ) {
118
- return true;
119
- }
120
- return false;
121
- });
116
+ void params.scope;
117
+ return params.findings;
122
118
  };
123
119
 
124
120
  const buildDefaultMemoryShadowRecommendation = (params: {
@@ -195,6 +191,8 @@ const defaultDependencies: GateDependencies = {
195
191
  stage,
196
192
  branch,
197
193
  }),
194
+ evaluateContextGate,
195
+ notifyGateBlocked: emitGateBlockedNotification,
198
196
  };
199
197
 
200
198
  const readSymbolicBranchRef = (git: IGitService, repoRoot: string): string | null => {
@@ -283,6 +281,34 @@ const toSkillsUnsupportedAutoRulesBlockingFinding = (params: {
283
281
  };
284
282
  };
285
283
 
284
+ const toSkillsDeclarativeRulesClassificationFinding = (params: {
285
+ stage: 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
286
+ declarativeRuleIds: ReadonlyArray<string>;
287
+ facts: ReadonlyArray<Fact>;
288
+ }): Finding | undefined => {
289
+ if (params.declarativeRuleIds.length === 0) {
290
+ return undefined;
291
+ }
292
+ if (collectObservedCodePathsFromFacts(params.facts).length === 0) {
293
+ return undefined;
294
+ }
295
+
296
+ const sampleRuleIds = [...params.declarativeRuleIds].sort().slice(0, 12).join(', ');
297
+
298
+ return {
299
+ ruleId: 'governance.skills.declarative-rules.classification-required',
300
+ severity: 'ERROR',
301
+ code: 'SKILLS_DECLARATIVE_RULES_CLASSIFICATION_REQUIRED',
302
+ message:
303
+ `Skills declarative rules require AST/nodal classification at ${params.stage}: ` +
304
+ `total=${params.declarativeRuleIds.length} sample_rule_ids=[${sampleRuleIds}]. ` +
305
+ 'Classify every rule as IMPLEMENTABLE_AHORA, REQUIERE_ESTUDIO, or NO_ES_REGLA_DE_CODIGO; code skills cannot remain as hidden declarative/advisory rules.',
306
+ filePath: 'skills.lock.json',
307
+ matchedBy: 'SkillsDeclarativeRulesClassificationGuard',
308
+ source: 'skills-declarative-rules-classification',
309
+ };
310
+ };
311
+
286
312
  const PLATFORM_SKILLS_RULE_PREFIXES: Record<
287
313
  'ios' | 'android' | 'backend' | 'frontend',
288
314
  string
@@ -801,9 +827,18 @@ const shouldBlockFromFinding = (finding: Finding | undefined): boolean => {
801
827
  if (!finding) {
802
828
  return false;
803
829
  }
804
- return finding.severity === 'ERROR' || finding.severity === 'CRITICAL';
830
+ return finding.blocking !== false;
805
831
  };
806
832
 
833
+ const resolvePrimaryBlockingFinding = (
834
+ findings: ReadonlyArray<Finding>
835
+ ): Finding | undefined => (
836
+ findings.find((finding) => shouldBlockFromFinding(finding) && finding.severity === 'CRITICAL') ??
837
+ findings.find((finding) => shouldBlockFromFinding(finding) && finding.severity === 'ERROR') ??
838
+ findings.find((finding) => shouldBlockFromFinding(finding)) ??
839
+ findings[0]
840
+ );
841
+
807
842
  const applySkillsFindingEnforcement = (
808
843
  finding: Finding | undefined
809
844
  ): Finding | undefined => {
@@ -817,6 +852,7 @@ const applySkillsFindingEnforcement = (
817
852
  return {
818
853
  ...finding,
819
854
  severity: 'WARN',
855
+ blocking: false,
820
856
  };
821
857
  };
822
858
 
@@ -825,21 +861,9 @@ const toSoftPreCommitSkillsFinding = (params: {
825
861
  enabled: boolean;
826
862
  observedCodePaths: ReadonlyArray<string>;
827
863
  }): Finding | undefined => {
828
- if (!params.finding) {
829
- return undefined;
830
- }
831
- if (!params.enabled || !shouldBlockFromFinding(params.finding)) {
832
- return params.finding;
833
- }
834
- return {
835
- ...params.finding,
836
- severity: 'WARN',
837
- code: `${params.finding.code}_SOFT_PRECOMMIT`,
838
- message:
839
- `${params.finding.message} ` +
840
- `Soft-enforced at PRE_COMMIT for low-risk scope (observed_code_paths=${params.observedCodePaths.length}). ` +
841
- 'Strict enforcement remains active at PRE_PUSH/CI.',
842
- };
864
+ void params.enabled;
865
+ void params.observedCodePaths;
866
+ return params.finding;
843
867
  };
844
868
 
845
869
  export async function runPlatformGate(params: {
@@ -862,6 +886,11 @@ export async function runPlatformGate(params: {
862
886
  const repoRoot = git.resolveRepoRoot();
863
887
  const auditMode = params.auditMode ?? 'gate';
864
888
  const shouldShortCircuitSdd = params.sddShortCircuit ?? false;
889
+ const contextGate: ContextGateResult = dependencies.evaluateContextGate({
890
+ repoRoot,
891
+ stage: params.policy.stage,
892
+ });
893
+ const contextBlockingFinding = contextGate.finding;
865
894
  let sddDecision:
866
895
  | Pick<SddDecision, 'allowed' | 'code' | 'message'>
867
896
  | undefined;
@@ -939,6 +968,18 @@ export async function runPlatformGate(params: {
939
968
  : facts;
940
969
  const filesScanned = countScannedFilesFromFacts(factsForPlatformEvaluation);
941
970
  const observedCodePaths = collectObservedCodePathsFromFacts(facts);
971
+ const preWriteLeaseFinding =
972
+ params.policy.stage === 'PRE_COMMIT' ||
973
+ params.policy.stage === 'PRE_PUSH' ||
974
+ params.policy.stage === 'CI'
975
+ ? toPreWriteEnforcementGapFinding({
976
+ stage: params.policy.stage,
977
+ status: readPreWriteLeaseStatus({
978
+ repoRoot,
979
+ git,
980
+ }),
981
+ })
982
+ : undefined;
942
983
 
943
984
  const platformEvaluation = dependencies.evaluatePlatformGateFindings({
944
985
  facts: factsForPlatformEvaluation,
@@ -998,6 +1039,16 @@ export async function runPlatformGate(params: {
998
1039
  unsupportedAutoRuleIds: skillsRuleSet.unsupportedAutoRuleIds ?? [],
999
1040
  })
1000
1041
  : undefined;
1042
+ const declarativeRulesClassificationFinding =
1043
+ params.policy.stage === 'PRE_COMMIT' ||
1044
+ params.policy.stage === 'PRE_PUSH' ||
1045
+ params.policy.stage === 'CI'
1046
+ ? toSkillsDeclarativeRulesClassificationFinding({
1047
+ stage: params.policy.stage,
1048
+ declarativeRuleIds: skillsRuleSet.registryCoverage?.declarativeRuleIds ?? [],
1049
+ facts: factsForPlatformEvaluation,
1050
+ })
1051
+ : undefined;
1001
1052
  const effectiveUnsupportedSkillsMappingInput = applySkillsFindingEnforcement(
1002
1053
  unsupportedSkillsMappingFinding
1003
1054
  );
@@ -1184,12 +1235,12 @@ export async function runPlatformGate(params: {
1184
1235
  ? tddBddEvaluation.snapshot
1185
1236
  : undefined;
1186
1237
  const hasTddBddBlockingFinding = tddBddEvaluation.findings.some(
1187
- (finding) => finding.severity === 'ERROR' || finding.severity === 'CRITICAL'
1238
+ (finding) => shouldBlockFromFinding(finding)
1188
1239
  );
1189
1240
  const hasNativeBlockingFinding = findings.some(
1190
- (finding) => finding.severity === 'ERROR' || finding.severity === 'CRITICAL'
1241
+ (finding) => shouldBlockFromFinding(finding)
1191
1242
  );
1192
- const preCommitSoftSkillsEnabled = process.env.PUMUKI_PRE_COMMIT_SOFT_SKILLS !== '0';
1243
+ const preCommitSoftSkillsEnabled = process.env.PUMUKI_PRE_COMMIT_SOFT_SKILLS === '1';
1193
1244
  const lowRiskPreCommitWindow = observedCodePaths.length > 0 && observedCodePaths.length <= 3;
1194
1245
  const shouldSoftEnforceSkillsFindings =
1195
1246
  params.policy.stage === 'PRE_COMMIT'
@@ -1199,6 +1250,7 @@ export async function runPlatformGate(params: {
1199
1250
  && !degradedModeBlocks
1200
1251
  && !shouldBlockFromFinding(policyAsCodeBlockingFinding)
1201
1252
  && !shouldBlockFromFinding(coverageBlockingFinding)
1253
+ && !shouldBlockFromFinding(declarativeRulesClassificationFinding)
1202
1254
  && !shouldBlockFromFinding(activeRulesEmptyForCodeChangesFinding)
1203
1255
  && !shouldBlockFromFinding(effectiveIosTestsQualityFinding)
1204
1256
  && !shouldBlockFromFinding(astIntelligenceDualFinding)
@@ -1226,8 +1278,10 @@ export async function runPlatformGate(params: {
1226
1278
  });
1227
1279
  const effectiveFindings = sddBlockingFinding
1228
1280
  ? [
1281
+ ...(contextBlockingFinding ? [contextBlockingFinding] : []),
1229
1282
  sddBlockingFinding,
1230
1283
  ...(degradedModeFinding ? [degradedModeFinding] : []),
1284
+ ...(preWriteLeaseFinding ? [preWriteLeaseFinding] : []),
1231
1285
  ...(policyAsCodeBlockingFinding ? [policyAsCodeBlockingFinding] : []),
1232
1286
  ...(effectiveUnsupportedSkillsMappingFinding ? [effectiveUnsupportedSkillsMappingFinding] : []),
1233
1287
  ...(effectivePlatformSkillsCoverageFinding ? [effectivePlatformSkillsCoverageFinding] : []),
@@ -1237,6 +1291,7 @@ export async function runPlatformGate(params: {
1237
1291
  ...(effectiveIosTestsQualityFinding ? [effectiveIosTestsQualityFinding] : []),
1238
1292
  ...(astIntelligenceDualFinding ? [astIntelligenceDualFinding] : []),
1239
1293
  ...(coverageBlockingFinding ? [coverageBlockingFinding] : []),
1294
+ ...(declarativeRulesClassificationFinding ? [declarativeRulesClassificationFinding] : []),
1240
1295
  ...brownfieldHotspotFindings,
1241
1296
  ...tddBddEvaluation.findings,
1242
1297
  ...findings,
@@ -1245,16 +1300,21 @@ export async function runPlatformGate(params: {
1245
1300
  || effectivePlatformSkillsCoverageFinding
1246
1301
  || effectiveCrossPlatformCriticalFinding
1247
1302
  || effectiveSkillsScopeComplianceFinding
1303
+ || preWriteLeaseFinding
1248
1304
  || activeRulesEmptyForCodeChangesFinding
1249
1305
  || effectiveIosTestsQualityFinding
1250
1306
  || astIntelligenceDualFinding
1251
1307
  || coverageBlockingFinding
1308
+ || declarativeRulesClassificationFinding
1252
1309
  || brownfieldHotspotFindings.length > 0
1253
1310
  || policyAsCodeBlockingFinding
1311
+ || contextBlockingFinding
1254
1312
  || degradedModeFinding
1255
1313
  || tddBddEvaluation.findings.length > 0
1256
1314
  ? [
1315
+ ...(contextBlockingFinding ? [contextBlockingFinding] : []),
1257
1316
  ...(degradedModeFinding ? [degradedModeFinding] : []),
1317
+ ...(preWriteLeaseFinding ? [preWriteLeaseFinding] : []),
1258
1318
  ...(policyAsCodeBlockingFinding ? [policyAsCodeBlockingFinding] : []),
1259
1319
  ...(effectiveUnsupportedSkillsMappingFinding ? [effectiveUnsupportedSkillsMappingFinding] : []),
1260
1320
  ...(effectivePlatformSkillsCoverageFinding ? [effectivePlatformSkillsCoverageFinding] : []),
@@ -1264,6 +1324,7 @@ export async function runPlatformGate(params: {
1264
1324
  ...(effectiveIosTestsQualityFinding ? [effectiveIosTestsQualityFinding] : []),
1265
1325
  ...(astIntelligenceDualFinding ? [astIntelligenceDualFinding] : []),
1266
1326
  ...(coverageBlockingFinding ? [coverageBlockingFinding] : []),
1327
+ ...(declarativeRulesClassificationFinding ? [declarativeRulesClassificationFinding] : []),
1267
1328
  ...brownfieldHotspotFindings,
1268
1329
  ...tddBddEvaluation.findings,
1269
1330
  ...findings,
@@ -1283,7 +1344,9 @@ export async function runPlatformGate(params: {
1283
1344
  );
1284
1345
  const baseGateOutcome =
1285
1346
  sddBlockingFinding ||
1347
+ shouldBlockFromFinding(contextBlockingFinding) ||
1286
1348
  degradedModeBlocks ||
1349
+ shouldBlockFromFinding(preWriteLeaseFinding) ||
1287
1350
  shouldBlockFromFinding(policyAsCodeBlockingFinding) ||
1288
1351
  shouldBlockFromFinding(effectiveUnsupportedSkillsMappingFinding) ||
1289
1352
  shouldBlockFromFinding(effectivePlatformSkillsCoverageFinding) ||
@@ -1293,6 +1356,7 @@ export async function runPlatformGate(params: {
1293
1356
  shouldBlockFromFinding(effectiveIosTestsQualityFinding) ||
1294
1357
  hasAstIntelligenceBlockingFinding ||
1295
1358
  shouldBlockFromFinding(coverageBlockingFinding) ||
1359
+ shouldBlockFromFinding(declarativeRulesClassificationFinding) ||
1296
1360
  brownfieldHotspotFindings.some((finding) => shouldBlockFromFinding(finding)) ||
1297
1361
  hasTddBddBlockingFinding
1298
1362
  ? 'BLOCK'
@@ -1410,6 +1474,34 @@ export async function runPlatformGate(params: {
1410
1474
  });
1411
1475
 
1412
1476
  if (gateOutcome === 'BLOCK') {
1477
+ const primaryBlockingFinding = resolvePrimaryBlockingFinding(findingsWithWaiver);
1478
+ if (
1479
+ primaryBlockingFinding &&
1480
+ (
1481
+ params.policy.stage === 'PRE_COMMIT' ||
1482
+ params.policy.stage === 'PRE_PUSH' ||
1483
+ params.policy.stage === 'CI'
1484
+ )
1485
+ ) {
1486
+ const notificationResult = dependencies.notifyGateBlocked({
1487
+ repoRoot,
1488
+ stage: params.policy.stage,
1489
+ totalViolations: findingsWithWaiver.length,
1490
+ causeCode: primaryBlockingFinding.code ?? primaryBlockingFinding.ruleId,
1491
+ causeMessage: primaryBlockingFinding.message,
1492
+ remediation:
1493
+ primaryBlockingFinding.expected_fix ??
1494
+ 'Corrige la causa bloqueante y reejecuta el gate.',
1495
+ });
1496
+ process.stderr.write(
1497
+ `[pumuki][blocked] code=${primaryBlockingFinding.code ?? primaryBlockingFinding.ruleId} ` +
1498
+ `stage=${params.policy.stage} reason=${primaryBlockingFinding.message}\n`
1499
+ );
1500
+ process.stderr.write(
1501
+ `[pumuki][notification] delivered=${notificationResult.delivered ? 'yes' : 'no'} ` +
1502
+ `reason=${notificationResult.reason}\n`
1503
+ );
1504
+ }
1413
1505
  if (params.silent !== true) {
1414
1506
  dependencies.printGateFindings(findingsWithWaiver);
1415
1507
  }
@@ -818,7 +818,7 @@ export async function runPreCommitStage(
818
818
  repoRoot,
819
819
  stage: 'PRE_COMMIT',
820
820
  scope: {
821
- kind: 'staged',
821
+ kind: 'workingTree',
822
822
  },
823
823
  });
824
824
  if (
@@ -898,9 +898,7 @@ export async function runPrePushStage(
898
898
  repoRoot,
899
899
  stage: 'PRE_PUSH',
900
900
  scope: {
901
- kind: 'range',
902
- fromRef: bootstrapBaseRef,
903
- toRef: 'HEAD',
901
+ kind: 'repoAndStaged',
904
902
  },
905
903
  });
906
904
  if (
@@ -1006,9 +1004,7 @@ export async function runPrePushStage(
1006
1004
  repoRoot,
1007
1005
  stage: 'PRE_PUSH',
1008
1006
  scope: {
1009
- kind: 'range',
1010
- fromRef: prePushFromRef,
1011
- toRef: prePushToRef,
1007
+ kind: 'repoAndStaged',
1012
1008
  },
1013
1009
  sddDecisionOverride: historicalPrePushSddOverride,
1014
1010
  });
@@ -1064,9 +1060,7 @@ export async function runCiStage(
1064
1060
  policy: resolved.policy,
1065
1061
  policyTrace: resolved.trace,
1066
1062
  scope: {
1067
- kind: 'range',
1068
- fromRef: ciBaseRef,
1069
- toRef: 'HEAD',
1063
+ kind: 'repo',
1070
1064
  },
1071
1065
  });
1072
1066
  if (exitCode !== 0) {