pumuki 6.3.317 → 6.3.318

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.317",
3
+ "version": "6.3.318",
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": {
@@ -192,6 +192,24 @@ const isGoldenFlowCause = (
192
192
  (cause.ruleId ?? '').startsWith('generic_tdd_') ||
193
193
  (cause.ruleId ?? '').startsWith('generic_red_green_refactor');
194
194
 
195
+ const isBddScenarioMissingCause = (
196
+ cause: NonNullable<Extract<PumukiCriticalNotificationEvent, { kind: 'gate.blocked' }>['blockingCauses']>[number]
197
+ ): boolean => {
198
+ const searchable = `${cause.code} ${cause.ruleId ?? ''} ${cause.message}`.toLowerCase();
199
+ return (
200
+ searchable.includes('tdd_bdd_scenario_file_missing') ||
201
+ searchable.includes('generic_bdd_feature_required') ||
202
+ searchable.includes('missing feature file')
203
+ );
204
+ };
205
+
206
+ const extractExpectedBddFeatureFile = (cause: NonNullable<Extract<PumukiCriticalNotificationEvent, { kind: 'gate.blocked' }>['blockingCauses']>[number]): string => {
207
+ const directFile = cause.file?.endsWith('.feature') ? cause.file : null;
208
+ const fileField = extractFieldFromCauseMessage(cause.message, 'file');
209
+ const missingFeature = cause.message.match(/missing feature file\s+([^\s.]+\.feature|[^\s]+\.feature)/i)?.[1];
210
+ return directFile ?? fileField ?? missingFeature ?? 'fichero .feature de la slice';
211
+ };
212
+
195
213
  const isSkillCause = (
196
214
  cause: NonNullable<Extract<PumukiCriticalNotificationEvent, { kind: 'gate.blocked' }>['blockingCauses']>[number]
197
215
  ): boolean =>
@@ -274,6 +292,15 @@ const formatBlockingCauseForDialog = (
274
292
  ` Falta: ${truncateNotificationText(formatGoldenFlowMissing(cause.message), 120)}`,
275
293
  ];
276
294
  }
295
+ if (isBddScenarioMissingCause(cause)) {
296
+ const expectedFeatureFile = extractExpectedBddFeatureFile(cause);
297
+ return [
298
+ `${index + 1}. Causa bloqueante: escenario BDD sin fichero .feature`,
299
+ ` Fichero esperado: ${truncateNotificationText(expectedFeatureFile, 120)}`,
300
+ ' Falla: La slice referencia un escenario BDD, pero no existe su fichero .feature.',
301
+ ' Solución: Crea ese .feature con el escenario de aceptación de la slice, o corrige la referencia si el id de tarea no es correcto.',
302
+ ];
303
+ }
277
304
 
278
305
  const causeLabel = isSkillCause(cause) ? 'Regla' : 'Causa bloqueante';
