pumuki 6.3.312 → 6.3.313

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.312",
3
+ "version": "6.3.313",
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": {
@@ -52,6 +52,10 @@ const stripTechnicalFieldsFromMessage = (message: string): string => {
52
52
  .replace(/\blines?=[0-9][0-9,\-\s]*/gi, '')
53
53
  .replace(/\bmessage=/gi, '')
54
54
  .replace(/\bremediation=/gi, '')
55
+ .replace(/\bmissing=\[[^\]]*\]/gi, '')
56
+ .replace(/\bsnippet=.+$/gi, '')
57
+ .replace(/\bnode=.+$/gi, '')
58
+ .replace(/\bprimary_node=.+$/gi, '')
55
59
  .replace(/\s+/g, ' ')
56
60
  .trim();
57
61
  };
@@ -98,6 +102,47 @@ const formatLocation = (cause: NonNullable<Extract<PumukiCriticalNotificationEve
98
102
  return line ? `${cause.file}:${line}` : cause.file;
99
103
  };
100
104
 
105
+ const humanizeRuleId = (ruleId: string): string => {
106
+ const knownRules: Record<string, string> = {
107
+ 'prefer-swift-testing': 'Swift Testing',
108
+ 'no-xctassert': 'XCTest assertions',
109
+ 'no-wait-for-expectations': 'waitForExpectations en XCTest',
110
+ 'ios-test-quality': 'calidad de tests iOS',
111
+ 'dynamic-type-font-scaling-automatico': 'Dynamic Type',
112
+ 'dynamic-type-fuentes-escalables-y-layouts-adaptativos': 'fuentes escalables y layouts adaptativos',
113
+ 'use-relative-layout-over-hard-coded-constants': 'layout relativo en vez de constantes fijas',
114
+ 'magic-numbers-usar-constantes-con-nombres': 'números mágicos sin constantes con nombre',
115
+ 'prefer-static-member-lookup-blue-vs-color-blue': 'colores semánticos en SwiftUI',
116
+ };
117
+ const normalized = ruleId
118
+ .replace(/^skills\./u, '')
119
+ .replace(/^governance\./u, '')
120
+ .replace(/^ai_gate\./u, '')
121
+ .replace(/_/gu, '-');
122
+ const parts = normalized.split('.').filter(Boolean);
123
+ const leaf = parts.at(-1) ?? normalized;
124
+ if (knownRules[leaf]) {
125
+ return knownRules[leaf];
126
+ }
127
+ return leaf
128
+ .replace(/-/gu, ' ')
129
+ .replace(/\bios\b/giu, 'iOS')
130
+ .replace(/\bui\b/giu, 'UI')
131
+ .replace(/\bapi\b/giu, 'API')
132
+ .replace(/\btdd\b/giu, 'TDD')
133
+ .replace(/\bsdd\b/giu, 'SDD')
134
+ .replace(/\bguard\b/giu, 'guard')
135
+ .trim();
136
+ };
137
+
138
+ const formatVisibleRule = (
139
+ cause: NonNullable<Extract<PumukiCriticalNotificationEvent, { kind: 'gate.blocked' }>['blockingCauses']>[number]
140
+ ): string => {
141
+ const ruleId = cause.ruleId ?? cause.code;
142
+ const readable = humanizeRuleId(ruleId);
143
+ return readable.length > 0 ? readable : ruleId;
144
+ };
145
+
101
146
  const isWorktreeHygieneCause = (
102
147
  cause: NonNullable<Extract<PumukiCriticalNotificationEvent, { kind: 'gate.blocked' }>['blockingCauses']>[number]
103
148
  ): boolean =>
