pumuki 6.3.268 → 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.
package/VERSION CHANGED
@@ -1 +1 @@
1
- v6.3.238
1
+ v6.3.269
@@ -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
+ };
@@ -43,6 +43,8 @@ import {
43
43
  readPreWriteLeaseStatus,
44
44
  toPreWriteEnforcementGapFinding,
45
45
  } from '../lifecycle/preWriteLease';
46
+ import { evaluateContextGate, type ContextGateResult } from '../context/contextGate';
47
+ import { emitGateBlockedNotification } from '../notifications/emitAuditSummaryNotification';
46
48
 
47
49
  export type OperationalMemoryShadowRecommendation = {
48
50
  recommendedOutcome: 'ALLOW' | 'WARN' | 'BLOCK';
@@ -77,6 +79,8 @@ export type GateDependencies = {
77
79
  stage: 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
78
80
  branch: string | null;
79
81
  }) => GateWaiverResult;
82
+ evaluateContextGate: typeof evaluateContextGate;
83
+ notifyGateBlocked: typeof emitGateBlockedNotification;
80
84
  };
81
85
 
82
86
  const defaultServices: GateServices = {
@@ -109,20 +113,8 @@ const normalizeScopedRuleEngineFindings = (params: {
109
113
  findings: ReadonlyArray<Finding>;
110
114
  scope: GateScope;
111
115
  }): ReadonlyArray<Finding> => {
112
- if (params.scope.kind !== 'staged' && params.scope.kind !== 'range') {
113
- return params.findings;
114
- }
115
-
116
- return params.findings.filter((finding) => {
117
- if (
118
- !finding.filePath ||
119
- hasActionableFindingLocation(finding) ||
120
- (!finding.ruleId.startsWith('skills.') && !finding.ruleId.startsWith('heuristics.'))
121
- ) {
122
- return true;
123
- }
124
- return false;
125
- });
116
+ void params.scope;
117
+ return params.findings;
126
118
  };
127
119
 
128
120
  const buildDefaultMemoryShadowRecommendation = (params: {
@@ -199,6 +191,8 @@ const defaultDependencies: GateDependencies = {
199
191
  stage,
200
192
  branch,
201
193
  }),
194
+ evaluateContextGate,
195
+ notifyGateBlocked: emitGateBlockedNotification,
202
196
  };
203
197
 
204
198
  const readSymbolicBranchRef = (git: IGitService, repoRoot: string): string | null => {
@@ -287,6 +281,34 @@ const toSkillsUnsupportedAutoRulesBlockingFinding = (params: {
287
281
  };
288
282
  };
289
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
+
290
312
  const PLATFORM_SKILLS_RULE_PREFIXES: Record<
291
313
  'ios' | 'android' | 'backend' | 'frontend',
292
314
  string
@@ -808,6 +830,15 @@ const shouldBlockFromFinding = (finding: Finding | undefined): boolean => {
808
830
  return finding.blocking !== false;
809
831
  };
810
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
+
811
842
  const applySkillsFindingEnforcement = (
812
843
  finding: Finding | undefined
813
844
  ): Finding | undefined => {
@@ -830,22 +861,9 @@ const toSoftPreCommitSkillsFinding = (params: {
830
861
  enabled: boolean;
831
862
  observedCodePaths: ReadonlyArray<string>;
832
863
  }): Finding | undefined => {
833
- if (!params.finding) {
834
- return undefined;
835
- }
836
- if (!params.enabled || !shouldBlockFromFinding(params.finding)) {
837
- return params.finding;
838
- }
839
- return {
840
- ...params.finding,
841
- severity: 'WARN',
842
- blocking: false,
843
- code: `${params.finding.code}_SOFT_PRECOMMIT`,
844
- message:
845
- `${params.finding.message} ` +
846
- `Soft-enforced at PRE_COMMIT for low-risk scope (observed_code_paths=${params.observedCodePaths.length}). ` +
847
- 'Strict enforcement remains active at PRE_PUSH/CI.',
848
- };
864
+ void params.enabled;
865
+ void params.observedCodePaths;
866
+ return params.finding;
849
867
  };
850
868
 
851
869
  export async function runPlatformGate(params: {
@@ -868,6 +886,11 @@ export async function runPlatformGate(params: {
868
886
  const repoRoot = git.resolveRepoRoot();
869
887
  const auditMode = params.auditMode ?? 'gate';
870
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;
871
894
  let sddDecision:
872
895
  | Pick<SddDecision, 'allowed' | 'code' | 'message'>
873
896
  | undefined;
@@ -1016,6 +1039,16 @@ export async function runPlatformGate(params: {
1016
1039
  unsupportedAutoRuleIds: skillsRuleSet.unsupportedAutoRuleIds ?? [],
1017
1040
  })
1018
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;
1019
1052
  const effectiveUnsupportedSkillsMappingInput = applySkillsFindingEnforcement(
1020
1053
  unsupportedSkillsMappingFinding
1021
1054
  );
@@ -1217,6 +1250,7 @@ export async function runPlatformGate(params: {
1217
1250
  && !degradedModeBlocks
1218
1251
  && !shouldBlockFromFinding(policyAsCodeBlockingFinding)
1219
1252
  && !shouldBlockFromFinding(coverageBlockingFinding)
1253
+ && !shouldBlockFromFinding(declarativeRulesClassificationFinding)
1220
1254
  && !shouldBlockFromFinding(activeRulesEmptyForCodeChangesFinding)
1221
1255
  && !shouldBlockFromFinding(effectiveIosTestsQualityFinding)
1222
1256
  && !shouldBlockFromFinding(astIntelligenceDualFinding)
@@ -1244,6 +1278,7 @@ export async function runPlatformGate(params: {
1244
1278
  });
1245
1279
  const effectiveFindings = sddBlockingFinding
1246
1280
  ? [
1281
+ ...(contextBlockingFinding ? [contextBlockingFinding] : []),
1247
1282
  sddBlockingFinding,
1248
1283
  ...(degradedModeFinding ? [degradedModeFinding] : []),
1249
1284
  ...(preWriteLeaseFinding ? [preWriteLeaseFinding] : []),
@@ -1256,6 +1291,7 @@ export async function runPlatformGate(params: {
1256
1291
  ...(effectiveIosTestsQualityFinding ? [effectiveIosTestsQualityFinding] : []),
1257
1292
  ...(astIntelligenceDualFinding ? [astIntelligenceDualFinding] : []),
1258
1293
  ...(coverageBlockingFinding ? [coverageBlockingFinding] : []),
1294
+ ...(declarativeRulesClassificationFinding ? [declarativeRulesClassificationFinding] : []),
1259
1295
  ...brownfieldHotspotFindings,
1260
1296
  ...tddBddEvaluation.findings,
1261
1297
  ...findings,
@@ -1269,11 +1305,14 @@ export async function runPlatformGate(params: {
1269
1305
  || effectiveIosTestsQualityFinding
1270
1306
  || astIntelligenceDualFinding
1271
1307
  || coverageBlockingFinding
1308
+ || declarativeRulesClassificationFinding
1272
1309
  || brownfieldHotspotFindings.length > 0
1273
1310
  || policyAsCodeBlockingFinding
1311
+ || contextBlockingFinding
1274
1312
  || degradedModeFinding
1275
1313
  || tddBddEvaluation.findings.length > 0
1276
1314
  ? [
1315
+ ...(contextBlockingFinding ? [contextBlockingFinding] : []),
1277
1316
  ...(degradedModeFinding ? [degradedModeFinding] : []),
1278
1317
  ...(preWriteLeaseFinding ? [preWriteLeaseFinding] : []),
1279
1318
  ...(policyAsCodeBlockingFinding ? [policyAsCodeBlockingFinding] : []),
@@ -1285,6 +1324,7 @@ export async function runPlatformGate(params: {
1285
1324
  ...(effectiveIosTestsQualityFinding ? [effectiveIosTestsQualityFinding] : []),
1286
1325
  ...(astIntelligenceDualFinding ? [astIntelligenceDualFinding] : []),
1287
1326
  ...(coverageBlockingFinding ? [coverageBlockingFinding] : []),
1327
+ ...(declarativeRulesClassificationFinding ? [declarativeRulesClassificationFinding] : []),
1288
1328
  ...brownfieldHotspotFindings,
1289
1329
  ...tddBddEvaluation.findings,
1290
1330
  ...findings,
@@ -1304,6 +1344,7 @@ export async function runPlatformGate(params: {
1304
1344
  );
1305
1345
  const baseGateOutcome =
1306
1346
  sddBlockingFinding ||
1347
+ shouldBlockFromFinding(contextBlockingFinding) ||
1307
1348
  degradedModeBlocks ||
1308
1349
  shouldBlockFromFinding(preWriteLeaseFinding) ||
1309
1350
  shouldBlockFromFinding(policyAsCodeBlockingFinding) ||
@@ -1315,6 +1356,7 @@ export async function runPlatformGate(params: {
1315
1356
  shouldBlockFromFinding(effectiveIosTestsQualityFinding) ||
1316
1357
  hasAstIntelligenceBlockingFinding ||
1317
1358
  shouldBlockFromFinding(coverageBlockingFinding) ||
1359
+ shouldBlockFromFinding(declarativeRulesClassificationFinding) ||
1318
1360
  brownfieldHotspotFindings.some((finding) => shouldBlockFromFinding(finding)) ||
1319
1361
  hasTddBddBlockingFinding
1320
1362
  ? 'BLOCK'
@@ -1432,6 +1474,34 @@ export async function runPlatformGate(params: {
1432
1474
  });
1433
1475
 
1434
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
+ }
1435
1505
  if (params.silent !== true) {
1436
1506
  dependencies.printGateFindings(findingsWithWaiver);
1437
1507
  }
@@ -76,6 +76,11 @@ import {
76
76
  import { runPolicyReconcile } from './policyReconcile';
77
77
  import { runLifecycleAudit, type LifecycleAuditStage } from './audit';
78
78
  import { resolvePreWriteEnforcement, type PreWriteEnforcementResolution } from '../policy/preWriteEnforcement';
79
+ import {
80
+ readLocalContextStatus,
81
+ repairLocalContext,
82
+ writeLocalContext,
83
+ } from '../context/contextGate';
79
84
 
80
85
  type LifecycleCommand =
81
86
  | 'bootstrap'
@@ -92,6 +97,7 @@ type LifecycleCommand =
92
97
  | 'adapter'
93
98
  | 'analytics'
94
99
  | 'policy'
100
+ | 'context'
95
101
  | 'audit';
96
102
 
97
103
  type SddCommand =
@@ -107,6 +113,7 @@ type LoopCommand = 'run' | 'status' | 'stop' | 'resume' | 'list' | 'export';
107
113
  type AnalyticsCommand = 'hotspots';
108
114
  type AnalyticsHotspotsCommand = 'report' | 'diagnose';
109
115
  type PolicyCommand = 'reconcile';
116
+ type ContextCommand = 'init' | 'status' | 'repair';
110
117
 
111
118
  type SddSessionAction = 'open' | 'refresh' | 'close';
112
119
 
@@ -178,6 +185,7 @@ export type ParsedArgs = {
178
185
  policyCommand?: PolicyCommand;
179
186
  policyStrict?: boolean;
180
187
  policyApply?: boolean;
188
+ contextCommand?: ContextCommand;
181
189
  auditStage?: LifecycleAuditStage;
182
190
  auditEngine?: boolean;
183
191
  };
@@ -204,6 +212,9 @@ Pumuki lifecycle commands:
204
212
  pumuki analytics hotspots report [--top=<n>] [--since-days=<n>] [--json] [--output-json=<path>] [--output-markdown=<path>]
205
213
  pumuki analytics hotspots diagnose [--json]
206
214
  pumuki policy reconcile [--strict] [--apply] [--json]
215
+ pumuki context init [--json]
216
+ pumuki context status [--json]
217
+ pumuki context repair [--json]
207
218
  pumuki sdd status [--json]
208
219
  pumuki sdd validate [--stage=PRE_WRITE|PRE_COMMIT|PRE_PUSH|CI] [--json]
209
220
  pumuki sdd session --open --change=<change-id|auto> [--ttl-minutes=<n>] [--json]
@@ -239,6 +250,7 @@ const isLifecycleCommand = (value: string): value is LifecycleCommand =>
239
250
  value === 'adapter' ||
240
251
  value === 'analytics' ||
241
252
  value === 'policy' ||
253
+ value === 'context' ||
242
254
  value === 'audit';
243
255
 
244
256
  const parseAdapterAgent = (value?: string): AdapterAgent => {
@@ -651,6 +663,7 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
651
663
  let analyticsSinceDays: ParsedArgs['analyticsSinceDays'];
652
664
  let analyticsJsonOutputPath: ParsedArgs['analyticsJsonOutputPath'];
653
665
  let analyticsMarkdownOutputPath: ParsedArgs['analyticsMarkdownOutputPath'];
666
+ let contextCommand: ParsedArgs['contextCommand'];
654
667
  let auditStage: LifecycleAuditStage | undefined;
655
668
  let auditEngine = false;
656
669
 
@@ -863,6 +876,31 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
863
876
  };
864
877
  }
865
878
 
879
+ if (commandRaw === 'context') {
880
+ const subcommandRaw = argv[1] ?? 'status';
881
+ if (
882
+ subcommandRaw !== 'init' &&
883
+ subcommandRaw !== 'status' &&
884
+ subcommandRaw !== 'repair'
885
+ ) {
886
+ throw new Error(`Unsupported context subcommand "${subcommandRaw}".\n\n${HELP_TEXT}`);
887
+ }
888
+ contextCommand = subcommandRaw;
889
+ for (const arg of argv.slice(2)) {
890
+ if (arg === '--json') {
891
+ json = true;
892
+ continue;
893
+ }
894
+ throw new Error(`Unsupported argument "${arg}".\n\n${HELP_TEXT}`);
895
+ }
896
+ return {
897
+ command: commandRaw,
898
+ purgeArtifacts: false,
899
+ json,
900
+ contextCommand,
901
+ };
902
+ }
903
+
866
904
  if (commandRaw === 'loop') {
867
905
  const subcommandRaw = argv[1] ?? '';
868
906
  if (
@@ -1582,6 +1620,16 @@ const printDoctorReport = (
1582
1620
  `PRE_PUSH=${report.policyValidation.stages.PRE_PUSH.validationCode ?? 'n/a'} strict=${report.policyValidation.stages.PRE_PUSH.strict ? 'yes' : 'no'} ` +
1583
1621
  `CI=${report.policyValidation.stages.CI.validationCode ?? 'n/a'} strict=${report.policyValidation.stages.CI.strict ? 'yes' : 'no'}`
1584
1622
  );
1623
+ writeInfo(
1624
+ `[pumuki] notifications: enabled=${report.notifications.enabled ? 'yes' : 'no'} ` +
1625
+ `deliverable=${report.notifications.deliverable ? 'yes' : 'no'} ` +
1626
+ `channel=${report.notifications.channel} ` +
1627
+ `blocked_dialog=${report.notifications.blockedDialogEnabled ? 'yes' : 'no'} ` +
1628
+ `reason=${report.notifications.disabledReason ?? 'none'}`
1629
+ );
1630
+ if (report.notifications.remediation) {
1631
+ writeInfo(`[pumuki] notifications remediation: ${report.notifications.remediation}`);
1632
+ }
1585
1633
 
1586
1634
  for (const issue of report.issues) {
1587
1635
  writeInfo(`[pumuki] ${issue.severity.toUpperCase()}: ${issue.message}`);
@@ -2377,6 +2425,28 @@ export const runLifecycleCli = async (
2377
2425
  }
2378
2426
  return result.gate_exit_code;
2379
2427
  }
2428
+ case 'context': {
2429
+ const repoRoot = process.cwd();
2430
+ const result =
2431
+ parsed.contextCommand === 'init'
2432
+ ? writeLocalContext(repoRoot)
2433
+ : parsed.contextCommand === 'repair'
2434
+ ? repairLocalContext(repoRoot)
2435
+ : readLocalContextStatus(repoRoot);
2436
+ if (parsed.json) {
2437
+ writeInfo(JSON.stringify(result, null, 2));
2438
+ } else {
2439
+ writeInfo(`[pumuki][context] status=${result.status}`);
2440
+ writeInfo(`[pumuki][context] path=${result.path}`);
2441
+ writeInfo(`[pumuki][context] ${result.message}`);
2442
+ if (result.document) {
2443
+ writeInfo(`[pumuki][context] repo=${result.document.repo_root}`);
2444
+ writeInfo(`[pumuki][context] agent=${result.document.contract.agent_instruction}`);
2445
+ writeInfo(`[pumuki][context] user=${result.document.contract.user_remediation}`);
2446
+ }
2447
+ }
2448
+ return result.status === 'ok' ? 0 : 1;
2449
+ }
2380
2450
  case 'doctor': {
2381
2451
  const report = runLifecycleDoctor({
2382
2452
  deep: parsed.doctorDeep === true,
@@ -2467,6 +2537,16 @@ export const runLifecycleCli = async (
2467
2537
  `PRE_PUSH=${status.policyValidation.stages.PRE_PUSH.validationCode ?? 'n/a'} strict=${status.policyValidation.stages.PRE_PUSH.strict ? 'yes' : 'no'} ` +
2468
2538
  `CI=${status.policyValidation.stages.CI.validationCode ?? 'n/a'} strict=${status.policyValidation.stages.CI.strict ? 'yes' : 'no'}`
2469
2539
  );
2540
+ writeInfo(
2541
+ `[pumuki] notifications: enabled=${status.notifications.enabled ? 'yes' : 'no'} ` +
2542
+ `deliverable=${status.notifications.deliverable ? 'yes' : 'no'} ` +
2543
+ `channel=${status.notifications.channel} ` +
2544
+ `blocked_dialog=${status.notifications.blockedDialogEnabled ? 'yes' : 'no'} ` +
2545
+ `reason=${status.notifications.disabledReason ?? 'none'}`
2546
+ );
2547
+ if (status.notifications.remediation) {
2548
+ writeInfo(`[pumuki] notifications remediation: ${status.notifications.remediation}`);
2549
+ }
2470
2550
  writeInfo(
2471
2551
  `[pumuki] experimental: ANALYTICS=${status.experimentalFeatures.features.analytics.mode} source=${status.experimentalFeatures.features.analytics.source} layer=${status.experimentalFeatures.features.analytics.layer} blocking=${status.experimentalFeatures.features.analytics.blocking ? 'yes' : 'no'} env=${status.experimentalFeatures.features.analytics.activationVariable}`
2472
2552
  );
@@ -21,6 +21,10 @@ import {
21
21
  type LifecycleDependencyInventory,
22
22
  } from './dependencyInventory';
23
23
  import { readPreWriteLeaseStatus } from './preWriteLease';
24
+ import {
25
+ readLifecycleNotificationStatus,
26
+ type LifecycleNotificationStatus,
27
+ } from './notificationStatus';
24
28
 
25
29
  export type DoctorIssueSeverity = 'warning' | 'error';
26
30
 
@@ -102,6 +106,7 @@ export type LifecycleDoctorReport = {
102
106
  hooksDirectory: string;
103
107
  hooksDirectoryResolution: 'git-rev-parse' | 'git-config' | 'default';
104
108
  policyValidation: LifecyclePolicyValidationSnapshot;
109
+ notifications: LifecycleNotificationStatus;
105
110
  policy_signature_remediation?: string;
106
111
  issues: ReadonlyArray<DoctorIssue>;
107
112
  deep?: DoctorDeepReport;
@@ -117,6 +122,9 @@ const buildDoctorIssues = (params: {
117
122
  lifecycleState: LifecycleState;
118
123
  }): ReadonlyArray<DoctorIssue> => {
119
124
  const issues: DoctorIssue[] = [];
125
+ const notificationStatus = readLifecycleNotificationStatus({
126
+ repoRoot: params.repoRoot,
127
+ });
120
128
  const evidenceResult = readEvidenceResult(params.repoRoot);
121
129
  const preWriteLeaseStatus = readPreWriteLeaseStatus({
122
130
  repoRoot: params.repoRoot,
@@ -132,6 +140,15 @@ const buildDoctorIssues = (params: {
132
140
  });
133
141
  }
134
142
 
143
+ if (!notificationStatus.deliverable) {
144
+ issues.push({
145
+ severity: 'warning',
146
+ message:
147
+ `System notifications are not deliverable (${notificationStatus.disabledReason ?? 'unknown'}). ` +
148
+ `${notificationStatus.remediation ?? 'Review notification configuration.'}`,
149
+ });
150
+ }
151
+
135
152
  if (params.trackedNodeModulesPaths.length > 0) {
136
153
  issues.push({
137
154
  severity: 'error',
@@ -900,6 +917,7 @@ export const runLifecycleDoctor = (params?: {
900
917
 
901
918
  const policyValidation = readLifecyclePolicyValidationSnapshot(repoRoot);
902
919
  const policySignatureRemediation = buildPolicySignatureRemediation(policyValidation);
920
+ const notifications = readLifecycleNotificationStatus({ repoRoot });
903
921
 
904
922
  return {
905
923
  repoRoot,
@@ -912,6 +930,7 @@ export const runLifecycleDoctor = (params?: {
912
930
  hooksDirectory: hooksDirectory.path,
913
931
  hooksDirectoryResolution: hooksDirectory.source,
914
932
  policyValidation,
933
+ notifications,
915
934
  ...(policySignatureRemediation
916
935
  ? { policy_signature_remediation: policySignatureRemediation }
917
936
  : {}),
@@ -15,6 +15,11 @@ import { ensureRuntimeArtifactsIgnored } from './artifacts';
15
15
  import { runLifecycleAdapterInstall } from './adapter';
16
16
  import { runPolicyReconcile } from './policyReconcile';
17
17
  import { emitGateBlockedNotification } from '../notifications/emitAuditSummaryNotification';
18
+ import { persistSystemNotificationsConfig } from '../../scripts/framework-menu-system-notifications-config';
19
+ import {
20
+ readLifecycleNotificationStatus,
21
+ type LifecycleNotificationStatus,
22
+ } from './notificationStatus';
18
23
 
19
24
  export type LifecycleInstallResult = {
20
25
  repoRoot: string;
@@ -22,6 +27,7 @@ export type LifecycleInstallResult = {
22
27
  changedHooks: ReadonlyArray<string>;
23
28
  openSpecBootstrap?: OpenSpecBootstrapResult;
24
29
  degradedDoctorBypass?: boolean;
30
+ notifications: LifecycleNotificationStatus;
25
31
  };
26
32
 
27
33
  const shouldBootstrapEvidence = (repoRoot: string): boolean =>
@@ -102,6 +108,11 @@ const materializeStrictPolicyAsCode = (repoRoot: string): void => {
102
108
  const isPreWriteEnforcementGapIssue = (message: string): boolean =>
103
109
  message.startsWith('ENFORCEMENT_GAP: PRE_WRITE hard-stop lease is not valid');
104
110
 
111
+ const ensureInstallNotificationsEnabled = (repoRoot: string): LifecycleNotificationStatus => {
112
+ persistSystemNotificationsConfig(repoRoot, true);
113
+ return readLifecycleNotificationStatus({ repoRoot });
114
+ };
115
+
105
116
  export const runLifecycleInstall = (params?: {
106
117
  cwd?: string;
107
118
  git?: ILifecycleGitService;
@@ -117,6 +128,7 @@ export const runLifecycleInstall = (params?: {
117
128
  cwd: params?.cwd,
118
129
  git,
119
130
  });
131
+ const notifications = ensureInstallNotificationsEnabled(report.repoRoot);
120
132
  const installBlockingIssues = report.issues.filter(
121
133
  (issue) => issue.severity === 'error' && !isPreWriteEnforcementGapIssue(issue.message)
122
134
  );
@@ -141,6 +153,7 @@ export const runLifecycleInstall = (params?: {
141
153
  changedHooks,
142
154
  openSpecBootstrap: undefined,
143
155
  degradedDoctorBypass: true,
156
+ notifications,
144
157
  };
145
158
  }
146
159
  const renderedIssues = installBlockingIssues.map((issue) => `- [${issue.severity}] ${issue.message}`).join('\n');
@@ -193,5 +206,6 @@ export const runLifecycleInstall = (params?: {
193
206
  version,
194
207
  changedHooks,
195
208
  openSpecBootstrap,
209
+ notifications,
196
210
  };
197
211
  };
@@ -0,0 +1,54 @@
1
+ import { resolveEffectiveSystemNotificationsConfig } from '../../scripts/framework-menu-system-notifications-effective-config';
2
+ import { resolveSystemNotificationGate } from '../../scripts/framework-menu-system-notifications-gate';
3
+
4
+ export type LifecycleNotificationStatus = {
5
+ enabled: boolean;
6
+ blockedDialogEnabled: boolean;
7
+ channel: string;
8
+ muteUntil: string | null;
9
+ deliverable: boolean;
10
+ disabledReason: string | null;
11
+ remediation: string | null;
12
+ };
13
+
14
+ const remediationForReason = (reason: string | null): string | null => {
15
+ if (reason === null) {
16
+ return null;
17
+ }
18
+ if (reason === 'disabled') {
19
+ return 'Activa notificaciones con la configuracion repo-local de Pumuki y elimina env vars PUMUKI_*NOTIFICATIONS que las deshabiliten.';
20
+ }
21
+ if (reason === 'muted') {
22
+ return 'Quita el silencio temporal desde la notificacion o elimina muteUntil en .pumuki/system-notifications.json.';
23
+ }
24
+ if (reason === 'unsupported-platform') {
25
+ return 'Usa el fallback stderr o configura un canal compatible para este sistema.';
26
+ }
27
+ return 'Revisa la configuracion de notificaciones y reejecuta pumuki status/doctor.';
28
+ };
29
+
30
+ export const readLifecycleNotificationStatus = (params: {
31
+ repoRoot: string;
32
+ env?: NodeJS.ProcessEnv;
33
+ now?: () => number;
34
+ }): LifecycleNotificationStatus => {
35
+ const env = params.env ?? process.env;
36
+ const config = resolveEffectiveSystemNotificationsConfig({
37
+ repoRoot: params.repoRoot,
38
+ });
39
+ const gate = resolveSystemNotificationGate({
40
+ config,
41
+ nowMs: (params.now ?? Date.now)(),
42
+ env,
43
+ });
44
+ const disabledReason = gate?.reason ?? null;
45
+ return {
46
+ enabled: config.enabled,
47
+ blockedDialogEnabled: config.blockedDialogEnabled === true,
48
+ channel: config.channel,
49
+ muteUntil: config.muteUntil ?? null,
50
+ deliverable: gate === null,
51
+ disabledReason,
52
+ remediation: remediationForReason(disabledReason),
53
+ };
54
+ };
@@ -18,6 +18,10 @@ import {
18
18
  type LifecycleDependencyInventory,
19
19
  } from './dependencyInventory';
20
20
  import { readPreWriteLeaseStatus } from './preWriteLease';
21
+ import {
22
+ readLifecycleNotificationStatus,
23
+ type LifecycleNotificationStatus,
24
+ } from './notificationStatus';
21
25
 
22
26
  export type LifecycleStatus = {
23
27
  repoRoot: string;
@@ -31,15 +35,27 @@ export type LifecycleStatus = {
31
35
  dependencyInventory: LifecycleDependencyInventory;
32
36
  policyValidation: LifecyclePolicyValidationSnapshot;
33
37
  experimentalFeatures: LifecycleExperimentalFeaturesSnapshot;
38
+ notifications: LifecycleNotificationStatus;
34
39
  issues: ReadonlyArray<DoctorIssue>;
35
40
  };
36
41
 
37
42
  const buildLifecycleIssues = (repoRoot: string): ReadonlyArray<DoctorIssue> => {
43
+ const notificationStatus = readLifecycleNotificationStatus({ repoRoot });
38
44
  const evidenceResult = readEvidenceResult(repoRoot);
39
45
  const preWriteLeaseStatus = readPreWriteLeaseStatus({
40
46
  repoRoot,
41
47
  git: new LifecycleGitService(),
42
48
  });
49
+ const notificationIssues: DoctorIssue[] = notificationStatus.deliverable
50
+ ? []
51
+ : [
52
+ {
53
+ severity: 'warning',
54
+ message:
55
+ `System notifications are not deliverable (${notificationStatus.disabledReason ?? 'unknown'}). ` +
56
+ `${notificationStatus.remediation ?? 'Review notification configuration.'}`,
57
+ },
58
+ ];
43
59
  const leaseIssues: DoctorIssue[] =
44
60
  !preWriteLeaseStatus.valid && preWriteLeaseStatus.changedCodePaths.length > 0
45
61
  ? [
@@ -52,7 +68,7 @@ const buildLifecycleIssues = (repoRoot: string): ReadonlyArray<DoctorIssue> => {
52
68
  ]
53
69
  : [];
54
70
  if (evidenceResult.kind !== 'valid') {
55
- return leaseIssues;
71
+ return [...notificationIssues, ...leaseIssues];
56
72
  }
57
73
 
58
74
  const evidence = evidenceResult.evidence;
@@ -63,11 +79,12 @@ const buildLifecycleIssues = (repoRoot: string): ReadonlyArray<DoctorIssue> => {
63
79
 
64
80
  if (!blocked) {
65
81
  if (evidence.snapshot.outcome !== 'WARN') {
66
- return leaseIssues;
82
+ return [...notificationIssues, ...leaseIssues];
67
83
  }
68
84
 
69
85
  const warnStage = evidence?.snapshot?.stage ?? 'PRE_WRITE';
70
86
  return [
87
+ ...notificationIssues,
71
88
  ...leaseIssues,
72
89
  {
73
90
  severity: 'warning',
@@ -85,6 +102,7 @@ const buildLifecycleIssues = (repoRoot: string): ReadonlyArray<DoctorIssue> => {
85
102
  message: `Governance is blocked (${blockedStage}).`,
86
103
  });
87
104
  return [
105
+ ...notificationIssues,
88
106
  ...leaseIssues,
89
107
  {
90
108
  severity: 'error',
@@ -110,6 +128,7 @@ export const readLifecycleStatus = (params?: {
110
128
  });
111
129
  const policyValidation = readLifecyclePolicyValidationSnapshot(repoRoot);
112
130
  const experimentalFeatures = readLifecycleExperimentalFeaturesSnapshot();
131
+ const notifications = readLifecycleNotificationStatus({ repoRoot });
113
132
  const issues = buildLifecycleIssues(repoRoot);
114
133
 
115
134
  return {
@@ -124,6 +143,7 @@ export const readLifecycleStatus = (params?: {
124
143
  dependencyInventory,
125
144
  policyValidation,
126
145
  experimentalFeatures,
146
+ notifications,
127
147
  issues,
128
148
  };
129
149
  };
@@ -155,7 +155,8 @@ const defaultDependencies: LifecycleWatchDependencies = {
155
155
  ensureRuntimeArtifactsIgnored: (repoRoot) => {
156
156
  try {
157
157
  ensureRuntimeArtifactsIgnored(repoRoot);
158
- } catch {
158
+ } catch (error) {
159
+ void error;
159
160
  }
160
161
  },
161
162
  emitAuditSummaryNotificationFromEvidence,
@@ -108,8 +108,8 @@ const startOrReuseEvidenceHttp = async (): Promise<{
108
108
  if (health.status === 'ok') {
109
109
  return { host, port: requestedPort, route };
110
110
  }
111
- } catch {
112
- // ignored
111
+ } catch (error) {
112
+ void error;
113
113
  }
114
114
 
115
115
  const portInUse = await isPortInUse(host, requestedPort);
@@ -1,4 +1,4 @@
1
- export type SkillsEnforcementMode = 'advisory' | 'strict';
1
+ export type SkillsEnforcementMode = 'strict';
2
2
 
3
3
  export type SkillsEnforcementResolution = {
4
4
  mode: SkillsEnforcementMode;
@@ -24,17 +24,6 @@ const toSkillsEnforcementMode = (
24
24
  ) {
25
25
  return 'strict';
26
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
27
  return null;
39
28
  };
40
29
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.268",
3
+ "version": "6.3.269",
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": {
@@ -109,6 +109,7 @@
109
109
  "validation:consumer-startup-unblock-status": "npx --yes tsx@4.21.0 scripts/build-consumer-startup-unblock-status.ts",
110
110
  "validation:consumer-startup-triage": "npx --yes tsx@4.21.0 scripts/build-consumer-startup-triage.ts",
111
111
  "validation:mock-consumer-ab-report": "npx --yes tsx@4.21.0 scripts/build-mock-consumer-ab-report.ts",
112
+ "skills:classify": "npx --yes tsx@4.21.0 scripts/classify-skills-rules.ts",
112
113
  "validation:adapter-readiness": "npx --yes tsx@4.21.0 scripts/build-adapter-readiness.ts",
113
114
  "validation:adapter-real-session-report": "npx --yes tsx@4.21.0 scripts/build-adapter-real-session-report.ts",
114
115
  "validation:adapter-session-status": "npx --yes tsx@4.21.0 scripts/build-adapter-session-status.ts",
@@ -0,0 +1,32 @@
1
+ import { loadSkillsLock } from '../integrations/config/skillsLock';
2
+ import { classifySkillsRules } from '../integrations/config/skillsRuleClassification';
3
+
4
+ const repoRoot = process.argv[2] ?? process.cwd();
5
+ const lock = loadSkillsLock(repoRoot);
6
+
7
+ if (!lock) {
8
+ process.stderr.write(`skills.lock.json not found or invalid at ${repoRoot}\n`);
9
+ process.exit(1);
10
+ }
11
+
12
+ const summary = classifySkillsRules(lock);
13
+
14
+ process.stdout.write(`${JSON.stringify({
15
+ total: summary.total,
16
+ byStatus: summary.byStatus,
17
+ byPlatform: summary.byPlatform,
18
+ samples: {
19
+ AST_IMPLEMENTED: summary.rules
20
+ .filter((rule) => rule.status === 'AST_IMPLEMENTED')
21
+ .slice(0, 12),
22
+ IMPLEMENTABLE_AHORA: summary.rules
23
+ .filter((rule) => rule.status === 'IMPLEMENTABLE_AHORA')
24
+ .slice(0, 12),
25
+ REQUIERE_ESTUDIO: summary.rules
26
+ .filter((rule) => rule.status === 'REQUIERE_ESTUDIO')
27
+ .slice(0, 12),
28
+ NO_ES_REGLA_DE_CODIGO: summary.rules
29
+ .filter((rule) => rule.status === 'NO_ES_REGLA_DE_CODIGO')
30
+ .slice(0, 12),
31
+ },
32
+ }, null, 2)}\n`);
@@ -34,6 +34,10 @@ const BLOCKED_CAUSE_SUMMARY_BY_CODE: Readonly<Record<string, string>> = {
34
34
  'Falta enforcement crítico de skills para la plataforma detectada.',
35
35
  EVIDENCE_SKILLS_CONTRACT_INCOMPLETE:
36
36
  'El contrato de skills está incompleto para este stage.',
37
+ CONTEXT_NOT_APPLIED:
38
+ 'Contexto local obligatorio no aplicado.',
39
+ CONTEXT_INVALID:
40
+ 'Contexto local obligatorio inválido.',
37
41
  };
38
42
 
39
43
  const ENGLISH_CAUSE_HINTS = [
@@ -70,6 +74,9 @@ const resolvePriorityCauseFromMessage = (message?: string): string | null => {
70
74
  return PRIORITY_CODES_FROM_MESSAGE.find((code) => message.includes(code)) ?? null;
71
75
  };
72
76
 
77
+ const isCauseCodePriorityOverTracking = (causeCode: string): boolean =>
78
+ causeCode === 'CONTEXT_NOT_APPLIED' || causeCode === 'CONTEXT_INVALID';
79
+
73
80
  const buildGenericSpanishBlockedCauseSummary = (
74
81
  event: Extract<PumukiCriticalNotificationEvent, { kind: 'gate.blocked' }>,
75
82
  causeCode: string
@@ -118,10 +125,13 @@ export const resolveBlockedCauseSummary = (
118
125
  if (priorityCode) {
119
126
  return BLOCKED_CAUSE_SUMMARY_BY_CODE[priorityCode];
120
127
  }
128
+ const mapped = BLOCKED_CAUSE_SUMMARY_BY_CODE[causeCode];
129
+ if (mapped && isCauseCodePriorityOverTracking(causeCode)) {
130
+ return mapped;
131
+ }
121
132
  if (trackingContext) {
122
133
  return buildNotificationTrackingCauseSummary(trackingContext);
123
134
  }
124
- const mapped = BLOCKED_CAUSE_SUMMARY_BY_CODE[causeCode];
125
135
  if (mapped) {
126
136
  return mapped;
127
137
  }
@@ -15,7 +15,7 @@ export const commitBaseline = (
15
15
  runGitStep(workspace, ['add', '.'], 'git add baseline');
16
16
  runGitStep(
17
17
  workspace,
18
- ['commit', '--no-verify', '-m', 'chore: baseline'],
18
+ ['commit', '-m', 'chore: baseline'],
19
19
  'git commit baseline'
20
20
  );
21
21
  };
@@ -32,7 +32,7 @@ export const writeAndCommitRangePayloadForBlockMode = (
32
32
  runGitStep(workspace, ['add', '.'], 'git add range payload');
33
33
  runGitStep(
34
34
  workspace,
35
- ['commit', '--no-verify', '-m', 'test: range payload for package smoke'],
35
+ ['commit', '-m', 'test: range payload for package smoke'],
36
36
  'git commit range payload'
37
37
  );
38
38
  };
@@ -39,7 +39,7 @@ export const configureRemoteAndFeatureBranch = (
39
39
  workspace.tmpRoot
40
40
  );
41
41
  runGitStep(workspace, ['remote', 'add', 'origin', workspace.bareRemote], 'git remote add origin');
42
- runGitStep(workspace, ['push', '--no-verify', '-u', 'origin', 'main'], 'git push origin main');
42
+ runGitStep(workspace, ['push', '-u', 'origin', 'main'], 'git push origin main');
43
43
  runGitStep(workspace, ['checkout', '-b', 'feature/package-smoke'], 'git checkout feature branch');
44
44
  runGitStep(
45
45
  workspace,