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 +1 -1
- package/docs/operations/RELEASE_NOTES.md +13 -0
- package/integrations/evidence/buildEvidence.ts +18 -8
- package/integrations/evidence/trackingContract.ts +1 -0
- package/integrations/evidence/writeEvidence.ts +18 -8
- package/integrations/gate/stagePolicies.ts +6 -44
- package/integrations/lifecycle/cli.ts +13 -40
- package/integrations/lifecycle/cliGovernanceConsole.ts +69 -0
- package/integrations/lifecycle/policyReconcile.ts +13 -50
- package/integrations/policy/policyAsCode.ts +13 -63
- package/package.json +1 -1
- package/scripts/framework-menu-advanced-view-lib.ts +15 -0
- package/scripts/framework-menu-consumer-preflight-render.ts +10 -6
- package/scripts/framework-menu-consumer-preflight-run.ts +6 -2
- package/scripts/framework-menu-consumer-preflight-types.ts +4 -0
- package/scripts/framework-menu-consumer-runtime-actions.ts +11 -1
- package/scripts/framework-menu-consumer-runtime-lib.ts +10 -0
- package/scripts/framework-menu-consumer-runtime-menu.ts +18 -4
- package/scripts/framework-menu-consumer-runtime-types.ts +2 -0
- package/scripts/framework-menu.ts +2 -0
- package/scripts/package-install-smoke-consumer-git-repo-lib.ts +10 -1
- package/scripts/package-install-smoke-consumer-npm-lib.ts +46 -9
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
v6.3.
|
|
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:
|
|
721
|
-
canonical_path:
|
|
722
|
-
canonical_present:
|
|
723
|
-
source_file:
|
|
724
|
-
in_progress_count:
|
|
725
|
-
single_in_progress_valid:
|
|
726
|
-
conflict:
|
|
727
|
-
declarations:
|
|
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,
|
|
@@ -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:
|
|
222
|
-
canonical_path:
|
|
223
|
-
canonical_present:
|
|
224
|
-
source_file:
|
|
225
|
-
in_progress_count:
|
|
226
|
-
single_in_progress_valid:
|
|
227
|
-
conflict:
|
|
228
|
-
declarations:
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
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
|
-
|
|
2362
|
-
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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 {
|
|
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(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
98
|
-
policyValidation
|
|
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
|
|
17
|
-
|
|
18
|
-
|
|
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(
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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');
|