pumuki 6.3.110 → 6.3.111

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,12 +6,12 @@ This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
- ## [6.3.110] - 2026-04-23
9
+ ## [6.3.111] - 2026-04-23
10
10
 
11
11
  ### Fixed
12
12
 
13
- - **`status` expone `issues` canónicos cuando el runtime está bloqueado:** la salida JSON ya no obliga a combinar `governanceEffective`, `attention_codes` y `human_summary_preview` para entender un bloqueo real.
14
- - **Contrato alineado en `main` y `develop`:** la lista de issues consumible queda fijada en ambas líneas para los consumers que leen `status` como fuente principal de diagnóstico.
13
+ - **Alignment commands now respect the consumer runtime declaration:** `status`, `doctor` and the lifecycle helpers prepend the matching Node runtime switch when the consumer declares `volta`, `.nvmrc`, or `package.json.engines.node` and the current shell Node does not match.
14
+ - **Reproducible remediation stays repo-aware:** the suggested command keeps the consumer's runtime manager in the loop before the Pumuki install/alignment step, so fixes stay tied to the repo that is being audited.
15
15
 
16
16
  ## [6.3.102] - 2026-04-22
17
17
 
@@ -6,11 +6,10 @@ 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-23 (v6.3.110)
9
+ ### 2026-04-23 (v6.3.111)
10
10
 
11
- - **Salida canónica de bloqueos en `status`:** el payload añade `issues` cuando el runtime está bloqueado, evitando que el consumo automatizado tenga que recomponer el fallo a partir de varias señales.
12
- - **Contrato alineado entre ramas:** el mismo criterio queda disponible en `develop` y `main`, por lo que el rollout no depende de interpretar superficies distintas.
13
- - **Rollout recomendado:** publicar `pumuki@6.3.110`, repin inmediato en `RuralGo` y revalidar `status` / `doctor` para cerrar `PUMUKI-INC-084`.
11
+ - **Runtime-aware remediation:** `alignmentCommand` now respects the consumer's declared Node runtime when the shell Node does not match, using `volta`, `.nvmrc`, or `package.json.engines.node` as the source of truth.
12
+ - **Rollout recommended:** publish `pumuki@6.3.111`, repin the active consumer, and revalidate `status` / `doctor` to confirm the consumer runtime is aligned before following the remediation command.
14
13
 
15
14
  ### 2026-04-22 (v6.3.102)
16
15
 
@@ -174,7 +174,7 @@ Interpretation contract:
174
174
  - `consumerInstalled`: version installed in the consumer repository.
175
175
  - `lifecycleInstalled`: version recorded in managed lifecycle metadata.
176
176
  - `driftWarning`: compact human-readable explanation when those values diverge.
177
- - `alignmentCommand`: exact remediation command to align dependency and lifecycle state with the current runtime version.
177
+ - `alignmentCommand`: exact remediation command to align dependency and lifecycle state with the current runtime version. When the consumer repo declares a Node runtime via `volta`, `.nvmrc`, or `package.json.engines.node`, this command prepends the matching runtime switch before the Pumuki install step.
178
178
  - `pathExecutionHazard`: boolean flag raised when the repo root contains the platform `PATH` delimiter and `npx/npm exec` can fail to resolve `pumuki`.
179
179
  - `pathExecutionWarning`: compact warning explaining why `PATH` resolution is unsafe in that repo.
180
180
  - `pathExecutionWorkaroundCommand`: safe local-node entrypoint to use when `pathExecutionHazard=true`.
@@ -403,7 +403,7 @@ How to read it:
403
403
  - `lifecycleInstalled`: version persisted in managed lifecycle state.
404
404
  - `source`: source chosen to compute `effective`.
405
405
  - `driftWarning`: compact explanation when those values diverge.
