pumuki 6.3.315 → 6.3.316
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/integrations/lifecycle/preWriteAutomation.ts +19 -1
- package/integrations/tdd/enforcement.test.ts +77 -0
- package/integrations/tdd/enforcement.ts +21 -8
- package/package.json +1 -1
- package/scripts/framework-menu-system-notifications-macos-dialog-payload.ts +3 -3
- package/scripts/framework-menu-system-notifications-payloads-blocked.ts +3 -1
|
@@ -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
|
-
|
|
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:
|
|
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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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.
|
|
3
|
+
"version": "6.3.316",
|
|
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
|
|
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
|
};
|