pumuki 6.3.45 → 6.3.47
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/docs/RELEASE_NOTES.md +26 -0
- package/docs/USAGE.md +19 -0
- package/docs/seguimiento-activo-pumuki-saas-supermercados.md +332 -5
- package/integrations/git/stageRunners.ts +250 -31
- package/integrations/lifecycle/watch.ts +25 -0
- package/package.json +5 -2
- package/scripts/backlog-id-issue-map-lib.ts +1 -1
- package/scripts/reconcile-consumer-backlog-issues-lib.ts +2 -2
- package/scripts/watch-consumer-backlog-fleet-tick.ts +225 -0
- package/scripts/watch-consumer-backlog-fleet.ts +200 -0
- package/scripts/watch-consumer-backlog-lib.ts +2 -2
|
@@ -18,10 +18,12 @@ import {
|
|
|
18
18
|
emitAuditSummaryNotificationFromEvidence,
|
|
19
19
|
emitGateBlockedNotification,
|
|
20
20
|
} from '../notifications/emitAuditSummaryNotification';
|
|
21
|
-
import { readFileSync } from 'node:fs';
|
|
21
|
+
import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
22
|
+
import { join } from 'node:path';
|
|
22
23
|
import { readEvidence, readEvidenceResult } from '../evidence/readEvidence';
|
|
23
24
|
import type { EvidenceReadResult } from '../evidence/readEvidence';
|
|
24
25
|
import { ensureRuntimeArtifactsIgnored } from '../lifecycle/artifacts';
|
|
26
|
+
import { runPolicyReconcile } from '../lifecycle/policyReconcile';
|
|
25
27
|
|
|
26
28
|
const PRE_PUSH_UPSTREAM_REQUIRED_MESSAGE =
|
|
27
29
|
'pumuki pre-push blocked: branch has no upstream tracking reference. Configure upstream first (for example: git push --set-upstream origin <branch>) and retry.';
|
|
@@ -52,8 +54,18 @@ const BLOCKED_REMEDIATION_BY_CODE: Readonly<Record<string, string>> = {
|
|
|
52
54
|
PRE_PUSH_UPSTREAM_MISSING: 'Ejecuta: git push --set-upstream origin <branch>',
|
|
53
55
|
PRE_PUSH_UPSTREAM_MISALIGNED:
|
|
54
56
|
'Alinea upstream con la rama actual: git branch --unset-upstream && git push --set-upstream origin <branch>',
|
|
57
|
+
MANIFEST_MUTATION_DETECTED:
|
|
58
|
+
'Los hooks/gates no deben modificar manifests. Revisa wiring y ejecuta upgrade explícito solo cuando aplique (por ejemplo: pumuki update --latest).',
|
|
55
59
|
};
|
|
56
60
|
|
|
61
|
+
const HOOK_POLICY_RECONCILE_CODES = new Set<string>([
|
|
62
|
+
'SKILLS_PLATFORM_COVERAGE_INCOMPLETE_HIGH',
|
|
63
|
+
'SKILLS_SCOPE_COMPLIANCE_INCOMPLETE_HIGH',
|
|
64
|
+
'EVIDENCE_PLATFORM_SKILLS_SCOPE_INCOMPLETE',
|
|
65
|
+
'EVIDENCE_PLATFORM_SKILLS_BUNDLES_MISSING',
|
|
66
|
+
'EVIDENCE_CROSS_PLATFORM_CRITICAL_ENFORCEMENT_INCOMPLETE',
|
|
67
|
+
]);
|
|
68
|
+
|
|
57
69
|
type StageRunnerDependencies = {
|
|
58
70
|
resolvePolicyForStage: typeof resolvePolicyForStage;
|
|
59
71
|
resolveUpstreamRef: typeof resolveUpstreamRef;
|
|
@@ -89,6 +101,7 @@ type StageRunnerDependencies = {
|
|
|
89
101
|
writeHookGateSummary: (message: string) => void;
|
|
90
102
|
isQuietMode: () => boolean;
|
|
91
103
|
ensureRuntimeArtifactsIgnored: (repoRoot: string) => void;
|
|
104
|
+
runPolicyReconcile: typeof runPolicyReconcile;
|
|
92
105
|
};
|
|
93
106
|
|
|
94
107
|
const defaultDependencies: StageRunnerDependencies = {
|
|
@@ -144,6 +157,7 @@ const defaultDependencies: StageRunnerDependencies = {
|
|
|
144
157
|
} catch {
|
|
145
158
|
}
|
|
146
159
|
},
|
|
160
|
+
runPolicyReconcile,
|
|
147
161
|
};
|
|
148
162
|
|
|
149
163
|
const getDependencies = (
|
|
@@ -196,6 +210,169 @@ const notifyGateBlockedForStage = (params: {
|
|
|
196
210
|
});
|
|
197
211
|
};
|
|
198
212
|
|
|
213
|
+
const isHookPolicyAutoReconcileEnabled = (): boolean =>
|
|
214
|
+
process.env.PUMUKI_HOOK_POLICY_AUTO_RECONCILE !== '0';
|
|
215
|
+
|
|
216
|
+
const shouldRetryAfterPolicyReconcile = (params: {
|
|
217
|
+
dependencies: StageRunnerDependencies;
|
|
218
|
+
repoRoot: string;
|
|
219
|
+
stage: 'PRE_COMMIT' | 'PRE_PUSH';
|
|
220
|
+
}): boolean => {
|
|
221
|
+
const evidence = params.dependencies.readEvidence(params.repoRoot);
|
|
222
|
+
if (!evidence) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
const stageCodes = new Set<string>();
|
|
226
|
+
if (evidence.snapshot.stage === params.stage) {
|
|
227
|
+
for (const finding of evidence.snapshot.findings) {
|
|
228
|
+
stageCodes.add(finding.code);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
for (const violation of evidence.ai_gate.violations) {
|
|
232
|
+
stageCodes.add(violation.code);
|
|
233
|
+
}
|
|
234
|
+
for (const code of stageCodes) {
|
|
235
|
+
if (HOOK_POLICY_RECONCILE_CODES.has(code)) {
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return false;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
type HookStage = 'PRE_COMMIT' | 'PRE_PUSH';
|
|
243
|
+
type HookPolicyTrace = NonNullable<ReturnType<typeof resolvePolicyForStage>['trace']>;
|
|
244
|
+
const MANIFEST_GUARD_FILES = ['package.json', 'package-lock.json'] as const;
|
|
245
|
+
|
|
246
|
+
type ManifestGuardEntry = {
|
|
247
|
+
relativePath: (typeof MANIFEST_GUARD_FILES)[number];
|
|
248
|
+
absolutePath: string;
|
|
249
|
+
existed: boolean;
|
|
250
|
+
contents: string;
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const captureManifestGuardSnapshot = (repoRoot: string): Array<ManifestGuardEntry> =>
|
|
254
|
+
MANIFEST_GUARD_FILES.map((relativePath) => {
|
|
255
|
+
const absolutePath = join(repoRoot, relativePath);
|
|
256
|
+
const existed = existsSync(absolutePath);
|
|
257
|
+
return {
|
|
258
|
+
relativePath,
|
|
259
|
+
absolutePath,
|
|
260
|
+
existed,
|
|
261
|
+
contents: existed ? readFileSync(absolutePath, 'utf8') : '',
|
|
262
|
+
};
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
const restoreManifestGuardSnapshot = (
|
|
266
|
+
snapshot: ReadonlyArray<ManifestGuardEntry>
|
|
267
|
+
): Array<string> => {
|
|
268
|
+
const mutated: Array<string> = [];
|
|
269
|
+
for (const entry of snapshot) {
|
|
270
|
+
const existsNow = existsSync(entry.absolutePath);
|
|
271
|
+
if (entry.existed) {
|
|
272
|
+
if (!existsNow) {
|
|
273
|
+
writeFileSync(entry.absolutePath, entry.contents, 'utf8');
|
|
274
|
+
mutated.push(entry.relativePath);
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
const current = readFileSync(entry.absolutePath, 'utf8');
|
|
278
|
+
if (current !== entry.contents) {
|
|
279
|
+
writeFileSync(entry.absolutePath, entry.contents, 'utf8');
|
|
280
|
+
mutated.push(entry.relativePath);
|
|
281
|
+
}
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
if (existsNow) {
|
|
285
|
+
unlinkSync(entry.absolutePath);
|
|
286
|
+
mutated.push(entry.relativePath);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return Array.from(new Set(mutated));
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const enforceManifestGuard = (params: {
|
|
293
|
+
dependencies: StageRunnerDependencies;
|
|
294
|
+
repoRoot: string;
|
|
295
|
+
stage: HookStage;
|
|
296
|
+
snapshot: ReadonlyArray<ManifestGuardEntry>;
|
|
297
|
+
}): boolean => {
|
|
298
|
+
const mutated = restoreManifestGuardSnapshot(params.snapshot);
|
|
299
|
+
if (mutated.length === 0) {
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
const summary = mutated.join(', ');
|
|
303
|
+
process.stderr.write(
|
|
304
|
+
`[pumuki][manifest-guard] unexpected manifest mutation detected and reverted: ${summary}\n`
|
|
305
|
+
);
|
|
306
|
+
params.dependencies.notifyGateBlocked({
|
|
307
|
+
repoRoot: params.repoRoot,
|
|
308
|
+
stage: params.stage,
|
|
309
|
+
totalViolations: mutated.length,
|
|
310
|
+
causeCode: 'MANIFEST_MUTATION_DETECTED',
|
|
311
|
+
causeMessage:
|
|
312
|
+
`Unexpected manifest mutation detected during ${params.stage}: ${summary}. ` +
|
|
313
|
+
'Hooks/gates must not mutate consumer manifests without explicit upgrade command.',
|
|
314
|
+
remediation: BLOCKED_REMEDIATION_BY_CODE.MANIFEST_MUTATION_DETECTED
|
|
315
|
+
?? DEFAULT_BLOCKED_REMEDIATION,
|
|
316
|
+
});
|
|
317
|
+
notifyAuditSummaryForStage(params.dependencies, params.stage);
|
|
318
|
+
return true;
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const runHookGateAttempt = async (params: {
|
|
322
|
+
dependencies: StageRunnerDependencies;
|
|
323
|
+
stage: HookStage;
|
|
324
|
+
scope: Parameters<typeof runPlatformGate>[0]['scope'];
|
|
325
|
+
}): Promise<{ exitCode: number; policyTrace: HookPolicyTrace }> => {
|
|
326
|
+
const resolved = params.dependencies.resolvePolicyForStage(params.stage);
|
|
327
|
+
const exitCode = await params.dependencies.runPlatformGate({
|
|
328
|
+
policy: resolved.policy,
|
|
329
|
+
policyTrace: resolved.trace,
|
|
330
|
+
scope: params.scope,
|
|
331
|
+
});
|
|
332
|
+
return {
|
|
333
|
+
exitCode,
|
|
334
|
+
policyTrace: resolved.trace,
|
|
335
|
+
};
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
const runHookGateWithPolicyRetry = async (params: {
|
|
339
|
+
dependencies: StageRunnerDependencies;
|
|
340
|
+
repoRoot: string;
|
|
341
|
+
stage: HookStage;
|
|
342
|
+
scope: Parameters<typeof runPlatformGate>[0]['scope'];
|
|
343
|
+
}): Promise<{ exitCode: number; policyTrace: HookPolicyTrace }> => {
|
|
344
|
+
const firstAttempt = await runHookGateAttempt({
|
|
345
|
+
dependencies: params.dependencies,
|
|
346
|
+
stage: params.stage,
|
|
347
|
+
scope: params.scope,
|
|
348
|
+
});
|
|
349
|
+
if (firstAttempt.exitCode === 0) {
|
|
350
|
+
return firstAttempt;
|
|
351
|
+
}
|
|
352
|
+
if (!isHookPolicyAutoReconcileEnabled()) {
|
|
353
|
+
return firstAttempt;
|
|
354
|
+
}
|
|
355
|
+
if (
|
|
356
|
+
!shouldRetryAfterPolicyReconcile({
|
|
357
|
+
dependencies: params.dependencies,
|
|
358
|
+
repoRoot: params.repoRoot,
|
|
359
|
+
stage: params.stage,
|
|
360
|
+
})
|
|
361
|
+
) {
|
|
362
|
+
return firstAttempt;
|
|
363
|
+
}
|
|
364
|
+
params.dependencies.runPolicyReconcile({
|
|
365
|
+
repoRoot: params.repoRoot,
|
|
366
|
+
strict: true,
|
|
367
|
+
apply: true,
|
|
368
|
+
});
|
|
369
|
+
return runHookGateAttempt({
|
|
370
|
+
dependencies: params.dependencies,
|
|
371
|
+
stage: params.stage,
|
|
372
|
+
scope: params.scope,
|
|
373
|
+
});
|
|
374
|
+
};
|
|
375
|
+
|
|
199
376
|
const ZERO_HASH = /^0+$/;
|
|
200
377
|
|
|
201
378
|
const toEvidenceAgeSeconds = (
|
|
@@ -357,6 +534,7 @@ export async function runPreCommitStage(
|
|
|
357
534
|
): Promise<number> {
|
|
358
535
|
const activeDependencies = getDependencies(dependencies);
|
|
359
536
|
const repoRoot = activeDependencies.resolveRepoRoot();
|
|
537
|
+
const manifestSnapshot = captureManifestGuardSnapshot(repoRoot);
|
|
360
538
|
activeDependencies.ensureRuntimeArtifactsIgnored(repoRoot);
|
|
361
539
|
if (
|
|
362
540
|
enforceGitAtomicityGate({
|
|
@@ -367,21 +545,31 @@ export async function runPreCommitStage(
|
|
|
367
545
|
) {
|
|
368
546
|
return 1;
|
|
369
547
|
}
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
548
|
+
const result = await runHookGateWithPolicyRetry({
|
|
549
|
+
dependencies: activeDependencies,
|
|
550
|
+
repoRoot,
|
|
551
|
+
stage: 'PRE_COMMIT',
|
|
374
552
|
scope: {
|
|
375
553
|
kind: 'staged',
|
|
376
554
|
},
|
|
377
555
|
});
|
|
556
|
+
if (
|
|
557
|
+
enforceManifestGuard({
|
|
558
|
+
dependencies: activeDependencies,
|
|
559
|
+
repoRoot,
|
|
560
|
+
stage: 'PRE_COMMIT',
|
|
561
|
+
snapshot: manifestSnapshot,
|
|
562
|
+
})
|
|
563
|
+
) {
|
|
564
|
+
return 1;
|
|
565
|
+
}
|
|
378
566
|
emitSuccessfulHookGateSummary({
|
|
379
567
|
dependencies: activeDependencies,
|
|
380
568
|
stage: 'PRE_COMMIT',
|
|
381
|
-
policyTrace:
|
|
382
|
-
exitCode,
|
|
569
|
+
policyTrace: result.policyTrace,
|
|
570
|
+
exitCode: result.exitCode,
|
|
383
571
|
});
|
|
384
|
-
if (exitCode !== 0) {
|
|
572
|
+
if (result.exitCode !== 0) {
|
|
385
573
|
notifyGateBlockedForStage({
|
|
386
574
|
dependencies: activeDependencies,
|
|
387
575
|
stage: 'PRE_COMMIT',
|
|
@@ -391,7 +579,7 @@ export async function runPreCommitStage(
|
|
|
391
579
|
});
|
|
392
580
|
}
|
|
393
581
|
notifyAuditSummaryForStage(activeDependencies, 'PRE_COMMIT');
|
|
394
|
-
return exitCode;
|
|
582
|
+
return result.exitCode;
|
|
395
583
|
}
|
|
396
584
|
|
|
397
585
|
export async function runPrePushStage(
|
|
@@ -399,6 +587,7 @@ export async function runPrePushStage(
|
|
|
399
587
|
): Promise<number> {
|
|
400
588
|
const activeDependencies = getDependencies(dependencies);
|
|
401
589
|
const repoRoot = activeDependencies.resolveRepoRoot();
|
|
590
|
+
const manifestSnapshot = captureManifestGuardSnapshot(repoRoot);
|
|
402
591
|
activeDependencies.ensureRuntimeArtifactsIgnored(repoRoot);
|
|
403
592
|
const upstreamRef = activeDependencies.resolveUpstreamRef();
|
|
404
593
|
if (!upstreamRef) {
|
|
@@ -427,43 +616,63 @@ export async function runPrePushStage(
|
|
|
427
616
|
) {
|
|
428
617
|
return 1;
|
|
429
618
|
}
|
|
430
|
-
const
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
619
|
+
const result = await runHookGateWithPolicyRetry({
|
|
620
|
+
dependencies: activeDependencies,
|
|
621
|
+
repoRoot,
|
|
622
|
+
stage: 'PRE_PUSH',
|
|
434
623
|
scope: {
|
|
435
624
|
kind: 'range',
|
|
436
625
|
fromRef: bootstrapBaseRef,
|
|
437
626
|
toRef: 'HEAD',
|
|
438
627
|
},
|
|
439
628
|
});
|
|
629
|
+
if (
|
|
630
|
+
enforceManifestGuard({
|
|
631
|
+
dependencies: activeDependencies,
|
|
632
|
+
repoRoot,
|
|
633
|
+
stage: 'PRE_PUSH',
|
|
634
|
+
snapshot: manifestSnapshot,
|
|
635
|
+
})
|
|
636
|
+
) {
|
|
637
|
+
return 1;
|
|
638
|
+
}
|
|
440
639
|
emitSuccessfulHookGateSummary({
|
|
441
640
|
dependencies: activeDependencies,
|
|
442
641
|
stage: 'PRE_PUSH',
|
|
443
|
-
policyTrace:
|
|
444
|
-
exitCode,
|
|
642
|
+
policyTrace: result.policyTrace,
|
|
643
|
+
exitCode: result.exitCode,
|
|
445
644
|
});
|
|
446
645
|
notifyAuditSummaryForStage(activeDependencies, 'PRE_PUSH');
|
|
447
|
-
return exitCode;
|
|
646
|
+
return result.exitCode;
|
|
448
647
|
}
|
|
449
648
|
if (manualInvocationFallback) {
|
|
450
649
|
process.stderr.write(`${PRE_PUSH_MANUAL_FALLBACK_MESSAGE}\n`);
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
650
|
+
const result = await runHookGateWithPolicyRetry({
|
|
651
|
+
dependencies: activeDependencies,
|
|
652
|
+
repoRoot,
|
|
653
|
+
stage: 'PRE_PUSH',
|
|
455
654
|
scope: {
|
|
456
655
|
kind: 'workingTree',
|
|
457
656
|
},
|
|
458
657
|
});
|
|
658
|
+
if (
|
|
659
|
+
enforceManifestGuard({
|
|
660
|
+
dependencies: activeDependencies,
|
|
661
|
+
repoRoot,
|
|
662
|
+
stage: 'PRE_PUSH',
|
|
663
|
+
snapshot: manifestSnapshot,
|
|
664
|
+
})
|
|
665
|
+
) {
|
|
666
|
+
return 1;
|
|
667
|
+
}
|
|
459
668
|
emitSuccessfulHookGateSummary({
|
|
460
669
|
dependencies: activeDependencies,
|
|
461
670
|
stage: 'PRE_PUSH',
|
|
462
|
-
policyTrace:
|
|
463
|
-
exitCode,
|
|
671
|
+
policyTrace: result.policyTrace,
|
|
672
|
+
exitCode: result.exitCode,
|
|
464
673
|
});
|
|
465
674
|
notifyAuditSummaryForStage(activeDependencies, 'PRE_PUSH');
|
|
466
|
-
return exitCode;
|
|
675
|
+
return result.exitCode;
|
|
467
676
|
}
|
|
468
677
|
process.stderr.write(`${PRE_PUSH_UPSTREAM_REQUIRED_MESSAGE}\n`);
|
|
469
678
|
notifyGateBlockedForStage({
|
|
@@ -506,23 +715,33 @@ export async function runPrePushStage(
|
|
|
506
715
|
return 1;
|
|
507
716
|
}
|
|
508
717
|
|
|
509
|
-
const
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
718
|
+
const result = await runHookGateWithPolicyRetry({
|
|
719
|
+
dependencies: activeDependencies,
|
|
720
|
+
repoRoot,
|
|
721
|
+
stage: 'PRE_PUSH',
|
|
513
722
|
scope: {
|
|
514
723
|
kind: 'range',
|
|
515
724
|
fromRef: upstreamRef,
|
|
516
725
|
toRef: 'HEAD',
|
|
517
726
|
},
|
|
518
727
|
});
|
|
728
|
+
if (
|
|
729
|
+
enforceManifestGuard({
|
|
730
|
+
dependencies: activeDependencies,
|
|
731
|
+
repoRoot,
|
|
732
|
+
stage: 'PRE_PUSH',
|
|
733
|
+
snapshot: manifestSnapshot,
|
|
734
|
+
})
|
|
735
|
+
) {
|
|
736
|
+
return 1;
|
|
737
|
+
}
|
|
519
738
|
emitSuccessfulHookGateSummary({
|
|
520
739
|
dependencies: activeDependencies,
|
|
521
740
|
stage: 'PRE_PUSH',
|
|
522
|
-
policyTrace:
|
|
523
|
-
exitCode,
|
|
741
|
+
policyTrace: result.policyTrace,
|
|
742
|
+
exitCode: result.exitCode,
|
|
524
743
|
});
|
|
525
|
-
if (exitCode !== 0) {
|
|
744
|
+
if (result.exitCode !== 0) {
|
|
526
745
|
notifyGateBlockedForStage({
|
|
527
746
|
dependencies: activeDependencies,
|
|
528
747
|
stage: 'PRE_PUSH',
|
|
@@ -532,7 +751,7 @@ export async function runPrePushStage(
|
|
|
532
751
|
});
|
|
533
752
|
}
|
|
534
753
|
notifyAuditSummaryForStage(activeDependencies, 'PRE_PUSH');
|
|
535
|
-
return exitCode;
|
|
754
|
+
return result.exitCode;
|
|
536
755
|
}
|
|
537
756
|
|
|
538
757
|
export async function runCiStage(
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
emitGateBlockedNotification,
|
|
16
16
|
} from '../notifications/emitAuditSummaryNotification';
|
|
17
17
|
import { runPolicyReconcile } from './policyReconcile';
|
|
18
|
+
import { resolvePumukiVersionMetadata, type PumukiVersionMetadata } from './packageInfo';
|
|
18
19
|
|
|
19
20
|
export type LifecycleWatchStage = 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
|
|
20
21
|
export type LifecycleWatchScope = 'workingTree' | 'staged' | 'repoAndStaged' | 'repo';
|
|
@@ -49,6 +50,14 @@ export type LifecycleWatchTickResult = {
|
|
|
49
50
|
export type LifecycleWatchResult = {
|
|
50
51
|
command: 'pumuki watch';
|
|
51
52
|
repoRoot: string;
|
|
53
|
+
version: {
|
|
54
|
+
effective: string;
|
|
55
|
+
runtime: string;
|
|
56
|
+
consumerInstalled: string | null;
|
|
57
|
+
source: PumukiVersionMetadata['source'];
|
|
58
|
+
driftFromRuntime: boolean;
|
|
59
|
+
driftWarning: string | null;
|
|
60
|
+
};
|
|
52
61
|
stage: LifecycleWatchStage;
|
|
53
62
|
scope: LifecycleWatchScope;
|
|
54
63
|
intervalMs: number;
|
|
@@ -73,6 +82,7 @@ type LifecycleWatchDependencies = {
|
|
|
73
82
|
emitAuditSummaryNotificationFromEvidence: typeof emitAuditSummaryNotificationFromEvidence;
|
|
74
83
|
emitGateBlockedNotification: typeof emitGateBlockedNotification;
|
|
75
84
|
runPolicyReconcile: typeof runPolicyReconcile;
|
|
85
|
+
resolvePumukiVersionMetadata: (params?: { repoRoot?: string }) => PumukiVersionMetadata;
|
|
76
86
|
nowMs: () => number;
|
|
77
87
|
sleep: (ms: number) => Promise<void>;
|
|
78
88
|
};
|
|
@@ -96,6 +106,7 @@ const defaultDependencies: LifecycleWatchDependencies = {
|
|
|
96
106
|
emitAuditSummaryNotificationFromEvidence,
|
|
97
107
|
emitGateBlockedNotification,
|
|
98
108
|
runPolicyReconcile,
|
|
109
|
+
resolvePumukiVersionMetadata,
|
|
99
110
|
nowMs: () => Date.now(),
|
|
100
111
|
sleep: async (ms) => {
|
|
101
112
|
await sleepTimer(ms);
|
|
@@ -231,6 +242,12 @@ export const runLifecycleWatch = async (
|
|
|
231
242
|
...dependencies,
|
|
232
243
|
};
|
|
233
244
|
const repoRoot = params?.repoRoot ?? activeDependencies.resolveRepoRoot();
|
|
245
|
+
const versionMetadata = activeDependencies.resolvePumukiVersionMetadata({ repoRoot });
|
|
246
|
+
const driftFromRuntime = versionMetadata.resolvedVersion !== versionMetadata.runtimeVersion;
|
|
247
|
+
const driftWarning = driftFromRuntime
|
|
248
|
+
? `Version drift detectado: effective=${versionMetadata.resolvedVersion} runtime=${versionMetadata.runtimeVersion}. ` +
|
|
249
|
+
'Actualiza el consumer para alinear el binario local con @latest y evitar diagnósticos inconsistentes.'
|
|
250
|
+
: null;
|
|
234
251
|
const stage = params?.stage ?? 'PRE_COMMIT';
|
|
235
252
|
const scope = params?.scope ?? 'workingTree';
|
|
236
253
|
const intervalMs = Math.max(250, Math.trunc(params?.intervalMs ?? 3000));
|
|
@@ -450,6 +467,14 @@ export const runLifecycleWatch = async (
|
|
|
450
467
|
return {
|
|
451
468
|
command: 'pumuki watch',
|
|
452
469
|
repoRoot,
|
|
470
|
+
version: {
|
|
471
|
+
effective: versionMetadata.resolvedVersion,
|
|
472
|
+
runtime: versionMetadata.runtimeVersion,
|
|
473
|
+
consumerInstalled: versionMetadata.consumerInstalledVersion,
|
|
474
|
+
source: versionMetadata.source,
|
|
475
|
+
driftFromRuntime,
|
|
476
|
+
driftWarning,
|
|
477
|
+
},
|
|
453
478
|
stage,
|
|
454
479
|
scope,
|
|
455
480
|
intervalMs,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.47",
|
|
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": {
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"test:heuristics": "npx --yes tsx@4.21.0 --test core/facts/__tests__/extractHeuristicFacts.test.ts",
|
|
50
50
|
"test:evidence": "npx --yes tsx@4.21.0 --test integrations/evidence/__tests__/buildEvidence.test.ts integrations/evidence/__tests__/humanIntent.test.ts",
|
|
51
51
|
"test:mcp": "npx --yes tsx@4.21.0 --test integrations/mcp/__tests__/*.test.ts",
|
|
52
|
-
"test:backlog-tooling": "npx --yes tsx@4.21.0 --test scripts/__tests__/backlog-action-reasons-lib.test.ts scripts/__tests__/backlog-json-contract-lib.test.ts scripts/__tests__/backlog-cli-help-exit-code.test.ts scripts/__tests__/backlog-id-issue-map-lib.test.ts scripts/__tests__/reconcile-consumer-backlog-issues.test.ts scripts/__tests__/watch-consumer-backlog.test.ts",
|
|
52
|
+
"test:backlog-tooling": "npx --yes tsx@4.21.0 --test scripts/__tests__/backlog-action-reasons-lib.test.ts scripts/__tests__/backlog-json-contract-lib.test.ts scripts/__tests__/backlog-cli-help-exit-code.test.ts scripts/__tests__/backlog-id-issue-map-lib.test.ts scripts/__tests__/reconcile-consumer-backlog-issues.test.ts scripts/__tests__/watch-consumer-backlog.test.ts scripts/__tests__/watch-consumer-backlog-fleet.test.ts scripts/__tests__/watch-consumer-backlog-fleet-tick.test.ts",
|
|
53
53
|
"test:saas-ingestion": "npx --yes tsx@4.21.0 --test integrations/lifecycle/__tests__/saasIngestionContract.test.ts integrations/lifecycle/__tests__/saasIngestionBuilder.test.ts integrations/lifecycle/__tests__/saasIngestionTransport.test.ts integrations/lifecycle/__tests__/saasIngestionIdempotency.test.ts integrations/lifecycle/__tests__/saasIngestionAuth.test.ts integrations/lifecycle/__tests__/saasIngestionAudit.test.ts integrations/lifecycle/__tests__/saasIngestionMetrics.test.ts integrations/lifecycle/__tests__/saasIngestionGovernance.test.ts integrations/lifecycle/__tests__/saasFederation.test.ts integrations/lifecycle/__tests__/saasEnterpriseAnalytics.test.ts integrations/lifecycle/__tests__/cli.test.ts",
|
|
54
54
|
"test:operational-memory": "npx --yes tsx@4.21.0 --test integrations/lifecycle/__tests__/operationalMemoryContract.test.ts integrations/lifecycle/__tests__/operationalMemorySignals.test.ts integrations/lifecycle/__tests__/operationalMemorySnapshot.test.ts integrations/git/__tests__/runPlatformGate.test.ts integrations/git/__tests__/runPlatformGateEvidence.test.ts integrations/evidence/__tests__/buildEvidence.test.ts integrations/evidence/writeEvidence.test.ts integrations/evidence/generateEvidence.test.ts",
|
|
55
55
|
"test:stage-gates": "npx --yes tsx@4.21.0 --test integrations/config/__tests__/*.test.ts integrations/gate/__tests__/*.test.ts integrations/git/__tests__/*.test.ts integrations/lifecycle/__tests__/*.test.ts integrations/sdd/__tests__/*.test.ts scripts/__tests__/*.test.ts",
|
|
@@ -101,6 +101,9 @@
|
|
|
101
101
|
"validation:tracking-single-active": "bash scripts/check-tracking-single-active.sh",
|
|
102
102
|
"validation:backlog-reconcile": "node --import tsx scripts/reconcile-consumer-backlog-issues.ts",
|
|
103
103
|
"validation:backlog-watch": "node --import tsx scripts/watch-consumer-backlog.ts",
|
|
104
|
+
"validation:backlog-watch:fleet": "node --import tsx scripts/watch-consumer-backlog-fleet.ts",
|
|
105
|
+
"validation:backlog-watch:tick": "node --import tsx scripts/watch-consumer-backlog-fleet-tick.ts --json --no-fail",
|
|
106
|
+
"validation:backlog-watch:gate": "node --import tsx scripts/watch-consumer-backlog-fleet-tick.ts --json",
|
|
104
107
|
"validation:phase5-escalation:ready-to-submit": "bash scripts/check-phase5-escalation-ready-to-submit.sh",
|
|
105
108
|
"validation:phase5-escalation:prepare": "bash scripts/prepare-phase5-escalation-submission.sh",
|
|
106
109
|
"validation:phase5-escalation:close-submission": "bash scripts/close-phase5-escalation-submission.sh",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
3
|
|
|
4
|
-
export const BACKLOG_ID_PATTERN = /^(PUMUKI-(?:M)?\d+|PUMUKI-INC-\d+|FP-\d+|AST-GAP-\d+)$/;
|
|
4
|
+
export const BACKLOG_ID_PATTERN = /^(PUMUKI-(?:M)?\d+|PUM-\d+|PUMUKI-INC-\d+|FP-\d+|AST-GAP-\d+)$/;
|
|
5
5
|
|
|
6
6
|
export type BacklogIdIssueMapRecord = Readonly<Record<string, number>>;
|
|
7
7
|
|
|
@@ -75,10 +75,10 @@ export type BacklogReconcileResult = {
|
|
|
75
75
|
|
|
76
76
|
const STATUS_EMOJI_PATTERN = /(✅|🚧|⏳|⛔)/;
|
|
77
77
|
const ISSUE_REF_PATTERN = /#(\d+)/;
|
|
78
|
-
const BACKLOG_ID_PATTERN = /^(PUMUKI-(?:M)?\d+|PUMUKI-INC-\d+|FP-\d+|AST-GAP-\d+)$/;
|
|
78
|
+
const BACKLOG_ID_PATTERN = /^(PUMUKI-(?:M)?\d+|PUM-\d+|PUMUKI-INC-\d+|FP-\d+|AST-GAP-\d+)$/;
|
|
79
79
|
const PENDING_REFERENCE_PATTERN = /\|\s*Pendiente(?:\s*\(rel\.\s*#\d+\))?\s*\|/;
|
|
80
80
|
const BACKLOG_SECTION_HEADING_PATTERN =
|
|
81
|
-
/^(\s*###\s*)(✅|🚧|⏳|⛔)(\s+)(PUMUKI-(?:M)?\d+|PUMUKI-INC-\d+|FP-\d+|AST-GAP-\d+)\b/;
|
|
81
|
+
/^(\s*###\s*)(✅|🚧|⏳|⛔)(\s+)(PUMUKI-(?:M)?\d+|PUM-\d+|PUMUKI-INC-\d+|FP-\d+|AST-GAP-\d+)\b/;
|
|
82
82
|
|
|
83
83
|
export type BacklogIssueNumberResolver = (
|
|
84
84
|
backlogId: string,
|