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.
|
|
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 =
|
|
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}.
|
|
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)} · ${
|
|
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: ${
|
|
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
|
-
|
|
227
|
+
96
|
|
144
228
|
);
|
|
145
229
|
const remediation =
|
|
146
230
|
buildBlockingCausesRemediation(event.blockingCauses) ?? resolveBlockedRemediation(event, causeCode);
|