@@ -105,16 +150,78 @@ const isWorktreeHygieneCause = (
105
150
  cause.code === 'EVIDENCE_PREWRITE_WORKTREE_OVER_LIMIT' ||
106
151
  (cause.ruleId ?? '').includes('EVIDENCE_PREWRITE_WORKTREE');
107
152
 
153
+ const isGoldenFlowCause = (
154
+ cause: NonNullable<Extract<PumukiCriticalNotificationEvent, { kind: 'gate.blocked' }>['blockingCauses']>[number]
155
+ ): boolean =>
156
+ cause.code.startsWith('GOLDEN_FLOW_') ||
157
+ cause.code.startsWith('TDD_') ||
158
+ (cause.ruleId ?? '').includes('golden_flow') ||
159
+ (cause.ruleId ?? '').startsWith('generic_tdd_') ||
160
+ (cause.ruleId ?? '').startsWith('generic_red_green_refactor');
161
+
162
+ const isSkillCause = (
163
+ cause: NonNullable<Extract<PumukiCriticalNotificationEvent, { kind: 'gate.blocked' }>['blockingCauses']>[number]
164
+ ): boolean =>
165
+ (cause.ruleId ?? '').startsWith('skills.') ||
166
+ cause.code.startsWith('SKILLS_');
167
+
108
168
  const WORKTREE_HYGIENE_REMEDIATION =
109
169
  'Reduce el worktree pendiente a un slice atómico: stagea solo la tarea activa o guarda el resto en stash nombrado, y reejecuta PRE_WRITE.';
110
170
 
171
+ const GOLDEN_FLOW_REMEDIATION =
172
+ 'Ejecuta los tests de la implementación. Si están en verde, haz un commit atómico de los cambios.';
173
+
174
+ const normalizeGoldenFlowMissingToken = (message: string): string => {
175
+ const missing = (extractMissingContractFromCauseMessage(message) ?? 'VERIFY passed')
176
+ .replace(/^Falta:\s*/u, '');
177
+ return missing.toUpperCase();
178
+ };
179
+
180
+ const formatGoldenFlowMissing = (message: string): string => {
181
+ const missing = normalizeGoldenFlowMissingToken(message);
182
+ if (missing.includes('RED')) {
183
+ return 'fase RED del ciclo TDD sin implementar';
184
+ }
185
+ if (missing.includes('GREEN')) {
186
+ return 'fase GREEN del ciclo TDD sin implementar';
187
+ }
188
+ if (missing.includes('REFACTOR')) {
189
+ return 'fase REFACTOR del ciclo TDD sin implementar';
190
+ }
191
+ if (missing.includes('VERIFY')) {
192
+ return 'tests finales en verde sin ejecutar antes del commit';
193
+ }
194
+ return missing.toLowerCase();
195
+ };
196
+
197
+ const formatGoldenFlowRemediation = (message: string): string => {
198
+ const missing = normalizeGoldenFlowMissingToken(message);
199
+ if (missing.includes('RED')) {
200
+ return 'Implementa la fase RED antes de continuar. Las fases del ciclo TDD (RED, GREEN, REFACTOR) deben completarse para desbloquear.';
201
+ }
202
+ if (missing.includes('GREEN')) {
203
+ return 'Implementa la fase GREEN antes de continuar. Haz el cambio mínimo para que el test pase y continúa el ciclo TDD.';
204
+ }
205
+ if (missing.includes('REFACTOR')) {
206
+ return 'Implementa la fase REFACTOR antes de continuar. Limpia la solución manteniendo los tests en verde.';
207
+ }
208
+ if (missing.includes('VERIFY')) {
209
+ return GOLDEN_FLOW_REMEDIATION;
210
+ }
211
+ return 'Completa el ciclo TDD en orden: RED, GREEN, REFACTOR y tests finales en verde antes del commit.';
212
+ };
213
+
214
+
111
215
  const formatBlockingCauseForDialog = (
112
216
  cause: NonNullable<Extract<PumukiCriticalNotificationEvent, { kind: 'gate.blocked' }>['blockingCauses']>[number],
113
217
  index: number
114
218
  ): readonly string[] => {
115
219
  const rule = cause.ruleId ?? cause.code;
220
+ const visibleRule = formatVisibleRule(cause);
116
221
  const problem = stripTechnicalFieldsFromMessage(cause.message) || cause.code;
117
- const remediation = isWorktreeHygieneCause(cause)
222
+ const remediation = isGoldenFlowCause(cause)
223
+ ? GOLDEN_FLOW_REMEDIATION
224
+ : isWorktreeHygieneCause(cause)
118
225
  ? WORKTREE_HYGIENE_REMEDIATION
119
226
  : cause.remediation
120
227
  ? normalizeNotificationText(cause.remediation)
@@ -127,9 +234,17 @@ const formatBlockingCauseForDialog = (
127
234
  ` Solución: ${truncateNotificationText(WORKTREE_HYGIENE_REMEDIATION, 180)}`,
128
235
  ];
129
236
  }
237
+ if (isGoldenFlowCause(cause)) {
238
+ return [
239
+ `${index + 1}. Causa bloqueante: Violación del ciclo TDD`,
240
+ ` Registro TDD: ${truncateNotificationText(formatLocation(cause), 120)}`,
241
+ ` Falta: ${truncateNotificationText(formatGoldenFlowMissing(cause.message), 120)}`,
242
+ ];
243
+ }
130
244
 
245
+ const causeLabel = isSkillCause(cause) ? 'Regla' : 'Causa bloqueante';
131
246
  return [
132
- `${index + 1}. Regla: ${truncateNotificationText(rule, 96)}`,
247
+ `${index + 1}. ${causeLabel}: ${truncateNotificationText(visibleRule, 96)}`,
133
248
  ` Fichero: ${truncateNotificationText(formatLocation(cause), 120)}`,
134
249
  ` Viola: ${truncateNotificationText(problem, 160)}`,
135
250
  buildEvidenceLine(cause),
@@ -142,6 +257,18 @@ const hasOnlyWorktreeHygieneCauses = (
142
257
  ): boolean =>
143
258
  Boolean(causes?.length) && causes!.every(isWorktreeHygieneCause);
144
259
 
260
+ const hasOnlyGoldenFlowCauses = (
261
+ causes: Extract<PumukiCriticalNotificationEvent, { kind: 'gate.blocked' }>['blockingCauses']
262
+ ): boolean =>
263
+ Boolean(causes?.length) && causes!.every(isGoldenFlowCause);
264
+
265
+ const resolveGoldenFlowDialogRemediation = (
266
+ causes: Extract<PumukiCriticalNotificationEvent, { kind: 'gate.blocked' }>['blockingCauses']
267
+ ): string => {
268
+ const first = causes?.find(isGoldenFlowCause);
269
+ return first ? formatGoldenFlowRemediation(first.message) : GOLDEN_FLOW_REMEDIATION;
270
+ };
271
+
145
272
  export const buildBlockedDialogPayload = (params: {
146
273
  event: Extract<PumukiCriticalNotificationEvent, { kind: 'gate.blocked' }>;
147
274
  repoRoot: string;
@@ -152,6 +279,8 @@ export const buildBlockedDialogPayload = (params: {
152
279
  ?? resolveBlockedCauseSummary(params.event, causeCode);
153
280
  const remediation = hasOnlyWorktreeHygieneCauses(params.event.blockingCauses)
154
281
  ? WORKTREE_HYGIENE_REMEDIATION
282
+ : hasOnlyGoldenFlowCauses(params.event.blockingCauses)
283
+ ? resolveGoldenFlowDialogRemediation(params.event.blockingCauses)
155
284
  : params.event.blockingCauses && params.event.blockingCauses.length > 0
156
285
  ? 'Corrige las violaciones listadas y vuelve a intentar el commit.'
157
286
  : resolveBlockedRemediation(params.event, causeCode);
@@ -26,6 +26,40 @@ const isSkillCause = (cause: BlockedCause): boolean => {
26
26
  return ruleId.startsWith('skills.') || code.startsWith('SKILLS_');
27
27
  };
28
28
 
29
+ const isGoldenFlowCause = (cause: BlockedCause): boolean => {
30
+ const ruleId = cause.ruleId ?? '';
31
+ const code = cause.code ?? '';
32
+ return (
33
+ code.startsWith('GOLDEN_FLOW_') ||
34
+ code.startsWith('TDD_') ||
35
+ ruleId.includes('golden_flow') ||
36
+ ruleId.startsWith('generic_tdd_') ||
37
+ ruleId.startsWith('generic_red_green_refactor')
38
+ );
39
+ };
40
+
41
+ const normalizeGoldenFlowMissingToken = (message: string): string => {
42
+ const match = message.match(/missing=\[([^\]]+)\]/i);
43
+ return (match?.[1] ?? '').toUpperCase();
44
+ };
45
+
46
+ const formatGoldenFlowBannerRemediation = (cause: BlockedCause): string => {
47
+ const missing = normalizeGoldenFlowMissingToken(cause.message);
48
+ if (missing.includes('RED')) {
49
+ return 'Implementa la fase RED antes de continuar. Las fases del ciclo TDD (RED, GREEN, REFACTOR) deben completarse para desbloquear.';
50
+ }
51
+ if (missing.includes('GREEN')) {
52
+ return 'Implementa la fase GREEN antes de continuar. Haz el cambio mínimo para que el test pase y continúa el ciclo TDD.';
53
+ }
54
+ if (missing.includes('REFACTOR')) {
55
+ return 'Implementa la fase REFACTOR antes de continuar. Limpia la solución manteniendo los tests en verde.';
56
+ }
57
+ if (missing.includes('VERIFY')) {
58
+ return 'Ejecuta los tests de la implementación. Si están en verde, haz un commit atómico de los cambios.';
59
+ }
60
+ return 'Completa el ciclo TDD en orden: RED, GREEN, REFACTOR y tests finales en verde antes del commit.';
61
+ };
62
+
29
63
  const formatCauseLocation = (cause: BlockedCause): string => {
30
64
  if (!cause.file) {
31
65
  return 'sin fichero';
@@ -40,6 +74,45 @@ const formatCauseLocation = (cause: BlockedCause): string => {
40
74
 
41
75
  const formatCauseRule = (cause: BlockedCause): string => cause.ruleId ?? cause.code;
42
76
 
77
+ const humanizeRuleId = (ruleId: string): string => {
78
+ const knownRules: Record<string, string> = {
79
+ 'prefer-swift-testing': 'Swift Testing',
80
+ 'no-xctassert': 'XCTest assertions',
81
+ 'no-wait-for-expectations': 'waitForExpectations en XCTest',
82
+ 'ios-test-quality': 'calidad de tests iOS',
83
+ 'dynamic-type-font-scaling-automatico': 'Dynamic Type',
84
+ 'dynamic-type-fuentes-escalables-y-layouts-adaptativos': 'fuentes escalables y layouts adaptativos',
85
+ 'use-relative-layout-over-hard-coded-constants': 'layout relativo en vez de constantes fijas',
86
+ 'magic-numbers-usar-constantes-con-nombres': 'números mágicos sin constantes con nombre',
87
+ 'prefer-static-member-lookup-blue-vs-color-blue': 'colores semánticos en SwiftUI',
88
+ };
89
+ const normalized = ruleId
90
+ .replace(/^skills\./u, '')
91
+ .replace(/^governance\./u, '')
92
+ .replace(/^ai_gate\./u, '')
93
+ .replace(/_/gu, '-');
94
+ const parts = normalized.split('.').filter(Boolean);
95
+ const leaf = parts.at(-1) ?? normalized;
96
+ if (knownRules[leaf]) {
97
+ return knownRules[leaf];
98
+ }
99
+ return leaf
100
+ .replace(/-/gu, ' ')
101
+ .replace(/\bios\b/giu, 'iOS')
102
+ .replace(/\bui\b/giu, 'UI')
103
+ .replace(/\bapi\b/giu, 'API')
104
+ .replace(/\btdd\b/giu, 'TDD')
105
+ .replace(/\bsdd\b/giu, 'SDD')
106
+ .replace(/\bguard\b/giu, 'guard')
107
+ .trim();
108
+ };
109
+
110
+ const formatVisibleRule = (cause: BlockedCause): string => {
111
+ const ruleId = formatCauseRule(cause);
112
+ const readable = humanizeRuleId(ruleId);
113
+ return readable.length > 0 ? readable : ruleId;
114
+ };
115
+
43
116
  const extractMessageField = (message: string, field: string): string | null => {
44
117
  const escapedField = field.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
45
118
  const match = message.match(new RegExp(`\\b${escapedField}=([^\\n]+?)(?=\\s(?:severity|code|rule|file|lines?|message|remediation|snippet|node|primary_node|missing)=|$)`, 'i'));
@@ -85,6 +158,11 @@ export const resolvePrioritizedBlockingCauses = (
85
158
  if (leftSkill !== rightSkill) {
86
159
  return leftSkill ? -1 : 1;
87
160
  }
161
+ const leftGoldenFlow = isGoldenFlowCause(left);
162
+ const rightGoldenFlow = isGoldenFlowCause(right);
163
+ if (leftGoldenFlow !== rightGoldenFlow) {
164
+ return leftGoldenFlow ? -1 : 1;
165
+ }
88
166
  return 0;
89
167
  });
90
168
  };
@@ -103,7 +181,10 @@ const buildBlockingCausesSummary = (
103
181
  const prefix = isSkillCause(first) ? 'Skill violada' : 'Causa bloqueante';
104
182
  const overflow = prioritized.length > 1 ? ` (+${prioritized.length - 1} más)` : '';
105
183
  if (isSkillCause(first)) {
106
- return `${prefix}: ${formatCauseLocation(first)} · ${formatCauseRule(first)}${overflow}`;
184
+ return `${prefix}: ${formatCauseLocation(first)} · ${formatVisibleRule(first)}${overflow}`;
185
+ }
186
+ if (isGoldenFlowCause(first)) {
187
+ return `Violación del ciclo TDD${overflow}`;
107
188
  }
108
189
  return `${prefix}: ${formatCauseRule(first)} · ${formatCauseLocation(first)}${overflow}`;
109
190
  };
@@ -121,7 +202,7 @@ const buildBlockingCausesRemediation = (
121
202
  }
122
203
  if (isSkillCause(first)) {
123
204
  return [
124
- `Regla: ${formatCauseRule(first)}.`,
205
+ `Regla: ${formatVisibleRule(first)}.`,
125
206
  `Fichero: ${formatCauseLocation(first)}.`,
126
207
  `Viola: ${formatCauseProblem(first)}.`,
127
208
  `Solución: ${formatCauseFix(first)}.`,
@@ -130,6 +211,9 @@ const buildBlockingCausesRemediation = (
130
211
  .filter((line) => line.length > 0)
131
212
  .join(' ');
132
213
  }
214
+ if (isGoldenFlowCause(first)) {
215
+ return formatGoldenFlowBannerRemediation(first);
216
+ }
133
217
  return `${formatCauseFix(first)} Revisa el reporte completo para el resto de causas bloqueantes.`;
134
218
  };
135
219
 
@@ -140,7 +224,7 @@ export const buildGateBlockedPayload = (
140
224
  const causeCode = event.causeCode ?? 'GATE_BLOCKED';
141
225
  const causeSummary = truncateNotificationText(
142
226
  buildBlockingCausesSummary(event.blockingCauses) ?? resolveBlockedCauseSummary(event, causeCode),
143
- 72
227
+ 96
144
228
  );
145
229
  const remediation =
146
230
  buildBlockingCausesRemediation(event.blockingCauses) ?? resolveBlockedRemediation(event, causeCode);