pumuki 6.3.145 → 6.3.146
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/integrations/gate/blockingCause.ts +40 -0
- package/integrations/gate/remediationCatalog.ts +8 -0
- package/integrations/git/stageRunners.ts +11 -4
- package/integrations/lifecycle/cliSdd.ts +4 -3
- package/integrations/lifecycle/watch.ts +27 -7
- package/package.json +1 -1
- package/scripts/framework-menu-consumer-preflight-run.ts +8 -5
- package/scripts/framework-menu-system-notifications-cause.ts +24 -1
- package/scripts/framework-menu-system-notifications-remediation.ts +14 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
type BlockingCauseCandidate = {
|
|
2
|
+
code?: string;
|
|
3
|
+
message?: string;
|
|
4
|
+
remediation?: string;
|
|
5
|
+
severity?: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const UMBRELLA_EVIDENCE_CODES = new Set([
|
|
9
|
+
'EVIDENCE_GATE_BLOCKED',
|
|
10
|
+
'AI_GATE_BLOCKED',
|
|
11
|
+
'GATE_BLOCKED',
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
export const isTddBddBlockingCause = (candidate?: {
|
|
15
|
+
code?: string;
|
|
16
|
+
message?: string;
|
|
17
|
+
}): boolean => {
|
|
18
|
+
const code = candidate?.code ?? '';
|
|
19
|
+
const message = candidate?.message ?? '';
|
|
20
|
+
return code.startsWith('TDD_BDD_') || /\bTDD_BDD_[A-Z0-9_]+\b/u.test(message);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const resolvePrimaryBlockingCause = <T extends BlockingCauseCandidate>(
|
|
24
|
+
candidates: ReadonlyArray<T>
|
|
25
|
+
): T | undefined => {
|
|
26
|
+
const tddBddCause = candidates.find(isTddBddBlockingCause);
|
|
27
|
+
if (tddBddCause) {
|
|
28
|
+
return tddBddCause;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const specificCause = candidates.find((candidate) => {
|
|
32
|
+
const code = candidate.code ?? '';
|
|
33
|
+
return code.length > 0 && !UMBRELLA_EVIDENCE_CODES.has(code);
|
|
34
|
+
});
|
|
35
|
+
if (specificCause) {
|
|
36
|
+
return specificCause;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return candidates[0];
|
|
40
|
+
};
|
|
@@ -12,6 +12,14 @@ export const REMEDIATION_HINT_BY_CODE: Readonly<Record<string, string>> = {
|
|
|
12
12
|
EVIDENCE_BRANCH_MISMATCH: 'Regenera evidencia en la rama actual y reintenta.',
|
|
13
13
|
TDD_BDD_BASELINE_BLOCKED:
|
|
14
14
|
'Corrige el baseline TDD/BDD roto y regenera la evidencia antes de continuar.',
|
|
15
|
+
TDD_BDD_EVIDENCE_INVALID:
|
|
16
|
+
'Regenera la evidencia TDD/BDD válida del escenario afectado y vuelve a ejecutar el gate.',
|
|
17
|
+
TDD_BDD_SCENARIO_FILE_MISSING:
|
|
18
|
+
'Crea o corrige el fichero .feature referenciado por la evidencia TDD/BDD y revalida.',
|
|
19
|
+
TDD_BDD_EVIDENCE_STALE:
|
|
20
|
+
'Reejecuta los tests baseline del componente tocado, refresca la evidencia TDD/BDD y revalida.',
|
|
21
|
+
TDD_BDD_EVIDENCE_MISSING:
|
|
22
|
+
'Genera evidencia TDD/BDD para el cambio actual antes de continuar.',
|
|
15
23
|
EVIDENCE_RULES_COVERAGE_MISSING: 'Ejecuta auditoría completa para recalcular rules_coverage.',
|
|
16
24
|
EVIDENCE_RULES_COVERAGE_INCOMPLETE: 'Asegura coverage_ratio=1 y unevaluated=0.',
|
|
17
25
|
ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES_HIGH:
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
DEFAULT_GATE_REMEDIATION as DEFAULT_BLOCKED_REMEDIATION,
|
|
36
36
|
REMEDIATION_HINT_BY_CODE as BLOCKED_REMEDIATION_BY_CODE,
|
|
37
37
|
} from '../gate/remediationCatalog';
|
|
38
|
+
import { resolvePrimaryBlockingCause } from '../gate/blockingCause';
|
|
38
39
|
|
|
39
40
|
const PRE_PUSH_UPSTREAM_REQUIRED_MESSAGE =
|
|
40
41
|
'pumuki pre-push blocked: branch has no upstream tracking reference. Configure upstream first (for example: git push --set-upstream origin <branch>) and retry.';
|
|
@@ -273,10 +274,16 @@ const notifyGateBlockedForStage = (params: {
|
|
|
273
274
|
evidence?.snapshot.stage === params.stage
|
|
274
275
|
? evidence.snapshot.findings
|
|
275
276
|
: [];
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
const
|
|
277
|
+
const blockingStageFindings = stageFindings.filter((finding) =>
|
|
278
|
+
isSeverityAtLeast(finding.severity, 'ERROR')
|
|
279
|
+
);
|
|
280
|
+
const primaryCause = resolvePrimaryBlockingCause([
|
|
281
|
+
...blockingStageFindings,
|
|
282
|
+
...(evidence?.ai_gate.violations ?? []),
|
|
283
|
+
]);
|
|
284
|
+
const primaryStageFinding = primaryCause ?? resolvePrimaryBlockedStageFinding(stageFindings);
|
|
285
|
+
const causeCode = primaryStageFinding?.code ?? params.fallbackCauseCode;
|
|
286
|
+
const causeMessage = primaryStageFinding?.message ?? params.fallbackCauseMessage;
|
|
280
287
|
const remediation =
|
|
281
288
|
BLOCKED_REMEDIATION_BY_CODE[causeCode]
|
|
282
289
|
?? params.fallbackRemediation
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
type PreWriteEnforcementResolution,
|
|
22
22
|
} from '../policy/preWriteEnforcement';
|
|
23
23
|
import { readLifecycleExperimentalFeaturesSnapshot } from './experimentalFeaturesSnapshot';
|
|
24
|
+
import { resolvePrimaryBlockingCause } from '../gate/blockingCause';
|
|
24
25
|
|
|
25
26
|
import {
|
|
26
27
|
type ParsedArgs,
|
|
@@ -321,9 +322,9 @@ export const runSddCommand = async (parsed: ParsedArgs, activeDependencies: Life
|
|
|
321
322
|
return 1;
|
|
322
323
|
}
|
|
323
324
|
if (aiGate && !aiGate.allowed && preWriteEnforcement.blocking) {
|
|
324
|
-
const
|
|
325
|
-
const causeCode =
|
|
326
|
-
const causeMessage =
|
|
325
|
+
const primaryViolation = resolvePrimaryBlockingCause(aiGate.violations);
|
|
326
|
+
const causeCode = primaryViolation?.code ?? 'AI_GATE_BLOCKED';
|
|
327
|
+
const causeMessage = primaryViolation?.message ?? 'AI gate blocked PRE_WRITE stage.';
|
|
327
328
|
activeDependencies.emitGateBlockedNotification({
|
|
328
329
|
repoRoot: process.cwd(),
|
|
329
330
|
stage: result.stage,
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
resolveGitAtomicityEnforcement,
|
|
37
37
|
type GitAtomicityEnforcementResolution,
|
|
38
38
|
} from '../policy/gitAtomicityEnforcement';
|
|
39
|
+
import { resolvePrimaryBlockingCause } from '../gate/blockingCause';
|
|
39
40
|
|
|
40
41
|
export type LifecycleWatchStage = 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
|
|
41
42
|
export type LifecycleWatchScope = 'workingTree' | 'staged' | 'repoAndStaged' | 'repo';
|
|
@@ -119,6 +120,11 @@ type LifecycleWatchDependencies = {
|
|
|
119
120
|
};
|
|
120
121
|
|
|
121
122
|
const defaultGitService = new GitService();
|
|
123
|
+
const GIT_STATUS_PORCELAIN_ARGS = [
|
|
124
|
+
'status',
|
|
125
|
+
'--porcelain=v1',
|
|
126
|
+
'--untracked-files=all',
|
|
127
|
+
] as const;
|
|
122
128
|
|
|
123
129
|
class RepoScopedGitService extends GitService implements IGitService {
|
|
124
130
|
constructor(private readonly repoRoot: string) {
|
|
@@ -137,7 +143,7 @@ class RepoScopedGitService extends GitService implements IGitService {
|
|
|
137
143
|
const defaultDependencies: LifecycleWatchDependencies = {
|
|
138
144
|
resolveRepoRoot: () => defaultGitService.resolveRepoRoot(),
|
|
139
145
|
readChangeToken: (repoRoot) =>
|
|
140
|
-
defaultGitService.runGit([
|
|
146
|
+
defaultGitService.runGit([...GIT_STATUS_PORCELAIN_ARGS], repoRoot),
|
|
141
147
|
resolvePolicyForStage: (stage) => resolvePolicyForStage(stage),
|
|
142
148
|
resolveUpstreamRef,
|
|
143
149
|
resolvePrePushBootstrapBaseRef,
|
|
@@ -155,7 +161,10 @@ const defaultDependencies: LifecycleWatchDependencies = {
|
|
|
155
161
|
ensureRuntimeArtifactsIgnored: (repoRoot) => {
|
|
156
162
|
try {
|
|
157
163
|
ensureRuntimeArtifactsIgnored(repoRoot);
|
|
158
|
-
} catch {
|
|
164
|
+
} catch (error) {
|
|
165
|
+
process.stderr.write(
|
|
166
|
+
`[pumuki][watch] unable to ensure runtime artifacts are ignored: ${String(error)}\n`
|
|
167
|
+
);
|
|
159
168
|
}
|
|
160
169
|
},
|
|
161
170
|
emitAuditSummaryNotificationFromEvidence,
|
|
@@ -180,6 +189,16 @@ const BLOCKED_REMEDIATION_BY_CODE: Readonly<Record<string, string>> = {
|
|
|
180
189
|
SDD_SESSION_MISSING: 'Abre sesión SDD y vuelve a intentar.',
|
|
181
190
|
SDD_SESSION_INVALID: 'Refresca la sesión SDD y vuelve a intentar.',
|
|
182
191
|
OPENSPEC_MISSING: 'Instala OpenSpec y reintenta la validación.',
|
|
192
|
+
TDD_BDD_EVIDENCE_INVALID:
|
|
193
|
+
'Regenera la evidencia TDD/BDD válida del escenario afectado y vuelve a ejecutar el gate.',
|
|
194
|
+
TDD_BDD_SCENARIO_FILE_MISSING:
|
|
195
|
+
'Crea o corrige el fichero .feature referenciado por la evidencia TDD/BDD y revalida.',
|
|
196
|
+
TDD_BDD_EVIDENCE_STALE:
|
|
197
|
+
'Reejecuta los tests baseline del componente tocado, refresca la evidencia TDD/BDD y revalida.',
|
|
198
|
+
TDD_BDD_EVIDENCE_MISSING:
|
|
199
|
+
'Genera evidencia TDD/BDD para el cambio actual antes de continuar.',
|
|
200
|
+
TDD_BDD_BASELINE_BLOCKED:
|
|
201
|
+
'Corrige el baseline TDD/BDD roto y regenera la evidencia antes de continuar.',
|
|
183
202
|
MCP_ENTERPRISE_RECEIPT_MISSING: 'Genera el receipt enterprise de MCP antes de continuar.',
|
|
184
203
|
MANIFEST_MUTATION_DETECTED:
|
|
185
204
|
'Validación no debe mutar package.json/lockfiles. Revisa wiring y realiza upgrades solo con comando explícito.',
|
|
@@ -369,12 +388,13 @@ const toFirstCause = (params: {
|
|
|
369
388
|
evidence: AiEvidenceV2_1 | undefined;
|
|
370
389
|
matchedFindings: ReadonlyArray<SnapshotFinding>;
|
|
371
390
|
}): { code: string; message: string; remediation: string } => {
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
|
|
391
|
+
const primaryCause = resolvePrimaryBlockingCause([
|
|
392
|
+
...params.matchedFindings,
|
|
393
|
+
...(params.evidence?.ai_gate.violations ?? []),
|
|
394
|
+
]);
|
|
395
|
+
const code = primaryCause?.code ?? 'WATCH_GATE_BLOCKED';
|
|
375
396
|
const message =
|
|
376
|
-
|
|
377
|
-
firstViolation?.message ??
|
|
397
|
+
primaryCause?.message ??
|
|
378
398
|
`Watch gate bloqueado (${code}).`;
|
|
379
399
|
const remediation =
|
|
380
400
|
BLOCKED_REMEDIATION_BY_CODE[code] ??
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.146",
|
|
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": {
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
evaluateAiGate,
|
|
3
3
|
type AiGateCheckResult,
|
|
4
4
|
} from '../integrations/gate/evaluateAiGate';
|
|
5
|
+
import { resolvePrimaryBlockingCause } from '../integrations/gate/blockingCause';
|
|
5
6
|
import { readLifecycleExperimentalFeaturesSnapshot } from '../integrations/lifecycle/experimentalFeaturesSnapshot';
|
|
6
7
|
import { LifecycleGitService } from '../integrations/lifecycle/gitService';
|
|
7
8
|
import { readGovernanceObservationSnapshot } from '../integrations/lifecycle/governanceObservationSnapshot';
|
|
@@ -36,6 +37,8 @@ const defaultDependencies: ConsumerPreflightDependencies = {
|
|
|
36
37
|
readGovernanceNextAction,
|
|
37
38
|
};
|
|
38
39
|
|
|
40
|
+
const SECONDS_PER_MINUTE = 60;
|
|
41
|
+
|
|
39
42
|
const buildNotificationEvents = (
|
|
40
43
|
result: AiGateCheckResult
|
|
41
44
|
): ReadonlyArray<PumukiCriticalNotificationEvent> => {
|
|
@@ -45,7 +48,7 @@ const buildNotificationEvents = (
|
|
|
45
48
|
events.push({
|
|
46
49
|
kind: 'evidence.stale',
|
|
47
50
|
evidencePath: '.ai_evidence.json',
|
|
48
|
-
ageMinutes: Math.max(1, Math.ceil(ageSeconds /
|
|
51
|
+
ageMinutes: Math.max(1, Math.ceil(ageSeconds / SECONDS_PER_MINUTE)),
|
|
49
52
|
});
|
|
50
53
|
}
|
|
51
54
|
if (hasViolationCode(result.violations, 'GITFLOW_PROTECTED_BRANCH')) {
|
|
@@ -56,13 +59,13 @@ const buildNotificationEvents = (
|
|
|
56
59
|
});
|
|
57
60
|
}
|
|
58
61
|
if (result.status === 'BLOCKED') {
|
|
59
|
-
const
|
|
60
|
-
const causeCode =
|
|
62
|
+
const primaryViolation = resolvePrimaryBlockingCause(result.violations);
|
|
63
|
+
const causeCode = primaryViolation?.code ?? 'GATE_BLOCKED';
|
|
61
64
|
const causeMessage =
|
|
62
|
-
|
|
65
|
+
primaryViolation?.message
|
|
63
66
|
?? `Detected ${result.violations.length} blocking violations in stage ${result.stage}.`;
|
|
64
67
|
const remediation =
|
|
65
|
-
(
|
|
68
|
+
(primaryViolation ? ACTIONABLE_HINTS_BY_CODE[primaryViolation.code] : undefined)
|
|
66
69
|
?? 'Corrige la causa bloqueante y vuelve a ejecutar el gate.';
|
|
67
70
|
events.push({
|
|
68
71
|
kind: 'gate.blocked',
|
|
@@ -7,9 +7,16 @@ import {
|
|
|
7
7
|
buildNotificationTrackingCauseSummary,
|
|
8
8
|
extractNotificationTrackingContext,
|
|
9
9
|
} from './framework-menu-system-notifications-tracking';
|
|
10
|
+
import { isTddBddBlockingCause } from '../integrations/gate/blockingCause';
|
|
10
11
|
|
|
11
12
|
const BLOCKED_CAUSE_SUMMARY_BY_CODE: Readonly<Record<string, string>> = {
|
|
12
13
|
EVIDENCE_GATE_BLOCKED: 'El gate de evidencia/gobernanza está bloqueado.',
|
|
14
|
+
TDD_BDD_EVIDENCE_INVALID: 'La evidencia TDD/BDD actual es inválida.',
|
|
15
|
+
TDD_BDD_SCENARIO_FILE_MISSING:
|
|
16
|
+
'Falta el fichero de escenario TDD/BDD referenciado por la evidencia.',
|
|
17
|
+
TDD_BDD_EVIDENCE_STALE: 'La evidencia TDD/BDD está caducada.',
|
|
18
|
+
TDD_BDD_EVIDENCE_MISSING: 'Falta evidencia TDD/BDD para el cambio actual.',
|
|
19
|
+
TDD_BDD_BASELINE_BLOCKED: 'La baseline TDD/BDD está bloqueada.',
|
|
13
20
|
EVIDENCE_MISSING: 'Falta evidencia para validar este paso.',
|
|
14
21
|
EVIDENCE_INVALID: 'La evidencia actual es inválida.',
|
|
15
22
|
EVIDENCE_CHAIN_INVALID: 'La cadena de evidencia no es válida.',
|
|
@@ -32,6 +39,13 @@ const BLOCKED_CAUSE_SUMMARY_BY_CODE: Readonly<Record<string, string>> = {
|
|
|
32
39
|
'No hay reglas activas para cambios de código.',
|
|
33
40
|
};
|
|
34
41
|
|
|
42
|
+
const TRACKING_COMPATIBLE_UMBRELLA_CODES = new Set([
|
|
43
|
+
'EVIDENCE_GATE_BLOCKED',
|
|
44
|
+
'AI_GATE_BLOCKED',
|
|
45
|
+
'AI_GATE_BLOCK',
|
|
46
|
+
'GATE_BLOCKED',
|
|
47
|
+
]);
|
|
48
|
+
|
|
35
49
|
const ENGLISH_CAUSE_HINTS = [
|
|
36
50
|
'detected',
|
|
37
51
|
'avoid explicit any',
|
|
@@ -97,13 +111,22 @@ export const resolveBlockedCauseSummary = (
|
|
|
97
111
|
causeCode: string
|
|
98
112
|
): string => {
|
|
99
113
|
const trackingContext = extractNotificationTrackingContext(event.causeMessage);
|
|
100
|
-
if (
|
|
114
|
+
if (isTddBddBlockingCause({ code: causeCode, message: event.causeMessage })) {
|
|
115
|
+
return (
|
|
116
|
+
BLOCKED_CAUSE_SUMMARY_BY_CODE[causeCode] ??
|
|
117
|
+
'La evidencia TDD/BDD bloquea el gate; revisa el escenario y el artefacto de evidencia.'
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
if (trackingContext && TRACKING_COMPATIBLE_UMBRELLA_CODES.has(causeCode)) {
|
|
101
121
|
return buildNotificationTrackingCauseSummary(trackingContext);
|
|
102
122
|
}
|
|
103
123
|
const mapped = BLOCKED_CAUSE_SUMMARY_BY_CODE[causeCode];
|
|
104
124
|
if (mapped) {
|
|
105
125
|
return mapped;
|
|
106
126
|
}
|
|
127
|
+
if (trackingContext) {
|
|
128
|
+
return buildNotificationTrackingCauseSummary(trackingContext);
|
|
129
|
+
}
|
|
107
130
|
if (event.causeMessage && event.causeMessage.trim().length > 0) {
|
|
108
131
|
const rawMessage = normalizeNotificationText(event.causeMessage).replace(/^[A-Z0-9_]+:\s*/, '');
|
|
109
132
|
const translated = toKnownSpanishCauseFromMessage(rawMessage);
|
|
@@ -7,12 +7,23 @@ import {
|
|
|
7
7
|
extractNotificationTrackingContext,
|
|
8
8
|
TRACKING_BLOCKED_REMEDIATION,
|
|
9
9
|
} from './framework-menu-system-notifications-tracking';
|
|
10
|
+
import { isTddBddBlockingCause } from '../integrations/gate/blockingCause';
|
|
10
11
|
|
|
11
12
|
type BlockedRemediationVariant = 'banner' | 'dialog';
|
|
12
13
|
|
|
13
14
|
const BLOCKED_REMEDIATION_BY_CODE: Readonly<Record<string, string>> = {
|
|
14
15
|
EVIDENCE_GATE_BLOCKED:
|
|
15
16
|
'Revisa status/doctor para ver la causa exacta del gate, corrígela y revalida.',
|
|
17
|
+
TDD_BDD_EVIDENCE_INVALID:
|
|
18
|
+
'Regenera la evidencia TDD/BDD válida del escenario afectado y vuelve a ejecutar el gate.',
|
|
19
|
+
TDD_BDD_SCENARIO_FILE_MISSING:
|
|
20
|
+
'Crea o corrige el fichero .feature referenciado por la evidencia TDD/BDD y revalida.',
|
|
21
|
+
TDD_BDD_EVIDENCE_STALE:
|
|
22
|
+
'Reejecuta los tests baseline del componente tocado, refresca la evidencia TDD/BDD y revalida.',
|
|
23
|
+
TDD_BDD_EVIDENCE_MISSING:
|
|
24
|
+
'Genera evidencia TDD/BDD para el cambio actual antes de continuar.',
|
|
25
|
+
TDD_BDD_BASELINE_BLOCKED:
|
|
26
|
+
'Corrige la baseline TDD/BDD rota, registra evidencia pasada y vuelve a ejecutar el gate.',
|
|
16
27
|
EVIDENCE_MISSING: 'Genera la evidencia del slice actual y vuelve a validar esta fase.',
|
|
17
28
|
EVIDENCE_INVALID: 'Regenera la evidencia de esta iteración y repite la validación.',
|
|
18
29
|
EVIDENCE_CHAIN_INVALID: 'Regenera la evidencia para restaurar la cadena de integridad y vuelve a validar.',
|
|
@@ -105,6 +116,9 @@ export const resolveBlockedRemediation = (
|
|
|
105
116
|
const fromEvent = event.remediation
|
|
106
117
|
? normalizeBlockedRemediation(event.remediation)
|
|
107
118
|
: '';
|
|
119
|
+
if (isTddBddBlockingCause({ code: causeCode, message: event.causeMessage })) {
|
|
120
|
+
return truncateNotificationText(resolveFallbackRemediation(causeCode), maxLength);
|
|
121
|
+
}
|
|
108
122
|
if (extractNotificationTrackingContext(event.causeMessage)) {
|
|
109
123
|
return truncateNotificationText(TRACKING_BLOCKED_REMEDIATION, maxLength);
|
|
110
124
|
}
|