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.
@@ -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 resolved = activeDependencies.resolvePolicyForStage('PRE_COMMIT');
371
- const exitCode = await activeDependencies.runPlatformGate({
372
- policy: resolved.policy,
373
- policyTrace: resolved.trace,
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: resolved.trace,
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 resolved = activeDependencies.resolvePolicyForStage('PRE_PUSH');
431
- const exitCode = await activeDependencies.runPlatformGate({
432
- policy: resolved.policy,
433
- policyTrace: resolved.trace,
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: resolved.trace,
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 resolved = activeDependencies.resolvePolicyForStage('PRE_PUSH');
452
- const exitCode = await activeDependencies.runPlatformGate({
453
- policy: resolved.policy,
454
- policyTrace: resolved.trace,
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: resolved.trace,
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 resolved = activeDependencies.resolvePolicyForStage('PRE_PUSH');
510
- const exitCode = await activeDependencies.runPlatformGate({
511
- policy: resolved.policy,
512
- policyTrace: resolved.trace,
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: resolved.trace,
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.45",
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,