pumuki 6.3.77 → 6.3.79

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/VERSION CHANGED
@@ -1 +1 @@
1
- v6.3.64
1
+ v6.3.79
@@ -6,6 +6,19 @@ 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-16 (v6.3.79)
10
+
11
+ - **Fix RuralGo (`PUMUKI-INC-078`)**: el parser de tracking canónico reconoce ya boards tabulares con la `🚧` en la primera columna, evitando falsos positivos `TRACKING_CANONICAL_IN_PROGRESS_INVALID` en `status` / `doctor`.
12
+ - **Convergencia con guardrails del consumer**: `captureRepoState` y la consola de governance quedan alineados con el shape real de `docs/RURALGO_SEGUIMIENTO.md`, sin exigir reestructurar el tablero del repo consumidor.
13
+ - **Rollout recomendado**: publicar `pumuki@6.3.79` y revalidar primero RuralGo (`pumuki status`, `pumuki doctor`) antes de extender el repin a otros consumers si aplica.
14
+
15
+ ### 2026-04-16 (v6.3.78)
16
+
17
+ - **Slice S1 consolidada**: `status`, `doctor`, `PRE-FLIGHT CHECK`, menú `Consumer` y menú `Advanced` muestran el mismo bloque canónico de governance sin duplicar lógica de cálculo.
18
+ - **Runtime del menú**: `Consumer` conserva `lastPreflight` y lo reutiliza en superficies derivadas para mantener paridad visible de `truth / next action / policy-as-code / experimentales`.
19
+ - **Base de release correcta**: la publicación se prepara desde `release/6.3.78` recortada sobre `develop` ya alineado con la línea viva `release/6.3.77`.
20
+ - **Rollout recomendado**: publicar `pumuki@6.3.78` y repin ordenado `RuralGo -> SAAS -> Flux`, validando después `pumuki status`, `pumuki doctor` y hooks Git en cada consumer.
21
+
9
22
  ### 2026-04-06 (v6.3.71)
10
23
 
11
24
  - **Evidencia v2.1**: bloque `operational_hints` (`requires_second_pass`, resumen operativo, desglose por severidad de reglas). Alineado con PRE_COMMIT solo-docs + evidencia trackeada (INC-069) cuando no se re-stagea el JSON automáticamente.
