pumuki 6.3.62 → 6.3.64

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/README.md CHANGED
@@ -37,6 +37,8 @@ npx --yes pumuki status
37
37
  npx --yes pumuki doctor --json
38
38
  ```
39
39
 
40
+ Desde **6.3.63**, `npm install` en la raíz de un repo **Git** dispara un `postinstall` que ejecuta `pumuki install` automáticamente (hooks `pre-commit` / `pre-push`). No configura MCP del IDE por sí solo: usa `pumuki install --with-mcp` o el adaptador. Desactivar el postinstall: `PUMUKI_SKIP_POSTINSTALL=1`. En CI suele saltarse solo (`CI=true`). En **6.3.64+**, las notificaciones del sistema en plataformas sin banner nativo se reflejan en **stderr** por defecto (`PUMUKI_DISABLE_STDERR_NOTIFICATIONS=1` para silenciarlas).
41
+
40
42
  Fallback (equivalent in pasos separados):
41
43
 
42
44
  ```bash
package/VERSION CHANGED
@@ -1 +1 @@
1
- v6.3.61
1
+ v6.3.64
@@ -6,6 +6,16 @@ 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-05 (v6.3.64)
10
+
11
+ - **Notificaciones multiplataforma**: fuera de macOS, avisos críticos van a **stderr** por defecto (terminal visible). `PUMUKI_DISABLE_STDERR_NOTIFICATIONS=1` lo silencia para CI/scripts. En macOS, `PUMUKI_NOTIFICATION_STDERR_MIRROR=1` añade copia en terminal; si `osascript`/banner falla, stderr actúa como respaldo.
12
+ - Rollout: repin a `pumuki@6.3.64`; validar hooks y, si usáis notificaciones, comprobar salida en entorno no-macOS.
13
+
14
+ ### 2026-04-05 (v6.3.63)
15
+
16
+ - **Auto-wire on install**: el paquete npm ejecuta `postinstall` → `pumuki install` en el repo del consumidor (`INIT_CWD`) cuando hay `.git`. Saltar con `PUMUKI_SKIP_POSTINSTALL=1` o en CI. OpenSpec bootstrap sigue omitido en ese camino (`PUMUKI_SKIP_OPENSPEC_BOOTSTRAP` por defecto ahí). MCP en Cursor/Codex no se configura solo: usar `pumuki install --with-mcp` / adaptador cuando proceda.
17
+ - **Best-effort**: si `doctor` bloquea, el postinstall puede cablear hooks en modo degradado para no dejar el paquete “muerto” en el hook chain.
18
+
9
19
  ### 2026-04-05 (v6.3.62)
10
20
 
11
21
  - Merged `refactor/cli-complexity-reduction-phase4-rebase2` into `develop` (PR #731); GitHub Actions quota may block CI—validate locally with `npm test` before consuming.
@@ -242,6 +242,7 @@ Stage mapping:
242
242
  If a scope is empty, the menu prints an explicit operational hint (`Scope vacío`), so `PASS` with zero findings is distinguishable from a clean repository scan.
243
243
 
244
244
  System notifications (macOS) can be enabled from advanced menu option `31` (persisted in `.pumuki/system-notifications.json`).
245
+ On non-macOS platforms, the same payloads are written to **stderr** by default (visible in the terminal) because there is no native banner API. Set `PUMUKI_DISABLE_STDERR_NOTIFICATIONS=1` to silence that path (delivery reports `unsupported-platform` on those OSes). On macOS, set `PUMUKI_NOTIFICATION_STDERR_MIRROR=1` to duplicate the banner text to stderr in addition to the system notification.
245
246
  Blocked notifications now use a native Swift floating modal (bottom-right) by default, with AppleScript fallback.
246
247
  Override mode with `PUMUKI_MACOS_BLOCKED_DIALOG_MODE=auto|swift-floating|applescript`.
247
248
  Custom skills import is available in advanced menu option `33` (writes `/.pumuki/custom-rules.json`).
@@ -2143,6 +2143,11 @@ export const runLifecycleCli = async (
2143
2143
  }
2144
2144
  case 'install': {
2145
2145
  const result = runLifecycleInstall();
2146
+ if (result.degradedDoctorBypass) {
2147
+ writeInfo(
2148
+ '[pumuki] install: hooks wired under best-effort mode (doctor reported blocking issues; fix baseline and run `pumuki doctor`).'
2149
+ );
2150
+ }
2146
2151
  writeInfo(
2147
2152
  `[pumuki] installed ${result.version} at ${result.repoRoot} (hooks changed: ${result.changedHooks.join(', ') || 'none'})`
2148
2153
  );
@@ -18,6 +18,7 @@ export type LifecycleInstallResult = {
18
18
  version: string;
19
19
  changedHooks: ReadonlyArray<string>;
20
20
  openSpecBootstrap?: OpenSpecBootstrapResult;
21
+ degradedDoctorBypass?: boolean;
21
22
  };
22
23
 
23
24
  const shouldBootstrapEvidence = (repoRoot: string): boolean =>
@@ -38,19 +39,59 @@ const writeBootstrapEvidence = (repoRoot: string): void => {
38
39
  });
39
40
  };