406
- - `alignmentCommand`: exact command to align consumer dependency and lifecycle state with the current runtime.
406
+ - `alignmentCommand`: exact command to align consumer dependency and lifecycle state with the current runtime. If the consumer declares Node via `volta`, `.nvmrc`, or `package.json.engines.node`, the command prepends the matching runtime switch before the Pumuki install step.
407
407
  - `pathExecutionHazard`: tells you whether the repo root itself can break `PATH`-based execution.
408
408
  - `pathExecutionWarning`: compact explanation of that hazard.
409
409
  - `pathExecutionWorkaroundCommand`: safe local-node entrypoint to use when `PATH` execution is unsafe.
@@ -49,15 +49,126 @@ const hasPathExecutionHazard = (repoRoot?: string): boolean =>
49
49
  repoRoot.trim().length > 0 &&
50
50
  repoRoot.includes(delimiter);
51
51
 
52
+ type ConsumerNodeRuntimeSpec = {
53
+ version: string;
54
+ source: 'volta' | '.nvmrc' | 'package.engines';
55
+ commandPrefix: 'volta' | 'nvm';
56
+ };
57
+
58
+ const normalizeNodeVersionToken = (value: string): string =>
59
+ value.trim().replace(/^node@/i, '').replace(/^v/i, '');
60
+
61
+ const extractNodeVersionToken = (value: string): string | null => {
62
+ const normalized = normalizeNodeVersionToken(value);
63
+ const exactVersion = normalized.match(/\d+\.\d+\.\d+/)?.[0];
64
+ if (exactVersion) {
65
+ return exactVersion;
66
+ }
67
+ const majorOnly = normalized.match(/^\d+$/)?.[0];
68
+ return majorOnly ?? null;
69
+ };
70
+
71
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
72
+ typeof value === 'object' && value !== null && !Array.isArray(value);
73
+
74
+ const readNestedString = (
75
+ source: Record<string, unknown>,
76
+ path: ReadonlyArray<string>
77
+ ): string | undefined => {
78
+ let cursor: unknown = source;
79
+ for (const segment of path) {
80
+ if (!isRecord(cursor)) {
81
+ return undefined;
82
+ }
83
+ cursor = cursor[segment];
84
+ }
85
+ return typeof cursor === 'string' && cursor.trim().length > 0 ? cursor.trim() : undefined;
86
+ };
87
+
88
+ const readConsumerNodeRuntimeSpec = (repoRoot?: string): ConsumerNodeRuntimeSpec | null => {
89
+ if (typeof repoRoot !== 'string' || repoRoot.trim().length === 0) {
90
+ return null;
91
+ }
92
+
93
+ const packageJsonPath = join(repoRoot, 'package.json');
94
+ if (existsSync(packageJsonPath)) {
95
+ try {
96
+ const parsed = JSON.parse(readFileSync(packageJsonPath, 'utf8')) as unknown;
97
+ if (isRecord(parsed)) {
98
+ const voltaNode = readNestedString(parsed, ['volta', 'node']);
99
+ const voltaVersion = voltaNode ? extractNodeVersionToken(voltaNode) : null;
100
+ if (voltaVersion) {
101
+ return {
102
+ version: voltaVersion,
103
+ source: 'volta',
104
+ commandPrefix: 'volta',
105
+ };
106
+ }
107
+
108
+ const enginesNode = readNestedString(parsed, ['engines', 'node']);
109
+ const enginesVersion = enginesNode ? extractNodeVersionToken(enginesNode) : null;
110
+ if (enginesVersion) {
111
+ return {
112
+ version: enginesVersion,
113
+ source: 'package.engines',
114
+ commandPrefix: 'nvm',
115
+ };
116
+ }
117
+ }
118
+ } catch {
119
+ return null;
120
+ }
121
+ }
122
+
123
+ const nvmrcPath = join(repoRoot, '.nvmrc');
124
+ if (existsSync(nvmrcPath)) {
125
+ try {
126
+ const nvmrcVersion = extractNodeVersionToken(readFileSync(nvmrcPath, 'utf8'));
127
+ if (nvmrcVersion) {
128
+ return {
129
+ version: nvmrcVersion,
130
+ source: '.nvmrc',
131
+ commandPrefix: 'nvm',
132
+ };
133
+ }
134
+ } catch {
135
+ return null;
136
+ }
137
+ }
138
+
139
+ return null;
140
+ };
141
+
142
+ const buildConsumerNodeAlignmentCommand = (repoRoot?: string): string | null => {
143
+ const runtimeSpec = readConsumerNodeRuntimeSpec(repoRoot);
144
+ if (!runtimeSpec) {
145
+ return null;
146
+ }
147
+
148
+ const currentNodeVersion = normalizeNodeVersionToken(process.version);
149
+ if (currentNodeVersion === runtimeSpec.version) {
150
+ return null;
151
+ }
152
+
153
+ if (runtimeSpec.commandPrefix === 'volta') {
154
+ return `volta install node@${runtimeSpec.version} && volta pin node@${runtimeSpec.version}`;
155
+ }
156
+
157
+ return `nvm install ${runtimeSpec.version} && nvm use ${runtimeSpec.version}`;
158
+ };
159
+
52
160
  export const buildLifecycleAlignmentCommand = (
53
161
  runtimeVersion: string,
54
162
  repoRoot?: string
55
163
  ): string => {
164
+ const consumerNodeAlignmentCommand = buildConsumerNodeAlignmentCommand(repoRoot);
56
165
  const installStep = `npm install --save-exact pumuki@${runtimeVersion}`;
57
166
  const runStep = hasPathExecutionHazard(repoRoot)
58
167
  ? `${buildLocalPumukiCommand()} install`
59
168
  : `npx --yes --package pumuki@${runtimeVersion} pumuki install`;
60
- return `${installStep} && ${runStep}`;
169
+ return [consumerNodeAlignmentCommand, installStep, runStep]
170
+ .filter((value): value is string => typeof value === 'string' && value.length > 0)
171
+ .join(' && ');
61
172
  };
