pumuki 6.3.100 → 6.3.102
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/CHANGELOG.md +15 -0
- package/VERSION +1 -1
- package/docs/operations/RELEASE_NOTES.md +12 -0
- package/integrations/gate/stagePolicies.ts +43 -6
- package/integrations/lifecycle/hookBlock.ts +2 -6
- package/integrations/lifecycle/policyReconcile.ts +6 -0
- package/integrations/policy/policyAsCode.ts +43 -6
- package/package.json +1 -1
- package/scripts/framework-menu-system-notifications-remediation.ts +21 -27
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,21 @@ This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [6.3.102] - 2026-04-22
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **`strict` efectivo alineado con el contrato firmado:** `policyAsCode` y `stagePolicies` dejan de publicar `validation.strict` desde el entorno cuando el contrato persistido ya declara el valor por stage.
|
|
14
|
+
- **`policy reconcile --strict --apply` materializa el contrato completo:** el archivo `.pumuki/policy-as-code.json` pasa a persistir el mapa `strict` por stage para que `status`, `doctor` y runtime converjan sobre la misma fuente.
|
|
15
|
+
- **Wiring robusto de `pre-push` con hooks previos terminados en `exec`:** el bloque gestionado de Pumuki se recoloca antes del `exec` también en `pre-push`, evitando que el enforcement quede detrás de código muerto.
|
|
16
|
+
|
|
17
|
+
## [6.3.101] - 2026-04-22
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- **`gate.blocked` sin `ReferenceError` en consumers:** `resolveBlockedRemediation` recupera su contrato con variantes (`banner`/`dialog`) y deja de romper la ruta bloqueante de `PRE_WRITE` por un `options is not defined`.
|
|
22
|
+
- **Cobertura de regresión del módulo de remediación:** la suite de `framework-menu-system-notifications-remediation` fija el caso de copy legacy en inglés y la compactación de banners sin truncados rotos.
|
|
23
|
+
|
|
9
24
|
## [6.3.100] - 2026-04-22
|
|
10
25
|
|
|
11
26
|
### Fixed
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
v6.3.
|
|
1
|
+
v6.3.102
|
|
@@ -6,6 +6,18 @@ This file keeps only the operational highlights and rollout notes that matter wh
|
|
|
6
6
|
|
|
7
7
|
## 2026-04 (CLI stability and macOS notifications)
|
|
8
8
|
|
|
9
|
+
### 2026-04-22 (v6.3.102)
|
|
10
|
+
|
|
11
|
+
- **Convergencia de policy efectiva:** `strict` deja de depender solo de `PUMUKI_POLICY_STRICT` cuando el contrato firmado ya lo declara por stage; `status`, `doctor` y runtime vuelven a hablar el mismo idioma.
|
|
12
|
+
- **Autofix persistente de contrato:** `policy reconcile --strict --apply` escribe el mapa `strict` completo en `.pumuki/policy-as-code.json`, cerrando la deriva entre reconcile y lectura posterior.
|
|
13
|
+
- **Wiring fiable en `pre-push`:** el hook gestionado se antepone también cuando el hook previo termina en `exec`, evitando bloques inalcanzables.
|
|
14
|
+
- **Rollout recomendado:** publicar `pumuki@6.3.102`, repin inmediato en `RuralGo` y revalidar `status` / `doctor` / `pre-push` para cerrar `PUMUKI-INC-080`.
|
|
15
|
+
|
|
16
|
+
### 2026-04-22 (v6.3.101)
|
|
17
|
+
|
|
18
|
+
- **Hotfix de ruta bloqueante:** `gate.blocked` deja de lanzar `ReferenceError: options is not defined` al construir la remediación visible en `PRE_WRITE`.
|
|
19
|
+
- **Rollout recomendado:** publicar `pumuki@6.3.101`, repin inmediato en `RuralGo` y revalidar que el bloqueo de `PRE_WRITE` termina limpio, sin error residual tras el panel.
|
|
20
|
+
|
|
9
21
|
### 2026-04-22 (v6.3.100)
|
|
10
22
|
|
|
11
23
|
- **Hotfix de activación efectiva:** la línea publicada deja de resolver `PRE_WRITE` a `off/default` en ausencia de override explícito; el default vuelve a ser coercitivo para el flujo real del agente/editor.
|
|
@@ -234,6 +234,7 @@ type PolicyAsCodeContract = {
|
|
|
234
234
|
source: 'default' | 'skills.policy' | 'hard-mode';
|
|
235
235
|
signatures: Partial<Record<SkillsStage, string>> & Record<'PRE_COMMIT' | 'PRE_PUSH' | 'CI', string>;
|
|
236
236
|
expires_at?: string;
|
|
237
|
+
strict?: Partial<Record<SkillsStage, boolean>>;
|
|
237
238
|
};
|
|
238
239
|
|
|
239
240
|
const resolveContractSignatureForStage = (
|
|
@@ -264,6 +265,23 @@ const isIsoDateString = (value: unknown): value is string => {
|
|
|
264
265
|
return Number.isFinite(Date.parse(value));
|
|
265
266
|
};
|
|
266
267
|
|
|
268
|
+
const isPolicyStrictRecord = (
|
|
269
|
+
value: unknown
|
|
270
|
+
): value is Partial<Record<SkillsStage, boolean>> => {
|
|
271
|
+
if (!isObject(value)) {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
return Object.entries(value).every(([stage, strict]) => {
|
|
275
|
+
return (
|
|
276
|
+
(stage === 'PRE_WRITE' ||
|
|
277
|
+
stage === 'PRE_COMMIT' ||
|
|
278
|
+
stage === 'PRE_PUSH' ||
|
|
279
|
+
stage === 'CI') &&
|
|
280
|
+
typeof strict === 'boolean'
|
|
281
|
+
);
|
|
282
|
+
});
|
|
283
|
+
};
|
|
284
|
+
|
|
267
285
|
const policyStrictModeFromEnv = (): boolean => {
|
|
268
286
|
const raw = process.env.PUMUKI_POLICY_STRICT?.trim().toLowerCase();
|
|
269
287
|
if (!raw) {
|
|
@@ -292,6 +310,9 @@ const isPolicyAsCodeContract = (value: unknown): value is PolicyAsCodeContract =
|
|
|
292
310
|
if (typeof value.expires_at !== 'undefined' && !isIsoDateString(value.expires_at)) {
|
|
293
311
|
return false;
|
|
294
312
|
}
|
|
313
|
+
if (typeof value.strict !== 'undefined' && !isPolicyStrictRecord(value.strict)) {
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
295
316
|
return (
|
|
296
317
|
(typeof value.signatures.PRE_WRITE === 'undefined' || isSha256Hex(value.signatures.PRE_WRITE)) &&
|
|
297
318
|
isSha256Hex(value.signatures.PRE_COMMIT) &&
|
|
@@ -300,6 +321,17 @@ const isPolicyAsCodeContract = (value: unknown): value is PolicyAsCodeContract =
|
|
|
300
321
|
);
|
|
301
322
|
};
|
|
302
323
|
|
|
324
|
+
const resolvePolicyAsCodeStrict = (params: {
|
|
325
|
+
contract?: PolicyAsCodeContract;
|
|
326
|
+
stage: SkillsStage;
|
|
327
|
+
}): boolean => {
|
|
328
|
+
const declared = params.contract?.strict?.[params.stage];
|
|
329
|
+
if (typeof declared === 'boolean') {
|
|
330
|
+
return declared;
|
|
331
|
+
}
|
|
332
|
+
return policyStrictModeFromEnv();
|
|
333
|
+
};
|
|
334
|
+
|
|
303
335
|
const createPolicyAsCodeSignature = (params: {
|
|
304
336
|
stage: SkillsStage;
|
|
305
337
|
source: 'default' | 'skills.policy' | 'hard-mode';
|
|
@@ -332,7 +364,7 @@ const resolvePolicyAsCodeTraceMetadata = (params: {
|
|
|
332
364
|
policySource: string;
|
|
333
365
|
validation: NonNullable<ResolvedStagePolicy['trace']['validation']>;
|
|
334
366
|
} => {
|
|
335
|
-
const
|
|
367
|
+
const envStrict = policyStrictModeFromEnv();
|
|
336
368
|
const computedVersion = `policy-as-code/${params.source}@${POLICY_AS_CODE_VERSION}`;
|
|
337
369
|
const computedSignature = createPolicyAsCodeSignature({
|
|
338
370
|
stage: params.stage,
|
|
@@ -344,7 +376,7 @@ const resolvePolicyAsCodeTraceMetadata = (params: {
|
|
|
344
376
|
const contractPath = join(params.repoRoot, POLICY_AS_CODE_CONTRACT_PATH);
|
|
345
377
|
|
|
346
378
|
if (!existsSync(contractPath)) {
|
|
347
|
-
if (
|
|
379
|
+
if (envStrict) {
|
|
348
380
|
return {
|
|
349
381
|
version: computedVersion,
|
|
350
382
|
signature: computedSignature,
|
|
@@ -354,7 +386,7 @@ const resolvePolicyAsCodeTraceMetadata = (params: {
|
|
|
354
386
|
code: 'POLICY_AS_CODE_UNSIGNED',
|
|
355
387
|
message:
|
|
356
388
|
'Policy-as-code contract is missing; runtime policy metadata is unsigned.',
|
|
357
|
-
strict,
|
|
389
|
+
strict: envStrict,
|
|
358
390
|
},
|
|
359
391
|
};
|
|
360
392
|
}
|
|
@@ -367,7 +399,7 @@ const resolvePolicyAsCodeTraceMetadata = (params: {
|
|
|
367
399
|
status: 'valid',
|
|
368
400
|
code: 'POLICY_AS_CODE_VALID',
|
|
369
401
|
message: 'Policy-as-code metadata generated from active runtime policy.',
|
|
370
|
-
strict,
|
|
402
|
+
strict: envStrict,
|
|
371
403
|
},
|
|
372
404
|
};
|
|
373
405
|
}
|
|
@@ -383,11 +415,16 @@ const resolvePolicyAsCodeTraceMetadata = (params: {
|
|
|
383
415
|
status: 'invalid',
|
|
384
416
|
code: 'POLICY_AS_CODE_CONTRACT_INVALID',
|
|
385
417
|
message: 'Policy-as-code contract is malformed.',
|
|
386
|
-
strict,
|
|
418
|
+
strict: envStrict,
|
|
387
419
|
},
|
|
388
420
|
};
|
|
389
421
|
}
|
|
390
422
|
|
|
423
|
+
const strict = resolvePolicyAsCodeStrict({
|
|
424
|
+
contract: raw,
|
|
425
|
+
stage: params.stage,
|
|
426
|
+
});
|
|
427
|
+
|
|
391
428
|
if (raw.source !== params.source) {
|
|
392
429
|
return {
|
|
393
430
|
version: `policy-as-code/${raw.source}@${raw.version}`,
|
|
@@ -465,7 +502,7 @@ const resolvePolicyAsCodeTraceMetadata = (params: {
|
|
|
465
502
|
status: 'invalid',
|
|
466
503
|
code: 'POLICY_AS_CODE_CONTRACT_INVALID',
|
|
467
504
|
message: 'Policy-as-code contract cannot be parsed as JSON.',
|
|
468
|
-
strict,
|
|
505
|
+
strict: envStrict,
|
|
469
506
|
},
|
|
470
507
|
};
|
|
471
508
|
}
|
|
@@ -104,7 +104,7 @@ const ensureExecutableHeader = (contents: string): string => {
|
|
|
104
104
|
return contents;
|
|
105
105
|
};
|
|
106
106
|
|
|
107
|
-
const
|
|
107
|
+
const isFrameworkHookWithExecTerminator = (contents: string): boolean => {
|
|
108
108
|
if (!contents.includes('pre-commit.com') && !contents.includes('pre_commit')) {
|
|
109
109
|
return false;
|
|
110
110
|
}
|
|
@@ -141,11 +141,7 @@ export const upsertPumukiManagedBlock = (params: {
|
|
|
141
141
|
)
|
|
142
142
|
);
|
|
143
143
|
|
|
144
|
-
if (
|
|
145
|
-
params.hook === 'pre-commit' &&
|
|
146
|
-
core.length > 0 &&
|
|
147
|
-
isPreCommitFrameworkWithExecTerminator(core)
|
|
148
|
-
) {
|
|
144
|
+
if (core.length > 0 && isFrameworkHookWithExecTerminator(core)) {
|
|
149
145
|
const merged = prependManagedBlockAfterShebang({ contents: core, block });
|
|
150
146
|
return `${trimTrailingWhitespace(merged)}\n`;
|
|
151
147
|
}
|
|
@@ -148,6 +148,12 @@ const tryApplyPolicyAutofix = (params: {
|
|
|
148
148
|
PRE_PUSH: signatures.PRE_PUSH,
|
|
149
149
|
CI: signatures.CI,
|
|
150
150
|
},
|
|
151
|
+
strict: {
|
|
152
|
+
PRE_WRITE: params.report.stages.PRE_WRITE.strict,
|
|
153
|
+
PRE_COMMIT: params.report.stages.PRE_COMMIT.strict,
|
|
154
|
+
PRE_PUSH: params.report.stages.PRE_PUSH.strict,
|
|
155
|
+
CI: params.report.stages.CI.strict,
|
|
156
|
+
},
|
|
151
157
|
expires_at: '2999-01-01T00:00:00.000Z',
|
|
152
158
|
};
|
|
153
159
|
|
|
@@ -32,6 +32,7 @@ type PolicyAsCodeContract = {
|
|
|
32
32
|
source: PolicyProfileSource;
|
|
33
33
|
signatures: Record<SkillsStage, string>;
|
|
34
34
|
expires_at?: string;
|
|
35
|
+
strict?: Partial<Record<SkillsStage, boolean>>;
|
|
35
36
|
};
|
|
36
37
|
|
|
37
38
|
const isObject = (value: unknown): value is Record<string, unknown> => {
|
|
@@ -49,6 +50,23 @@ const isIsoDateString = (value: unknown): value is string => {
|
|
|
49
50
|
return Number.isFinite(Date.parse(value));
|
|
50
51
|
};
|
|
51
52
|
|
|
53
|
+
const isPolicyStrictRecord = (
|
|
54
|
+
value: unknown
|
|
55
|
+
): value is Partial<Record<SkillsStage, boolean>> => {
|
|
56
|
+
if (!isObject(value)) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
return Object.entries(value).every(([stage, strict]) => {
|
|
60
|
+
return (
|
|
61
|
+
(stage === 'PRE_WRITE' ||
|
|
62
|
+
stage === 'PRE_COMMIT' ||
|
|
63
|
+
stage === 'PRE_PUSH' ||
|
|
64
|
+
stage === 'CI') &&
|
|
65
|
+
typeof strict === 'boolean'
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
|
|
52
70
|
const policyStrictModeFromEnv = (): boolean => {
|
|
53
71
|
const raw = process.env.PUMUKI_POLICY_STRICT?.trim().toLowerCase();
|
|
54
72
|
if (!raw) {
|
|
@@ -77,6 +95,9 @@ const isPolicyAsCodeContract = (value: unknown): value is PolicyAsCodeContract =
|
|
|
77
95
|
if (typeof value.expires_at !== 'undefined' && !isIsoDateString(value.expires_at)) {
|
|
78
96
|
return false;
|
|
79
97
|
}
|
|
98
|
+
if (typeof value.strict !== 'undefined' && !isPolicyStrictRecord(value.strict)) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
80
101
|
return (
|
|
81
102
|
isSha256Hex(value.signatures.PRE_COMMIT) &&
|
|
82
103
|
isSha256Hex(value.signatures.PRE_PUSH) &&
|
|
@@ -84,6 +105,17 @@ const isPolicyAsCodeContract = (value: unknown): value is PolicyAsCodeContract =
|
|
|
84
105
|
);
|
|
85
106
|
};
|
|
86
107
|
|
|
108
|
+
const resolvePolicyAsCodeStrict = (params: {
|
|
109
|
+
contract?: PolicyAsCodeContract;
|
|
110
|
+
stage: SkillsStage;
|
|
111
|
+
}): boolean => {
|
|
112
|
+
const declared = params.contract?.strict?.[params.stage];
|
|
113
|
+
if (typeof declared === 'boolean') {
|
|
114
|
+
return declared;
|
|
115
|
+
}
|
|
116
|
+
return policyStrictModeFromEnv();
|
|
117
|
+
};
|
|
118
|
+
|
|
87
119
|
export const createPolicyAsCodeSignature = (params: {
|
|
88
120
|
stage: SkillsStage;
|
|
89
121
|
source: PolicyProfileSource;
|
|
@@ -111,7 +143,7 @@ export const resolvePolicyAsCodeTraceMetadata = (params: {
|
|
|
111
143
|
hash: string;
|
|
112
144
|
repoRoot: string;
|
|
113
145
|
}): PolicyAsCodeTraceMetadata => {
|
|
114
|
-
const
|
|
146
|
+
const envStrict = policyStrictModeFromEnv();
|
|
115
147
|
const computedVersion = `policy-as-code/${params.source}@${POLICY_AS_CODE_VERSION}`;
|
|
116
148
|
const computedSignature = createPolicyAsCodeSignature({
|
|
117
149
|
stage: params.stage,
|
|
@@ -123,7 +155,7 @@ export const resolvePolicyAsCodeTraceMetadata = (params: {
|
|
|
123
155
|
const contractPath = join(params.repoRoot, POLICY_AS_CODE_CONTRACT_PATH);
|
|
124
156
|
|
|
125
157
|
if (!existsSync(contractPath)) {
|
|
126
|
-
if (
|
|
158
|
+
if (envStrict) {
|
|
127
159
|
return {
|
|
128
160
|
version: computedVersion,
|
|
129
161
|
signature: computedSignature,
|
|
@@ -133,7 +165,7 @@ export const resolvePolicyAsCodeTraceMetadata = (params: {
|
|
|
133
165
|
code: 'POLICY_AS_CODE_UNSIGNED',
|
|
134
166
|
message:
|
|
135
167
|
'Policy-as-code contract is missing; runtime policy metadata is unsigned.',
|
|
136
|
-
strict,
|
|
168
|
+
strict: envStrict,
|
|
137
169
|
},
|
|
138
170
|
};
|
|
139
171
|
}
|
|
@@ -146,7 +178,7 @@ export const resolvePolicyAsCodeTraceMetadata = (params: {
|
|
|
146
178
|
status: 'valid',
|
|
147
179
|
code: 'POLICY_AS_CODE_VALID',
|
|
148
180
|
message: 'Policy-as-code metadata generated from active runtime policy.',
|
|
149
|
-
strict,
|
|
181
|
+
strict: envStrict,
|
|
150
182
|
},
|
|
151
183
|
};
|
|
152
184
|
}
|
|
@@ -162,11 +194,16 @@ export const resolvePolicyAsCodeTraceMetadata = (params: {
|
|
|
162
194
|
status: 'invalid',
|
|
163
195
|
code: 'POLICY_AS_CODE_CONTRACT_INVALID',
|
|
164
196
|
message: 'Policy-as-code contract is malformed.',
|
|
165
|
-
strict,
|
|
197
|
+
strict: envStrict,
|
|
166
198
|
},
|
|
167
199
|
};
|
|
168
200
|
}
|
|
169
201
|
|
|
202
|
+
const strict = resolvePolicyAsCodeStrict({
|
|
203
|
+
contract: raw,
|
|
204
|
+
stage: params.stage,
|
|
205
|
+
});
|
|
206
|
+
|
|
170
207
|
if (raw.source !== params.source) {
|
|
171
208
|
return {
|
|
172
209
|
version: `policy-as-code/${raw.source}@${raw.version}`,
|
|
@@ -243,7 +280,7 @@ export const resolvePolicyAsCodeTraceMetadata = (params: {
|
|
|
243
280
|
status: 'invalid',
|
|
244
281
|
code: 'POLICY_AS_CODE_CONTRACT_INVALID',
|
|
245
282
|
message: 'Policy-as-code contract cannot be parsed as JSON.',
|
|
246
|
-
strict,
|
|
283
|
+
strict: envStrict,
|
|
247
284
|
},
|
|
248
285
|
};
|
|
249
286
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.102",
|
|
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": {
|
|
@@ -5,21 +5,26 @@ import {
|
|
|
5
5
|
} from './framework-menu-system-notifications-text';
|
|
6
6
|
|
|
7
7
|
const BLOCKED_REMEDIATION_BY_CODE: Readonly<Record<string, string>> = {
|
|
8
|
-
EVIDENCE_MISSING: 'Genera evidencia del slice actual y vuelve a validar
|
|
9
|
-
EVIDENCE_INVALID: 'Regenera la evidencia de
|
|
8
|
+
EVIDENCE_MISSING: 'Genera la evidencia del slice actual y vuelve a validar esta fase.',
|
|
9
|
+
EVIDENCE_INVALID: 'Regenera la evidencia de esta iteración y repite la validación.',
|
|
10
10
|
EVIDENCE_CHAIN_INVALID: 'Regenera la evidencia para restaurar la cadena de integridad y vuelve a validar.',
|
|
11
|
-
EVIDENCE_STALE: '
|
|
12
|
-
EVIDENCE_BRANCH_MISMATCH: '
|
|
13
|
-
EVIDENCE_REPO_ROOT_MISMATCH: 'Regenera evidencia desde este repositorio y
|
|
14
|
-
PRE_PUSH_UPSTREAM_MISSING: 'Configura upstream con `git push --set-upstream origin <branch>` y
|
|
15
|
-
SDD_SESSION_MISSING: 'Abre sesión SDD del change activo y repite la validación
|
|
16
|
-
SDD_SESSION_INVALID: 'Refresca la sesión SDD
|
|
17
|
-
OPENSPEC_MISSING: 'Instala OpenSpec en
|
|
18
|
-
MCP_ENTERPRISE_RECEIPT_MISSING: 'Genera el receipt enterprise de MCP y vuelve a
|
|
19
|
-
BACKEND_AVOID_EXPLICIT_ANY: '
|
|
11
|
+
EVIDENCE_STALE: 'Refresca la evidencia y vuelve a validar PRE_WRITE/PRE_PUSH.',
|
|
12
|
+
EVIDENCE_BRANCH_MISMATCH: 'La evidencia no corresponde a esta rama. Regenera el receipt/evidencia y vuelve a validar.',
|
|
13
|
+
EVIDENCE_REPO_ROOT_MISMATCH: 'Regenera la evidencia desde este repositorio y vuelve a validar.',
|
|
14
|
+
PRE_PUSH_UPSTREAM_MISSING: 'Configura upstream con `git push --set-upstream origin <branch>` y repite PRE_PUSH.',
|
|
15
|
+
SDD_SESSION_MISSING: 'Abre la sesión SDD del change activo y repite la validación.',
|
|
16
|
+
SDD_SESSION_INVALID: 'Refresca la sesión SDD activa y vuelve a validar esta fase.',
|
|
17
|
+
OPENSPEC_MISSING: 'Instala OpenSpec en este repositorio y vuelve a validar el gate.',
|
|
18
|
+
MCP_ENTERPRISE_RECEIPT_MISSING: 'Genera el receipt enterprise de MCP y vuelve a validar.',
|
|
19
|
+
BACKEND_AVOID_EXPLICIT_ANY: 'Sustituye `any` por tipos concretos en backend y relanza el gate.',
|
|
20
|
+
GIT_ATOMICITY_TOO_MANY_SCOPES: 'Divide el cambio por scope o en commits más pequeños y vuelve a ejecutar el gate.',
|
|
21
|
+
SOLID_HEURISTIC: 'Corrige la violación detectada y vuelve a ejecutar el gate.',
|
|
20
22
|
};
|
|
21
23
|
|
|
22
|
-
const
|
|
24
|
+
const BLOCKED_REMEDIATION_MAX_LENGTH_BY_VARIANT: Readonly<Record<BlockedRemediationVariant, number>> = {
|
|
25
|
+
banner: 120,
|
|
26
|
+
dialog: 220,
|
|
27
|
+
};
|
|
23
28
|
|
|
24
29
|
const GENERIC_BLOCKED_REMEDIATION =
|
|
25
30
|
'Corrige el bloqueo indicado y vuelve a ejecutar el comando.';
|
|
@@ -37,7 +42,6 @@ const resolveFallbackRemediation = (causeCode: string): string =>
|
|
|
37
42
|
const hasEnglishHints = (message: string): boolean => {
|
|
38
43
|
const normalized = message.toLowerCase();
|
|
39
44
|
return [
|
|
40
|
-
'detected',
|
|
41
45
|
'avoid explicit any',
|
|
42
46
|
'set-upstream',
|
|
43
47
|
'refresh evidence',
|
|
@@ -48,16 +52,6 @@ const hasEnglishHints = (message: string): boolean => {
|
|
|
48
52
|
'rerun',
|
|
49
53
|
'retry',
|
|
50
54
|
'to continue',
|
|
51
|
-
'protected branch',
|
|
52
|
-
'open spec',
|
|
53
|
-
'openspec',
|
|
54
|
-
'session',
|
|
55
|
-
'missing',
|
|
56
|
-
'invalid',
|
|
57
|
-
'failed',
|
|
58
|
-
'worktree',
|
|
59
|
-
'callback usage',
|
|
60
|
-
'usage.',
|
|
61
55
|
'run ',
|
|
62
56
|
].some((hint) => normalized.includes(hint));
|
|
63
57
|
};
|
|
@@ -73,9 +67,6 @@ const toKnownSpanishRemediationFromMessage = (message: string, causeCode: string
|
|
|
73
67
|
if (normalized.includes('refresh evidence') || normalized.includes('evidence is stale')) {
|
|
74
68
|
return BLOCKED_REMEDIATION_BY_CODE.EVIDENCE_STALE;
|
|
75
69
|
}
|
|
76
|
-
if (normalized.includes('evidence ai gate status is blocked')) {
|
|
77
|
-
return BLOCKED_REMEDIATION_BY_CODE.EVIDENCE_GATE_BLOCKED;
|
|
78
|
-
}
|
|
79
70
|
if (normalized.includes('split the change')) {
|
|
80
71
|
return BLOCKED_REMEDIATION_BY_CODE.GIT_ATOMICITY_TOO_MANY_SCOPES;
|
|
81
72
|
}
|
|
@@ -87,7 +78,10 @@ const toKnownSpanishRemediationFromMessage = (message: string, causeCode: string
|
|
|
87
78
|
|
|
88
79
|
export const resolveBlockedRemediation = (
|
|
89
80
|
event: Extract<PumukiCriticalNotificationEvent, { kind: 'gate.blocked' }>,
|
|
90
|
-
causeCode: string
|
|
81
|
+
causeCode: string,
|
|
82
|
+
options?: {
|
|
83
|
+
variant?: BlockedRemediationVariant;
|
|
84
|
+
}
|
|
91
85
|
): string => {
|
|
92
86
|
const variant = options?.variant ?? 'dialog';
|
|
93
87
|
const maxLength = BLOCKED_REMEDIATION_MAX_LENGTH_BY_VARIANT[variant];
|