40
41
 
42
+ const wireHooksLifecycleAndBootstrapEvidence = (params: {
43
+ git: ILifecycleGitService;
44
+ repoRoot: string;
45
+ version: string;
46
+ openSpecManagedArtifacts?: ReadonlyArray<string>;
47
+ }): ReadonlyArray<string> => {
48
+ const hookResult = installPumukiHooks(params.repoRoot);
49
+ writeLifecycleState({
50
+ git: params.git,
51
+ repoRoot: params.repoRoot,
52
+ version: params.version,
53
+ openSpecManagedArtifacts: params.openSpecManagedArtifacts,
54
+ });
55
+ ensureRuntimeArtifactsIgnored(params.repoRoot);
56
+ if (shouldBootstrapEvidence(params.repoRoot)) {
57
+ writeBootstrapEvidence(params.repoRoot);
58
+ }
59
+ return hookResult.changedHooks;
60
+ };
61
+
41
62
  export const runLifecycleInstall = (params?: {
42
63
  cwd?: string;
43
64
  git?: ILifecycleGitService;
44
65
  npm?: ILifecycleNpmService;
45
66
  bootstrapOpenSpec?: boolean;
67
+ bestEffortAfterDoctorBlock?: boolean;
46
68
  }): LifecycleInstallResult => {
47
69
  const git = params?.git ?? new LifecycleGitService();
70
+ const bestEffortAfterDoctorBlock =
71
+ params?.bestEffortAfterDoctorBlock ?? process.env.PUMUKI_AUTO_POSTINSTALL === '1';
48
72
  const report = runLifecycleDoctor({
49
73
  cwd: params?.cwd,
50
74
  git,
51
75
  });
52
76
 
53
77
  if (doctorHasBlockingIssues(report)) {
78
+ if (bestEffortAfterDoctorBlock) {
79
+ const version = getCurrentPumukiVersion();
80
+ const priorArtifacts = readOpenSpecManagedArtifacts(git, report.repoRoot);
81
+ const changedHooks = wireHooksLifecycleAndBootstrapEvidence({
82
+ git,
83
+ repoRoot: report.repoRoot,
84
+ version,
85
+ openSpecManagedArtifacts: priorArtifacts.length > 0 ? priorArtifacts : undefined,
86
+ });
87
+ return {
88
+ repoRoot: report.repoRoot,
89
+ version,
90
+ changedHooks,
91
+ openSpecBootstrap: undefined,
92
+ degradedDoctorBypass: true,
93
+ };
94
+ }
54
95
  const renderedIssues = report.issues.map((issue) => `- [${issue.severity}] ${issue.message}`).join('\n');
55
96
  throw new Error(
56
97
  `pumuki install blocked by repository safety checks.\n${renderedIssues}\n` +
@@ -68,7 +109,6 @@ export const runLifecycleInstall = (params?: {
68
109
  })
69
110
  : undefined;
70
111
 
71
- const hookResult = installPumukiHooks(report.repoRoot);
72
112
  const version = getCurrentPumukiVersion();
73
113
  const mergedOpenSpecArtifacts = new Set(
74
114
  readOpenSpecManagedArtifacts(git, report.repoRoot)
@@ -76,22 +116,17 @@ export const runLifecycleInstall = (params?: {
76
116
  for (const artifact of openSpecBootstrap?.managedArtifacts ?? []) {
77
117
  mergedOpenSpecArtifacts.add(artifact);
78
118
  }
79
- writeLifecycleState({
119
+ const changedHooks = wireHooksLifecycleAndBootstrapEvidence({
80
120
  git,
81
121
  repoRoot: report.repoRoot,
82
122
  version,
83
123
  openSpecManagedArtifacts: Array.from(mergedOpenSpecArtifacts),
84
124
  });
85
- ensureRuntimeArtifactsIgnored(report.repoRoot);
86
-
87
- if (shouldBootstrapEvidence(report.repoRoot)) {
88
- writeBootstrapEvidence(report.repoRoot);
89
- }
90
125
 
91
126
  return {
92
127
  repoRoot: report.repoRoot,
93
128
  version,
94
- changedHooks: hookResult.changedHooks,
129
+ changedHooks,
95
130
  openSpecBootstrap,
96
131
  };
97
132
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.62",
3
+ "version": "6.3.64",
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": {
@@ -18,6 +18,7 @@
18
18
  "pumuki-mcp-enterprise-stdio": "bin/pumuki-mcp-enterprise-stdio.js"
19
19
  },
20
20
  "scripts": {
21
+ "postinstall": "node scripts/consumer-postinstall.cjs",
21
22
  "install-hooks": "node bin/pumuki.js install",
22
23
  "check-version": "node bin/pumuki.js status",
23
24
  "pumuki:install": "node bin/pumuki.js install",
@@ -245,6 +246,7 @@
245
246
  "integrations/platform/*.ts",
246
247
  "integrations/telemetry/*.ts",
247
248
  "scripts/*.ts",
249
+ "scripts/consumer-postinstall.cjs",
248
250
  "scripts/adapters/*.ts",
249
251
  "scripts/adapters/*.md",
250
252
  "scripts/*.sh",
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { existsSync } = require('node:fs');
5
+ const { join, resolve } = require('node:path');
6
+ const { spawnSync } = require('node:child_process');
7
+
8
+ const skipReason = () => {
9
+ if (process.env.PUMUKI_SKIP_POSTINSTALL === '1') {
10
+ return 'PUMUKI_SKIP_POSTINSTALL=1';
11
+ }
12
+ if (process.env.CI === 'true' || process.env.CI === '1') {
13
+ return 'CI';
14
+ }
15
+ if (process.env.npm_config_ignore_scripts === 'true') {
16
+ return 'npm ignore-scripts';
17
+ }
18
+ return '';
19
+ };
20
+
21
+ const main = () => {
22
+ const reason = skipReason();
23
+ if (reason) {
24
+ return 0;
25
+ }
26
+
27
+ const consumerRoot = process.env.INIT_CWD || process.cwd();
28
+ if (!existsSync(join(consumerRoot, '.git'))) {
29
+ return 0;
30
+ }
31
+
32
+ const packageRoot = resolve(__dirname, '..');
33
+ const pumukiCli = join(packageRoot, 'bin', 'pumuki.js');
34
+ if (!existsSync(pumukiCli)) {
35
+ return 0;
36
+ }
37
+
38
+ const env = {
39
+ ...process.env,
40
+ PUMUKI_AUTO_POSTINSTALL: '1',
41
+ PUMUKI_SKIP_OPENSPEC_BOOTSTRAP: process.env.PUMUKI_SKIP_OPENSPEC_BOOTSTRAP ?? '1',
42
+ };
43
+
44
+ const result = spawnSync(process.execPath, [pumukiCli, 'install'], {
45
+ cwd: consumerRoot,
46
+ env,
47
+ stdio: 'inherit',
48
+ });
49
+
50
+ const code = typeof result.status === 'number' ? result.status : 1;
51
+ if (code !== 0) {
52
+ console.error(
53
+ '[pumuki] postinstall: `pumuki install` exited non-zero; npm install will still succeed. Run `npx pumuki install` or `npx pumuki doctor` from the repo root.'
54
+ );
55
+ }
56
+ return 0;
57
+ };
58
+
59
+ process.exitCode = main();
@@ -8,18 +8,33 @@ import type {
8
8
  } from './framework-menu-system-notifications-types';
9
9
  import { applyDialogChoice } from './framework-menu-system-notifications-config';
10
10
  import { deliverMacOsNotification } from './framework-menu-system-notifications-macos';
11
+ import { isTruthyEnvValue } from './framework-menu-system-notifications-env';
12
+ import {
13
+ deliverStderrNotificationBanner,
14
+ isStderrNotificationFallbackDisabled,
15
+ } from './framework-menu-system-notifications-stdio-fallback';
11
16
 
12
17
  export const dispatchSystemNotification = (params: {
13
18
  event: PumukiCriticalNotificationEvent;
14
19
  payload: SystemNotificationPayload;
20
+ platform: NodeJS.Platform;
15
21
  repoRoot?: string;
16
22
  config: SystemNotificationsConfig;
17
23
  env: NodeJS.ProcessEnv;
18
24
  nowMs: number;
19
25
  runCommand?: SystemNotificationCommandRunner;
20
26
  runCommandWithOutput?: SystemNotificationCommandRunnerWithOutput;
21
- }): SystemNotificationEmitResult =>
22
- deliverMacOsNotification({
27
+ }): SystemNotificationEmitResult => {
28
+ const stderrOff = isStderrNotificationFallbackDisabled(params.env);
29
+
30
+ if (params.platform !== 'darwin') {
31
+ if (stderrOff) {
32
+ return { delivered: false, reason: 'unsupported-platform' };
33
+ }
34
+ return deliverStderrNotificationBanner({ payload: params.payload });
35
+ }
36
+
37
+ const macResult = deliverMacOsNotification({
23
38
  event: params.event,
24
39
  payload: params.payload,
25
40
  repoRoot: params.repoRoot,
@@ -30,3 +45,14 @@ export const dispatchSystemNotification = (params: {
30
45
  runCommandWithOutput: params.runCommandWithOutput,
31
46
  applyDialogChoice,
32
47
  });
48
+
49
+ if (macResult.delivered && isTruthyEnvValue(params.env.PUMUKI_NOTIFICATION_STDERR_MIRROR)) {
50
+ deliverStderrNotificationBanner({ payload: params.payload });
51
+ }
52
+
53
+ if (!macResult.delivered && macResult.reason === 'command-failed' && !stderrOff) {
54
+ return deliverStderrNotificationBanner({ payload: params.payload });
55
+ }
56
+
57
+ return macResult;
58
+ };
@@ -0,0 +1,7 @@
1
+ export const isTruthyEnvValue = (value?: string): boolean => {
2
+ if (!value) {
3
+ return false;
4
+ }
5
+ const normalized = value.trim().toLowerCase();
6
+ return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on';
7
+ };
@@ -3,19 +3,11 @@ import type {
3
3
  SystemNotificationEmitResult,
4
4
  SystemNotificationsConfig,
5
5
  } from './framework-menu-system-notifications-types';
6
-
7
- const isTruthyEnvValue = (value?: string): boolean => {
8
- if (!value) {
9
- return false;
10
- }
11
- const normalized = value.trim().toLowerCase();
12
- return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on';
13
- };
6
+ import { isTruthyEnvValue } from './framework-menu-system-notifications-env';
14
7
 
15
8
  export const resolveSystemNotificationGate = (params: {
16
9
  config: SystemNotificationsConfig;
17
10
  nowMs: number;
18
- platform: NodeJS.Platform;
19
11
  env?: NodeJS.ProcessEnv;
20
12
  }): SystemNotificationEmitResult | null => {
21
13
  if (isTruthyEnvValue(params.env?.PUMUKI_DISABLE_SYSTEM_NOTIFICATIONS)) {
@@ -27,8 +19,5 @@ export const resolveSystemNotificationGate = (params: {
27
19
  if (isMutedAt(params.config, params.nowMs)) {
28
20
  return { delivered: false, reason: 'muted' };
29
21
  }
30
- if (params.platform !== 'darwin') {
31
- return { delivered: false, reason: 'unsupported-platform' };
32
- }
33
22
  return null;
34
23
  };
@@ -48,7 +48,6 @@ export const emitSystemNotification = (params: {
48
48
  const gateResult = resolveSystemNotificationGate({
49
49
  config,
50
50
  nowMs,
51
- platform,
52
51
  env: params.env ?? process.env,
53
52
  });
54
53
  if (gateResult) {
@@ -62,6 +61,7 @@ export const emitSystemNotification = (params: {
62
61
  return dispatchSystemNotification({
63
62
  event: params.event,
64
63
  payload,
64
+ platform,
65
65
  repoRoot: params.repoRoot,
66
66
  config,
67
67
  env: params.env ?? process.env,
@@ -6,5 +6,5 @@ export type SystemNotificationPayload = {
6
6
  };
7
7
 
8
8
  export type SystemNotificationEmitResult =
9
- | { delivered: true; reason: 'delivered' }
9
+ | { delivered: true; reason: 'delivered' | 'stderr-fallback' }
10
10
  | { delivered: false; reason: 'disabled' | 'muted' | 'unsupported-platform' | 'command-failed' };
@@ -0,0 +1,23 @@
1
+ import type {
2
+ SystemNotificationEmitResult,
3
+ SystemNotificationPayload,
4
+ } from './framework-menu-system-notifications-types';
5
+ import { isTruthyEnvValue } from './framework-menu-system-notifications-env';
6
+
7
+ export const isStderrNotificationFallbackDisabled = (env: NodeJS.ProcessEnv): boolean =>
8
+ isTruthyEnvValue(env.PUMUKI_DISABLE_STDERR_NOTIFICATIONS);
9
+
10
+ export const deliverStderrNotificationBanner = (params: {
11
+ payload: SystemNotificationPayload;
12
+ stderr?: NodeJS.WriteStream;
13
+ }): SystemNotificationEmitResult => {
14
+ const stderr = params.stderr ?? process.stderr;
15
+ const lines = [
16
+ '[pumuki]',
17
+ `title: ${params.payload.title}`,
18
+ ...(params.payload.subtitle ? [`subtitle: ${params.payload.subtitle}`] : []),
19
+ `message: ${params.payload.message}`,
20
+ ];
21
+ stderr.write(`${lines.join('\n')}\n`);
22
+ return { delivered: true, reason: 'stderr-fallback' };
23
+ };