pumuki 6.3.315 → 6.3.317

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.
@@ -98,11 +98,23 @@ const PRE_WRITE_FUNCTIONAL_EXTENSIONS = [
98
98
  '.kts',
99
99
  ] as const;
100
100
 
101
+ const PRE_WRITE_RUNTIME_ARTIFACT_PATHS = new Set<string>([
102
+ '.ai_evidence.json',
103
+ '.AI_EVIDENCE.json',
104
+ '.pumuki/artifacts/mcp-ai-gate-receipt.json',
105
+ '.pumuki/prewrite-lease.json',
106
+ ]);
107
+
101
108
  const isFunctionalPath = (filePath: string): boolean => {
102
109
  const normalized = filePath.trim().toLowerCase();
103
110
  return PRE_WRITE_FUNCTIONAL_EXTENSIONS.some((extension) => normalized.endsWith(extension));
104
111
  };
105
112
 
113
+ const isRuntimeArtifactPath = (filePath: string): boolean => {
114
+ const normalized = filePath.trim().replace(/\\/g, '/');
115
+ return PRE_WRITE_RUNTIME_ARTIFACT_PATHS.has(normalized);
116
+ };
117
+
106
118
  const collectStagedPaths = (repoRoot: string): ReadonlyArray<string> | null => {
107
119
  try {
108
120
  return execFileSync('git', ['diff', '--cached', '--name-only'], {
@@ -127,7 +139,13 @@ const resolvePreWriteRefreshScope = (aiGate: ReturnType<typeof evaluateAiGate>):
127
139
  if (stagedPaths === null || stagedPaths.some(isFunctionalPath)) {
128
140
  return { kind: 'staged' };
129
141
  }
130
- return { kind: 'workingTree' };
142
+ if (stagedPaths.length === 0) {
143
+ return { kind: 'workingTree' };
144
+ }
145
+ if (stagedPaths.length > 0 && stagedPaths.every(isRuntimeArtifactPath)) {
146
+ return { kind: 'workingTree' };
147
+ }
148
+ return { kind: 'staged' };
131
149
  };
132
150
 
133
151
  export const buildPreWriteAutomationTrace = async (params: {
@@ -0,0 +1,77 @@
1
+ import assert from 'node:assert/strict';
2
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import test from 'node:test';
6
+ import type { Fact } from '../../core/facts/Fact';
7
+ import { enforceTddBddPolicy } from './enforcement';
8
+
9
+ test('enforceTddBddPolicy explica cómo registrar XCTest verde cuando falta VERIFY', () => {
10
+ const repoRoot = mkdtempSync(join(tmpdir(), 'pumuki-tdd-verify-xctest-'));
11
+ try {
12
+ mkdirSync(join(repoRoot, 'features'), { recursive: true });
13
+ mkdirSync(join(repoRoot, '.pumuki', 'artifacts'), { recursive: true });
14
+ writeFileSync(join(repoRoot, 'features', 'rgo-1900.feature'), 'Feature: social auth\n', 'utf8');
15
+ writeFileSync(
16
+ join(repoRoot, '.pumuki', 'artifacts', 'pumuki-evidence-v1.json'),
17
+ JSON.stringify({
18
+ version: '1',
19
+ generated_at: '2026-05-20T08:00:00.000Z',
20
+ slices: [
21
+ {
22
+ id: 'rgo-1900',
23
+ scenario_ref: 'features/rgo-1900.feature',
24
+ red: {
25
+ status: 'failed',
26
+ timestamp: '2026-05-20T08:00:00.000Z',
27
+ test_ref: 'RuralGoMacTests/BuyerAppViewModelAuthTests',
28
+ },
29
+ green: {
30
+ status: 'passed',
31
+ timestamp: '2026-05-20T08:10:00.000Z',
32
+ test_ref: 'RuralGoMacTests/BuyerAppViewModelAuthTests',
33
+ },
34
+ refactor: {
35
+ status: 'passed',
36
+ timestamp: '2026-05-20T08:20:00.000Z',
37
+ test_ref: 'RuralGoMacTests/BuyerCommerceScreensSnapshotTests/test_checkoutSnapshot_matchesReference',
38
+ },
39
+ },
40
+ ],
41
+ }),
42
+ 'utf8'
43
+ );
44
+ const facts: ReadonlyArray<Fact> = [
45
+ {
46
+ kind: 'FileChange',
47
+ path: 'apps/ios/Presentation/Features/BuyerCommerce/BuyerAuthScreen.swift',
48
+ changeType: 'modified',
49
+ source: 'git:staged',
50
+ },
51
+ {
52
+ kind: 'FileContent',
53
+ path: 'apps/ios/Presentation/Features/BuyerCommerce/BuyerAuthScreen.swift',
54
+ content: 'public struct BuyerAuthScreen {}',
55
+ source: 'git:staged',
56
+ },
57
+ ];
58
+
59
+ const result = enforceTddBddPolicy({
60
+ facts,
61
+ repoRoot,
62
+ branch: 'feature/rgo-1900-02-checkout-pixel-perfect',
63
+ });
64
+
65
+ const finding = result.findings.find(
66
+ (item) => item.code === 'GOLDEN_FLOW_VERIFY_EVIDENCE_MISSING'
67
+ );
68
+ assert.ok(finding);
69
+ assert.match(finding.message, /missing=\[verify\.status=passed\]/);
70
+ assert.match(finding.message, /BuyerCommerceScreensSnapshotTests/);
71
+ assert.match(finding.expected_fix ?? '', /pumuki sdd evidence/);
72
+ assert.match(finding.expected_fix ?? '', /--test-status=passed/);
73
+ assert.match(finding.expected_fix ?? '', /commit atómico/);
74
+ } finally {
75
+ rmSync(repoRoot, { recursive: true, force: true });
76
+ }
77
+ });
@@ -19,6 +19,7 @@ const buildFinding = (params: {
19
19
  severity?: Finding['severity'];
20
20
  filePath?: string;
21
21
  source?: string;
22
+ expected_fix?: string;
22
23
  }): Finding => {
23
24
  return {
24
25
  ruleId: params.ruleId,
@@ -28,6 +29,7 @@ const buildFinding = (params: {
28
29
  filePath: params.filePath,
29
30
  matchedBy: 'TddBddEnforcer',
30
31
  source: params.source ?? 'tdd-bdd-contract',
32
+ expected_fix: params.expected_fix,
31
33
  };
32
34
  };
33
35
 
@@ -301,8 +303,14 @@ export const enforceTddBddPolicy = (params: {
301
303
  buildFinding({
302
304
  ruleId: 'generic_golden_flow_verify_required',
303
305
  code: 'GOLDEN_FLOW_VERIFY_EVIDENCE_MISSING',
304
- message: `Slice ${slice.id} must include VERIFY passing evidence after REFACTOR.`,
306
+ message:
307
+ `Slice ${slice.id} is missing final test verification after REFACTOR. ` +
308
+ `missing=[verify.status=passed] test_ref=${slice.refactor.test_ref ?? slice.green.test_ref ?? 'unregistered'}`,
305
309
  filePath: evidenceRead.path,
310
+ expected_fix:
311
+ `Si el test/build ya pasó, informa a Pumuki del resultado real con: ` +
312
+ `npx pumuki sdd evidence --scenario-id=${slice.id} --test-command="<comando XCTest/build ejecutado>" --test-status=passed. ` +
313
+ `Después haz un commit atómico del slice.`,
306
314
  })
307
315
  );
308
316
  } else {
@@ -310,13 +318,18 @@ export const enforceTddBddPolicy = (params: {
310
318
  if (slice.verify.status !== 'passed') {
311
319
  sliceFindings.push(
312
320
  buildFinding({
313
- ruleId: 'generic_golden_flow_verify_required',
314
- code: 'GOLDEN_FLOW_VERIFY_MUST_PASS',
315
- message: `Slice ${slice.id} must finish with VERIFY passing evidence.`,
316
- filePath: evidenceRead.path,
317
- })
318
- );
319
- }
321
+ ruleId: 'generic_golden_flow_verify_required',
322
+ code: 'GOLDEN_FLOW_VERIFY_MUST_PASS',
323
+ message:
324
+ `Slice ${slice.id} has final test verification but it is not green. ` +
325
+ `missing=[verify.status=passed] test_ref=${slice.verify.test_ref ?? 'unregistered'}`,
326
+ filePath: evidenceRead.path,
327
+ expected_fix:
328
+ `Corrige el fallo del test/build final y vuelve a informar a Pumuki con ` +
329
+ `npx pumuki sdd evidence --scenario-id=${slice.id} --test-command="<comando XCTest/build ejecutado>" --test-status=passed.`,
330
+ })
331
+ );
332
+ }
320
333
  }
321
334
 
322
335
  if (
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.315",
3
+ "version": "6.3.317",
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": {
@@ -227,7 +227,7 @@ const formatGoldenFlowMissing = (message: string): string => {
227
227
  return missing.toLowerCase();
228
228
  };
229
229
 
230
- const formatGoldenFlowRemediation = (message: string): string => {
230
+ const formatGoldenFlowRemediation = (message: string, remediation?: string): string => {
231
231
  const missing = normalizeGoldenFlowMissingToken(message);
232
232
  if (missing.includes('RED')) {
233
233
  return 'Implementa la fase RED antes de continuar. Las fases del ciclo TDD (RED, GREEN, REFACTOR) deben completarse para desbloquear.';
@@ -239,7 +239,7 @@ const formatGoldenFlowRemediation = (message: string): string => {
239
239
  return 'Implementa la fase REFACTOR antes de continuar. Limpia la solución manteniendo los tests en verde.';
240
240
  }
241
241
  if (missing.includes('VERIFY')) {
242
- return GOLDEN_FLOW_REMEDIATION;
242
+ return remediation ? localizeDeveloperText(remediation) : GOLDEN_FLOW_REMEDIATION;
243
243
  }
244
244
  return 'Completa el ciclo TDD en orden: RED, GREEN, REFACTOR y tests finales en verde antes del commit.';
245
245
  };
@@ -299,7 +299,7 @@ const resolveGoldenFlowDialogRemediation = (
299
299
  causes: Extract<PumukiCriticalNotificationEvent, { kind: 'gate.blocked' }>['blockingCauses']
300
300
  ): string => {
301
301
  const first = causes?.find(isGoldenFlowCause);
302
- return first ? formatGoldenFlowRemediation(first.message) : GOLDEN_FLOW_REMEDIATION;
302
+ return first ? formatGoldenFlowRemediation(first.message, first.remediation) : GOLDEN_FLOW_REMEDIATION;
303
303
  };
304
304
 
305
305
  export const buildBlockedDialogPayload = (params: {
@@ -55,7 +55,9 @@ const formatGoldenFlowBannerRemediation = (cause: BlockedCause): string => {
55
55
  return 'Implementa la fase REFACTOR antes de continuar. Limpia la solución manteniendo los tests en verde.';
56
56
  }
57
57
  if (missing.includes('VERIFY')) {
58
- return 'Ejecuta los tests de la implementación. Si están en verde, haz un commit atómico de los cambios.';
58
+ return cause.remediation
59
+ ? normalizeNotificationText(cause.remediation)
60
+ : 'Ejecuta los tests de la implementación. Si están en verde, haz un commit atómico de los cambios.';
59
61
  }
60
62
  return 'Completa el ciclo TDD en orden: RED, GREEN, REFACTOR y tests finales en verde antes del commit.';
61
63
  };
@@ -143,7 +145,7 @@ const formatCauseProblem = (cause: BlockedCause): string => {
143
145
  const formatCauseFix = (cause: BlockedCause): string =>
144
146
  normalizeNotificationText(
145
147
  cause.remediation ??
146
- 'Corrige la violación indicada con regla, fichero y línea, y vuelve a ejecutar el gate.'
148
+ 'Corrige la violación indicada con regla, fichero y línea, y vuelve a intentar el commit.'
147
149
  );
148
150
 
149
151
  export const resolvePrioritizedBlockingCauses = (
@@ -204,7 +206,7 @@ const buildBlockingCausesRemediation = (
204
206
  return [
205
207
  `Regla: ${formatVisibleRule(first)}.`,
206
208
  `Fichero: ${formatCauseLocation(first)}.`,
207
- `Viola: ${formatCauseProblem(first)}.`,
209
+ `Falla: ${formatCauseProblem(first)}.`,
208
210
  `Solución: ${formatCauseFix(first)}.`,
209
211
  prioritized.length > 1 ? `Quedan ${prioritized.length - 1} causa(s) más en el reporte completo.` : '',
210
212
  ]
@@ -21,9 +21,9 @@ const BLOCKED_REMEDIATION_BY_CODE: Readonly<Record<string, string>> = {
21
21
  EVIDENCE_REPO_ROOT_MISMATCH: 'Regenera la evidencia desde este repositorio y vuelve a validar.',
22
22
  PRE_PUSH_UPSTREAM_MISSING: 'Configura upstream con `git push --set-upstream origin <branch>` y repite PRE_PUSH.',
23
23
  SDD_SESSION_MISSING:
24
- 'Abre una sesión SDD válida para el change activo (`pumuki sdd session --open --change=<id>`) y reejecuta PRE_WRITE. Agente IA: STOP hasta que exista esa sesión.',
24
+ 'Abre la sesión SDD del cambio activo: `pumuki sdd session --open --change=<id>`. Sustituye <id> por la carpeta en openspec/changes. Después vuelve a intentar el commit; Pumuki ejecutará PRE_WRITE automáticamente.',
25
25
  SDD_SESSION_INVALID:
26
- 'Refresca la sesión SDD activa (`pumuki sdd session --refresh --ttl-minutes=90`) y reejecuta PRE_WRITE. Agente IA: STOP hasta que sea válida.',
26
+ 'Refresca la sesión SDD activa: `pumuki sdd session --refresh --ttl-minutes=90`. Después vuelve a intentar el commit; Pumuki ejecutará PRE_WRITE automáticamente.',
27
27
  OPENSPEC_MISSING: 'Instala OpenSpec en este repositorio y vuelve a validar el gate.',
28
28
  MCP_ENTERPRISE_RECEIPT_MISSING: 'Genera el receipt enterprise de MCP y vuelve a validar.',
29
29
  BACKEND_AVOID_EXPLICIT_ANY: 'Sustituye `any` por tipos concretos en backend y relanza el gate.',
@@ -34,7 +34,7 @@ const BLOCKED_REMEDIATION_BY_CODE: Readonly<Record<string, string>> = {
34
34
  ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES_HIGH:
35
35
  'Ejecuta `pumuki policy reconcile --strict --json` y revalida antes de continuar.',
36
36
  EVIDENCE_PREWRITE_WORKTREE_WARN:
37
- 'Reduce el worktree pendiente a un slice atómico antes del commit: stagea solo la tarea activa o guarda el resto en stash nombrado, y reejecuta PRE_WRITE.',
37
+ 'Reduce el worktree pendiente a un slice atómico antes del commit: stagea solo la tarea activa o guarda el resto en stash nombrado, y vuelve a intentar el commit.',
38
38
  EVIDENCE_PREWRITE_WORKTREE_OVER_LIMIT:
39
39
  'El worktree supera el límite permitido: divide cambios por scope en commits atómicos o stashes nombrados antes de continuar.',
40
40
  };