279
306
  return [
@@ -295,6 +322,11 @@ const hasOnlyGoldenFlowCauses = (
295
322
  ): boolean =>
296
323
  Boolean(causes?.length) && causes!.every(isGoldenFlowCause);
297
324
 
325
+ const hasOnlyBddScenarioMissingCauses = (
326
+ causes: Extract<PumukiCriticalNotificationEvent, { kind: 'gate.blocked' }>['blockingCauses']
327
+ ): boolean =>
328
+ Boolean(causes?.length) && causes!.every(isBddScenarioMissingCause);
329
+
298
330
  const resolveGoldenFlowDialogRemediation = (
299
331
  causes: Extract<PumukiCriticalNotificationEvent, { kind: 'gate.blocked' }>['blockingCauses']
300
332
  ): string => {
@@ -314,6 +346,8 @@ export const buildBlockedDialogPayload = (params: {
314
346
  ? WORKTREE_HYGIENE_REMEDIATION
315
347
  : hasOnlyGoldenFlowCauses(params.event.blockingCauses)
316
348
  ? resolveGoldenFlowDialogRemediation(params.event.blockingCauses)
349
+ : hasOnlyBddScenarioMissingCauses(params.event.blockingCauses)
350
+ ? 'Crea el fichero .feature esperado para la slice activa o corrige la referencia de tarea. Después vuelve a intentar el commit.'
317
351
  : params.event.blockingCauses && params.event.blockingCauses.length > 0
318
352
  ? 'Corrige las violaciones listadas y vuelve a intentar el commit.'
319
353
  : resolveBlockedRemediation(params.event, causeCode);
@@ -38,6 +38,23 @@ const isGoldenFlowCause = (cause: BlockedCause): boolean => {
38
38
  );
39
39
  };
40
40
 
41
+ const isBddScenarioMissingCause = (cause: BlockedCause): boolean => {
42
+ const searchable = `${cause.code} ${cause.ruleId ?? ''} ${cause.message}`.toLowerCase();
43
+ return (
44
+ searchable.includes('tdd_bdd_scenario_file_missing') ||
45
+ searchable.includes('generic_bdd_feature_required') ||
46
+ searchable.includes('missing feature file')
47
+ );
48
+ };
49
+
50
+ const extractExpectedBddFeatureFile = (cause: BlockedCause): string => {
51
+ const directFile = cause.file?.endsWith('.feature') ? cause.file : null;
52
+ const fieldMatch = cause.message.match(/\bfile=([^\s]+\.feature)\b/i)?.[1];
53
+ const missingFeature = cause.message.match(/missing feature file\s+([^\s.]+\.feature|[^\s]+\.feature)/i)?.[1];
54
+ const raw = directFile ?? fieldMatch ?? missingFeature ?? 'fichero .feature de la slice';
55
+ return raw.split('/').filter(Boolean).pop() ?? raw;
56
+ };
57
+
41
58
  const normalizeGoldenFlowMissingToken = (message: string): string => {
42
59
  const match = message.match(/missing=\[([^\]]+)\]/i);
43
60
  return (match?.[1] ?? '').toUpperCase();
@@ -165,6 +182,11 @@ export const resolvePrioritizedBlockingCauses = (
165
182
  if (leftGoldenFlow !== rightGoldenFlow) {
166
183
  return leftGoldenFlow ? -1 : 1;
167
184
  }
185
+ const leftBddScenarioMissing = isBddScenarioMissingCause(left);
186
+ const rightBddScenarioMissing = isBddScenarioMissingCause(right);
187
+ if (leftBddScenarioMissing !== rightBddScenarioMissing) {
188
+ return leftBddScenarioMissing ? -1 : 1;
189
+ }
168
190
  return 0;
169
191
  });
170
192
  };
@@ -188,6 +210,9 @@ const buildBlockingCausesSummary = (
188
210
  if (isGoldenFlowCause(first)) {
189
211
  return `Violación del ciclo TDD${overflow}`;
190
212
  }
213
+ if (isBddScenarioMissingCause(first)) {
214
+ return `BDD sin fichero .feature: ${extractExpectedBddFeatureFile(first)}${overflow}`;
215
+ }
191
216
  return `${prefix}: ${formatCauseRule(first)} · ${formatCauseLocation(first)}${overflow}`;
192
217
  };
193
218
 
@@ -216,6 +241,9 @@ const buildBlockingCausesRemediation = (
216
241
  if (isGoldenFlowCause(first)) {
217
242
  return formatGoldenFlowBannerRemediation(first);
218
243
  }
244
+ if (isBddScenarioMissingCause(first)) {
245
+ return `Falta el fichero ${extractExpectedBddFeatureFile(first)}. Crea ese .feature con el escenario de aceptación de la slice, o corrige la referencia de tarea si no corresponde.`;
246
+ }
219
247
  return `${formatCauseFix(first)} Revisa el reporte completo para el resto de causas bloqueantes.`;
220
248
  };
221
249
 
@@ -4,6 +4,7 @@ import {
4
4
  truncateNotificationText,
5
5
  } from './framework-menu-system-notifications-text';
6
6
  import {
7
+ buildNotificationTrackingRemediation,
7
8
  extractNotificationTrackingContext,
8
9
  TRACKING_BLOCKED_REMEDIATION,
9
10
  } from './framework-menu-system-notifications-tracking';
@@ -219,8 +220,9 @@ export const resolveBlockedRemediation = (
219
220
  const fromEvent = event.remediation
220
221
  ? normalizeBlockedRemediation(event.remediation)
221
222
  : '';
222
- if (extractNotificationTrackingContext(event.causeMessage)) {
223
- return truncateNotificationText(TRACKING_BLOCKED_REMEDIATION, maxLength);
223
+ const trackingContext = extractNotificationTrackingContext(event.causeMessage);
224
+ if (trackingContext) {
225
+ return truncateNotificationText(buildNotificationTrackingRemediation(trackingContext), maxLength);
224
226
  }
225
227
  if (fromEvent.length > 0) {
226
228
  if (
@@ -1,6 +1,7 @@
1
1
  export type NotificationTrackingContext = {
2
2
  activeEntry?: string;
3
3
  trackingSource?: string;
4
+ line?: string;
4
5
  };
5
6
 
6
7
  const TRACKING_CONTEXT_PATTERN = /\b(active_entries=|tracking_source=|TRACKING_CANONICAL_)/u;
@@ -15,28 +16,46 @@ export const extractNotificationTrackingContext = (
15
16
  .match(/\bactive_entries=([^,\s]+)/u)?.[1]
16
17
  ?.replace(/@L\d+$/u, '')
17
18
  .trim();
19
+ const line = message.match(/\bline[_=]([0-9]+)\b/u)?.[1]?.trim();
18
20
  const trackingSource = message.match(/\btracking_source=([^\s]+)/u)?.[1]?.trim();
19
21
  return {
20
- activeEntry: activeEntry && activeEntry.length > 0 ? activeEntry : undefined,
22
+ activeEntry: activeEntry && activeEntry.length > 0 && !/^line_[0-9]+$/u.test(activeEntry)
23
+ ? activeEntry
24
+ : undefined,
21
25
  trackingSource: trackingSource && trackingSource.length > 0 ? trackingSource : undefined,
26
+ line: line && line.length > 0 ? line : undefined,
22
27
  };
23
28
  };
24
29
 
25
30
  export const buildNotificationTrackingCauseSummary = (
26
31
  context: NotificationTrackingContext
27
32
  ): string => {
33
+ const lineSuffix = context.line ? `, línea ${context.line}` : '';
28
34
  if (context.activeEntry && context.trackingSource) {
29
- return `Tracking bloqueado: ${context.activeEntry} en ${context.trackingSource}.`;
35
+ return `Tracking bloqueado: ${context.activeEntry} en ${context.trackingSource}${lineSuffix}.`;
30
36
  }
31
37
  if (context.activeEntry) {
32
- return `Tracking bloqueado: ${context.activeEntry}.`;
38
+ return `Tracking bloqueado: ${context.activeEntry}${lineSuffix}.`;
33
39
  }
34
40
  if (context.trackingSource) {
35
- return `Tracking bloqueado en ${context.trackingSource}.`;
41
+ return `Tracking bloqueado en ${context.trackingSource}${lineSuffix}.`;
42
+ }
43
+ if (context.line) {
44
+ return `Tracking bloqueado en la línea ${context.line}.`;
36
45
  }
37
46
  return 'El tracking canónico del repo bloquea la governance.';
38
47
  };
39
48
 
40
49
  export const TRACKING_BLOCKED_REMEDIATION =
41
- 'Corrige el MD de tracking: deja una única tarea activa válida y vuelve a ejecutar el gate.';
50
+ 'Corrige el MD de tracking: deja una única tarea activa válida, elimina estados activos duplicados o inválidos y vuelve a intentar el commit.';
42
51
 
52
+ export const buildNotificationTrackingRemediation = (
53
+ context: NotificationTrackingContext
54
+ ): string => {
55
+ const target = context.trackingSource
56
+ ? `${context.trackingSource}${context.line ? `, línea ${context.line}` : ''}`
57
+ : context.line
58
+ ? `la línea ${context.line} del MD de tracking`
59
+ : 'el MD de tracking';
60
+ return `Abre el MD de tracking ${target}. Deja una única tarea activa válida, elimina estados activos duplicados o inválidos y vuelve a intentar el commit.`;
61
+ };