pumuki 6.3.124 → 6.3.126

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 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.126] - 2026-04-28
10
+
11
+ ### Fixed
12
+
13
+ - **Suite lifecycle determinista:** `cli.test.ts` deja de esperar `PRE_WRITE=off` cuando el contrato actual lo define como `strict` por defecto, eliminando el falso fallo de fichero en `node:test`.
14
+ - **Snapshot experimental completo:** el payload `status --json --remote-checks` valida también `mcp_enterprise=strict`, alineado con la configuración enterprise real.
15
+
16
+ ## [6.3.125] - 2026-04-28
17
+
18
+ ### Fixed
19
+
20
+ - **Mensajes coherentes para bloqueos `gate.blocked`:** las notificaciones y diálogos traducen `EVIDENCE_GATE_BLOCKED`, tracking canónico y atomicidad a causas humanas en vez de mostrar copy interno en inglés.
21
+ - **Tracking como causa accionable:** cuando existe `active_entries` / `tracking_source`, la remediación prioriza corregir el MD de tracking y deja de sugerir `policy reconcile && sdd validate` como solución principal.
22
+ - **Cierre de `PUMUKI-INC-118`:** el evento central de bloqueo enriquece la causa con contexto de tracking antes de construir banners, diálogos macOS y payloads de sistema.
23
+
9
24
  ## [6.3.123] - 2026-04-28
10
25
 
11
26
  ### Fixed
