pumuki 6.3.129 → 6.3.130

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.
Files changed (77) hide show
  1. package/AGENTS.md +16 -1
  2. package/CHANGELOG.md +9 -0
  3. package/README.md +14 -10
  4. package/VERSION +1 -1
  5. package/core/facts/detectors/text/android.test.ts +2583 -24
  6. package/core/facts/detectors/text/android.ts +4633 -34
  7. package/core/facts/detectors/typescript/index.test.ts +3639 -73
  8. package/core/facts/detectors/typescript/index.ts +4819 -270
  9. package/core/facts/extractHeuristicFacts.ts +271 -6
  10. package/core/rules/presets/heuristics/android.test.ts +314 -1
  11. package/core/rules/presets/heuristics/android.ts +1220 -50
  12. package/core/rules/presets/heuristics/typescript.test.ts +158 -2
  13. package/core/rules/presets/heuristics/typescript.ts +508 -0
  14. package/docs/README.md +3 -3
  15. package/docs/operations/RELEASE_NOTES.md +7 -1
  16. package/docs/operations/framework-menu-consumer-walkthrough.md +18 -15
  17. package/docs/product/API_REFERENCE.md +1 -1
  18. package/docs/product/USAGE.md +1 -1
  19. package/docs/validation/README.md +3 -1
  20. package/integrations/config/skillsCompilerTemplates.test.ts +131 -0
  21. package/integrations/config/skillsCompilerTemplates.ts +953 -7
  22. package/integrations/config/skillsDetectorRegistry.ts +451 -8
  23. package/integrations/config/skillsMarkdownRules.ts +884 -2
  24. package/integrations/evidence/buildEvidence.ts +29 -1
  25. package/integrations/evidence/platformSummary.test.ts +73 -9
  26. package/integrations/evidence/platformSummary.ts +165 -7
  27. package/integrations/evidence/repoState.ts +3 -0
  28. package/integrations/evidence/schema.ts +18 -0
  29. package/integrations/evidence/trackingContract.ts +17 -0
  30. package/integrations/evidence/writeEvidence.test.ts +3 -0
  31. package/integrations/evidence/writeEvidence.ts +29 -1
  32. package/integrations/gate/evaluateAiGate.ts +251 -8
  33. package/integrations/gate/governanceActionCatalog.ts +275 -0
  34. package/integrations/gate/remediationCatalog.ts +8 -0
  35. package/integrations/git/runPlatformGate.ts +9 -1
  36. package/integrations/lifecycle/adapter.ts +24 -0
  37. package/integrations/lifecycle/bootstrapManifest.ts +248 -0
  38. package/integrations/lifecycle/cliGovernanceConsole.ts +69 -0
  39. package/integrations/lifecycle/governanceNextAction.ts +171 -0
  40. package/integrations/lifecycle/governanceObservationSnapshot.ts +369 -0
  41. package/integrations/lifecycle/packageInfo.ts +118 -1
  42. package/integrations/lifecycle/state.ts +8 -1
  43. package/integrations/lifecycle/trackingState.ts +403 -0
  44. package/integrations/lifecycle/watch.ts +1 -1
  45. package/integrations/mcp/aiGateCheck.ts +194 -10
  46. package/integrations/mcp/alignedPlatformGate.ts +232 -0
  47. package/integrations/mcp/enterpriseServer.ts +19 -3
  48. package/integrations/mcp/preFlightCheck.ts +66 -3
  49. package/integrations/mcp/readMcpPrePushStdin.ts +7 -0
  50. package/package.json +1 -1
  51. package/scripts/build-ruralgo-s1-evidence-pack.ts +85 -0
  52. package/scripts/check-tracking-single-active.sh +1 -1
  53. package/scripts/framework-menu-advanced-view-lib.ts +49 -0
  54. package/scripts/framework-menu-consumer-actions-lib.ts +32 -32
  55. package/scripts/framework-menu-consumer-preflight-render.ts +10 -0
  56. package/scripts/framework-menu-consumer-preflight-run.ts +23 -0
  57. package/scripts/framework-menu-consumer-preflight-types.ts +12 -0
  58. package/scripts/framework-menu-consumer-runtime-actions.ts +11 -5
  59. package/scripts/framework-menu-consumer-runtime-audit.ts +0 -28
  60. package/scripts/framework-menu-consumer-runtime-evidence-classic.ts +118 -42
  61. package/scripts/framework-menu-consumer-runtime-lib.ts +38 -0
  62. package/scripts/framework-menu-consumer-runtime-menu.ts +55 -15
  63. package/scripts/framework-menu-consumer-runtime-types.ts +4 -0
  64. package/scripts/framework-menu-evidence-summary-read.ts +17 -1
  65. package/scripts/framework-menu-evidence-summary-types.ts +3 -0
  66. package/scripts/framework-menu-layout-data.ts +3 -23
  67. package/scripts/framework-menu-system-notifications-macos-applescript-dialog.ts +1 -1
  68. package/scripts/framework-menu-system-notifications-macos-dialog-payload.ts +14 -2
  69. package/scripts/framework-menu-system-notifications-macos-swift-source.ts +1 -1
  70. package/scripts/framework-menu-system-notifications-payloads-blocked.ts +128 -4
  71. package/scripts/framework-menu-system-notifications-payloads.ts +8 -1
  72. package/scripts/framework-menu-system-notifications-text.ts +7 -1
  73. package/scripts/framework-menu.ts +37 -2
  74. package/scripts/package-install-smoke-consumer-git-repo-lib.ts +10 -1
  75. package/scripts/package-install-smoke-consumer-npm-lib.ts +46 -9
  76. package/scripts/ruralgo-s1-evidence-pack-lib.ts +200 -0
  77. package/skills.lock.json +613 -698
