pumuki 6.3.93 → 6.3.95
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.
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## 2026-04-20 (v6.3.95)
|
|
2
|
+
- **Enforcement operativo de trazabilidad contractual**: `pumuki sdd evidence` exige ahora `--traceability-markdown=<path>` cuando el repositorio declara en `AGENTS.md` la matriz mínima `ARCHIVO | SKILL | REGLA | EVIDENCIA | ESTADO`; el comando falla si falta la cabecera exacta o si no existe al menos una fila real.
|
|
3
|
+
- **Rollout recomendado**: publicar `pumuki@6.3.95`, repin inmediato en `Flux_training` y repetir la doble repro de `pumuki sdd evidence`: sin `--traceability-markdown` debe bloquear, y con un markdown repo-local que contenga la matriz contractual debe pasar.
|
|
4
|
+
|
|
5
|
+
## 2026-04-20 (v6.3.94)
|
|
6
|
+
- **Subtitle de bloqueo 100% en español**: la causa primaria visible de notificaciones ya traduce explícitamente los mensajes exactos de `console.log usage is not allowed in frontend code.` y `...backend code.` antes de construir el `subtitle`, evitando el último escape de copy inglés en bloqueos reales de frontend/backend.
|
|
7
|
+
- **Rollout recomendado**: publicar `pumuki@6.3.94`, repin inmediato en `Flux_training` y repetir un rojo real de frontend con `PUMUKI_SKIP_CHAINED_PRE_WRITE=1 pnpm exec pumuki-pre-commit`, confirmando que el `subtitle` contiene `Se detectó uso de "console.log" en frontend.` y no la frase inglesa raw.
|
|
8
|
+
|
|
1
9
|
## 2026-04-20 (v6.3.93)
|
|
2
10
|
- **Tracking terminal 100% en español**: las violaciones de repo policy `TRACKING_CANONICAL_SOURCE_CONFLICT`, `TRACKING_CANONICAL_FILE_MISSING` y `TRACKING_CANONICAL_IN_PROGRESS_INVALID` traducen ya la línea `[ERROR]` del gate en terminal, no solo el banner/modal.
|
|
3
11
|
- **Rollout recomendado**: publicar `pumuki@6.3.93`, repin inmediato en `Flux_training` y repetir el bloqueo con doble `🚧` para confirmar que desaparece la frase inglesa `Tracking canonical file must contain exactly one in-progress task`.
|
|
@@ -152,6 +152,7 @@ export type ParsedArgs = {
|
|
|
152
152
|
sddEvidenceTestStatus?: SddEvidenceScaffoldTestStatus;
|
|
153
153
|
sddEvidenceTestOutput?: string;
|
|
154
154
|
sddEvidenceFromEvidence?: string;
|
|
155
|
+
sddEvidenceTraceabilityMarkdown?: string;
|
|
155
156
|
sddStateSyncDryRun?: boolean;
|
|
156
157
|
sddStateSyncScenarioId?: string;
|
|
157
158
|
sddStateSyncStatus?: SddStateSyncStatus;
|
|
@@ -208,7 +209,7 @@ Pumuki lifecycle commands:
|
|
|
208
209
|
pumuki sdd sync [--change=<change-id>] [--stage=PRE_WRITE|PRE_COMMIT|PRE_PUSH|CI] [--task=<task-id>] [--from-evidence=<path>] [--dry-run] [--json]
|
|
209
210
|
pumuki sdd learn --change=<change-id> [--stage=PRE_WRITE|PRE_COMMIT|PRE_PUSH|CI] [--task=<task-id>] [--from-evidence=<path>] [--dry-run] [--json]
|
|
210
211
|
pumuki sdd auto-sync --change=<change-id> [--stage=PRE_WRITE|PRE_COMMIT|PRE_PUSH|CI] [--task=<task-id>] [--from-evidence=<path>] [--dry-run] [--json]
|
|
211
|
-
pumuki sdd evidence --scenario-id=<id> --test-command=<command> --test-status=passed|failed [--test-output=<path>] [--from-evidence=<path>] [--dry-run] [--json]
|
|
212
|
+
pumuki sdd evidence --scenario-id=<id> --test-command=<command> --test-status=passed|failed [--test-output=<path>] [--from-evidence=<path>] [--traceability-markdown=<path>] [--dry-run] [--json]
|
|
212
213
|
pumuki sdd state-sync [--scenario-id=<id>] [--status=todo|in_progress|blocked|done] [--from-evidence=<path>] [--board-path=<path>] [--force] [--dry-run] [--json]
|
|
213
214
|
aliases de --stage: RED=PRE_WRITE, GREEN=PRE_COMMIT, REFACTOR=PRE_PUSH, CLOSE=CI
|
|
214
215
|
`.trim();
|
|
@@ -612,6 +613,7 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
|
|
|
612
613
|
let sddEvidenceTestStatus: ParsedArgs['sddEvidenceTestStatus'];
|
|
613
614
|
let sddEvidenceTestOutput: ParsedArgs['sddEvidenceTestOutput'];
|
|
614
615
|
let sddEvidenceFromEvidence: ParsedArgs['sddEvidenceFromEvidence'];
|
|
616
|
+
let sddEvidenceTraceabilityMarkdown: ParsedArgs['sddEvidenceTraceabilityMarkdown'];
|
|
615
617
|
let sddStateSyncDryRun = false;
|
|
616
618
|
let sddStateSyncScenarioId: ParsedArgs['sddStateSyncScenarioId'];
|
|
617
619
|
let sddStateSyncStatus: ParsedArgs['sddStateSyncStatus'];
|
|
@@ -1095,6 +1097,17 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
|
|
|
1095
1097
|
sddEvidenceTestOutput = testOutputPath;
|
|
1096
1098
|
continue;
|
|
1097
1099
|
}
|
|
1100
|
+
if (arg.startsWith('--traceability-markdown=')) {
|
|
1101
|
+
if (sddCommand !== 'evidence') {
|
|
1102
|
+
throw new Error(`--traceability-markdown is only supported with "pumuki sdd evidence".\n\n${HELP_TEXT}`);
|
|
1103
|
+
}
|
|
1104
|
+
const traceabilityMarkdownPath = arg.slice('--traceability-markdown='.length).trim();
|
|
1105
|
+
if (traceabilityMarkdownPath.length === 0) {
|
|
1106
|
+
throw new Error(`Invalid --traceability-markdown value "${arg}".`);
|
|
1107
|
+
}
|
|
1108
|
+
sddEvidenceTraceabilityMarkdown = traceabilityMarkdownPath;
|
|
1109
|
+
continue;
|
|
1110
|
+
}
|
|
1098
1111
|
if (arg.startsWith('--from-evidence=')) {
|
|
1099
1112
|
if (sddCommand === 'sync-docs') {
|
|
1100
1113
|
sddSyncDocsFromEvidence = parseSddEvidencePath(
|
|
@@ -1255,7 +1268,7 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
|
|
|
1255
1268
|
sddAutoSyncChange
|
|
1256
1269
|
) {
|
|
1257
1270
|
throw new Error(
|
|
1258
|
-
`"pumuki sdd evidence" only supports --scenario-id=<id> --test-command=<command> --test-status=passed|failed [--test-output=<path>] [--from-evidence=<path>] [--dry-run] [--json].\n\n${HELP_TEXT}`
|
|
1271
|
+
`"pumuki sdd evidence" only supports --scenario-id=<id> --test-command=<command> --test-status=passed|failed [--test-output=<path>] [--from-evidence=<path>] [--traceability-markdown=<path>] [--dry-run] [--json].\n\n${HELP_TEXT}`
|
|
1259
1272
|
);
|
|
1260
1273
|
}
|
|
1261
1274
|
if (!sddEvidenceScenarioId) {
|
|
@@ -1278,6 +1291,7 @@ export const parseLifecycleCliArgs = (argv: ReadonlyArray<string>): ParsedArgs =
|
|
|
1278
1291
|
sddEvidenceTestStatus,
|
|
1279
1292
|
...(sddEvidenceTestOutput ? { sddEvidenceTestOutput } : {}),
|
|
1280
1293
|
...(sddEvidenceFromEvidence ? { sddEvidenceFromEvidence } : {}),
|
|
1294
|
+
...(sddEvidenceTraceabilityMarkdown ? { sddEvidenceTraceabilityMarkdown } : {}),
|
|
1281
1295
|
};
|
|
1282
1296
|
}
|
|
1283
1297
|
if (sddCommand === 'state-sync') {
|
|
@@ -451,6 +451,7 @@ export const runSddCommand = async (parsed: ParsedArgs, activeDependencies: Life
|
|
|
451
451
|
testStatus: parsed.sddEvidenceTestStatus,
|
|
452
452
|
testOutputPath: parsed.sddEvidenceTestOutput,
|
|
453
453
|
fromEvidencePath: parsed.sddEvidenceFromEvidence,
|
|
454
|
+
traceabilityMarkdownPath: parsed.sddEvidenceTraceabilityMarkdown,
|
|
454
455
|
});
|
|
455
456
|
if (parsed.json) {
|
|
456
457
|
writeInfo(JSON.stringify(evidenceResult, null, 2));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { basename, dirname, isAbsolute, relative, resolve } from 'node:path';
|
|
4
4
|
import { readEvidenceResult, type EvidenceReadResult } from '../evidence/readEvidence';
|
|
5
5
|
|
|
@@ -15,6 +15,7 @@ export type SddEvidenceScaffoldResult = {
|
|
|
15
15
|
testStatus: SddEvidenceScaffoldTestStatus;
|
|
16
16
|
testOutputPath: string | null;
|
|
17
17
|
fromEvidencePath: string | null;
|
|
18
|
+
traceabilityMarkdownPath: string | null;
|
|
18
19
|
};
|
|
19
20
|
output: {
|
|
20
21
|
path: string;
|
|
@@ -44,6 +45,13 @@ export type SddEvidenceScaffoldResult = {
|
|
|
44
45
|
source: 'pumuki-sdd-evidence';
|
|
45
46
|
stack: 'sdd-evidence-scaffold';
|
|
46
47
|
};
|
|
48
|
+
traceability: {
|
|
49
|
+
required: boolean;
|
|
50
|
+
path: string | null;
|
|
51
|
+
header_present: boolean;
|
|
52
|
+
rows: number;
|
|
53
|
+
status: 'valid';
|
|
54
|
+
};
|
|
47
55
|
// Legacy fields kept for state-sync compatibility in existing consumers.
|
|
48
56
|
scenario_id: string;
|
|
49
57
|
test_run: {
|
|
@@ -65,10 +73,13 @@ export type SddEvidenceScaffoldResult = {
|
|
|
65
73
|
const computeDigest = (value: string): string =>
|
|
66
74
|
`sha256:${createHash('sha256').update(value, 'utf8').digest('hex')}`;
|
|
67
75
|
|
|
76
|
+
const TRACEABILITY_HEADER = '| ARCHIVO | SKILL | REGLA | EVIDENCIA | ESTADO |';
|
|
77
|
+
const TRACEABILITY_CONTRACT_MARKER = 'ARCHIVO | SKILL | REGLA | EVIDENCIA | ESTADO';
|
|
78
|
+
|
|
68
79
|
const resolveRepoBoundPath = (params: {
|
|
69
80
|
repoRoot: string;
|
|
70
81
|
candidatePath: string;
|
|
71
|
-
flagName: '--from-evidence' | '--test-output';
|
|
82
|
+
flagName: '--from-evidence' | '--test-output' | '--traceability-markdown';
|
|
72
83
|
}): string => {
|
|
73
84
|
const repoRootAbsolute = resolve(params.repoRoot);
|
|
74
85
|
const resolved = isAbsolute(params.candidatePath)
|
|
@@ -141,6 +152,87 @@ const resolveScenarioReference = (scenarioId: string): string => {
|
|
|
141
152
|
return `${normalized}.feature`;
|
|
142
153
|
};
|
|
143
154
|
|
|
155
|
+
const repoRequiresTraceabilityMatrix = (repoRoot: string): boolean => {
|
|
156
|
+
const agentsPath = resolve(repoRoot, 'AGENTS.md');
|
|
157
|
+
if (!existsSync(agentsPath)) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
const contents = readFileSync(agentsPath, 'utf8');
|
|
161
|
+
return (
|
|
162
|
+
contents.includes('Plantilla obligatoria de trazabilidad por turno') ||
|
|
163
|
+
contents.includes(TRACEABILITY_CONTRACT_MARKER)
|
|
164
|
+
);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const validateTraceabilityMarkdown = (params: {
|
|
168
|
+
repoRoot: string;
|
|
169
|
+
required: boolean;
|
|
170
|
+
traceabilityMarkdownPath?: string;
|
|
171
|
+
}): {
|
|
172
|
+
path: string | null;
|
|
173
|
+
header_present: boolean;
|
|
174
|
+
rows: number;
|
|
175
|
+
} => {
|
|
176
|
+
if (!params.required) {
|
|
177
|
+
return {
|
|
178
|
+
path: params.traceabilityMarkdownPath?.trim() ? params.traceabilityMarkdownPath.trim() : null,
|
|
179
|
+
header_present: false,
|
|
180
|
+
rows: 0,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
const candidate = params.traceabilityMarkdownPath?.trim() ?? '';
|
|
184
|
+
if (candidate.length === 0) {
|
|
185
|
+
throw new Error(
|
|
186
|
+
`[pumuki][sdd] evidence requires --traceability-markdown=<path> because this repository declares the contractual traceability matrix (${TRACEABILITY_HEADER}).`
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
const absolutePath = resolveRepoBoundPath({
|
|
190
|
+
repoRoot: params.repoRoot,
|
|
191
|
+
candidatePath: candidate,
|
|
192
|
+
flagName: '--traceability-markdown',
|
|
193
|
+
});
|
|
194
|
+
if (!existsSync(absolutePath)) {
|
|
195
|
+
throw new Error(
|
|
196
|
+
`[pumuki][sdd] traceability markdown file does not exist: ${candidate}. Add the contractual matrix and retry.`
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
const markdown = readFileSync(absolutePath, 'utf8');
|
|
200
|
+
const lines = markdown.split(/\r?\n/);
|
|
201
|
+
const headerIndex = lines.findIndex((line) => line.trim() === TRACEABILITY_HEADER);
|
|
202
|
+
if (headerIndex < 0) {
|
|
203
|
+
throw new Error(
|
|
204
|
+
`[pumuki][sdd] traceability markdown must include the contractual header ${TRACEABILITY_HEADER}.`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
let rows = 0;
|
|
208
|
+
for (const line of lines.slice(headerIndex + 1)) {
|
|
209
|
+
const trimmed = line.trim();
|
|
210
|
+
if (!trimmed.startsWith('|')) {
|
|
211
|
+
if (rows > 0) {
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (/^\|\s*-+\s*\|/.test(trimmed)) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (trimmed === TRACEABILITY_HEADER) {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
rows += 1;
|
|
223
|
+
}
|
|
224
|
+
if (rows === 0) {
|
|
225
|
+
throw new Error(
|
|
226
|
+
'[pumuki][sdd] traceability markdown must include at least one data row under the contractual matrix header.'
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
path: relative(params.repoRoot, absolutePath).split('\\').join('/'),
|
|
231
|
+
header_present: true,
|
|
232
|
+
rows,
|
|
233
|
+
};
|
|
234
|
+
};
|
|
235
|
+
|
|
144
236
|
export const runSddEvidenceScaffold = (params?: {
|
|
145
237
|
repoRoot?: string;
|
|
146
238
|
dryRun?: boolean;
|
|
@@ -149,6 +241,7 @@ export const runSddEvidenceScaffold = (params?: {
|
|
|
149
241
|
testStatus?: SddEvidenceScaffoldTestStatus;
|
|
150
242
|
testOutputPath?: string;
|
|
151
243
|
fromEvidencePath?: string;
|
|
244
|
+
traceabilityMarkdownPath?: string;
|
|
152
245
|
outputPath?: string;
|
|
153
246
|
now?: () => Date;
|
|
154
247
|
evidenceReader?: (repoRoot: string) => EvidenceReadResult;
|
|
@@ -195,6 +288,12 @@ export const runSddEvidenceScaffold = (params?: {
|
|
|
195
288
|
evidenceResult: evidenceReader(repoRoot),
|
|
196
289
|
fromEvidencePath,
|
|
197
290
|
});
|
|
291
|
+
const traceabilityRequired = repoRequiresTraceabilityMatrix(repoRoot);
|
|
292
|
+
const traceability = validateTraceabilityMarkdown({
|
|
293
|
+
repoRoot,
|
|
294
|
+
required: traceabilityRequired,
|
|
295
|
+
traceabilityMarkdownPath: params?.traceabilityMarkdownPath,
|
|
296
|
+
});
|
|
198
297
|
|
|
199
298
|
const now = params?.now ?? (() => new Date());
|
|
200
299
|
const generatedAt = now().toISOString();
|
|
@@ -232,6 +331,13 @@ export const runSddEvidenceScaffold = (params?: {
|
|
|
232
331
|
source: 'pumuki-sdd-evidence',
|
|
233
332
|
stack: 'sdd-evidence-scaffold',
|
|
234
333
|
},
|
|
334
|
+
traceability: {
|
|
335
|
+
required: traceabilityRequired,
|
|
336
|
+
path: traceability.path,
|
|
337
|
+
header_present: traceability.header_present,
|
|
338
|
+
rows: traceability.rows,
|
|
339
|
+
status: 'valid',
|
|
340
|
+
},
|
|
235
341
|
scenario_id: scenarioId,
|
|
236
342
|
test_run: {
|
|
237
343
|
command: testCommand,
|
|
@@ -267,6 +373,7 @@ export const runSddEvidenceScaffold = (params?: {
|
|
|
267
373
|
testStatus,
|
|
268
374
|
testOutputPath,
|
|
269
375
|
fromEvidencePath,
|
|
376
|
+
traceabilityMarkdownPath: traceability.path,
|
|
270
377
|
},
|
|
271
378
|
output: {
|
|
272
379
|
path: outputRelativePath,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.95",
|
|
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,6 +19,8 @@ const BLOCKED_CAUSE_SUMMARY_BY_CODE: Readonly<Record<string, string>> = {
|
|
|
19
19
|
OPENSPEC_MISSING: 'OpenSpec no está instalado en este repositorio.',
|
|
20
20
|
MCP_ENTERPRISE_RECEIPT_MISSING: 'Falta el recibo enterprise de MCP.',
|
|
21
21
|
BACKEND_AVOID_EXPLICIT_ANY: 'Se detectó uso de "any" explícito en backend.',
|
|
22
|
+
FRONTEND_NO_CONSOLE_LOG: 'Se detectó uso de "console.log" en frontend.',
|
|
23
|
+
BACKEND_NO_CONSOLE_LOG: 'Se detectó uso de "console.log" en backend.',
|
|
22
24
|
GIT_ATOMICITY_TOO_MANY_SCOPES: 'Se han cambiado demasiados scopes en el mismo commit.',
|
|
23
25
|
SOLID_HEURISTIC: 'Se detectó una violación estructural en el cambio actual.',
|
|
24
26
|
TRACKING_CANONICAL_SOURCE_CONFLICT:
|
|
@@ -63,6 +65,12 @@ const toKnownSpanishCauseFromMessage = (message: string): string | null => {
|
|
|
63
65
|
if (normalized.includes('avoid explicit any')) {
|
|
64
66
|
return BLOCKED_CAUSE_SUMMARY_BY_CODE.BACKEND_AVOID_EXPLICIT_ANY;
|
|
65
67
|
}
|
|
68
|
+
if (normalized.includes('console.log usage is not allowed in frontend code')) {
|
|
69
|
+
return BLOCKED_CAUSE_SUMMARY_BY_CODE.FRONTEND_NO_CONSOLE_LOG;
|
|
70
|
+
}
|
|
71
|
+
if (normalized.includes('console.log usage is not allowed in backend code')) {
|
|
72
|
+
return BLOCKED_CAUSE_SUMMARY_BY_CODE.BACKEND_NO_CONSOLE_LOG;
|
|
73
|
+
}
|
|
66
74
|
if (normalized.includes('evidence is stale')) {
|
|
67
75
|
return BLOCKED_CAUSE_SUMMARY_BY_CODE.EVIDENCE_STALE;
|
|
68
76
|
}
|