62
173
 
63
174
  export const resolvePumukiVersionMetadata = (params?: { repoRoot?: string }): PumukiVersionMetadata => {
@@ -114,11 +225,17 @@ export const buildLifecycleVersionReport = (params?: {
114
225
  const driftFromConsumerInstalled =
115
226
  metadata.consumerInstalledVersion !== null &&
116
227
  metadata.consumerInstalledVersion !== metadata.runtimeVersion;
228
+ const consumerNodeSpec = readConsumerNodeRuntimeSpec(params?.repoRoot);
229
+ const consumerNodeVersion = consumerNodeSpec?.version ?? null;
230
+ const currentNodeVersion = normalizeNodeVersionToken(process.version);
231
+ const driftFromConsumerNode =
232
+ consumerNodeVersion !== null && currentNodeVersion !== consumerNodeVersion;
117
233
  const driftFromLifecycleInstalled =
118
234
  lifecycleInstalled !== null && metadata.resolvedVersion !== lifecycleInstalled;
119
235
  const driftTargets = [
120
236
  driftFromRuntime ? `runtime=${metadata.runtimeVersion}` : null,
121
237
  driftFromConsumerInstalled ? `consumer=${metadata.consumerInstalledVersion}` : null,
238
+ driftFromConsumerNode ? `node=${consumerNodeVersion}` : null,
122
239
  driftFromLifecycleInstalled ? `lifecycle=${lifecycleInstalled}` : null,
123
240
  ].filter((value): value is string => value !== null);
124
241
  const pathExecutionHazard = hasPathExecutionHazard(params?.repoRoot);
@@ -736,7 +736,7 @@ export const runLifecycleWatch = async (
736
736
  driftFromRuntime,
737
737
  driftWarning,
738
738
  alignmentCommand: driftFromRuntime
739
- ? buildLifecycleAlignmentCommand(versionMetadata.runtimeVersion)
739
+ ? buildLifecycleAlignmentCommand(versionMetadata.runtimeVersion, repoRoot)
740
740
  : null,
741
741
  },
742
742
  stage,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.110",
3
+ "version": "6.3.111",
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": {