@@ -687,6 +687,16 @@ const normalizeRepoState = (repoState?: RepoState): RepoState | undefined => {
687
687
  if (!repoState) {
688
688
  return undefined;
689
689
  }
690
+ const tracking = repoState.lifecycle.tracking ?? {
691
+ enforced: false,
692
+ canonical_path: null,
693
+ canonical_present: false,
694
+ source_file: null,
695
+ in_progress_count: null,
696
+ single_in_progress_valid: null,
697
+ conflict: false,
698
+ declarations: [],
699
+ };
690
700
  return {
691
701
  repo_root: repoState.repo_root,
692
702
  git: {
@@ -717,14 +727,14 @@ const normalizeRepoState = (repoState?: RepoState): RepoState | undefined => {
717
727
  }
718
728
  : undefined,
719
729
  tracking: {
720
- enforced: repoState.lifecycle.tracking.enforced,
721
- canonical_path: repoState.lifecycle.tracking.canonical_path,
722
- canonical_present: repoState.lifecycle.tracking.canonical_present,
723
- source_file: repoState.lifecycle.tracking.source_file,
724
- in_progress_count: repoState.lifecycle.tracking.in_progress_count,
725
- single_in_progress_valid: repoState.lifecycle.tracking.single_in_progress_valid,
726
- conflict: repoState.lifecycle.tracking.conflict,
727
- declarations: repoState.lifecycle.tracking.declarations.map((entry) => ({
730
+ enforced: tracking.enforced,
731
+ canonical_path: tracking.canonical_path,
732
+ canonical_present: tracking.canonical_present,
733
+ source_file: tracking.source_file,
734
+ in_progress_count: tracking.in_progress_count,
735
+ single_in_progress_valid: tracking.single_in_progress_valid,
736
+ conflict: tracking.conflict,
737
+ declarations: tracking.declarations.map((entry) => ({
728
738
  source_file: entry.source_file,
729
739
  declared_path: entry.declared_path,
730
740
  resolved_path: entry.resolved_path,
@@ -15,6 +15,7 @@ const TRACKING_PRIORITY_SOURCE = new Set(['AGENTS.md']);
15
15
  const IN_PROGRESS_PATTERNS = [
16
16
  /^- Estado:\s*🚧/m,
17
17
  /^- 🚧 (\`?P[0-9A-Za-z.-]+\`?)/m,
18
+ /^\|\s*🚧(?:\s|\|)/m,
18
19
  /^\|[^|\n]+\|\s*🚧(?:\s|\|)/m,
19
20
  ] as const;
20
21
 
@@ -188,6 +188,16 @@ const normalizeRepoState = (
188
188
  if (!repoState) {
189
189
  return undefined;
190
190
  }
191
+ const tracking = repoState.lifecycle.tracking ?? {
192
+ enforced: false,
193
+ canonical_path: null,
194
+ canonical_present: false,
195
+ source_file: null,
196
+ in_progress_count: null,
197
+ single_in_progress_valid: null,
198
+ conflict: false,
199
+ declarations: [],
200
+ };
191
201
  return {
192
202
  repo_root: toRelativeRepoPath(repoRoot, repoState.repo_root),
193
203
  git: {
@@ -218,14 +228,14 @@ const normalizeRepoState = (
218
228
  }
219
229
  : undefined,
220
230
  tracking: {
221
- enforced: repoState.lifecycle.tracking.enforced,
222
- canonical_path: repoState.lifecycle.tracking.canonical_path,
223
- canonical_present: repoState.lifecycle.tracking.canonical_present,
224
- source_file: repoState.lifecycle.tracking.source_file,
225
- in_progress_count: repoState.lifecycle.tracking.in_progress_count,
226
- single_in_progress_valid: repoState.lifecycle.tracking.single_in_progress_valid,
227
- conflict: repoState.lifecycle.tracking.conflict,
228
- declarations: repoState.lifecycle.tracking.declarations.map((entry) => ({
231
+ enforced: tracking.enforced,
232
+ canonical_path: tracking.canonical_path,
233
+ canonical_present: tracking.canonical_present,
234
+ source_file: tracking.source_file,
235
+ in_progress_count: tracking.in_progress_count,
236
+ single_in_progress_valid: tracking.single_in_progress_valid,
237
+ conflict: tracking.conflict,
238
+ declarations: tracking.declarations.map((entry) => ({
229
239
  source_file: entry.source_file,
230
240
  declared_path: entry.declared_path,
231
241
  resolved_path: entry.resolved_path,
@@ -233,8 +233,6 @@ type PolicyAsCodeContract = {
233
233
  version: '1.0';
234
234
  source: 'default' | 'skills.policy' | 'hard-mode';
235
235
  signatures: Partial<Record<SkillsStage, string>> & Record<'PRE_COMMIT' | 'PRE_PUSH' | 'CI', string>;
236
- strict?: Partial<Record<SkillsStage, boolean>> &
237
- Record<'PRE_COMMIT' | 'PRE_PUSH' | 'CI', boolean>;
238
236
  expires_at?: string;
239
237
  };
240
238
 
@@ -274,30 +272,6 @@ const policyStrictModeFromEnv = (): boolean => {
274
272
  return raw === '1' || raw === 'true' || raw === 'yes' || raw === 'on';
275
273
  };
276
274
 
277
- const isBoolean = (value: unknown): value is boolean => {
278
- return typeof value === 'boolean';
279
- };
280
-
281
- const resolveContractStrictForStage = (
282
- strictByStage: PolicyAsCodeContract['strict'],
283
- stage: SkillsStage
284
- ): boolean | null => {
285
- if (!strictByStage) {
286
- return null;
287
- }
288
- if (stage === 'PRE_WRITE') {
289
- return strictByStage.PRE_WRITE ?? strictByStage.PRE_COMMIT ?? null;
290
- }
291
- return strictByStage[stage] ?? null;
292
- };
293
-
294
- const resolvePolicyStrict = (strictByContract: boolean | null): boolean => {
295
- if (typeof strictByContract === 'boolean') {
296
- return strictByContract;
297
- }
298
- return policyStrictModeFromEnv();
299
- };
300
-
301
275
  const isPolicyAsCodeContract = (value: unknown): value is PolicyAsCodeContract => {
302
276
  if (!isObject(value)) {
303
277
  return false;
@@ -315,16 +289,6 @@ const isPolicyAsCodeContract = (value: unknown): value is PolicyAsCodeContract =
315
289
  if (!isObject(value.signatures)) {
316
290
  return false;
317
291
  }
318
- if (
319
- typeof value.strict !== 'undefined' &&
320
- (!isObject(value.strict)
321
- || (typeof value.strict.PRE_WRITE !== 'undefined' && !isBoolean(value.strict.PRE_WRITE))
322
- || !isBoolean(value.strict.PRE_COMMIT)
323
- || !isBoolean(value.strict.PRE_PUSH)
324
- || !isBoolean(value.strict.CI))
325
- ) {
326
- return false;
327
- }
328
292
  if (typeof value.expires_at !== 'undefined' && !isIsoDateString(value.expires_at)) {
329
293
  return false;
330
294
  }
@@ -368,7 +332,7 @@ const resolvePolicyAsCodeTraceMetadata = (params: {
368
332
  policySource: string;
369
333
  validation: NonNullable<ResolvedStagePolicy['trace']['validation']>;
370
334
  } => {
371
- const envStrict = policyStrictModeFromEnv();
335
+ const strict = policyStrictModeFromEnv();
372
336
  const computedVersion = `policy-as-code/${params.source}@${POLICY_AS_CODE_VERSION}`;
373
337
  const computedSignature = createPolicyAsCodeSignature({
374
338
  stage: params.stage,
@@ -380,7 +344,7 @@ const resolvePolicyAsCodeTraceMetadata = (params: {
380
344
  const contractPath = join(params.repoRoot, POLICY_AS_CODE_CONTRACT_PATH);
381
345
 
382
346
  if (!existsSync(contractPath)) {
383
- if (envStrict) {
347
+ if (strict) {
384
348
  return {
385
349
  version: computedVersion,
386
350
  signature: computedSignature,
@@ -390,7 +354,7 @@ const resolvePolicyAsCodeTraceMetadata = (params: {
390
354
  code: 'POLICY_AS_CODE_UNSIGNED',
391
355
  message:
392
356
  'Policy-as-code contract is missing; runtime policy metadata is unsigned.',
393
- strict: envStrict,
357
+ strict,
394
358
  },
395
359
  };
396
360
  }
@@ -403,7 +367,7 @@ const resolvePolicyAsCodeTraceMetadata = (params: {
403
367
  status: 'valid',
404
368
  code: 'POLICY_AS_CODE_VALID',
405
369
  message: 'Policy-as-code metadata generated from active runtime policy.',
406
- strict: envStrict,
370
+ strict,
407
371
  },
408
372
  };
409
373
  }
@@ -419,13 +383,11 @@ const resolvePolicyAsCodeTraceMetadata = (params: {
419
383
  status: 'invalid',
420
384
  code: 'POLICY_AS_CODE_CONTRACT_INVALID',
421
385
  message: 'Policy-as-code contract is malformed.',
422
- strict: envStrict,
386
+ strict,
423
387
  },
424
388
  };
425
389
  }
426
390
 
427
- const strict = resolvePolicyStrict(resolveContractStrictForStage(raw.strict, params.stage));
428
-
429
391
  if (raw.source !== params.source) {
430
392
  return {
431
393
  version: `policy-as-code/${raw.source}@${raw.version}`,
@@ -503,7 +465,7 @@ const resolvePolicyAsCodeTraceMetadata = (params: {
503
465
  status: 'invalid',
504
466
  code: 'POLICY_AS_CODE_CONTRACT_INVALID',
505
467
  message: 'Policy-as-code contract cannot be parsed as JSON.',
506
- strict: envStrict,
468
+ strict,
507
469
  },
508
470
  };
509
471
  }
@@ -12,8 +12,7 @@ import {
12
12
  runLifecycleDoctor,
13
13
  type LifecycleDoctorReport,
14
14
  } from './doctor';
15
- import { printGovernanceObservationHuman } from './governanceObservationSnapshot';
16
- import { printGovernanceNextActionHuman } from './governanceNextAction';
15
+ import { printGovernanceConsoleHuman } from './cliGovernanceConsole';
17
16
  import { runLifecycleInstall } from './install';
18
17
  import { runLifecycleRemove } from './remove';
19
18
  import { readLifecycleStatus } from './status';
@@ -1522,13 +1521,12 @@ const printDoctorReport = (
1522
1521
  writeInfo(
1523
1522
  `[pumuki] hook pre-push: ${report.hookStatus['pre-push'].managedBlockPresent ? 'managed' : 'missing'}`
1524
1523
  );
1525
- printGovernanceObservationHuman(report.governanceObservation);
1526
- printGovernanceNextActionHuman(report.governanceNextAction);
1527
- writeInfo(
1528
- `[pumuki] policy-as-code: PRE_COMMIT=${report.policyValidation.stages.PRE_COMMIT.validationCode ?? 'n/a'} strict=${report.policyValidation.stages.PRE_COMMIT.strict ? 'yes' : 'no'} ` +
1529
- `PRE_PUSH=${report.policyValidation.stages.PRE_PUSH.validationCode ?? 'n/a'} strict=${report.policyValidation.stages.PRE_PUSH.strict ? 'yes' : 'no'} ` +
1530
- `CI=${report.policyValidation.stages.CI.validationCode ?? 'n/a'} strict=${report.policyValidation.stages.CI.strict ? 'yes' : 'no'}`
1531
- );
1524
+ printGovernanceConsoleHuman({
1525
+ governanceObservation: report.governanceObservation,
1526
+ governanceNextAction: report.governanceNextAction,
1527
+ policyValidation: report.policyValidation,
1528
+ experimentalFeatures: report.experimentalFeatures,
1529
+ });
1532
1530
 
1533
1531
  for (const issue of report.issues) {
1534
1532
  writeInfo(`[pumuki] ${issue.severity.toUpperCase()}: ${issue.message}`);
@@ -2358,40 +2356,15 @@ export const runLifecycleCli = async (
2358
2356
  writeInfo(
2359
2357
  `[pumuki] hooks: pre-commit=${status.hookStatus['pre-commit'].managedBlockPresent ? 'managed' : 'missing'}, pre-push=${status.hookStatus['pre-push'].managedBlockPresent ? 'managed' : 'missing'}`
2360
2358
  );
2361
- printGovernanceObservationHuman(status.governanceObservation);
2362
- printGovernanceNextActionHuman(status.governanceNextAction);
2359
+ printGovernanceConsoleHuman({
2360
+ governanceObservation: status.governanceObservation,
2361
+ governanceNextAction: status.governanceNextAction,
2362
+ policyValidation: status.policyValidation,
2363
+ experimentalFeatures: status.experimentalFeatures,
2364
+ });
2363
2365
  writeInfo(
2364
2366
  `[pumuki] tracked node_modules paths: ${status.trackedNodeModulesCount}`
2365
2367
  );
2366
- writeInfo(
2367
- `[pumuki] policy-as-code: PRE_COMMIT=${status.policyValidation.stages.PRE_COMMIT.validationCode ?? 'n/a'} strict=${status.policyValidation.stages.PRE_COMMIT.strict ? 'yes' : 'no'} ` +
2368
- `PRE_PUSH=${status.policyValidation.stages.PRE_PUSH.validationCode ?? 'n/a'} strict=${status.policyValidation.stages.PRE_PUSH.strict ? 'yes' : 'no'} ` +
2369
- `CI=${status.policyValidation.stages.CI.validationCode ?? 'n/a'} strict=${status.policyValidation.stages.CI.strict ? 'yes' : 'no'}`
2370
- );
2371
- writeInfo(
2372
- `[pumuki] experimental: ANALYTICS=${status.experimentalFeatures.features.analytics.mode} source=${status.experimentalFeatures.features.analytics.source} layer=${status.experimentalFeatures.features.analytics.layer} blocking=${status.experimentalFeatures.features.analytics.blocking ? 'yes' : 'no'} env=${status.experimentalFeatures.features.analytics.activationVariable}`
2373
- );
2374
- writeInfo(
2375
- `[pumuki] experimental: HEURISTICS=${status.experimentalFeatures.features.heuristics.mode} source=${status.experimentalFeatures.features.heuristics.source} layer=${status.experimentalFeatures.features.heuristics.layer} blocking=${status.experimentalFeatures.features.heuristics.blocking ? 'yes' : 'no'} env=${status.experimentalFeatures.features.heuristics.activationVariable}`
2376
- );
2377
- writeInfo(
2378
- `[pumuki] experimental: LEARNING_CONTEXT=${status.experimentalFeatures.features.learning_context.mode} source=${status.experimentalFeatures.features.learning_context.source} layer=${status.experimentalFeatures.features.learning_context.layer} blocking=${status.experimentalFeatures.features.learning_context.blocking ? 'yes' : 'no'} env=${status.experimentalFeatures.features.learning_context.activationVariable}`
2379
- );
2380
- writeInfo(
2381
- `[pumuki] experimental: MCP_ENTERPRISE=${status.experimentalFeatures.features.mcp_enterprise.mode} source=${status.experimentalFeatures.features.mcp_enterprise.source} layer=${status.experimentalFeatures.features.mcp_enterprise.layer} blocking=${status.experimentalFeatures.features.mcp_enterprise.blocking ? 'yes' : 'no'} env=${status.experimentalFeatures.features.mcp_enterprise.activationVariable}`
2382
- );
2383
- writeInfo(
2384
- `[pumuki] experimental: OPERATIONAL_MEMORY=${status.experimentalFeatures.features.operational_memory.mode} source=${status.experimentalFeatures.features.operational_memory.source} layer=${status.experimentalFeatures.features.operational_memory.layer} blocking=${status.experimentalFeatures.features.operational_memory.blocking ? 'yes' : 'no'} env=${status.experimentalFeatures.features.operational_memory.activationVariable}`
2385
- );
2386
- writeInfo(
2387
- `[pumuki] experimental: PRE_WRITE=${status.experimentalFeatures.features.pre_write.mode} source=${status.experimentalFeatures.features.pre_write.source} layer=${status.experimentalFeatures.features.pre_write.layer} blocking=${status.experimentalFeatures.features.pre_write.blocking ? 'yes' : 'no'} env=${status.experimentalFeatures.features.pre_write.activationVariable}`
2388
- );
2389
- writeInfo(
2390
- `[pumuki] experimental: SAAS_INGESTION=${status.experimentalFeatures.features.saas_ingestion.mode} source=${status.experimentalFeatures.features.saas_ingestion.source} layer=${status.experimentalFeatures.features.saas_ingestion.layer} blocking=${status.experimentalFeatures.features.saas_ingestion.blocking ? 'yes' : 'no'} env=${status.experimentalFeatures.features.saas_ingestion.activationVariable}`
2391
- );
2392
- writeInfo(
2393
- `[pumuki] experimental: SDD=${status.experimentalFeatures.features.sdd.mode} source=${status.experimentalFeatures.features.sdd.source} layer=${status.experimentalFeatures.features.sdd.layer} blocking=${status.experimentalFeatures.features.sdd.blocking ? 'yes' : 'no'} env=${status.experimentalFeatures.features.sdd.activationVariable}`
2394
- );
2395
2368
  if (remoteCiDiagnostics) {
2396
2369
  printRemoteCiDiagnostics(remoteCiDiagnostics);
2397
2370
  }
@@ -0,0 +1,69 @@
1
+ import type { LifecycleExperimentalFeaturesSnapshot } from './experimentalFeaturesSnapshot';
2
+ import type { GovernanceNextActionSummary } from './governanceNextAction';
3
+ import { buildGovernanceNextActionSummaryLines } from './governanceNextAction';
4
+ import type { GovernanceObservationSnapshot } from './governanceObservationSnapshot';
5
+ import { buildGovernanceObservationSummaryLines } from './governanceObservationSnapshot';
6
+ import { writeInfo } from './cliOutputs';
7
+ import type { LifecyclePolicyValidationSnapshot } from './policyValidationSnapshot';
8
+
9
+ export type GovernanceConsoleSnapshot = {
10
+ governanceObservation: GovernanceObservationSnapshot;
11
+ governanceNextAction: GovernanceNextActionSummary;
12
+ policyValidation: LifecyclePolicyValidationSnapshot;
13
+ experimentalFeatures: LifecycleExperimentalFeaturesSnapshot;
14
+ };
15
+
16
+ const GOVERNANCE_CONSOLE_FEATURE_ORDER = [
17
+ 'analytics',
18
+ 'heuristics',
19
+ 'learning_context',
20
+ 'mcp_enterprise',
21
+ 'operational_memory',
22
+ 'pre_write',
23
+ 'saas_ingestion',
24
+ 'sdd',
25
+ ] as const;
26
+
27
+ const buildGovernancePolicySummaryLine = (
28
+ policyValidation: LifecyclePolicyValidationSnapshot
29
+ ): string =>
30
+ `Policy-as-code: PRE_WRITE=${policyValidation.stages.PRE_WRITE.validationCode ?? 'n/a'} strict=${policyValidation.stages.PRE_WRITE.strict ? 'yes' : 'no'} ` +
31
+ `PRE_COMMIT=${policyValidation.stages.PRE_COMMIT.validationCode ?? 'n/a'} strict=${policyValidation.stages.PRE_COMMIT.strict ? 'yes' : 'no'} ` +
32
+ `PRE_PUSH=${policyValidation.stages.PRE_PUSH.validationCode ?? 'n/a'} strict=${policyValidation.stages.PRE_PUSH.strict ? 'yes' : 'no'} ` +
33
+ `CI=${policyValidation.stages.CI.validationCode ?? 'n/a'} strict=${policyValidation.stages.CI.strict ? 'yes' : 'no'}`;
34
+
35
+ const buildGovernanceExperimentalSummaryLines = (
36
+ experimentalFeatures: LifecycleExperimentalFeaturesSnapshot
37
+ ): string[] =>
38
+ GOVERNANCE_CONSOLE_FEATURE_ORDER.map((featureKey) => {
39
+ const feature = experimentalFeatures.features[featureKey];
40
+ return `Experimental: ${featureKey.toUpperCase()}=${feature.mode} source=${feature.source} layer=${feature.layer} blocking=${feature.blocking ? 'yes' : 'no'} env=${feature.activationVariable}`;
41
+ });
42
+
43
+ export const buildGovernanceConsoleSummaryLines = (
44
+ snapshot: GovernanceConsoleSnapshot
45
+ ): string[] => {
46
+ const lines = ['Governance truth:'];
47
+ for (const line of buildGovernanceObservationSummaryLines(snapshot.governanceObservation)) {
48
+ lines.push(` ${line}`);
49
+ }
50
+ for (const hint of snapshot.governanceObservation.evidence.human_summary_preview) {
51
+ lines.push(` Evidence hint: ${hint}`);
52
+ }
53
+ lines.push('Governance next action:');
54
+ for (const line of buildGovernanceNextActionSummaryLines(snapshot.governanceNextAction)) {
55
+ lines.push(` ${line}`);
56
+ }
57
+ lines.push(buildGovernancePolicySummaryLine(snapshot.policyValidation));
58
+ lines.push(...buildGovernanceExperimentalSummaryLines(snapshot.experimentalFeatures));
59
+ return lines;
60
+ };
61
+
62
+ export const printGovernanceConsoleHuman = (
63
+ snapshot: GovernanceConsoleSnapshot
64
+ ): void => {
65
+ writeInfo('[pumuki] governance console (S1 / shared status-doctor baseline):');
66
+ for (const line of buildGovernanceConsoleSummaryLines(snapshot)) {
67
+ writeInfo(`[pumuki] ${line}`);
68
+ }
69
+ };
@@ -103,9 +103,19 @@ const toContractSource = (
103
103
  return 'default';
104
104
  };
105
105
 
106
- const writePolicyAsCodeContract = (params: {
106
+ const tryApplyPolicyAutofix = (params: {
107
107
  report: Omit<PolicyReconcileReport, 'applyRequested' | 'autofix'>;
108
108
  }): PolicyReconcileReport['autofix'] => {
109
+ const actionableDrifts = params.report.drifts.filter((drift) => POLICY_AUTOFIX_DRIFT_CODES.has(drift.code));
110
+ if (actionableDrifts.length === 0) {
111
+ return {
112
+ attempted: false,
113
+ status: 'SKIPPED',
114
+ actions: [],
115
+ details: 'No policy-as-code drift eligible for autofix.',
116
+ };
117
+ }
118
+
109
119
  const preWriteStage = params.report.stages.PRE_WRITE;
110
120
  const signatures = {
111
121
  PRE_WRITE: createPolicyAsCodeSignature({
@@ -124,7 +134,7 @@ const writePolicyAsCodeContract = (params: {
124
134
  attempted: true,
125
135
  status: 'FAILED',
126
136
  actions: [],
127
- details: 'Cannot write policy-as-code contract: missing computed signatures for one or more stages.',
137
+ details: 'Cannot autofix: missing computed signatures for one or more stages.',
128
138
  };
129
139
  }
130
140
 
@@ -132,12 +142,6 @@ const writePolicyAsCodeContract = (params: {
132
142
  const contract = {
133
143
  version: '1.0',
134
144
  source: toContractSource(params.report.stages.PRE_WRITE.source),
135
- strict: {
136
- PRE_WRITE: params.report.stages.PRE_WRITE.strict,
137
- PRE_COMMIT: params.report.stages.PRE_COMMIT.strict,
138
- PRE_PUSH: params.report.stages.PRE_PUSH.strict,
139
- CI: params.report.stages.CI.strict,
140
- },
141
145
  signatures: {
142
146
  PRE_WRITE: signatures.PRE_WRITE,
143
147
  PRE_COMMIT: signatures.PRE_COMMIT,
@@ -154,7 +158,7 @@ const writePolicyAsCodeContract = (params: {
154
158
  attempted: true,
155
159
  status: 'APPLIED',
156
160
  actions: ['WRITE_POLICY_AS_CODE_CONTRACT'],
157
- details: `Wrote ${POLICY_AS_CODE_CONTRACT_PATH} with deterministic stage signatures and strict flags.`,
161
+ details: `Wrote ${POLICY_AS_CODE_CONTRACT_PATH} with deterministic stage signatures.`,
158
162
  };
159
163
  } catch (error) {
160
164
  return {
@@ -166,22 +170,6 @@ const writePolicyAsCodeContract = (params: {
166
170
  }
167
171
  };
168
172
 
169
- const tryApplyPolicyAutofix = (params: {
170
- report: Omit<PolicyReconcileReport, 'applyRequested' | 'autofix'>;
171
- }): PolicyReconcileReport['autofix'] => {
172
- const actionableDrifts = params.report.drifts.filter((drift) => POLICY_AUTOFIX_DRIFT_CODES.has(drift.code));
173
- if (actionableDrifts.length === 0) {
174
- return {
175
- attempted: false,
176
- status: 'SKIPPED',
177
- actions: [],
178
- details: 'No policy-as-code drift eligible for autofix.',
179
- };
180
- }
181
-
182
- return writePolicyAsCodeContract(params);
183
- };
184
-
185
173
  export const runPolicyReconcile = (params?: {
186
174
  repoRoot?: string;
187
175
  now?: () => Date;
@@ -424,31 +412,6 @@ export const runPolicyReconcile = (params?: {
424
412
  };
425
413
  }
426
414
 
427
- if (strictRequested && baseReport.summary.status === 'PASS') {
428
- const strictPersistence = writePolicyAsCodeContract({
429
- report: baseReport,
430
- });
431
- if (strictPersistence.status !== 'APPLIED') {
432
- return {
433
- ...baseReport,
434
- applyRequested: true,
435
- autofix: strictPersistence,
436
- };
437
- }
438
-
439
- const reevaluated = runPolicyReconcile({
440
- repoRoot,
441
- now,
442
- strict: strictRequested,
443
- apply: false,
444
- });
445
- return {
446
- ...reevaluated,
447
- applyRequested: true,
448
- autofix: strictPersistence,
449
- };
450
- }
451
-
452
415
  const autofix = tryApplyPolicyAutofix({
453
416
  report: baseReport,
454
417
  });
@@ -30,10 +30,7 @@ export type PolicyAsCodeTraceMetadata = {
30
30
  type PolicyAsCodeContract = {
31
31
  version: '1.0';
32
32
  source: PolicyProfileSource;
33
- signatures: Partial<Record<SkillsStage, string>> &
34
- Record<'PRE_COMMIT' | 'PRE_PUSH' | 'CI', string>;
35
- strict?: Partial<Record<SkillsStage, boolean>> &
36
- Record<'PRE_COMMIT' | 'PRE_PUSH' | 'CI', boolean>;
33
+ signatures: Record<SkillsStage, string>;
37
34
  expires_at?: string;
38
35
  };
39
36
 
@@ -60,40 +57,6 @@ const policyStrictModeFromEnv = (): boolean => {
60
57
  return raw === '1' || raw === 'true' || raw === 'yes' || raw === 'on';
61
58
  };
62
59
 
63
- const isBoolean = (value: unknown): value is boolean => {
64
- return typeof value === 'boolean';
65
- };
66
-
67
- const resolveContractSignatureForStage = (
68
- signatures: PolicyAsCodeContract['signatures'],
69
- stage: SkillsStage
70
- ): string | undefined => {
71
- if (stage === 'PRE_WRITE') {
72
- return signatures.PRE_WRITE ?? signatures.PRE_COMMIT;
73
- }
74
- return signatures[stage];
75
- };
76
-
77
- const resolveContractStrictForStage = (
78
- strictByStage: PolicyAsCodeContract['strict'],
79
- stage: SkillsStage
80
- ): boolean | null => {
81
- if (!strictByStage) {
82
- return null;
83
- }
84
- if (stage === 'PRE_WRITE') {
85
- return strictByStage.PRE_WRITE ?? strictByStage.PRE_COMMIT ?? null;
86
- }
87
- return strictByStage[stage] ?? null;
88
- };
89
-
90
- const resolvePolicyStrict = (strictByContract: boolean | null): boolean => {
91
- if (typeof strictByContract === 'boolean') {
92
- return strictByContract;
93
- }
94
- return policyStrictModeFromEnv();
95
- };
96
-
97
60
  const isPolicyAsCodeContract = (value: unknown): value is PolicyAsCodeContract => {
98
61
  if (!isObject(value)) {
99
62
  return false;
@@ -111,21 +74,10 @@ const isPolicyAsCodeContract = (value: unknown): value is PolicyAsCodeContract =
111
74
  if (!isObject(value.signatures)) {
112
75
  return false;
113
76
  }
114
- if (
115
- typeof value.strict !== 'undefined' &&
116
- (!isObject(value.strict)
117
- || (typeof value.strict.PRE_WRITE !== 'undefined' && !isBoolean(value.strict.PRE_WRITE))
118
- || !isBoolean(value.strict.PRE_COMMIT)
119
- || !isBoolean(value.strict.PRE_PUSH)
120
- || !isBoolean(value.strict.CI))
121
- ) {
122
- return false;
123
- }
124
77
  if (typeof value.expires_at !== 'undefined' && !isIsoDateString(value.expires_at)) {
125
78
  return false;
126
79
  }
127
80
  return (
128
- (typeof value.signatures.PRE_WRITE === 'undefined' || isSha256Hex(value.signatures.PRE_WRITE)) &&
129
81
  isSha256Hex(value.signatures.PRE_COMMIT) &&
130
82
  isSha256Hex(value.signatures.PRE_PUSH) &&
131
83
  isSha256Hex(value.signatures.CI)
@@ -159,7 +111,7 @@ export const resolvePolicyAsCodeTraceMetadata = (params: {
159
111
  hash: string;
160
112
  repoRoot: string;
161
113
  }): PolicyAsCodeTraceMetadata => {
162
- const envStrict = policyStrictModeFromEnv();
114
+ const strict = policyStrictModeFromEnv();
163
115
  const computedVersion = `policy-as-code/${params.source}@${POLICY_AS_CODE_VERSION}`;
164
116
  const computedSignature = createPolicyAsCodeSignature({
165
117
  stage: params.stage,
@@ -171,7 +123,7 @@ export const resolvePolicyAsCodeTraceMetadata = (params: {
171
123
  const contractPath = join(params.repoRoot, POLICY_AS_CODE_CONTRACT_PATH);
172
124
 
173
125
  if (!existsSync(contractPath)) {
174
- if (envStrict) {
126
+ if (strict) {
175
127
  return {
176
128
  version: computedVersion,
177
129
  signature: computedSignature,
@@ -181,7 +133,7 @@ export const resolvePolicyAsCodeTraceMetadata = (params: {
181
133
  code: 'POLICY_AS_CODE_UNSIGNED',
182
134
  message:
183
135
  'Policy-as-code contract is missing; runtime policy metadata is unsigned.',
184
- strict: envStrict,
136
+ strict,
185
137
  },
186
138
  };
187
139
  }
@@ -194,7 +146,7 @@ export const resolvePolicyAsCodeTraceMetadata = (params: {
194
146
  status: 'valid',
195
147
  code: 'POLICY_AS_CODE_VALID',
196
148
  message: 'Policy-as-code metadata generated from active runtime policy.',
197
- strict: envStrict,
149
+ strict,
198
150
  },
199
151
  };
200
152
  }
@@ -210,17 +162,15 @@ export const resolvePolicyAsCodeTraceMetadata = (params: {
210
162
  status: 'invalid',
211
163
  code: 'POLICY_AS_CODE_CONTRACT_INVALID',
212
164
  message: 'Policy-as-code contract is malformed.',
213
- strict: envStrict,
165
+ strict,
214
166
  },
215
167
  };
216
168
  }
217
169
 
218
- const strict = resolvePolicyStrict(resolveContractStrictForStage(raw.strict, params.stage));
219
-
220
170
  if (raw.source !== params.source) {
221
171
  return {
222
172
  version: `policy-as-code/${raw.source}@${raw.version}`,
223
- signature: resolveContractSignatureForStage(raw.signatures, params.stage) ?? computedSignature,
173
+ signature: raw.signatures[params.stage],
224
174
  policySource: `file:${POLICY_AS_CODE_CONTRACT_PATH}`,
225
175
  validation: {
226
176
  status: 'unknown-source',
@@ -233,18 +183,18 @@ export const resolvePolicyAsCodeTraceMetadata = (params: {
233
183
  }
234
184
 
235
185
  const expectedSignature = createPolicyAsCodeSignature({
236
- stage: params.stage === 'PRE_WRITE' ? 'PRE_COMMIT' : params.stage,
186
+ stage: params.stage,
237
187
  source: params.source,
238
188
  bundle: params.bundle,
239
189
  hash: params.hash,
240
190
  version: raw.version,
241
191
  });
242
- const stageSignature = resolveContractSignatureForStage(raw.signatures, params.stage);
192
+ const stageSignature = raw.signatures[params.stage];
243
193
 
244
194
  if (stageSignature !== expectedSignature) {
245
195
  return {
246
196
  version: `policy-as-code/${raw.source}@${raw.version}`,
247
- signature: stageSignature ?? computedSignature,
197
+ signature: stageSignature,
248
198
  policySource: `file:${POLICY_AS_CODE_CONTRACT_PATH}`,
249
199
  validation: {
250
200
  status: 'invalid',
@@ -261,7 +211,7 @@ export const resolvePolicyAsCodeTraceMetadata = (params: {
261
211
  if (Number.isFinite(expiresAtTimestamp) && Date.now() >= expiresAtTimestamp) {
262
212
  return {
263
213
  version: `policy-as-code/${raw.source}@${raw.version}`,
264
- signature: stageSignature ?? computedSignature,
214
+ signature: stageSignature,
265
215
  policySource: `file:${POLICY_AS_CODE_CONTRACT_PATH}`,
266
216
  validation: {
267
217
  status: 'expired',
@@ -275,7 +225,7 @@ export const resolvePolicyAsCodeTraceMetadata = (params: {
275
225
 
276
226
  return {
277
227
  version: `policy-as-code/${raw.source}@${raw.version}`,
278
- signature: stageSignature ?? computedSignature,
228
+ signature: stageSignature,
279
229
  policySource: `file:${POLICY_AS_CODE_CONTRACT_PATH}`,
280
230
  validation: {
281
231
  status: 'valid',
@@ -293,7 +243,7 @@ export const resolvePolicyAsCodeTraceMetadata = (params: {
293
243
  status: 'invalid',
294
244
  code: 'POLICY_AS_CODE_CONTRACT_INVALID',
295
245
  message: 'Policy-as-code contract cannot be parsed as JSON.',
296
- strict: envStrict,
246
+ strict,
297
247
  },
298
248
  };
299
249
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.77",
3
+ "version": "6.3.79",
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": {
@@ -19,11 +19,14 @@ import {
19
19
  renderActionRow,
20
20
  renderBadge,
21
21
  } from './framework-menu-ui-components-lib';
22
+ import { buildGovernanceConsoleSummaryLines } from '../integrations/lifecycle/cliGovernanceConsole';
23
+ import type { ConsumerPreflightResult } from './framework-menu-consumer-preflight-types';
22
24
 
23
25
  export const formatAdvancedMenuView = (
24
26
  actions: ReadonlyArray<MenuAction>,
25
27
  options?: {
26
28
  evidenceSummary?: FrameworkMenuEvidenceSummary;
29
+ preflight?: ConsumerPreflightResult | null;
27
30
  }
28
31
  ): string => {
29
32
  const evidenceSummary = options?.evidenceSummary ?? readEvidenceSummaryForMenu(process.cwd());
@@ -33,6 +36,18 @@ export const formatAdvancedMenuView = (
33
36
  const lines = [
34
37
  'Pumuki Framework Menu (Advanced)',
35
38
  `Status: ${statusBadge}`,
39
+ ...(options?.preflight
40
+ ? [
41
+ 'Governance Console',
42
+ ...buildGovernanceConsoleSummaryLines({
43
+ governanceObservation: options.preflight.governanceObservation,
44
+ governanceNextAction: options.preflight.governanceNextAction,
45
+ policyValidation: options.preflight.policyValidation,
46
+ experimentalFeatures: options.preflight.experimentalFeatures,
47
+ }).map((line) => ` ${line}`),
48
+ '',
49
+ ]
50
+ : []),
36
51
  'C. Switch to consumer menu',
37
52
  '',
38
53
  ...groupedActions.flatMap((group, groupIndex) => [
@@ -1,7 +1,6 @@
1
1
  import { renderLegacyPanel, resolveLegacyPanelOuterWidth } from './framework-menu-legacy-audit-lib';
2
2
  import { buildConsumerPreflightBlockingCauseLines } from './framework-menu-consumer-preflight-hints';
3
- import { buildGovernanceNextActionSummaryLines } from '../integrations/lifecycle/governanceNextAction';
4
- import { buildGovernanceObservationSummaryLines } from '../integrations/lifecycle/governanceObservationSnapshot';
3
+ import { buildGovernanceConsoleSummaryLines } from '../integrations/lifecycle/cliGovernanceConsole';
5
4
  import type {
6
5
  ConsumerPreflightRenderOptions,
7
6
  ConsumerPreflightResult,
@@ -21,10 +20,15 @@ const buildConsumerPreflightPanelLines = (
21
20
  `Evidence source: source=${evidence.source.source} path=${evidence.source.path} digest=${evidence.source.digest ?? 'null'} generated_at=${evidence.source.generated_at ?? 'null'}`,
22
21
  `Gate: ${preflight.status} (${preflight.result.violations.length} violations)`,
23
22
  ];
24
- lines.push('', 'Governance truth:');
25
- lines.push(...buildGovernanceObservationSummaryLines(preflight.governanceObservation));
26
- lines.push('', 'Governance next action:');
27
- lines.push(...buildGovernanceNextActionSummaryLines(preflight.governanceNextAction));
23
+ lines.push(
24
+ '',
25
+ ...buildGovernanceConsoleSummaryLines({
26
+ governanceObservation: preflight.governanceObservation,
27
+ governanceNextAction: preflight.governanceNextAction,
28
+ policyValidation: preflight.policyValidation,
29
+ experimentalFeatures: preflight.experimentalFeatures,
30
+ })
31
+ );
28
32
  lines.push(...buildConsumerPreflightBlockingCauseLines(preflight));
29
33
 
30
34
  if (preflight.hints.length > 0) {
@@ -92,10 +92,12 @@ export const runConsumerPreflight = (
92
92
  repoRoot,
93
93
  stage: params.stage,
94
94
  });
95
+ const experimentalFeatures = readLifecycleExperimentalFeaturesSnapshot();
96
+ const policyValidation = readLifecyclePolicyValidationSnapshot(repoRoot);
95
97
  const governanceObservation = readGovernanceObservationSnapshot({
96
98
  repoRoot,
97
- experimentalFeatures: readLifecycleExperimentalFeaturesSnapshot(),
98
- policyValidation: readLifecyclePolicyValidationSnapshot(repoRoot),
99
+ experimentalFeatures,
100
+ policyValidation,
99
101
  git: new LifecycleGitService(),
100
102
  });
101
103
  const governanceNextAction = activeDependencies.readGovernanceNextAction({
@@ -118,6 +120,8 @@ export const runConsumerPreflight = (
118
120
  result,
119
121
  governanceObservation,
120
122
  governanceNextAction,
123
+ policyValidation,
124
+ experimentalFeatures,
121
125
  hints,
122
126
  notificationResults,
123
127
  };
@@ -7,6 +7,8 @@ import type {
7
7
  GovernanceNextActionSummary,
8
8
  } from '../integrations/lifecycle/governanceNextAction';
9
9
  import type { GovernanceObservationSnapshot } from '../integrations/lifecycle/governanceObservationSnapshot';
10
+ import type { LifecycleExperimentalFeaturesSnapshot } from '../integrations/lifecycle/experimentalFeaturesSnapshot';
11
+ import type { LifecyclePolicyValidationSnapshot } from '../integrations/lifecycle/policyValidationSnapshot';
10
12
  import type {
11
13
  PumukiCriticalNotificationEvent,
12
14
  SystemNotificationEmitResult,
@@ -20,6 +22,8 @@ export type ConsumerPreflightResult = {
20
22
  result: AiGateCheckResult;
21
23
  governanceObservation: GovernanceObservationSnapshot;
22
24
  governanceNextAction: GovernanceNextActionSummary;
25
+ policyValidation: LifecyclePolicyValidationSnapshot;
26
+ experimentalFeatures: LifecycleExperimentalFeaturesSnapshot;
23
27
  hints: ReadonlyArray<string>;
24
28
  notificationResults: ReadonlyArray<SystemNotificationEmitResult>;
25
29
  };
@@ -11,6 +11,7 @@ import {
11
11
  renderConsumerRuntimePatternChecks,
12
12
  renderConsumerRuntimeSummary,
13
13
  } from './framework-menu-consumer-runtime-audit';
14
+ import type { ConsumerPreflightResult } from './framework-menu-consumer-preflight-types';
14
15
  import type { ConsumerAction, ConsumerRuntimeEmitNotification, ConsumerRuntimeWrite } from './framework-menu-consumer-runtime-types';
15
16
 
16
17
  type ConsumerRuntimeActionDependencies = {
@@ -30,17 +31,20 @@ type ConsumerRuntimeActionDependencies = {
30
31
  setSummaryOverride: (
31
32
  summary: import('./framework-menu-evidence-summary-lib').FrameworkMenuEvidenceSummary | null
32
33
  ) => void;
34
+ clearLastPreflight: () => void;
35
+ setLastPreflight: (result: ConsumerPreflightResult | null) => void;
33
36
  };
34
37
 
35
38
  const runConsumerRuntimePreflight = async (
36
39
  dependencies: Pick<
37
40
  ConsumerRuntimeActionDependencies,
38
- 'repoRoot' | 'runPreflight' | 'useColor' | 'write'
41
+ 'repoRoot' | 'runPreflight' | 'useColor' | 'write' | 'setLastPreflight'
39
42
  >,
40
43
  stage: 'PRE_COMMIT' | 'PRE_PUSH'
41
44
  ): Promise<void> => {
42
45
  if (dependencies.runPreflight) {
43
46
  const rendered = await dependencies.runPreflight(stage);
47
+ dependencies.setLastPreflight(null);
44
48
  if (typeof rendered === 'string' && rendered.trim().length > 0) {
45
49
  dependencies.write(`\n${rendered}\n`);
46
50
  }
@@ -51,6 +55,7 @@ const runConsumerRuntimePreflight = async (
51
55
  stage,
52
56
  repoRoot: dependencies.repoRoot,
53
57
  });
58
+ dependencies.setLastPreflight(preflight);
54
59
  dependencies.write(
55
60
  `\n${formatConsumerPreflight(preflight, {
56
61
  color: dependencies.useColor(),
@@ -138,15 +143,19 @@ export const createConsumerRuntimeActions = (
138
143
  printConsumerRuntimeEmptyScopeHint({ write: dependencies.write }, summary, 'workingTree');
139
144
  },
140
145
  runPatternChecks: async () => {
146
+ dependencies.clearLastPreflight();
141
147
  dependencies.write(`\n${renderConsumerRuntimePatternChecks(dependencies.repoRoot)}\n`);
142
148
  },
143
149
  runEslintAudit: async () => {
150
+ dependencies.clearLastPreflight();
144
151
  dependencies.write(`\n${renderConsumerRuntimeEslintAudit(dependencies.repoRoot)}\n`);
145
152
  },
146
153
  runAstIntelligence: async () => {
154
+ dependencies.clearLastPreflight();
147
155
  dependencies.write(`\n${renderConsumerRuntimeAstBreakdown(dependencies.repoRoot)}\n`);
148
156
  },
149
157
  runExportMarkdown: async () => {
158
+ dependencies.clearLastPreflight();
150
159
  const filePath = exportConsumerRuntimeMarkdown(
151
160
  dependencies.repoRoot,
152
161
  dependencies.getSummaryOverride()
@@ -154,6 +163,7 @@ export const createConsumerRuntimeActions = (
154
163
  dependencies.write(`\nLegacy read-only markdown exported: ${filePath}\n`);
155
164
  },
156
165
  runFileDiagnostics: async () => {
166
+ dependencies.clearLastPreflight();
157
167
  dependencies.write(`\n${renderConsumerRuntimeFileDiagnostics(dependencies.repoRoot)}\n`);
158
168
  },
159
169
  }) as ReadonlyArray<ConsumerAction>;
@@ -5,6 +5,7 @@ import {
5
5
  resolveConsumerRuntimeUseColor,
6
6
  } from './framework-menu-consumer-runtime-audit';
7
7
  import type { FrameworkMenuEvidenceSummary } from './framework-menu-evidence-summary-lib';
8
+ import type { ConsumerPreflightResult } from './framework-menu-consumer-preflight-types';
8
9
  import type {
9
10
  ConsumerAction,
10
11
  ConsumerMenuRuntime,
@@ -22,6 +23,7 @@ export const createConsumerMenuRuntime = (
22
23
  ): ConsumerMenuRuntime => {
23
24
  const repoRoot = process.cwd();
24
25
  let summaryOverride: FrameworkMenuEvidenceSummary | null = null;
26
+ let lastPreflight: ConsumerPreflightResult | null = null;
25
27
  const emitNotification =
26
28
  params.emitSystemNotification
27
29
  ?? ((payload: Parameters<typeof emitSystemNotification>[0]) =>
@@ -45,6 +47,12 @@ export const createConsumerMenuRuntime = (
45
47
  setSummaryOverride: (summary) => {
46
48
  summaryOverride = summary;
47
49
  },
50
+ clearLastPreflight: () => {
51
+ lastPreflight = null;
52
+ },
53
+ setLastPreflight: (result) => {
54
+ lastPreflight = result;
55
+ },
48
56
  });
49
57
 
50
58
  return {
@@ -55,9 +63,11 @@ export const createConsumerMenuRuntime = (
55
63
  repoRoot,
56
64
  useColor,
57
65
  write: params.write,
66
+ preflight: lastPreflight,
58
67
  });
59
68
  },
60
69
  readCurrentSummary: () => summaryOverride,
70
+ readLastPreflight: () => lastPreflight,
61
71
  };
62
72
  };
63
73
 
@@ -13,10 +13,9 @@ 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 { ConsumerPreflightResult } from './framework-menu-consumer-preflight-types';
18
+ import type { ConsumerAction, ConsumerRuntimeWrite } from './framework-menu-consumer-runtime-types';
20
19
 
21
20
  const buildConsumerRuntimeMenuStatus = (
22
21
  menuSummary: FrameworkMenuEvidenceSummary
@@ -58,6 +57,7 @@ export const renderConsumerRuntimeModernMenu = (
58
57
  actions: ReadonlyArray<ConsumerAction>;
59
58
  repoRoot: string;
60
59
  useColor: () => boolean;
60
+ preflight?: ConsumerPreflightResult | null;
61
61
  }
62
62
  ): string => {
63
63
  const menuSummary = readEvidenceSummaryForMenu(params.repoRoot);
@@ -71,6 +71,18 @@ export const renderConsumerRuntimeModernMenu = (
71
71
  'PUMUKI — Hook-System (run: npx ast-hooks)',
72
72
  'AST Intelligence System Overview',
73
73
  `Status: ${renderBadge(menuStatus.label, menuStatus.level, tokens)}`,
74
+ ...(params.preflight
75
+ ? [
76
+ '',
77
+ 'Governance Console',
78
+ ...buildGovernanceConsoleSummaryLines({
79
+ governanceObservation: params.preflight.governanceObservation,
80
+ governanceNextAction: params.preflight.governanceNextAction,
81
+ policyValidation: params.preflight.policyValidation,
82
+ experimentalFeatures: params.preflight.experimentalFeatures,
83
+ }).map((line) => ` ${line}`),
84
+ ]
85
+ : []),
74
86
  'A. Switch to advanced menu',
75
87
  '',
76
88
  ...groupedActions.flatMap((group) => [
@@ -93,6 +105,7 @@ export const printConsumerRuntimeMenu = (params: {
93
105
  repoRoot: string;
94
106
  useColor: () => boolean;
95
107
  write: ConsumerRuntimeWrite;
108
+ preflight?: ConsumerPreflightResult | null;
96
109
  }): void => {
97
110
  const classicMenu = renderConsumerRuntimeClassicMenu(params.actions, params.useColor);
98
111
  if (!isMenuUiV2Enabled()) {
@@ -105,6 +118,7 @@ export const printConsumerRuntimeMenu = (params: {
105
118
  actions: params.actions,
106
119
  repoRoot: params.repoRoot,
107
120
  useColor: params.useColor,
121
+ preflight: params.preflight,
108
122
  })}\n`);
109
123
  } catch {
110
124
  params.write('\n[pumuki][menu-ui-v2] Render failed. Falling back to classic menu.\n');
@@ -1,4 +1,5 @@
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,
@@ -45,6 +46,7 @@ export type ConsumerMenuRuntime = {
45
46
  actions: ReadonlyArray<ConsumerAction>;
46
47
  printMenu: () => void;
47
48
  readCurrentSummary: () => FrameworkMenuEvidenceSummary | null;
49
+ readLastPreflight: () => ConsumerPreflightResult | null;
48
50
  };
49
51
 
50
52
  export type ConsumerRuntimeScope = 'staged' | 'workingTree';
@@ -73,6 +73,7 @@ const menu = async (): Promise<void> => {
73
73
  consumerRuntime.printMenu();
74
74
  } else {
75
75
  const currentSummary = consumerRuntime.readCurrentSummary();
76
+ const lastPreflight = consumerRuntime.readLastPreflight();
76
77
  if (!isMenuUiV2Enabled()) {
77
78
  output.write(
78
79
  `\n${formatAdvancedMenuClassicView(advancedActions, {
@@ -84,6 +85,7 @@ const menu = async (): Promise<void> => {
84
85
  output.write(
85
86
  `\n${formatAdvancedMenuView(advancedActions, {
86
87
  evidenceSummary: currentSummary ?? undefined,
88
+ preflight: lastPreflight ?? undefined,
87
89
  })}\n`
88
90
  );
89
91
  } catch {
@@ -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'],
@@ -15,10 +15,11 @@ import packageJson from '../package.json';
15
15
  const runNpmStep = (
16
16
  workspace: SmokeWorkspace,
17
17
  args: string[],
18
- context: string
18
+ context: string,
19
+ env?: NodeJS.ProcessEnv
19
20
  ): void => {
20
21
  assertSuccess(
21
- runCommand({ cwd: workspace.consumerRepo, executable: 'npm', args }),
22
+ runCommand({ cwd: workspace.consumerRepo, executable: 'npm', args, env }),
22
23
  context
23
24
  );
24
25
  };
@@ -32,7 +33,14 @@ export const installTarballIntoConsumerRepo = (
32
33
  'node_modules/\n.ai_evidence.json\n',
33
34
  'utf8'
34
35
  );
35
- runNpmStep(workspace, ['install', workspace.tarballPath ?? ''], 'npm install <tarball>');
36
+ runNpmStep(
37
+ workspace,
38
+ ['install', workspace.tarballPath ?? ''],
39
+ 'npm install <tarball>',
40
+ {
41
+ PUMUKI_SKIP_POSTINSTALL: '1',
42
+ }
43
+ );
36
44
  };
37
45
 
38
46
  export const verifyInstalledPackageCanBeRequired = (
@@ -51,10 +59,40 @@ export const verifyInstalledPackageCanBeRequired = (
51
59
  export const verifyInstalledPumukiBinaryVersion = (
52
60
  workspace: SmokeWorkspace
53
61
  ): void => {
62
+ const hasInstalledStatusVersion = (
63
+ result: ReturnType<typeof runCommand>,
64
+ ): boolean => {
65
+ let parsed: unknown;
66
+ try {
67
+ parsed = JSON.parse(result.stdout);
68
+ } catch {
69
+ return false;
70
+ }
71
+
72
+ const packageVersion =
73
+ typeof parsed === 'object' && parsed !== null && 'packageVersion' in parsed
74
+ ? (parsed as { packageVersion?: unknown }).packageVersion
75
+ : null;
76
+ const effectiveVersion =
77
+ typeof parsed === 'object'
78
+ && parsed !== null
79
+ && 'version' in parsed
80
+ && typeof (parsed as { version?: unknown }).version === 'object'
81
+ && (parsed as { version: { effective?: unknown } }).version !== null
82
+ ? (parsed as { version: { effective?: unknown } }).version.effective
83
+ : null;
84
+
85
+ return packageVersion === packageJson.version || effectiveVersion === packageJson.version;
86
+ };
87
+
54
88
  const assertInstalledStatusVersion = (
55
89
  result: ReturnType<typeof runCommand>,
56
90
  context: string
57
91
  ): void => {
92
+ if (hasInstalledStatusVersion(result)) {
93
+ return;
94
+ }
95
+
58
96
  let parsed: unknown;
59
97
  try {
60
98
  parsed = JSON.parse(result.stdout);
@@ -75,11 +113,9 @@ export const verifyInstalledPumukiBinaryVersion = (
75
113
  ? (parsed as { version: { effective?: unknown } }).version.effective
76
114
  : null;
77
115
 
78
- if (packageVersion !== packageJson.version && effectiveVersion !== packageJson.version) {
79
- throw new Error(
80
- `${context} reported unexpected version (packageVersion=${String(packageVersion)}, effectiveVersion=${String(effectiveVersion)}, expected=${packageJson.version})`
81
- );
82
- }
116
+ throw new Error(
117
+ `${context} reported unexpected version (packageVersion=${String(packageVersion)}, effectiveVersion=${String(effectiveVersion)}, expected=${packageJson.version})`
118
+ );
83
119
  };
84
120
 
85
121
  const noInstallVersionCheck = runCommand({
@@ -93,7 +129,8 @@ export const verifyInstalledPumukiBinaryVersion = (
93
129
  noInstallVersionCheck.exitCode === 0
94
130
  && !/Cannot find module|ERR_MODULE_NOT_FOUND|failed to resolve tsx runtime/.test(
95
131
  noInstallVersionCheck.combined
96
- );
132
+ )
133
+ && hasInstalledStatusVersion(noInstallVersionCheck);
97
134
  if (noInstallPassed) {
98
135
  assertNoFatalOutput(noInstallVersionCheck, 'pumuki status --json smoke');
99
136
  assertInstalledStatusVersion(noInstallVersionCheck, 'pumuki status --json smoke');