pumuki 6.3.37 → 6.3.39
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/VERSION +1 -1
- package/docs/README.md +1 -1
- package/docs/RELEASE_NOTES.md +53 -0
- package/docs/registro-maestro-de-seguimiento.md +7 -6
- package/docs/seguimiento-activo-pumuki-saas-supermercados.md +129 -0
- package/docs/seguimiento-completo-validacion-ruralgo-03-03-2026.md +27 -3
- package/integrations/gate/evaluateAiGate.ts +20 -2
- package/integrations/git/GitService.ts +28 -1
- package/integrations/git/getCommitRangeFacts.ts +35 -5
- package/integrations/git/gitAtomicity.ts +274 -0
- package/integrations/git/runPlatformGate.ts +86 -0
- package/integrations/git/stageRunners.ts +193 -4
- package/integrations/lifecycle/adapter.templates.json +20 -20
- package/integrations/lifecycle/cli.ts +50 -1
- package/integrations/lifecycle/doctor.ts +17 -4
- package/integrations/lifecycle/hookBlock.ts +37 -11
- package/integrations/lifecycle/hookManager.ts +85 -1
- package/integrations/lifecycle/packageInfo.ts +27 -1
- package/integrations/lifecycle/status.ts +7 -2
- package/integrations/mcp/autoExecuteAiStart.ts +116 -0
- package/integrations/mcp/enterpriseServer.ts +56 -0
- package/integrations/mcp/preFlightCheck.ts +108 -0
- package/integrations/notifications/emitAuditSummaryNotification.ts +28 -0
- package/package.json +1 -1
- package/scripts/framework-menu-consumer-preflight-lib.ts +11 -0
- package/scripts/framework-menu-evidence-summary-lib.ts +1 -1
- package/scripts/framework-menu-system-notifications-lib.ts +281 -17
|
@@ -6,9 +6,17 @@ import {
|
|
|
6
6
|
} from './resolveGitRefs';
|
|
7
7
|
import { runPlatformGate } from './runPlatformGate';
|
|
8
8
|
import { GitService } from './GitService';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
evaluateGitAtomicity,
|
|
11
|
+
type GitAtomicityEvaluation,
|
|
12
|
+
type GitAtomicityStage,
|
|
13
|
+
} from './gitAtomicity';
|
|
14
|
+
import {
|
|
15
|
+
emitAuditSummaryNotificationFromEvidence,
|
|
16
|
+
emitGateBlockedNotification,
|
|
17
|
+
} from '../notifications/emitAuditSummaryNotification';
|
|
10
18
|
import { readFileSync } from 'node:fs';
|
|
11
|
-
import { readEvidenceResult } from '../evidence/readEvidence';
|
|
19
|
+
import { readEvidence, readEvidenceResult } from '../evidence/readEvidence';
|
|
12
20
|
import type { EvidenceReadResult } from '../evidence/readEvidence';
|
|
13
21
|
|
|
14
22
|
const PRE_PUSH_UPSTREAM_REQUIRED_MESSAGE =
|
|
@@ -16,6 +24,20 @@ const PRE_PUSH_UPSTREAM_REQUIRED_MESSAGE =
|
|
|
16
24
|
|
|
17
25
|
const PRE_COMMIT_EVIDENCE_MAX_AGE_SECONDS = 900;
|
|
18
26
|
const PRE_PUSH_EVIDENCE_MAX_AGE_SECONDS = 1800;
|
|
27
|
+
const DEFAULT_BLOCKED_REMEDIATION = 'Corrige la causa del bloqueo y vuelve a ejecutar el gate.';
|
|
28
|
+
|
|
29
|
+
const BLOCKED_REMEDIATION_BY_CODE: Readonly<Record<string, string>> = {
|
|
30
|
+
EVIDENCE_MISSING: 'Regenera .ai_evidence.json ejecutando una auditoría.',
|
|
31
|
+
EVIDENCE_INVALID: 'Corrige/regenera .ai_evidence.json y vuelve a ejecutar el gate.',
|
|
32
|
+
EVIDENCE_CHAIN_INVALID: 'Regenera evidencia para restaurar la cadena criptográfica.',
|
|
33
|
+
EVIDENCE_STALE: 'Refresca evidencia antes de continuar.',
|
|
34
|
+
EVIDENCE_REPO_ROOT_MISMATCH: 'Regenera evidencia desde este mismo repositorio.',
|
|
35
|
+
EVIDENCE_BRANCH_MISMATCH: 'Regenera evidencia en la rama actual y reintenta.',
|
|
36
|
+
EVIDENCE_RULES_COVERAGE_MISSING: 'Ejecuta auditoría completa para recalcular rules_coverage.',
|
|
37
|
+
EVIDENCE_RULES_COVERAGE_INCOMPLETE: 'Asegura coverage_ratio=1 y unevaluated=0.',
|
|
38
|
+
GITFLOW_PROTECTED_BRANCH: 'Trabaja en feature/* y evita ramas protegidas.',
|
|
39
|
+
PRE_PUSH_UPSTREAM_MISSING: 'Ejecuta: git push --set-upstream origin <branch>',
|
|
40
|
+
};
|
|
19
41
|
|
|
20
42
|
type StageRunnerDependencies = {
|
|
21
43
|
resolvePolicyForStage: typeof resolvePolicyForStage;
|
|
@@ -23,13 +45,28 @@ type StageRunnerDependencies = {
|
|
|
23
45
|
resolvePrePushBootstrapBaseRef: typeof resolvePrePushBootstrapBaseRef;
|
|
24
46
|
resolveCiBaseRef: typeof resolveCiBaseRef;
|
|
25
47
|
runPlatformGate: typeof runPlatformGate;
|
|
48
|
+
evaluateGitAtomicity: (params: {
|
|
49
|
+
repoRoot: string;
|
|
50
|
+
stage: GitAtomicityStage;
|
|
51
|
+
fromRef?: string;
|
|
52
|
+
toRef?: string;
|
|
53
|
+
}) => GitAtomicityEvaluation;
|
|
26
54
|
resolveRepoRoot: () => string;
|
|
27
55
|
readPrePushStdin: () => string;
|
|
28
56
|
notifyAuditSummaryFromEvidence: (params: {
|
|
29
57
|
repoRoot: string;
|
|
30
58
|
stage: 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
|
|
31
59
|
}) => void;
|
|
60
|
+
notifyGateBlocked: (params: {
|
|
61
|
+
repoRoot: string;
|
|
62
|
+
stage: 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
|
|
63
|
+
totalViolations: number;
|
|
64
|
+
causeCode: string;
|
|
65
|
+
causeMessage: string;
|
|
66
|
+
remediation: string;
|
|
67
|
+
}) => void;
|
|
32
68
|
readEvidenceResult: (repoRoot: string) => EvidenceReadResult;
|
|
69
|
+
readEvidence: typeof readEvidence;
|
|
33
70
|
now: () => number;
|
|
34
71
|
writeHookGateSummary: (message: string) => void;
|
|
35
72
|
isQuietMode: () => boolean;
|
|
@@ -41,6 +78,13 @@ const defaultDependencies: StageRunnerDependencies = {
|
|
|
41
78
|
resolvePrePushBootstrapBaseRef,
|
|
42
79
|
resolveCiBaseRef,
|
|
43
80
|
runPlatformGate,
|
|
81
|
+
evaluateGitAtomicity: (params) =>
|
|
82
|
+
evaluateGitAtomicity({
|
|
83
|
+
repoRoot: params.repoRoot,
|
|
84
|
+
stage: params.stage,
|
|
85
|
+
fromRef: params.fromRef,
|
|
86
|
+
toRef: params.toRef,
|
|
87
|
+
}),
|
|
44
88
|
resolveRepoRoot: () => new GitService().resolveRepoRoot(),
|
|
45
89
|
readPrePushStdin: () => {
|
|
46
90
|
const envInput = process.env.PUMUKI_PRE_PUSH_STDIN;
|
|
@@ -62,7 +106,11 @@ const defaultDependencies: StageRunnerDependencies = {
|
|
|
62
106
|
stage,
|
|
63
107
|
});
|
|
64
108
|
},
|
|
109
|
+
notifyGateBlocked: (params) => {
|
|
110
|
+
emitGateBlockedNotification(params);
|
|
111
|
+
},
|
|
65
112
|
readEvidenceResult,
|
|
113
|
+
readEvidence,
|
|
66
114
|
now: () => Date.now(),
|
|
67
115
|
writeHookGateSummary: (message) => {
|
|
68
116
|
process.stdout.write(`${message}\n`);
|
|
@@ -87,6 +135,32 @@ const notifyAuditSummaryForStage = (
|
|
|
87
135
|
});
|
|
88
136
|
};
|
|
89
137
|
|
|
138
|
+
const notifyGateBlockedForStage = (params: {
|
|
139
|
+
dependencies: StageRunnerDependencies;
|
|
140
|
+
stage: 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
|
|
141
|
+
fallbackCauseCode: string;
|
|
142
|
+
fallbackCauseMessage: string;
|
|
143
|
+
fallbackRemediation: string;
|
|
144
|
+
}): void => {
|
|
145
|
+
const repoRoot = params.dependencies.resolveRepoRoot();
|
|
146
|
+
const evidence = params.dependencies.readEvidence(repoRoot);
|
|
147
|
+
const firstViolation = evidence?.ai_gate.violations[0];
|
|
148
|
+
const causeCode = firstViolation?.code ?? params.fallbackCauseCode;
|
|
149
|
+
const causeMessage = firstViolation?.message ?? params.fallbackCauseMessage;
|
|
150
|
+
const remediation =
|
|
151
|
+
BLOCKED_REMEDIATION_BY_CODE[causeCode]
|
|
152
|
+
?? params.fallbackRemediation
|
|
153
|
+
?? DEFAULT_BLOCKED_REMEDIATION;
|
|
154
|
+
params.dependencies.notifyGateBlocked({
|
|
155
|
+
repoRoot,
|
|
156
|
+
stage: params.stage,
|
|
157
|
+
totalViolations: evidence?.ai_gate.violations.length ?? 0,
|
|
158
|
+
causeCode,
|
|
159
|
+
causeMessage,
|
|
160
|
+
remediation,
|
|
161
|
+
});
|
|
162
|
+
};
|
|
163
|
+
|
|
90
164
|
const ZERO_HASH = /^0+$/;
|
|
91
165
|
|
|
92
166
|
const toEvidenceAgeSeconds = (
|
|
@@ -159,10 +233,53 @@ const shouldAllowBootstrapPrePush = (rawInput: string): boolean => {
|
|
|
159
233
|
return false;
|
|
160
234
|
};
|
|
161
235
|
|
|
236
|
+
const enforceGitAtomicityGate = (params: {
|
|
237
|
+
dependencies: StageRunnerDependencies;
|
|
238
|
+
repoRoot: string;
|
|
239
|
+
stage: 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
|
|
240
|
+
fromRef?: string;
|
|
241
|
+
toRef?: string;
|
|
242
|
+
}): boolean => {
|
|
243
|
+
const atomicity = params.dependencies.evaluateGitAtomicity({
|
|
244
|
+
repoRoot: params.repoRoot,
|
|
245
|
+
stage: params.stage,
|
|
246
|
+
fromRef: params.fromRef,
|
|
247
|
+
toRef: params.toRef,
|
|
248
|
+
});
|
|
249
|
+
if (!atomicity.enabled || atomicity.allowed) {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
const firstViolation = atomicity.violations[0];
|
|
253
|
+
if (!firstViolation) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
process.stderr.write(`[pumuki][git-atomicity] ${firstViolation.code}: ${firstViolation.message}\n`);
|
|
257
|
+
params.dependencies.notifyGateBlocked({
|
|
258
|
+
repoRoot: params.repoRoot,
|
|
259
|
+
stage: params.stage,
|
|
260
|
+
totalViolations: atomicity.violations.length,
|
|
261
|
+
causeCode: firstViolation.code,
|
|
262
|
+
causeMessage: firstViolation.message,
|
|
263
|
+
remediation: firstViolation.remediation,
|
|
264
|
+
});
|
|
265
|
+
notifyAuditSummaryForStage(params.dependencies, params.stage);
|
|
266
|
+
return true;
|
|
267
|
+
};
|
|
268
|
+
|
|
162
269
|
export async function runPreCommitStage(
|
|
163
270
|
dependencies: Partial<StageRunnerDependencies> = {}
|
|
164
271
|
): Promise<number> {
|
|
165
272
|
const activeDependencies = getDependencies(dependencies);
|
|
273
|
+
const repoRoot = activeDependencies.resolveRepoRoot();
|
|
274
|
+
if (
|
|
275
|
+
enforceGitAtomicityGate({
|
|
276
|
+
dependencies: activeDependencies,
|
|
277
|
+
repoRoot,
|
|
278
|
+
stage: 'PRE_COMMIT',
|
|
279
|
+
})
|
|
280
|
+
) {
|
|
281
|
+
return 1;
|
|
282
|
+
}
|
|
166
283
|
const resolved = activeDependencies.resolvePolicyForStage('PRE_COMMIT');
|
|
167
284
|
const exitCode = await activeDependencies.runPlatformGate({
|
|
168
285
|
policy: resolved.policy,
|
|
@@ -177,6 +294,15 @@ export async function runPreCommitStage(
|
|
|
177
294
|
policyTrace: resolved.trace,
|
|
178
295
|
exitCode,
|
|
179
296
|
});
|
|
297
|
+
if (exitCode !== 0) {
|
|
298
|
+
notifyGateBlockedForStage({
|
|
299
|
+
dependencies: activeDependencies,
|
|
300
|
+
stage: 'PRE_COMMIT',
|
|
301
|
+
fallbackCauseCode: 'PRE_COMMIT_GATE_BLOCKED',
|
|
302
|
+
fallbackCauseMessage: 'Gate blocked pre-commit stage.',
|
|
303
|
+
fallbackRemediation: DEFAULT_BLOCKED_REMEDIATION,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
180
306
|
notifyAuditSummaryForStage(activeDependencies, 'PRE_COMMIT');
|
|
181
307
|
return exitCode;
|
|
182
308
|
}
|
|
@@ -185,17 +311,30 @@ export async function runPrePushStage(
|
|
|
185
311
|
dependencies: Partial<StageRunnerDependencies> = {}
|
|
186
312
|
): Promise<number> {
|
|
187
313
|
const activeDependencies = getDependencies(dependencies);
|
|
314
|
+
const repoRoot = activeDependencies.resolveRepoRoot();
|
|
188
315
|
const upstreamRef = activeDependencies.resolveUpstreamRef();
|
|
189
316
|
if (!upstreamRef) {
|
|
190
317
|
const prePushInput = activeDependencies.readPrePushStdin();
|
|
191
318
|
if (shouldAllowBootstrapPrePush(prePushInput)) {
|
|
319
|
+
const bootstrapBaseRef = activeDependencies.resolvePrePushBootstrapBaseRef();
|
|
320
|
+
if (
|
|
321
|
+
enforceGitAtomicityGate({
|
|
322
|
+
dependencies: activeDependencies,
|
|
323
|
+
repoRoot,
|
|
324
|
+
stage: 'PRE_PUSH',
|
|
325
|
+
fromRef: bootstrapBaseRef,
|
|
326
|
+
toRef: 'HEAD',
|
|
327
|
+
})
|
|
328
|
+
) {
|
|
329
|
+
return 1;
|
|
330
|
+
}
|
|
192
331
|
const resolved = activeDependencies.resolvePolicyForStage('PRE_PUSH');
|
|
193
332
|
const exitCode = await activeDependencies.runPlatformGate({
|
|
194
333
|
policy: resolved.policy,
|
|
195
334
|
policyTrace: resolved.trace,
|
|
196
335
|
scope: {
|
|
197
336
|
kind: 'range',
|
|
198
|
-
fromRef:
|
|
337
|
+
fromRef: bootstrapBaseRef,
|
|
199
338
|
toRef: 'HEAD',
|
|
200
339
|
},
|
|
201
340
|
});
|
|
@@ -209,10 +348,29 @@ export async function runPrePushStage(
|
|
|
209
348
|
return exitCode;
|
|
210
349
|
}
|
|
211
350
|
process.stderr.write(`${PRE_PUSH_UPSTREAM_REQUIRED_MESSAGE}\n`);
|
|
351
|
+
notifyGateBlockedForStage({
|
|
352
|
+
dependencies: activeDependencies,
|
|
353
|
+
stage: 'PRE_PUSH',
|
|
354
|
+
fallbackCauseCode: 'PRE_PUSH_UPSTREAM_MISSING',
|
|
355
|
+
fallbackCauseMessage: 'Branch has no upstream tracking reference.',
|
|
356
|
+
fallbackRemediation: 'Ejecuta: git push --set-upstream origin <branch>',
|
|
357
|
+
});
|
|
212
358
|
notifyAuditSummaryForStage(activeDependencies, 'PRE_PUSH');
|
|
213
359
|
return 1;
|
|
214
360
|
}
|
|
215
361
|
|
|
362
|
+
if (
|
|
363
|
+
enforceGitAtomicityGate({
|
|
364
|
+
dependencies: activeDependencies,
|
|
365
|
+
repoRoot,
|
|
366
|
+
stage: 'PRE_PUSH',
|
|
367
|
+
fromRef: upstreamRef,
|
|
368
|
+
toRef: 'HEAD',
|
|
369
|
+
})
|
|
370
|
+
) {
|
|
371
|
+
return 1;
|
|
372
|
+
}
|
|
373
|
+
|
|
216
374
|
const resolved = activeDependencies.resolvePolicyForStage('PRE_PUSH');
|
|
217
375
|
const exitCode = await activeDependencies.runPlatformGate({
|
|
218
376
|
policy: resolved.policy,
|
|
@@ -229,6 +387,15 @@ export async function runPrePushStage(
|
|
|
229
387
|
policyTrace: resolved.trace,
|
|
230
388
|
exitCode,
|
|
231
389
|
});
|
|
390
|
+
if (exitCode !== 0) {
|
|
391
|
+
notifyGateBlockedForStage({
|
|
392
|
+
dependencies: activeDependencies,
|
|
393
|
+
stage: 'PRE_PUSH',
|
|
394
|
+
fallbackCauseCode: 'PRE_PUSH_GATE_BLOCKED',
|
|
395
|
+
fallbackCauseMessage: 'Gate blocked pre-push stage.',
|
|
396
|
+
fallbackRemediation: DEFAULT_BLOCKED_REMEDIATION,
|
|
397
|
+
});
|
|
398
|
+
}
|
|
232
399
|
notifyAuditSummaryForStage(activeDependencies, 'PRE_PUSH');
|
|
233
400
|
return exitCode;
|
|
234
401
|
}
|
|
@@ -237,16 +404,38 @@ export async function runCiStage(
|
|
|
237
404
|
dependencies: Partial<StageRunnerDependencies> = {}
|
|
238
405
|
): Promise<number> {
|
|
239
406
|
const activeDependencies = getDependencies(dependencies);
|
|
407
|
+
const repoRoot = activeDependencies.resolveRepoRoot();
|
|
408
|
+
const ciBaseRef = activeDependencies.resolveCiBaseRef();
|
|
409
|
+
if (
|
|
410
|
+
enforceGitAtomicityGate({
|
|
411
|
+
dependencies: activeDependencies,
|
|
412
|
+
repoRoot,
|
|
413
|
+
stage: 'CI',
|
|
414
|
+
fromRef: ciBaseRef,
|
|
415
|
+
toRef: 'HEAD',
|
|
416
|
+
})
|
|
417
|
+
) {
|
|
418
|
+
return 1;
|
|
419
|
+
}
|
|
240
420
|
const resolved = activeDependencies.resolvePolicyForStage('CI');
|
|
241
421
|
const exitCode = await activeDependencies.runPlatformGate({
|
|
242
422
|
policy: resolved.policy,
|
|
243
423
|
policyTrace: resolved.trace,
|
|
244
424
|
scope: {
|
|
245
425
|
kind: 'range',
|
|
246
|
-
fromRef:
|
|
426
|
+
fromRef: ciBaseRef,
|
|
247
427
|
toRef: 'HEAD',
|
|
248
428
|
},
|
|
249
429
|
});
|
|
430
|
+
if (exitCode !== 0) {
|
|
431
|
+
notifyGateBlockedForStage({
|
|
432
|
+
dependencies: activeDependencies,
|
|
433
|
+
stage: 'CI',
|
|
434
|
+
fallbackCauseCode: 'CI_GATE_BLOCKED',
|
|
435
|
+
fallbackCauseMessage: 'Gate blocked CI stage.',
|
|
436
|
+
fallbackRemediation: DEFAULT_BLOCKED_REMEDIATION,
|
|
437
|
+
});
|
|
438
|
+
}
|
|
250
439
|
notifyAuditSummaryForStage(activeDependencies, 'CI');
|
|
251
440
|
return exitCode;
|
|
252
441
|
}
|
|
@@ -5,16 +5,16 @@
|
|
|
5
5
|
"payload": {
|
|
6
6
|
"hooks": {
|
|
7
7
|
"pre_write": {
|
|
8
|
-
"command": "npx --yes pumuki-pre-write"
|
|
8
|
+
"command": "npx --yes --package pumuki@latest pumuki-pre-write"
|
|
9
9
|
},
|
|
10
10
|
"pre_commit": {
|
|
11
|
-
"command": "npx --yes pumuki-pre-commit"
|
|
11
|
+
"command": "npx --yes --package pumuki@latest pumuki-pre-commit"
|
|
12
12
|
},
|
|
13
13
|
"pre_push": {
|
|
14
|
-
"command": "npx --yes pumuki-pre-push"
|
|
14
|
+
"command": "npx --yes --package pumuki@latest pumuki-pre-push"
|
|
15
15
|
},
|
|
16
16
|
"ci": {
|
|
17
|
-
"command": "npx --yes pumuki-ci"
|
|
17
|
+
"command": "npx --yes --package pumuki@latest pumuki-ci"
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
20
|
"mcp": {
|
|
@@ -35,16 +35,16 @@
|
|
|
35
35
|
"pumuki": {
|
|
36
36
|
"hooks": {
|
|
37
37
|
"pre_write": {
|
|
38
|
-
"command": "npx --yes pumuki-pre-write"
|
|
38
|
+
"command": "npx --yes --package pumuki@latest pumuki-pre-write"
|
|
39
39
|
},
|
|
40
40
|
"pre_commit": {
|
|
41
|
-
"command": "npx --yes pumuki-pre-commit"
|
|
41
|
+
"command": "npx --yes --package pumuki@latest pumuki-pre-commit"
|
|
42
42
|
},
|
|
43
43
|
"pre_push": {
|
|
44
|
-
"command": "npx --yes pumuki-pre-push"
|
|
44
|
+
"command": "npx --yes --package pumuki@latest pumuki-pre-push"
|
|
45
45
|
},
|
|
46
46
|
"ci": {
|
|
47
|
-
"command": "npx --yes pumuki-ci"
|
|
47
|
+
"command": "npx --yes --package pumuki@latest pumuki-ci"
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
},
|
|
@@ -97,16 +97,16 @@
|
|
|
97
97
|
},
|
|
98
98
|
"pumukiHooks": {
|
|
99
99
|
"pre_write": {
|
|
100
|
-
"command": "npx --yes pumuki-pre-write"
|
|
100
|
+
"command": "npx --yes --package pumuki@latest pumuki-pre-write"
|
|
101
101
|
},
|
|
102
102
|
"pre_commit": {
|
|
103
|
-
"command": "npx --yes pumuki-pre-commit"
|
|
103
|
+
"command": "npx --yes --package pumuki@latest pumuki-pre-commit"
|
|
104
104
|
},
|
|
105
105
|
"pre_push": {
|
|
106
|
-
"command": "npx --yes pumuki-pre-push"
|
|
106
|
+
"command": "npx --yes --package pumuki@latest pumuki-pre-push"
|
|
107
107
|
},
|
|
108
108
|
"ci": {
|
|
109
|
-
"command": "npx --yes pumuki-ci"
|
|
109
|
+
"command": "npx --yes --package pumuki@latest pumuki-ci"
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
}
|
|
@@ -116,10 +116,10 @@
|
|
|
116
116
|
{
|
|
117
117
|
"path": ".codeium/adapter/hooks.json",
|
|
118
118
|
"payload": {
|
|
119
|
-
"preCommand": "npx --yes pumuki-pre-write",
|
|
120
|
-
"postCommand": "npx --yes pumuki-pre-commit",
|
|
121
|
-
"pushCommand": "npx --yes pumuki-pre-push",
|
|
122
|
-
"ciCommand": "npx --yes pumuki-ci",
|
|
119
|
+
"preCommand": "npx --yes --package pumuki@latest pumuki-pre-write",
|
|
120
|
+
"postCommand": "npx --yes --package pumuki@latest pumuki-pre-commit",
|
|
121
|
+
"pushCommand": "npx --yes --package pumuki@latest pumuki-pre-push",
|
|
122
|
+
"ciCommand": "npx --yes --package pumuki@latest pumuki-ci",
|
|
123
123
|
"mcpCommand": "npx --yes --package pumuki@latest pumuki-mcp-enterprise-stdio"
|
|
124
124
|
}
|
|
125
125
|
},
|
|
@@ -169,16 +169,16 @@
|
|
|
169
169
|
"payload": {
|
|
170
170
|
"hooks": {
|
|
171
171
|
"pre_write": {
|
|
172
|
-
"command": "npx --yes pumuki-pre-write"
|
|
172
|
+
"command": "npx --yes --package pumuki@latest pumuki-pre-write"
|
|
173
173
|
},
|
|
174
174
|
"pre_commit": {
|
|
175
|
-
"command": "npx --yes pumuki-pre-commit"
|
|
175
|
+
"command": "npx --yes --package pumuki@latest pumuki-pre-commit"
|
|
176
176
|
},
|
|
177
177
|
"pre_push": {
|
|
178
|
-
"command": "npx --yes pumuki-pre-push"
|
|
178
|
+
"command": "npx --yes --package pumuki@latest pumuki-pre-push"
|
|
179
179
|
},
|
|
180
180
|
"ci": {
|
|
181
|
-
"command": "npx --yes pumuki-ci"
|
|
181
|
+
"command": "npx --yes --package pumuki@latest pumuki-ci"
|
|
182
182
|
}
|
|
183
183
|
},
|
|
184
184
|
"mcp": {
|
|
@@ -38,7 +38,10 @@ import {
|
|
|
38
38
|
} from '../sdd';
|
|
39
39
|
import { evaluateAiGate } from '../gate/evaluateAiGate';
|
|
40
40
|
import { runEnterpriseAiGateCheck } from '../mcp/aiGateCheck';
|
|
41
|
-
import {
|
|
41
|
+
import {
|
|
42
|
+
emitAuditSummaryNotificationFromAiGate,
|
|
43
|
+
emitGateBlockedNotification,
|
|
44
|
+
} from '../notifications/emitAuditSummaryNotification';
|
|
42
45
|
import {
|
|
43
46
|
buildPreWriteAutomationTrace,
|
|
44
47
|
type PreWriteAutomationTrace,
|
|
@@ -972,6 +975,9 @@ const printDoctorReport = (
|
|
|
972
975
|
): void => {
|
|
973
976
|
writeInfo(`[pumuki] repo: ${report.repoRoot}`);
|
|
974
977
|
writeInfo(`[pumuki] package version: ${report.packageVersion}`);
|
|
978
|
+
writeInfo(
|
|
979
|
+
`[pumuki] hooks path: ${report.hooksDirectory} (${report.hooksDirectoryResolution})`
|
|
980
|
+
);
|
|
975
981
|
writeInfo(
|
|
976
982
|
`[pumuki] tracked node_modules paths: ${report.trackedNodeModulesPaths.length}`
|
|
977
983
|
);
|
|
@@ -1055,12 +1061,14 @@ type PreWriteOpenSpecBootstrapTrace = {
|
|
|
1055
1061
|
|
|
1056
1062
|
type LifecycleCliDependencies = {
|
|
1057
1063
|
emitAuditSummaryNotificationFromAiGate: typeof emitAuditSummaryNotificationFromAiGate;
|
|
1064
|
+
emitGateBlockedNotification: typeof emitGateBlockedNotification;
|
|
1058
1065
|
runPlatformGate: typeof runPlatformGate;
|
|
1059
1066
|
collectRemoteCiDiagnostics: typeof collectRemoteCiDiagnostics;
|
|
1060
1067
|
};
|
|
1061
1068
|
|
|
1062
1069
|
const defaultLifecycleCliDependencies: LifecycleCliDependencies = {
|
|
1063
1070
|
emitAuditSummaryNotificationFromAiGate,
|
|
1071
|
+
emitGateBlockedNotification,
|
|
1064
1072
|
runPlatformGate,
|
|
1065
1073
|
collectRemoteCiDiagnostics,
|
|
1066
1074
|
};
|
|
@@ -1082,6 +1090,9 @@ const PRE_WRITE_HINTS_BY_CODE: Readonly<Record<string, string>> = {
|
|
|
1082
1090
|
MCP_ENTERPRISE_RECEIPT_REPO_ROOT_MISMATCH: 'Genera el recibo MCP en este mismo repositorio.',
|
|
1083
1091
|
};
|
|
1084
1092
|
|
|
1093
|
+
const PRE_WRITE_DEFAULT_REMEDIATION =
|
|
1094
|
+
'Corrige la causa bloqueante y vuelve a ejecutar: npx --yes --package pumuki@latest pumuki-pre-write';
|
|
1095
|
+
|
|
1085
1096
|
const PRE_WRITE_OPENSPEC_AUTOREMEDIABLE_CODES = new Set<string>([
|
|
1086
1097
|
'OPENSPEC_MISSING',
|
|
1087
1098
|
'OPENSPEC_VERSION_UNSUPPORTED',
|
|
@@ -1113,6 +1124,16 @@ const resolvePreWriteNextAction = (params: {
|
|
|
1113
1124
|
};
|
|
1114
1125
|
};
|
|
1115
1126
|
|
|
1127
|
+
const resolvePreWriteBlockedRemediation = (params: {
|
|
1128
|
+
causeCode: string;
|
|
1129
|
+
nextAction?: PreWriteValidationEnvelope['next_action'];
|
|
1130
|
+
}): string => {
|
|
1131
|
+
if (params.nextAction?.command) {
|
|
1132
|
+
return params.nextAction.command;
|
|
1133
|
+
}
|
|
1134
|
+
return PRE_WRITE_HINTS_BY_CODE[params.causeCode] ?? PRE_WRITE_DEFAULT_REMEDIATION;
|
|
1135
|
+
};
|
|
1136
|
+
|
|
1116
1137
|
const wrapPreWritePanelLine = (value: string, width: number): string[] => {
|
|
1117
1138
|
if (width < 20 || value.length <= width) {
|
|
1118
1139
|
return [value];
|
|
@@ -1404,6 +1425,9 @@ export const runLifecycleCli = async (
|
|
|
1404
1425
|
writeInfo(`[pumuki] package version: ${status.packageVersion}`);
|
|
1405
1426
|
writeInfo(`[pumuki] lifecycle installed: ${status.lifecycleState.installed ?? 'false'}`);
|
|
1406
1427
|
writeInfo(`[pumuki] lifecycle version: ${status.lifecycleState.version ?? 'unknown'}`);
|
|
1428
|
+
writeInfo(
|
|
1429
|
+
`[pumuki] hooks path: ${status.hooksDirectory} (${status.hooksDirectoryResolution})`
|
|
1430
|
+
);
|
|
1407
1431
|
writeInfo(
|
|
1408
1432
|
`[pumuki] hooks: pre-commit=${status.hookStatus['pre-commit'].managedBlockPresent ? 'managed' : 'missing'}, pre-push=${status.hookStatus['pre-push'].managedBlockPresent ? 'managed' : 'missing'}`
|
|
1409
1433
|
);
|
|
@@ -1818,9 +1842,34 @@ export const runLifecycleCli = async (
|
|
|
1818
1842
|
});
|
|
1819
1843
|
}
|
|
1820
1844
|
if (!result.decision.allowed) {
|
|
1845
|
+
activeDependencies.emitGateBlockedNotification({
|
|
1846
|
+
repoRoot: process.cwd(),
|
|
1847
|
+
stage: result.stage,
|
|
1848
|
+
totalViolations: aiGate?.violations.length ?? 0,
|
|
1849
|
+
causeCode: result.decision.code,
|
|
1850
|
+
causeMessage: result.decision.message,
|
|
1851
|
+
remediation: resolvePreWriteBlockedRemediation({
|
|
1852
|
+
causeCode: result.decision.code,
|
|
1853
|
+
nextAction,
|
|
1854
|
+
}),
|
|
1855
|
+
});
|
|
1821
1856
|
return 1;
|
|
1822
1857
|
}
|
|
1823
1858
|
if (aiGate && !aiGate.allowed) {
|
|
1859
|
+
const firstViolation = aiGate.violations[0];
|
|
1860
|
+
const causeCode = firstViolation?.code ?? 'AI_GATE_BLOCKED';
|
|
1861
|
+
const causeMessage = firstViolation?.message ?? 'AI gate blocked PRE_WRITE stage.';
|
|
1862
|
+
activeDependencies.emitGateBlockedNotification({
|
|
1863
|
+
repoRoot: process.cwd(),
|
|
1864
|
+
stage: result.stage,
|
|
1865
|
+
totalViolations: aiGate.violations.length,
|
|
1866
|
+
causeCode,
|
|
1867
|
+
causeMessage,
|
|
1868
|
+
remediation: resolvePreWriteBlockedRemediation({
|
|
1869
|
+
causeCode,
|
|
1870
|
+
nextAction,
|
|
1871
|
+
}),
|
|
1872
|
+
});
|
|
1824
1873
|
return 1;
|
|
1825
1874
|
}
|
|
1826
1875
|
return 0;
|
|
@@ -2,7 +2,7 @@ import { existsSync, readFileSync, realpathSync } from 'node:fs';
|
|
|
2
2
|
import { join, resolve } from 'node:path';
|
|
3
3
|
import { readEvidenceResult } from '../evidence/readEvidence';
|
|
4
4
|
import { resolvePolicyForStage } from '../gate/stagePolicies';
|
|
5
|
-
import { getPumukiHooksStatus } from './hookManager';
|
|
5
|
+
import { getPumukiHooksStatus, resolvePumukiHooksDirectory } from './hookManager';
|
|
6
6
|
import { LifecycleGitService, type ILifecycleGitService } from './gitService';
|
|
7
7
|
import { getCurrentPumukiVersion } from './packageInfo';
|
|
8
8
|
import {
|
|
@@ -75,6 +75,8 @@ export type LifecycleDoctorReport = {
|
|
|
75
75
|
lifecycleState: LifecycleState;
|
|
76
76
|
trackedNodeModulesPaths: ReadonlyArray<string>;
|
|
77
77
|
hookStatus: ReturnType<typeof getPumukiHooksStatus>;
|
|
78
|
+
hooksDirectory: string;
|
|
79
|
+
hooksDirectoryResolution: 'git-rev-parse' | 'git-config' | 'default';
|
|
78
80
|
policyValidation: LifecyclePolicyValidationSnapshot;
|
|
79
81
|
issues: ReadonlyArray<DoctorIssue>;
|
|
80
82
|
deep?: DoctorDeepReport;
|
|
@@ -83,6 +85,7 @@ export type LifecycleDoctorReport = {
|
|
|
83
85
|
const buildDoctorIssues = (params: {
|
|
84
86
|
trackedNodeModulesPaths: ReadonlyArray<string>;
|
|
85
87
|
hookStatus: ReturnType<typeof getPumukiHooksStatus>;
|
|
88
|
+
hooksDirectory: string;
|
|
86
89
|
lifecycleState: LifecycleState;
|
|
87
90
|
}): ReadonlyArray<DoctorIssue> => {
|
|
88
91
|
const issues: DoctorIssue[] = [];
|
|
@@ -99,10 +102,16 @@ const buildDoctorIssues = (params: {
|
|
|
99
102
|
params.lifecycleState.installed === 'true' &&
|
|
100
103
|
!Object.values(params.hookStatus).every((entry) => entry.managedBlockPresent)
|
|
101
104
|
) {
|
|
105
|
+
const totalHooks = Object.keys(params.hookStatus).length;
|
|
106
|
+
const managedHooks = Object.values(params.hookStatus).filter(
|
|
107
|
+
(entry) => entry.managedBlockPresent
|
|
108
|
+
).length;
|
|
102
109
|
issues.push({
|
|
103
110
|
severity: 'warning',
|
|
104
111
|
message:
|
|
105
|
-
|
|
112
|
+
`Lifecycle state says installed=true but managed hook blocks are incomplete (${managedHooks}/${totalHooks}). ` +
|
|
113
|
+
`Effective hooks path: ${params.hooksDirectory}. ` +
|
|
114
|
+
'If you use versioned hooks via core.hooksPath, ensure those hooks include the PUMUKI MANAGED block or rerun "pumuki install".',
|
|
106
115
|
});
|
|
107
116
|
}
|
|
108
117
|
|
|
@@ -517,7 +526,7 @@ const buildCompatibilityContract = (params: {
|
|
|
517
526
|
overall: overallCompatible ? 'compatible' : 'incompatible',
|
|
518
527
|
pumuki: {
|
|
519
528
|
installed: pumukiInstalled,
|
|
520
|
-
version: getCurrentPumukiVersion(),
|
|
529
|
+
version: getCurrentPumukiVersion({ repoRoot: params.repoRoot }),
|
|
521
530
|
},
|
|
522
531
|
openspec: {
|
|
523
532
|
required: openSpecRequired,
|
|
@@ -635,12 +644,14 @@ export const runLifecycleDoctor = (params?: {
|
|
|
635
644
|
const cwd = params?.cwd ?? process.cwd();
|
|
636
645
|
const repoRoot = git.resolveRepoRoot(cwd);
|
|
637
646
|
const trackedNodeModulesPaths = git.trackedNodeModulesPaths(repoRoot);
|
|
647
|
+
const hooksDirectory = resolvePumukiHooksDirectory(repoRoot);
|
|
638
648
|
const hookStatus = getPumukiHooksStatus(repoRoot);
|
|
639
649
|
const lifecycleState = readLifecycleState(git, repoRoot);
|
|
640
650
|
|
|
641
651
|
const issues = buildDoctorIssues({
|
|
642
652
|
trackedNodeModulesPaths,
|
|
643
653
|
hookStatus,
|
|
654
|
+
hooksDirectory: hooksDirectory.path,
|
|
644
655
|
lifecycleState,
|
|
645
656
|
});
|
|
646
657
|
const deep = params?.deep
|
|
@@ -654,10 +665,12 @@ export const runLifecycleDoctor = (params?: {
|
|
|
654
665
|
|
|
655
666
|
return {
|
|
656
667
|
repoRoot,
|
|
657
|
-
packageVersion: getCurrentPumukiVersion(),
|
|
668
|
+
packageVersion: getCurrentPumukiVersion({ repoRoot }),
|
|
658
669
|
lifecycleState,
|
|
659
670
|
trackedNodeModulesPaths,
|
|
660
671
|
hookStatus,
|
|
672
|
+
hooksDirectory: hooksDirectory.path,
|
|
673
|
+
hooksDirectoryResolution: hooksDirectory.source,
|
|
661
674
|
policyValidation: readLifecyclePolicyValidationSnapshot(repoRoot),
|
|
662
675
|
issues,
|
|
663
676
|
deep,
|
|
@@ -24,26 +24,52 @@ const managedBlockPattern = new RegExp(
|
|
|
24
24
|
'g'
|
|
25
25
|
);
|
|
26
26
|
|
|
27
|
+
const toHookRunner = (params: {
|
|
28
|
+
hook: PumukiManagedHook;
|
|
29
|
+
runner: string;
|
|
30
|
+
}): string => {
|
|
31
|
+
if (params.hook === 'pre-push') {
|
|
32
|
+
return ` PUMUKI_PRE_PUSH_STDIN="$PUMUKI_PRE_PUSH_STDIN" ${params.runner} "$@"`;
|
|
33
|
+
}
|
|
34
|
+
return ` ${params.runner}`;
|
|
35
|
+
};
|
|
36
|
+
|
|
27
37
|
export const buildPumukiManagedHookBlock = (hook: PumukiManagedHook): string => {
|
|
28
38
|
const cli = HOOK_COMMANDS[hook];
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
` PUMUKI_PRE_PUSH_STDIN="$PUMUKI_PRE_PUSH_STDIN" npx --yes ${cli} "$@"`;
|
|
32
|
-
const hookCommand =
|
|
33
|
-
hook === 'pre-push' ? prePushCommand : ` npx --yes ${cli}`;
|
|
39
|
+
const localBinPath = `./node_modules/.bin/${cli}`;
|
|
40
|
+
const localNodeEntry = `./node_modules/pumuki/bin/${cli}.js`;
|
|
34
41
|
|
|
35
42
|
return [
|
|
36
43
|
PUMUKI_MANAGED_BLOCK_START,
|
|
37
|
-
|
|
38
|
-
|
|
44
|
+
...(hook === 'pre-push' ? ['PUMUKI_PRE_PUSH_STDIN="$(cat)"'] : []),
|
|
45
|
+
`if [ -x "${localBinPath}" ]; then`,
|
|
46
|
+
toHookRunner({
|
|
47
|
+
hook,
|
|
48
|
+
runner: localBinPath,
|
|
49
|
+
}),
|
|
50
|
+
`elif [ -f "${localNodeEntry}" ] && command -v node >/dev/null 2>&1; then`,
|
|
51
|
+
toHookRunner({
|
|
52
|
+
hook,
|
|
53
|
+
runner: `node ${localNodeEntry}`,
|
|
54
|
+
}),
|
|
55
|
+
`elif command -v ${cli} >/dev/null 2>&1; then`,
|
|
56
|
+
toHookRunner({
|
|
57
|
+
hook,
|
|
58
|
+
runner: cli,
|
|
59
|
+
}),
|
|
60
|
+
'elif command -v npx >/dev/null 2>&1; then',
|
|
61
|
+
toHookRunner({
|
|
62
|
+
hook,
|
|
63
|
+
runner: `npx --yes --package pumuki@latest ${cli}`,
|
|
64
|
+
}),
|
|
65
|
+
'else',
|
|
66
|
+
` echo "[pumuki] unable to resolve ${cli} runner. Install dependencies or ensure npx is available." >&2`,
|
|
67
|
+
' exit 1',
|
|
68
|
+
'fi',
|
|
39
69
|
' status=$?',
|
|
40
70
|
' if [ "$status" -ne 0 ]; then',
|
|
41
71
|
' exit "$status"',
|
|
42
72
|
' fi',
|
|
43
|
-
'else',
|
|
44
|
-
' echo "[pumuki] npx was not found in PATH." >&2',
|
|
45
|
-
' exit 1',
|
|
46
|
-
'fi',
|
|
47
73
|
PUMUKI_MANAGED_BLOCK_END,
|
|
48
74
|
].join('\n');
|
|
49
75
|
};
|