package/VERSION CHANGED
@@ -1 +1 @@
1
- v6.3.123
1
+ v6.3.126
@@ -54,7 +54,7 @@ export const collectTrackingActiveEntriesFromMarkdown = (
54
54
  const bulletMatch = line.match(/^- 🚧 (`?P[0-9A-Za-z.-]+`?)/u);
55
55
  if (bulletMatch) {
56
56
  entries.push({
57
- taskId: bulletMatch[1]!.replaceAll('`', '').trim(),
57
+ taskId: bulletMatch[1]!.replace(/`/gu, '').trim(),
58
58
  lineNumber: index + 1,
59
59
  });
60
60
  continue;
@@ -1689,16 +1689,18 @@ export type PreWriteOpenSpecBootstrapTrace = {
1689
1689
  export const buildPreWriteExperimentalEnableAdvisoryCommand = (
1690
1690
  repoRoot: string = process.cwd()
1691
1691
  ): string =>
1692
- `PUMUKI_EXPERIMENTAL_PRE_WRITE=advisory ${buildPinnedPumukiNpxCommand(
1693
- getCurrentPumukiVersion({ repoRoot })
1694
- )} sdd validate --stage=PRE_WRITE --json`;
1692
+ `PUMUKI_EXPERIMENTAL_PRE_WRITE=advisory ${buildPinnedPumukiNpxCommand({
1693
+ repoRoot,
1694
+ executableAndArgs: 'pumuki sdd validate --stage=PRE_WRITE --json',
1695
+ })}`;
1695
1696
  export const buildSddExperimentalEnableAdvisoryCommand = (
1696
1697
  stage: SddStage,
1697
1698
  repoRoot: string = process.cwd()
1698
1699
  ): string =>
1699
- `PUMUKI_EXPERIMENTAL_SDD=advisory ${buildPinnedPumukiNpxCommand(
1700
- getCurrentPumukiVersion({ repoRoot })
1701
- )} sdd validate --stage=${stage} --json`;
1700
+ `PUMUKI_EXPERIMENTAL_SDD=advisory ${buildPinnedPumukiNpxCommand({
1701
+ repoRoot,
1702
+ executableAndArgs: `pumuki sdd validate --stage=${stage} --json`,
1703
+ })}`;
1702
1704
  const buildAnalyticsExperimentalEnableCommand = (action: AnalyticsHotspotsCommand): string =>
1703
1705
  `PUMUKI_EXPERIMENTAL_ANALYTICS=advisory npx --yes --package pumuki@latest pumuki analytics hotspots ${action} --json`;
1704
1706
  const SAAS_INGESTION_ENABLE_ADVISORY_COMMAND =
@@ -1804,7 +1806,7 @@ export const buildPreWriteExperimentalDisabledResult = (params: {
1804
1806
  layer: 'experimental',
1805
1807
  activation_env: 'PUMUKI_EXPERIMENTAL_PRE_WRITE',
1806
1808
  legacy_activation_env: 'PUMUKI_PREWRITE_ENFORCEMENT',
1807
- activation_command: PRE_WRITE_ENABLE_ADVISORY_COMMAND,
1809
+ activation_command: buildPreWriteExperimentalEnableAdvisoryCommand(),
1808
1810
  },
1809
1811
  },
1810
1812
  });
@@ -5,6 +5,7 @@ import { execFileSync as runBinarySync } from 'node:child_process';
5
5
  import { existsSync } from 'node:fs';
6
6
  import { join } from 'node:path';
7
7
  import { readLifecycleStatus, runLifecycleAudit } from '../lifecycle';
8
+ import { GitService } from '../git/GitService';
8
9
  import { resolveMcpEnterpriseExperimentalFeature } from '../policy/experimentalFeatures';
9
10
  import { evaluateSddPolicy, readSddStatus } from '../sdd';
10
11
  import type { SddStage } from '../sdd';
@@ -337,6 +338,20 @@ type EnterpriseToolExecution = {
337
338
  warnings?: ReadonlyArray<string>;
338
339
  };
339
340
 
341
+ class EnterpriseRepoGitService extends GitService {
342
+ constructor(private readonly repoRoot: string) {
343
+ super();
344
+ }
345
+
346
+ override resolveRepoRoot(): string {
347
+ return this.repoRoot;
348
+ }
349
+
350
+ override runGit(args: ReadonlyArray<string>, cwd: string = this.repoRoot): string {
351
+ return super.runGit(args, cwd);
352
+ }
353
+ }
354
+
340
355
  type CriticalToolGuardResult = {
341
356
  allowed: boolean;
342
357
  stage: SddStage;
@@ -398,9 +413,11 @@ const executeEnterpriseTool = async (
398
413
  switch (toolName) {
399
414
  case 'pre_write_guard': {
400
415
  const audit = await runLifecycleAudit({
401
- repoRoot,
402
416
  stage: 'PRE_WRITE',
403
417
  auditMode: 'gate',
418
+ dependencies: {
419
+ git: new EnterpriseRepoGitService(repoRoot),
420
+ },
404
421
  });
405
422
  return {
406
423
  name: toolName,
@@ -1,6 +1,7 @@
1
1
  import type { AiEvidenceV2_1 } from '../evidence/schema';
2
2
  import { readEvidence } from '../evidence/readEvidence';
3
3
  import type { AiGateCheckResult } from '../gate/evaluateAiGate';
4
+ import { appendTrackingActionableContext } from '../git/aiGateRepoPolicyFindings';
4
5
  import {
5
6
  emitSystemNotification,
6
7
  type PumukiCriticalNotificationEvent,
@@ -34,6 +35,22 @@ const isTruthyEnvValue = (value?: string): boolean => {
34
35
  return normalized === '1' || normalized === 'true' || normalized === 'yes';
35
36
  };
36
37
 
38
+ const withTrackingContext = (params: {
39
+ repoRoot: string;
40
+ causeMessage: string;
41
+ }): string => {
42
+ if (
43
+ params.causeMessage.includes('active_entries=') ||
44
+ params.causeMessage.includes('tracking_source=')
45
+ ) {
46
+ return params.causeMessage;
47
+ }
48
+ return appendTrackingActionableContext({
49
+ repoRoot: params.repoRoot,
50
+ message: params.causeMessage,
51
+ });
52
+ };
53
+
37
54
  export const shouldEmitAuditSummaryNotificationForStage = (
38
55
  stage: AuditSummaryNotificationStage,
39
56
  env: NodeJS.ProcessEnv = process.env
@@ -155,7 +172,10 @@ export const emitGateBlockedNotification = (
155
172
  stage: params.stage,
156
173
  totalViolations: params.totalViolations,
157
174
  causeCode: params.causeCode,
158
- causeMessage: params.causeMessage,
175
+ causeMessage: withTrackingContext({
176
+ repoRoot: params.repoRoot,
177
+ causeMessage: params.causeMessage,
178
+ }),
159
179
  remediation: params.remediation,
160
180
  },
161
181
  repoRoot: params.repoRoot,
@@ -167,6 +167,7 @@ const evaluateActiveChangeCompleteness = (params: {
167
167
 
168
168
  const evaluateSessionRequirements = (params: {
169
169
  status: SddStatusPayload;
170
+ repoRoot: string;
170
171
  autoRefreshEnabled: boolean;
171
172
  autoRefreshAttempted: boolean;
172
173
  autoRefreshError?: string;
@@ -335,7 +336,7 @@ export const evaluateSddPolicy = (params: {
335
336
  experimentalSource: sddExperimentalFeature.source,
336
337
  activation_env: sddExperimentalFeature.activationVariable,
337
338
  legacy_activation_env: sddExperimentalFeature.legacyActivationVariable,
338
- activation_command: buildSddExperimentalEnableCommand(params.stage, params.repoRoot),
339
+ activation_command: buildSddExperimentalEnableCommand(params.stage, repoRoot),
339
340
  },
340
341
  },
341
342
  };
@@ -428,6 +429,7 @@ export const evaluateSddPolicy = (params: {
428
429
 
429
430
  const sessionDecision = evaluateSessionRequirements({
430
431
  status,
432
+ repoRoot,
431
433
  autoRefreshEnabled,
432
434
  autoRefreshAttempted,
433
435
  autoRefreshError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.124",
3
+ "version": "6.3.126",
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": {
@@ -3,8 +3,13 @@ import {
3
3
  normalizeNotificationText,
4
4
  truncateNotificationText,
5
5
  } from './framework-menu-system-notifications-text';
6
+ import {
7
+ buildNotificationTrackingCauseSummary,
8
+ extractNotificationTrackingContext,
9
+ } from './framework-menu-system-notifications-tracking';
6
10
 
7
11
  const BLOCKED_CAUSE_SUMMARY_BY_CODE: Readonly<Record<string, string>> = {
12
+ EVIDENCE_GATE_BLOCKED: 'El gate de evidencia/gobernanza está bloqueado.',
8
13
  EVIDENCE_MISSING: 'Falta evidencia para validar este paso.',
9
14
  EVIDENCE_INVALID: 'La evidencia actual es inválida.',
10
15
  EVIDENCE_CHAIN_INVALID: 'La cadena de evidencia no es válida.',
@@ -18,6 +23,13 @@ const BLOCKED_CAUSE_SUMMARY_BY_CODE: Readonly<Record<string, string>> = {
18
23
  OPENSPEC_MISSING: 'OpenSpec no está instalado en este repositorio.',
19
24
  MCP_ENTERPRISE_RECEIPT_MISSING: 'Falta el recibo enterprise de MCP.',
20
25
  BACKEND_AVOID_EXPLICIT_ANY: 'Se detectó uso de "any" explícito en backend.',
26
+ GIT_ATOMICITY_TOO_MANY_SCOPES: 'El cambio toca demasiados scopes para un commit atómico.',
27
+ TRACKING_CANONICAL_IN_PROGRESS_INVALID:
28
+ 'El tracking canónico tiene una tarea activa inválida.',
29
+ TRACKING_CANONICAL_SOURCE_CONFLICT:
30
+ 'Hay conflicto entre fuentes de tracking canónico.',
31
+ ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES_HIGH:
32
+ 'No hay reglas activas para cambios de código.',
21
33
  };
22
34
 
23
35
  const ENGLISH_CAUSE_HINTS = [
@@ -53,9 +65,19 @@ const buildGenericSpanishBlockedCauseSummary = (
53
65
 
54
66
  const toKnownSpanishCauseFromMessage = (message: string): string | null => {
55
67
  const normalized = message.toLowerCase();
68
+ const trackingContext = extractNotificationTrackingContext(message);
69
+ if (trackingContext) {
70
+ return buildNotificationTrackingCauseSummary(trackingContext);
71
+ }
56
72
  if (normalized.includes('avoid explicit any')) {
57
73
  return BLOCKED_CAUSE_SUMMARY_BY_CODE.BACKEND_AVOID_EXPLICIT_ANY;
58
74
  }
75
+ if (normalized.includes('atomicity')) {
76
+ return BLOCKED_CAUSE_SUMMARY_BY_CODE.GIT_ATOMICITY_TOO_MANY_SCOPES;
77
+ }
78
+ if (normalized.includes('evidence ai gate status is blocked')) {
79
+ return BLOCKED_CAUSE_SUMMARY_BY_CODE.EVIDENCE_GATE_BLOCKED;
80
+ }
59
81
  if (normalized.includes('evidence is stale')) {
60
82
  return BLOCKED_CAUSE_SUMMARY_BY_CODE.EVIDENCE_STALE;
61
83
  }
@@ -74,6 +96,10 @@ export const resolveBlockedCauseSummary = (
74
96
  event: Extract<PumukiCriticalNotificationEvent, { kind: 'gate.blocked' }>,
75
97
  causeCode: string
76
98
  ): string => {
99
+ const trackingContext = extractNotificationTrackingContext(event.causeMessage);
100
+ if (trackingContext) {
101
+ return buildNotificationTrackingCauseSummary(trackingContext);
102
+ }
77
103
  const mapped = BLOCKED_CAUSE_SUMMARY_BY_CODE[causeCode];
78
104
  if (mapped) {
79
105
  return mapped;
@@ -3,8 +3,16 @@ import {
3
3
  normalizeNotificationText,
4
4
  truncateNotificationText,
5
5
  } from './framework-menu-system-notifications-text';
6
+ import {
7
+ extractNotificationTrackingContext,
8
+ TRACKING_BLOCKED_REMEDIATION,
9
+ } from './framework-menu-system-notifications-tracking';
10
+
11
+ type BlockedRemediationVariant = 'banner' | 'dialog';
6
12
 
7
13
  const BLOCKED_REMEDIATION_BY_CODE: Readonly<Record<string, string>> = {
14
+ EVIDENCE_GATE_BLOCKED:
15
+ 'Revisa status/doctor para ver la causa exacta del gate, corrígela y revalida.',
8
16
  EVIDENCE_MISSING: 'Genera la evidencia del slice actual y vuelve a validar esta fase.',
9
17
  EVIDENCE_INVALID: 'Regenera la evidencia de esta iteración y repite la validación.',
10
18
  EVIDENCE_CHAIN_INVALID: 'Regenera la evidencia para restaurar la cadena de integridad y vuelve a validar.',
@@ -19,6 +27,10 @@ const BLOCKED_REMEDIATION_BY_CODE: Readonly<Record<string, string>> = {
19
27
  BACKEND_AVOID_EXPLICIT_ANY: 'Sustituye `any` por tipos concretos en backend y relanza el gate.',
20
28
  GIT_ATOMICITY_TOO_MANY_SCOPES: 'Divide el cambio por scope o en commits más pequeños y vuelve a ejecutar el gate.',
21
29
  SOLID_HEURISTIC: 'Corrige la violación detectada y vuelve a ejecutar el gate.',
30
+ TRACKING_CANONICAL_IN_PROGRESS_INVALID: TRACKING_BLOCKED_REMEDIATION,
31
+ TRACKING_CANONICAL_SOURCE_CONFLICT: TRACKING_BLOCKED_REMEDIATION,
32
+ ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES_HIGH:
33
+ 'Ejecuta `pumuki policy reconcile --strict --json` y revalida antes de continuar.',
22
34
  };
23
35
 
24
36
  const BLOCKED_REMEDIATION_MAX_LENGTH_BY_VARIANT: Readonly<Record<BlockedRemediationVariant, number>> = {
@@ -39,6 +51,11 @@ const normalizeBlockedRemediation = (value: string): string =>
39
51
  const resolveFallbackRemediation = (causeCode: string): string =>
40
52
  BLOCKED_REMEDIATION_BY_CODE[causeCode] ?? GENERIC_BLOCKED_REMEDIATION;
41
53
 
54
+ const isGenericPolicyReconcileRemediation = (message: string): boolean => {
55
+ const normalized = message.toLowerCase();
56
+ return normalized.includes('policy reconcile') && normalized.includes('sdd validate');
57
+ };
58
+
42
59
  const hasEnglishHints = (message: string): boolean => {
43
60
  const normalized = message.toLowerCase();
44
61
  return [
@@ -88,7 +105,16 @@ export const resolveBlockedRemediation = (
88
105
  const fromEvent = event.remediation
89
106
  ? normalizeBlockedRemediation(event.remediation)
90
107
  : '';
108
+ if (extractNotificationTrackingContext(event.causeMessage)) {
109
+ return truncateNotificationText(TRACKING_BLOCKED_REMEDIATION, maxLength);
110
+ }
91
111
  if (fromEvent.length > 0) {
112
+ if (
113
+ causeCode === 'EVIDENCE_GATE_BLOCKED' &&
114
+ isGenericPolicyReconcileRemediation(fromEvent)
115
+ ) {
116
+ return truncateNotificationText(resolveFallbackRemediation(causeCode), maxLength);
117
+ }
92
118
  const translated = toKnownSpanishRemediationFromMessage(fromEvent, causeCode);
93
119
  if (translated) {
94
120
  return truncateNotificationText(translated, maxLength);
@@ -0,0 +1,42 @@
1
+ export type NotificationTrackingContext = {
2
+ activeEntry?: string;
3
+ trackingSource?: string;
4
+ };
5
+
6
+ const TRACKING_CONTEXT_PATTERN = /\b(active_entries=|tracking_source=|TRACKING_CANONICAL_)/u;
7
+
8
+ export const extractNotificationTrackingContext = (
9
+ message?: string
10
+ ): NotificationTrackingContext | null => {
11
+ if (!message || !TRACKING_CONTEXT_PATTERN.test(message)) {
12
+ return null;
13
+ }
14
+ const activeEntry = message
15
+ .match(/\bactive_entries=([^,\s]+)/u)?.[1]
16
+ ?.replace(/@L\d+$/u, '')
17
+ .trim();
18
+ const trackingSource = message.match(/\btracking_source=([^\s]+)/u)?.[1]?.trim();
19
+ return {
20
+ activeEntry: activeEntry && activeEntry.length > 0 ? activeEntry : undefined,
21
+ trackingSource: trackingSource && trackingSource.length > 0 ? trackingSource : undefined,
22
+ };
23
+ };
24
+
25
+ export const buildNotificationTrackingCauseSummary = (
26
+ context: NotificationTrackingContext
27
+ ): string => {
28
+ if (context.activeEntry && context.trackingSource) {
29
+ return `Tracking bloqueado: ${context.activeEntry} en ${context.trackingSource}.`;
30
+ }
31
+ if (context.activeEntry) {
32
+ return `Tracking bloqueado: ${context.activeEntry}.`;
33
+ }
34
+ if (context.trackingSource) {
35
+ return `Tracking bloqueado en ${context.trackingSource}.`;
36
+ }
37
+ return 'El tracking canónico del repo bloquea la governance.';
38
+ };
39
+
40
+ export const TRACKING_BLOCKED_REMEDIATION =
41
+ 'Corrige el MD de tracking: deja una única tarea activa válida y vuelve a ejecutar el gate.';
42
+