@@ -13,10 +13,10 @@ import {
13
13
  renderBadge,
14
14
  } from './framework-menu-ui-components-lib';
15
15
  import { isMenuUiV2Enabled } from './framework-menu-ui-version-lib';
16
- import type {
17
- ConsumerAction,
18
- ConsumerRuntimeWrite,
19
- } from './framework-menu-consumer-runtime-types';
16
+ import { buildGovernanceConsoleSummaryLines } from '../integrations/lifecycle/cliGovernanceConsole';
17
+ import type { GovernanceConsoleSnapshot } from '../integrations/lifecycle/cliGovernanceConsole';
18
+ import type { ConsumerPreflightResult } from './framework-menu-consumer-preflight-types';
19
+ import type { ConsumerAction, ConsumerRuntimeWrite } from './framework-menu-consumer-runtime-types';
20
20
 
21
21
  const buildConsumerRuntimeMenuStatus = (
22
22
  menuSummary: FrameworkMenuEvidenceSummary
@@ -32,24 +32,35 @@ const buildConsumerRuntimeMenuStatus = (
32
32
  : { level: 'info', label: (menuSummary.outcome ?? 'UNKNOWN').trim().toUpperCase() };
33
33
 
34
34
  export const renderConsumerRuntimeClassicMenu = (
35
- actions: ReadonlyArray<ConsumerAction>,
36
- useColor: () => boolean
35
+ params: {
36
+ actions: ReadonlyArray<ConsumerAction>;
37
+ repoRoot: string;
38
+ useColor: () => boolean;
39
+ }
37
40
  ): string => {
38
- const groupedActions = resolveConsumerMenuLayout(actions);
41
+ const menuSummary = readEvidenceSummaryForMenu(params.repoRoot);
42
+ const menuStatus = buildConsumerRuntimeMenuStatus(menuSummary);
43
+ const tokens = buildCliDesignTokens({
44
+ width: resolveLegacyPanelOuterWidth(),
45
+ color: params.useColor(),
46
+ });
47
+ const groupedActions = resolveConsumerMenuLayout(params.actions);
39
48
  const lines = [
40
49
  'PUMUKI — Hook-System (run: npx ast-hooks)',
41
50
  'AST Intelligence System Overview',
42
- 'A. Switch to advanced menu',
43
51
  '',
44
52
  ...groupedActions.flatMap((group) => [
45
- group.title,
46
- ...group.items.map((item) => `${item.id}) ${item.action.label}`),
53
+ ...(group.title.trim().length > 0 ? [group.title] : []),
54
+ ...group.items.map((item) => renderActionRow({
55
+ id: item.id,
56
+ label: item.action.label,
57
+ })),
47
58
  '',
48
59
  ]),
49
60
  ];
50
61
  return renderLegacyPanel(lines, {
51
62
  width: resolveLegacyPanelOuterWidth(),
52
- color: useColor(),
63
+ color: params.useColor(),
53
64
  });
54
65
  };
55
66
 
@@ -58,6 +69,8 @@ export const renderConsumerRuntimeModernMenu = (
58
69
  actions: ReadonlyArray<ConsumerAction>;
59
70
  repoRoot: string;
60
71
  useColor: () => boolean;
72
+ preflight?: ConsumerPreflightResult | null;
73
+ governanceConsole?: GovernanceConsoleSnapshot | null;
61
74
  }
62
75
  ): string => {
63
76
  const menuSummary = readEvidenceSummaryForMenu(params.repoRoot);
@@ -67,14 +80,33 @@ export const renderConsumerRuntimeModernMenu = (
67
80
  color: params.useColor(),
68
81
  });
69
82
  const groupedActions = resolveConsumerMenuLayout(params.actions);
83
+ const governanceConsole =
84
+ params.preflight
85
+ ? {
86
+ governanceObservation: params.preflight.governanceObservation,
87
+ governanceNextAction: params.preflight.governanceNextAction,
88
+ policyValidation: params.preflight.policyValidation,
89
+ experimentalFeatures: params.preflight.experimentalFeatures,
90
+ }
91
+ : params.governanceConsole;
70
92
  const lines = [
71
93
  'PUMUKI — Hook-System (run: npx ast-hooks)',
72
94
  'AST Intelligence System Overview',
73
- `Status: ${renderBadge(menuStatus.label, menuStatus.level, tokens)}`,
74
- 'A. Switch to advanced menu',
95
+ ...(governanceConsole
96
+ ? [
97
+ '',
98
+ 'Governance Console',
99
+ ...buildGovernanceConsoleSummaryLines({
100
+ governanceObservation: governanceConsole.governanceObservation,
101
+ governanceNextAction: governanceConsole.governanceNextAction,
102
+ policyValidation: governanceConsole.policyValidation,
103
+ experimentalFeatures: governanceConsole.experimentalFeatures,
104
+ }).map((line) => ` ${line}`),
105
+ ]
106
+ : []),
75
107
  '',
76
108
  ...groupedActions.flatMap((group) => [
77
- group.title,
109
+ ...(group.title.trim().length > 0 ? [group.title] : []),
78
110
  ...group.items.map((item) => renderActionRow({
79
111
  id: item.id,
80
112
  label: item.action.label,
@@ -93,8 +125,14 @@ export const printConsumerRuntimeMenu = (params: {
93
125
  repoRoot: string;
94
126
  useColor: () => boolean;
95
127
  write: ConsumerRuntimeWrite;
128
+ preflight?: ConsumerPreflightResult | null;
129
+ governanceConsole?: GovernanceConsoleSnapshot | null;
96
130
  }): void => {
97
- const classicMenu = renderConsumerRuntimeClassicMenu(params.actions, params.useColor);
131
+ const classicMenu = renderConsumerRuntimeClassicMenu({
132
+ actions: params.actions,
133
+ repoRoot: params.repoRoot,
134
+ useColor: params.useColor,
135
+ });
98
136
  if (!isMenuUiV2Enabled()) {
99
137
  params.write(`\n${classicMenu}\n`);
100
138
  return;
@@ -105,6 +143,8 @@ export const printConsumerRuntimeMenu = (params: {
105
143
  actions: params.actions,
106
144
  repoRoot: params.repoRoot,
107
145
  useColor: params.useColor,
146
+ preflight: params.preflight,
147
+ governanceConsole: params.governanceConsole,
108
148
  })}\n`);
109
149
  } catch {
110
150
  params.write('\n[pumuki][menu-ui-v2] Render failed. Falling back to classic menu.\n');
@@ -1,8 +1,10 @@
1
1
  import type { FrameworkMenuEvidenceSummary } from './framework-menu-evidence-summary-lib';
2
+ import type { ConsumerPreflightResult } from './framework-menu-consumer-preflight-types';
2
3
  import type {
3
4
  PumukiCriticalNotificationEvent,
4
5
  SystemNotificationEmitResult,
5
6
  } from './framework-menu-system-notifications-lib';
7
+ import type { GovernanceConsoleSnapshot } from '../integrations/lifecycle/cliGovernanceConsole';
6
8
 
7
9
  export type ConsumerAction = {
8
10
  id: string;
@@ -40,6 +42,7 @@ export type ConsumerMenuRuntimeParams = {
40
42
  stage: 'PRE_COMMIT' | 'PRE_PUSH'
41
43
  ) => Promise<string | void> | string | void;
42
44
  emitSystemNotification?: ConsumerRuntimeEmitNotification;
45
+ readGovernanceConsole?: () => GovernanceConsoleSnapshot | null;
43
46
  write: ConsumerRuntimeWrite;
44
47
  };
45
48
 
@@ -47,6 +50,7 @@ export type ConsumerMenuRuntime = {
47
50
  actions: ReadonlyArray<ConsumerAction>;
48
51
  printMenu: () => void;
49
52
  readCurrentSummary: () => FrameworkMenuEvidenceSummary | null;
53
+ readLastPreflight: () => ConsumerPreflightResult | null;
50
54
  };
51
55
 
52
56
  export type ConsumerRuntimeScope = 'staged' | 'unstaged' | 'workingTree';
@@ -43,6 +43,13 @@ const sumLegacySeverityBand = (row: { by_severity?: unknown }): number => {
43
43
  return total;
44
44
  };
45
45
 
46
+ const optionalNonNegativeInt = (value: unknown): number | undefined => {
47
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
48
+ return undefined;
49
+ }
50
+ return Math.max(0, Math.trunc(value));
51
+ };
52
+
46
53
  const parsePlatformAuditRows = (
47
54
  snapshot: EvidenceSnapshot
48
55
  ): ReadonlyArray<FrameworkMenuEvidencePlatformRow> => {
@@ -57,7 +64,16 @@ const parsePlatformAuditRows = (
57
64
  }
58
65
  const platform = toStringOrNull((entry as { platform?: unknown }).platform) ?? 'Other';
59
66
  const violations = sumLegacySeverityBand(entry as { by_severity?: unknown });
60
- rows.push({ platform, violations });
67
+ const activeRules = optionalNonNegativeInt((entry as { active_rules?: unknown }).active_rules);
68
+ const evaluatedRules = optionalNonNegativeInt(
69
+ (entry as { evaluated_rules?: unknown }).evaluated_rules
70
+ );
71
+ rows.push({
72
+ platform,
73
+ violations,
74
+ ...(typeof activeRules === 'number' ? { activeRules } : {}),
75
+ ...(typeof evaluatedRules === 'number' ? { evaluatedRules } : {}),
76
+ });
61
77
  }
62
78
  return rows;
63
79
  };
@@ -19,6 +19,7 @@ export type EvidenceSnapshot = {
19
19
  outcome?: unknown;
20
20
  findings?: unknown;
21
21
  platforms?: unknown;
22
+ rules_coverage?: unknown;
22
23
  };
23
24
 
24
25
  export type EvidenceSeverityMetrics = {
@@ -28,6 +29,8 @@ export type EvidenceSeverityMetrics = {
28
29
  export type FrameworkMenuEvidencePlatformRow = {
29
30
  platform: string;
30
31
  violations: number;
32
+ activeRules?: number;
33
+ evaluatedRules?: number;
31
34
  };
32
35
 
33
36
  export type FrameworkMenuEvidenceSummary = {
@@ -2,29 +2,9 @@ import type { MenuLayoutGroup } from './framework-menu-layout-types';
2
2
 
3
3
  export const CONSUMER_MENU_LAYOUT: ReadonlyArray<MenuLayoutGroup> = [
4
4
  {
5
- key: 'read-only-gates',
6
- title: 'Read-Only Gate Flows',
7
- itemIds: ['1', '2', '3', '4'],
8
- },
9
- {
10
- key: 'engine-working-tree-no-preflight',
11
- title: 'Engine · working tree (no preflight)',
12
- itemIds: ['11', '12', '13', '14'],
13
- },
14
- {
15
- key: 'export',
16
- title: 'Legacy Read-Only Export',
17
- itemIds: ['8'],
18
- },
19
- {
20
- key: 'legacy-read-only-diagnostics',
21
- title: 'Legacy Read-Only Diagnostics',
22
- itemIds: ['5', '6', '7', '9'],
23
- },
24
- {
25
- key: 'system',
26
- title: 'System',
27
- itemIds: ['10'],
5
+ key: 'ast-intelligence-legacy',
6
+ title: '',
7
+ itemIds: ['1', '2', '3', '4', '5', '6', '7', '8', '9'],
28
8
  },
29
9
  ];
30
10
 
@@ -13,6 +13,6 @@ export const buildDisplayDialogScript = (params: {
13
13
  const title = escapeAppleScriptString(params.title);
14
14
  const cause = escapeAppleScriptString(params.cause);
15
15
  const remediation = escapeAppleScriptString(params.remediation);
16
- const message = escapeAppleScriptString(`Causa: ${cause}\n\nSolución: ${remediation}`);
16
+ const message = escapeAppleScriptString(`${cause}\n\n${remediation}`);
17
17
  return `display dialog "${message}" with title "${title}" buttons {"${BLOCKED_DIALOG_DISABLE}", "${BLOCKED_DIALOG_MUTE_30}", "${BLOCKED_DIALOG_KEEP}"} default button "${BLOCKED_DIALOG_KEEP}" with icon stop giving up after 15`;
18
18
  };
@@ -1,6 +1,8 @@
1
1
  import type { PumukiCriticalNotificationEvent } from './framework-menu-system-notifications-types';
2
2
  import {
3
3
  resolveBlockedCauseSummary,
4
+ resolveBlockedCommand,
5
+ resolveBlockedImpactSummary,
4
6
  resolveBlockedRemediation,
5
7
  resolveProjectLabel,
6
8
  } from './framework-menu-system-notifications-payloads';
@@ -17,8 +19,18 @@ export const buildBlockedDialogPayload = (params: {
17
19
  env: NodeJS.ProcessEnv;
18
20
  }): BlockedDialogPayload => {
19
21
  const causeCode = params.event.causeCode ?? 'GATE_BLOCKED';
20
- const cause = resolveBlockedCauseSummary(params.event, causeCode);
21
- const remediation = resolveBlockedRemediation(params.event, causeCode);
22
+ const cause = [
23
+ `Causa: ${resolveBlockedCauseSummary(params.event, causeCode)}`,
24
+ `Impacto: ${resolveBlockedImpactSummary(params.event, causeCode)}`,
25
+ ].join('\n');
26
+ const remediation = [
27
+ `Comando: ${resolveBlockedCommand({
28
+ event: params.event,
29
+ repoRoot: params.repoRoot,
30
+ causeCode,
31
+ })}`,
32
+ `Siguiente acción: ${resolveBlockedRemediation(params.event, causeCode)}`,
33
+ ].join('\n');
22
34
  const projectLabel = resolveProjectLabel({
23
35
  repoRoot: params.repoRoot,
24
36
  projectLabel: params.env.PUMUKI_PROJECT_LABEL,
@@ -41,7 +41,7 @@ final class DialogAppDelegate: NSObject, NSApplicationDelegate {
41
41
 
42
42
  let alert = NSAlert()
43
43
  alert.messageText = config.title
44
- alert.informativeText = "Causa: \(config.cause)\n\nSolución: \(config.remediation)"
44
+ alert.informativeText = "\(config.cause)\n\n\(config.remediation)"
45
45
  alert.alertStyle = .critical
46
46
  alert.addButton(withTitle: config.keepButton)
47
47
  alert.addButton(withTitle: config.muteButton)
@@ -2,6 +2,7 @@ import type {
2
2
  PumukiCriticalNotificationEvent,
3
3
  SystemNotificationPayload,
4
4
  } from './framework-menu-system-notifications-types';
5
+ import { getCurrentPumukiVersion } from '../integrations/lifecycle/packageInfo';
5
6
  import { resolveBlockedCauseSummary } from './framework-menu-system-notifications-cause';
6
7
  import { resolveBlockedRemediation } from './framework-menu-system-notifications-remediation';
7
8
  import { truncateNotificationText } from './framework-menu-system-notifications-text';
@@ -13,20 +14,143 @@ export {
13
14
  resolveBlockedRemediation,
14
15
  } from './framework-menu-system-notifications-remediation';
15
16
 
17
+ export const resolveBlockedImpactSummary = (
18
+ event: Extract<PumukiCriticalNotificationEvent, { kind: 'gate.blocked' }>,
19
+ causeCode: string
20
+ ): string => {
21
+ if (causeCode === 'EVIDENCE_GATE_BLOCKED') {
22
+ return 'El gate no deja avanzar hasta revalidar la evidencia y la gobernanza del repo.';
23
+ }
24
+ if (causeCode === 'TRACKING_CANONICAL_IN_PROGRESS_INVALID') {
25
+ return 'El tracking canónico bloquea el avance hasta dejar una única tarea activa real.';
26
+ }
27
+ if (causeCode === 'TRACKING_CANONICAL_SOURCE_CONFLICT') {
28
+ return 'Las fuentes de tracking no coinciden y el loop enterprise no puede decidir autoridad.';
29
+ }
30
+ if (causeCode === 'PRE_PUSH_UPSTREAM_MISSING') {
31
+ return 'No puedes publicar el cambio hasta fijar upstream en la rama actual.';
32
+ }
33
+ if (causeCode === 'EVIDENCE_STALE') {
34
+ return `La evidencia de ${event.stage} ya no describe el estado real del repo.`;
35
+ }
36
+ return `No se puede continuar en ${event.stage} hasta resolver el bloqueo detectado.`;
37
+ };
38
+
39
+ const buildPinnedPumukiCommand = (params: {
40
+ repoRoot: string;
41
+ executableAndArgs: string;
42
+ }): string =>
43
+ `npx --yes --package pumuki@${getCurrentPumukiVersion({ repoRoot: params.repoRoot })} ${params.executableAndArgs}`;
44
+
45
+ export const resolveBlockedCommand = (params: {
46
+ event: Extract<PumukiCriticalNotificationEvent, { kind: 'gate.blocked' }>;
47
+ repoRoot: string;
48
+ causeCode: string;
49
+ }): string => {
50
+ switch (params.causeCode) {
51
+ case 'EVIDENCE_MISSING':
52
+ case 'EVIDENCE_INVALID':
53
+ case 'EVIDENCE_CHAIN_INVALID':
54
+ case 'EVIDENCE_STALE':
55
+ return buildPinnedPumukiCommand({
56
+ repoRoot: params.repoRoot,
57
+ executableAndArgs: `pumuki sdd validate --stage=${params.event.stage} --json`,
58
+ });
59
+ case 'EVIDENCE_ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES':
60
+ case 'EVIDENCE_SKILLS_CONTRACT_INCOMPLETE':
61
+ case 'EVIDENCE_PLATFORM_SKILLS_SCOPE_INCOMPLETE':
62
+ case 'EVIDENCE_PLATFORM_SKILLS_BUNDLES_MISSING':
63
+ case 'EVIDENCE_PLATFORM_CRITICAL_SKILLS_RULES_MISSING':
64
+ case 'EVIDENCE_CROSS_PLATFORM_CRITICAL_ENFORCEMENT_INCOMPLETE':
65
+ return `${buildPinnedPumukiCommand({
66
+ repoRoot: params.repoRoot,
67
+ executableAndArgs: 'pumuki policy reconcile --strict --json',
68
+ })} && ${buildPinnedPumukiCommand({
69
+ repoRoot: params.repoRoot,
70
+ executableAndArgs: `pumuki sdd validate --stage=${params.event.stage} --json`,
71
+ })}`;
72
+ case 'TRACKING_CANONICAL_IN_PROGRESS_INVALID':
73
+ case 'TRACKING_CANONICAL_SOURCE_CONFLICT':
74
+ return 'Editar PUMUKI-RESET-MASTER-PLAN.md y dejar una sola tarea activa válida.';
75
+ case 'PRE_PUSH_UPSTREAM_MISSING':
76
+ return 'git push --set-upstream origin <branch>';
77
+ case 'GITFLOW_PROTECTED_BRANCH':
78
+ return 'git checkout -b feature/<descripcion-kebab-case>';
79
+ case 'EVIDENCE_GATE_BLOCKED':
80
+ return `${buildPinnedPumukiCommand({
81
+ repoRoot: params.repoRoot,
82
+ executableAndArgs: 'pumuki policy reconcile --strict --json',
83
+ })} && ${buildPinnedPumukiCommand({
84
+ repoRoot: params.repoRoot,
85
+ executableAndArgs: `pumuki sdd validate --stage=${params.event.stage} --json`,
86
+ })}`;
87
+ default:
88
+ return resolveBlockedRemediation(params.event, params.causeCode);
89
+ }
90
+ };
91
+
92
+ export const buildBlockedActionableMessage = (params: {
93
+ event: Extract<PumukiCriticalNotificationEvent, { kind: 'gate.blocked' }>;
94
+ repoRoot: string;
95
+ causeCode: string;
96
+ }): string => {
97
+ const cause = resolveBlockedCauseSummary(params.event, params.causeCode);
98
+ const impact = resolveBlockedImpactSummary(params.event, params.causeCode);
99
+ const command = resolveBlockedCommand(params);
100
+ const nextAction = resolveBlockedRemediation(params.event, params.causeCode);
101
+ return [
102
+ `Causa: ${cause}`,
103
+ `Impacto: ${impact}`,
104
+ `Comando: ${command}`,
105
+ `Siguiente acción: ${nextAction}`,
106
+ ].join('\n');
107
+ };
108
+
109
+ type BlockedPayloadContext =
110
+ | string
111
+ | {
112
+ projectPrefix: string;
113
+ repoRoot: string;
114
+ };
115
+
116
+ const normalizeBlockedPayloadContext = (
117
+ context: BlockedPayloadContext
118
+ ): {
119
+ projectPrefix: string;
120
+ repoRoot: string;
121
+ } => {
122
+ if (typeof context === 'string') {
123
+ return {
124
+ projectPrefix: context,
125
+ repoRoot: '',
126
+ };
127
+ }
128
+
129
+ return {
130
+ projectPrefix: context.projectPrefix,
131
+ repoRoot: context.repoRoot,
132
+ };
133
+ };
134
+
16
135
  export const buildGateBlockedPayload = (
17
136
  event: Extract<PumukiCriticalNotificationEvent, { kind: 'gate.blocked' }>,
18
- projectPrefix: string
137
+ context: BlockedPayloadContext
19
138
  ): SystemNotificationPayload => {
139
+ const params = normalizeBlockedPayloadContext(context);
20
140
  const causeCode = event.causeCode ?? 'GATE_BLOCKED';
21
141
  const causeSummary = truncateNotificationText(
22
142
  resolveBlockedCauseSummary(event, causeCode),
23
143
  72
24
144
  );
25
- const remediation = resolveBlockedRemediation(event, causeCode);
145
+ const actionableMessage = buildBlockedActionableMessage({
146
+ event,
147
+ repoRoot: params.repoRoot,
148
+ causeCode,
149
+ });
26
150
  return {
27
151
  title: '🔴 Pumuki bloqueado',
28
- subtitle: `${projectPrefix}${event.stage} · ${causeSummary}`,
29
- message: `Solución: ${remediation}`,
152
+ subtitle: `${params.projectPrefix}${event.stage} · ${causeSummary}`,
153
+ message: actionableMessage,
30
154
  soundName: 'Basso',
31
155
  };
32
156
  };
@@ -10,6 +10,8 @@ import { buildAuditSummaryPayload } from './framework-menu-system-notifications-
10
10
  import {
11
11
  buildGateBlockedPayload,
12
12
  resolveBlockedCauseSummary,
13
+ resolveBlockedImpactSummary,
14
+ resolveBlockedCommand,
13
15
  resolveBlockedRemediation,
14
16
  } from './framework-menu-system-notifications-payloads-blocked';
15
17
  import {
@@ -20,6 +22,8 @@ import {
20
22
  export { resolveProjectLabel } from './framework-menu-system-notifications-payloads-context';
21
23
  export {
22
24
  resolveBlockedCauseSummary,
25
+ resolveBlockedImpactSummary,
26
+ resolveBlockedCommand,
23
27
  resolveBlockedRemediation,
24
28
  } from './framework-menu-system-notifications-payloads-blocked';
25
29
  export {
@@ -46,7 +50,10 @@ export const buildSystemNotificationPayload = (
46
50
  return buildAuditSummaryPayload(event);
47
51
  }
48
52
  if (event.kind === 'gate.blocked') {
49
- return buildGateBlockedPayload(event, projectPrefix);
53
+ return buildGateBlockedPayload(event, {
54
+ projectPrefix,
55
+ repoRoot: context?.repoRoot ?? '',
56
+ });
50
57
  }
51
58
  if (event.kind === 'evidence.stale') {
52
59
  return buildEvidenceStalePayload(event);
@@ -7,7 +7,13 @@ export const truncateNotificationText = (value: string, maxLength: number): stri
7
7
  if (value.length <= maxLength) {
8
8
  return value;
9
9
  }
10
- return `${value.slice(0, Math.max(1, maxLength - 1)).trimEnd()}…`;
10
+ const limit = Math.max(1, maxLength - 1);
11
+ const trimmed = value.slice(0, limit).trimEnd();
12
+ const boundary = trimmed.lastIndexOf(' ');
13
+ const shortened = boundary >= Math.floor(limit * 0.6)
14
+ ? trimmed.slice(0, boundary).trimEnd()
15
+ : trimmed;
16
+ return `${shortened}…`;
11
17
  };
12
18
 
13
19
  export const resolveProjectLabel = (params: {
@@ -25,6 +25,8 @@ import {
25
25
  loadAndFormatActiveSkillsBundles,
26
26
  } from './framework-menu-skills-lib';
27
27
  import { isMenuUiV2Enabled } from './framework-menu-ui-version-lib';
28
+ import { readLifecycleStatus } from '../integrations/lifecycle/status';
29
+ import type { GovernanceConsoleSnapshot } from '../integrations/lifecycle/cliGovernanceConsole';
28
30
  export * from './framework-menu-builders';
29
31
  export { formatAdvancedMenuView } from './framework-menu-advanced-view-lib';
30
32
  export { buildMenuGateParams } from './framework-menu-gate-lib';
@@ -43,6 +45,20 @@ const resolveInitialMenuMode = (): MenuMode => {
43
45
  return 'consumer';
44
46
  };
45
47
 
48
+ const readAdvancedGovernanceConsoleSnapshot = (): GovernanceConsoleSnapshot | null => {
49
+ try {
50
+ const status = readLifecycleStatus({ cwd: process.cwd() });
51
+ return {
52
+ governanceObservation: status.governanceObservation,
53
+ governanceNextAction: status.governanceNextAction,
54
+ policyValidation: status.policyValidation,
55
+ experimentalFeatures: status.experimentalFeatures,
56
+ };
57
+ } catch {
58
+ return null;
59
+ }
60
+ };
61
+
46
62
  const menu = async (): Promise<void> => {
47
63
  const rl = createInterface({ input, output });
48
64
 
@@ -76,10 +92,14 @@ const menu = async (): Promise<void> => {
76
92
  consumerRuntime.printMenu();
77
93
  } else {
78
94
  const currentSummary = consumerRuntime.readCurrentSummary();
95
+ const lastPreflight = consumerRuntime.readLastPreflight();
96
+ const governanceConsole = lastPreflight ? null : readAdvancedGovernanceConsoleSnapshot();
79
97
  if (!isMenuUiV2Enabled()) {
80
98
  output.write(
81
99
  `\n${formatAdvancedMenuClassicView(advancedActions, {
82
100
  evidenceSummary: currentSummary ?? undefined,
101
+ preflight: lastPreflight ?? undefined,
102
+ governanceConsole: governanceConsole ?? undefined,
83
103
  })}\n`
84
104
  );
85
105
  } else {
@@ -87,6 +107,8 @@ const menu = async (): Promise<void> => {
87
107
  output.write(
88
108
  `\n${formatAdvancedMenuView(advancedActions, {
89
109
  evidenceSummary: currentSummary ?? undefined,
110
+ preflight: lastPreflight ?? undefined,
111
+ governanceConsole: governanceConsole ?? undefined,
90
112
  })}\n`
91
113
  );
92
114
  } catch {
@@ -94,12 +116,25 @@ const menu = async (): Promise<void> => {
94
116
  output.write(
95
117
  `\n${formatAdvancedMenuClassicView(advancedActions, {
96
118
  evidenceSummary: currentSummary ?? undefined,
119
+ preflight: lastPreflight ?? undefined,
120
+ governanceConsole: governanceConsole ?? undefined,
97
121
  })}\n`
98
122
  );
99
123
  }
100
124
  }
101
125
  }
102
- const option = (await rl.question('\nSelect option: ')).trim();
126
+ let option: string;
127
+ try {
128
+ option = (await rl.question('\nSelect option: ')).trim();
129
+ } catch (error) {
130
+ const code = error instanceof Error && 'code' in error
131
+ ? String((error as { code?: unknown }).code)
132
+ : '';
133
+ if (code === 'ABORT_ERR' || code === 'ERR_USE_AFTER_CLOSE') {
134
+ break;
135
+ }
136
+ throw error;
137
+ }
103
138
  const normalized = option.toUpperCase();
104
139
 
105
140
  if (mode === 'consumer' && normalized === 'A') {
@@ -118,7 +153,7 @@ const menu = async (): Promise<void> => {
118
153
  output.write('Invalid option.\n');
119
154
  continue;
120
155
  }
121
- if (selectedConsumer.id === '10') {
156
+ if (selectedConsumer.id === '9') {
122
157
  break;
123
158
  }
124
159
  try {
@@ -39,8 +39,17 @@ export const configureRemoteAndFeatureBranch = (
39
39
  workspace.tmpRoot
40
40
  );
41
41
  runGitStep(workspace, ['remote', 'add', 'origin', workspace.bareRemote], 'git remote add origin');
42
- runGitStep(workspace, ['push', '-u', 'origin', 'main'], 'git push origin main');
43
42
  runGitStep(workspace, ['checkout', '-b', 'feature/package-smoke'], 'git checkout feature branch');
43
+ runGitStep(
44
+ workspace,
45
+ ['push', '-u', 'origin', 'feature/package-smoke'],
46
+ 'git push origin feature branch'
47
+ );
48
+ runGitStep(
49
+ workspace,
50
+ ['push', 'origin', 'HEAD:refs/heads/main'],
51
+ 'git push origin main from feature branch'
52
+ );
44
53
  runGitStep(
45
54
  workspace,
46
55
  ['branch', '--set-upstream-to=origin/main', 'feature/package-smoke'],