pumuki 6.3.87 → 6.3.88

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.
@@ -814,3 +814,5 @@ This file keeps only the operational highlights and rollout notes that matter wh
814
814
 
815
815
  - Legacy 5.3.4 migration/release notes were removed from active docs to avoid drift.
816
816
  - Historical commit trace remains available in Git history.
817
+
818
+ - 6.3.88: PUM-027 cierra el caso MCP degradado; sin .pumuki/adapter.json el gate bloquea y ya no regenera mcp-ai-gate-receipt.
@@ -68,16 +68,24 @@ const hasAutoFixableEvidenceViolation = (aiGate: ReturnType<typeof evaluateAiGat
68
68
  const hasEvidenceGateBlockedViolation = (aiGate: ReturnType<typeof evaluateAiGate>): boolean =>
69
69
  aiGate.violations.some((violation) => violation.code === 'EVIDENCE_GATE_BLOCKED');
70
70
 
71
+ const hasAdapterMissingViolation = (aiGate: ReturnType<typeof evaluateAiGate>): boolean =>
72
+ aiGate.violations.some((violation) => violation.code === 'MCP_ENTERPRISE_ADAPTER_MISSING');
73
+
71
74
  const hasAutoFixableMcpReceiptViolation = (aiGate: ReturnType<typeof evaluateAiGate>): boolean =>
72
- aiGate.violations.some((violation) => PRE_WRITE_AUTOFIXABLE_MCP_RECEIPT_CODES.has(violation.code));
75
+ !hasAdapterMissingViolation(aiGate)
76
+ && aiGate.violations.some((violation) => PRE_WRITE_AUTOFIXABLE_MCP_RECEIPT_CODES.has(violation.code));
73
77
 
74
78
  const collectAutoFixableViolationCodes = (aiGate: ReturnType<typeof evaluateAiGate>): string[] =>
75
79
  aiGate.violations
76
- .filter(
77
- (violation) =>
78
- PRE_WRITE_AUTOFIXABLE_EVIDENCE_CODES.has(violation.code)
79
- || PRE_WRITE_AUTOFIXABLE_MCP_RECEIPT_CODES.has(violation.code)
80
- )
80
+ .filter((violation) => {
81
+ if (PRE_WRITE_AUTOFIXABLE_EVIDENCE_CODES.has(violation.code)) {
82
+ return true;
83
+ }
84
+ if (!PRE_WRITE_AUTOFIXABLE_MCP_RECEIPT_CODES.has(violation.code)) {
85
+ return false;
86
+ }
87
+ return !hasAdapterMissingViolation(aiGate);
88
+ })
81
89
  .map((violation) => violation.code)
82
90
  .sort((left, right) => left.localeCompare(right));
83
91
 
@@ -1,3 +1,5 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
1
3
  import { evaluateAiGate, type AiGateStage } from '../gate/evaluateAiGate';
2
4
  import { resolveRemediationHintForViolationCode } from '../gate/remediationCatalog';
3
5
  import { resolveLearningContextExperimentalFeature } from '../policy/experimentalFeatures';
@@ -48,6 +50,8 @@ const HOOK_STAGE_SET = new Set<AiGateStage>(['PRE_COMMIT', 'PRE_PUSH', 'CI']);
48
50
  const isHookRefreshableEvidenceCode = (code: string): boolean =>
49
51
  code.startsWith('EVIDENCE_');
50
52
 
53
+ const MCP_ENTERPRISE_ADAPTER_MISSING_CODE = 'MCP_ENTERPRISE_ADAPTER_MISSING';
54
+
51
55
  type AiGateCheckDependencies = {
52
56
  evaluateAiGate: typeof evaluateAiGate;
53
57
  runMcpAlignedPlatformGate: typeof runMcpAlignedPlatformGate;
@@ -120,6 +124,9 @@ const buildAutoFixes = (
120
124
  learningContext: SddLearningContext | null
121
125
  ): ReadonlyArray<string> => {
122
126
  const fixes: string[] = [];
127
+ if (evaluation.violations.some((violation) => violation.code === MCP_ENTERPRISE_ADAPTER_MISSING_CODE)) {
128
+ fixes.push('Regenera .pumuki/adapter.json con `pnpm exec pumuki install` antes de volver a validar el gate MCP.');
129
+ }
123
130
  const emittedCodes = new Set<string>();
124
131
  for (const violation of evaluation.violations) {
125
132
  if (emittedCodes.has(violation.code)) {
@@ -163,6 +170,39 @@ const resolveAiGateCheckMode = (): PlatformGateAlignment['mode'] => {
163
170
  return raw === 'full' || raw === 'aligned' ? 'full' : 'policy';
164
171
  };
165
172
 
173
+ const hasMcpAdapter = (repoRoot: string): boolean =>
174
+ existsSync(join(repoRoot, '.pumuki', 'adapter.json'));
175
+
176
+ const withRequiredMcpAdapter = (
177
+ evaluation: ReturnType<typeof evaluateAiGate>,
178
+ repoRoot: string,
179
+ requireMcpReceipt: boolean
180
+ ): ReturnType<typeof evaluateAiGate> => {
181
+ if (!requireMcpReceipt || hasMcpAdapter(repoRoot)) {
182
+ return evaluation;
183
+ }
184
+ if (evaluation.violations.some((violation) => violation.code === MCP_ENTERPRISE_ADAPTER_MISSING_CODE)) {
185
+ return {
186
+ ...evaluation,
187
+ allowed: false,
188
+ status: 'BLOCKED',
189
+ };
190
+ }
191
+ return {
192
+ ...evaluation,
193
+ allowed: false,
194
+ status: 'BLOCKED',
195
+ violations: [
196
+ {
197
+ code: MCP_ENTERPRISE_ADAPTER_MISSING_CODE,
198
+ severity: 'ERROR',
199
+ message: 'Missing .pumuki/adapter.json. MCP-dependent validation cannot prove a real MCP invocation.',
200
+ },
201
+ ...evaluation.violations,
202
+ ],
203
+ };
204
+ };
205
+
166
206
  const toPlatformGateAlignment = (
167
207
  mode: PlatformGateAlignment['mode'],
168
208
  platform?: { exitCode: number; aligned: boolean; skipReason: string | null }
@@ -192,40 +232,45 @@ export const runEnterpriseAiGateCheck = (params: {
192
232
  stage: params.stage,
193
233
  requireMcpReceipt: params.requireMcpReceipt ?? false,
194
234
  });
195
- const branch = evaluation.repo_state.git.branch;
196
- const timestamp = evaluation.evidence.source.generated_at;
235
+ const normalizedEvaluation = withRequiredMcpAdapter(
236
+ evaluation,
237
+ params.repoRoot,
238
+ params.requireMcpReceipt ?? false
239
+ );
240
+ const branch = normalizedEvaluation.repo_state.git.branch;
241
+ const timestamp = normalizedEvaluation.evidence.source.generated_at;
197
242
  const learningContextFeature = resolveLearningContextExperimentalFeature();
198
243
  const learningContext = learningContextFeature.mode === 'off'
199
244
  ? null
200
245
  : readSddLearningContext({
201
246
  repoRoot: params.repoRoot,
202
247
  });
203
- const warnings = buildWarnings(evaluation);
204
- const autoFixes = buildAutoFixes(evaluation, learningContext);
205
- const message = buildMessage(evaluation);
248
+ const warnings = buildWarnings(normalizedEvaluation);
249
+ const autoFixes = buildAutoFixes(normalizedEvaluation, learningContext);
250
+ const message = buildMessage(normalizedEvaluation);
206
251
 
207
252
  return {
208
253
  tool: 'ai_gate_check',
209
254
  dryRun: true,
210
255
  executed: true,
211
- success: evaluation.allowed,
256
+ success: normalizedEvaluation.allowed,
212
257
  result: {
213
- allowed: evaluation.allowed,
214
- status: evaluation.status,
258
+ allowed: normalizedEvaluation.allowed,
259
+ status: normalizedEvaluation.status,
215
260
  timestamp,
216
261
  branch,
217
262
  message,
218
- stage: evaluation.stage,
219
- policy: evaluation.policy,
220
- violations: evaluation.violations,
263
+ stage: normalizedEvaluation.stage,
264
+ policy: normalizedEvaluation.policy,
265
+ violations: normalizedEvaluation.violations,
221
266
  warnings,
222
267
  auto_fixes: autoFixes,
223
268
  learning_context: learningContext,
224
- evidence: evaluation.evidence,
225
- mcp_receipt: evaluation.mcp_receipt,
226
- skills_contract: evaluation.skills_contract,
227
- repo_state: evaluation.repo_state,
228
- consistency_hint: buildConsistencyHint(evaluation),
269
+ evidence: normalizedEvaluation.evidence,
270
+ mcp_receipt: normalizedEvaluation.mcp_receipt,
271
+ skills_contract: normalizedEvaluation.skills_contract,
272
+ repo_state: normalizedEvaluation.repo_state,
273
+ consistency_hint: buildConsistencyHint(normalizedEvaluation),
229
274
  },
230
275
  };
231
276
  };
@@ -245,6 +290,11 @@ export const runEnterpriseAiGateCheckAsync = async (params: {
245
290
  stage: params.stage,
246
291
  requireMcpReceipt: params.requireMcpReceipt ?? false,
247
292
  });
293
+ const normalizedEvaluation = withRequiredMcpAdapter(
294
+ evaluation,
295
+ params.repoRoot,
296
+ params.requireMcpReceipt ?? false
297
+ );
248
298
 
249
299
  let platform:
250
300
  | { exitCode: number; aligned: boolean; skipReason: string | null }
@@ -257,11 +307,11 @@ export const runEnterpriseAiGateCheckAsync = async (params: {
257
307
  }
258
308
 
259
309
  const platformBlocks = Boolean(platform && platform.exitCode !== 0);
260
- const allowed = evaluation.allowed && !platformBlocks;
310
+ const allowed = normalizedEvaluation.allowed && !platformBlocks;
261
311
  const status: 'ALLOWED' | 'BLOCKED' = allowed ? 'ALLOWED' : 'BLOCKED';
262
312
  const violations = platformBlocks && platform
263
313
  ? [
264
- ...evaluation.violations,
314
+ ...normalizedEvaluation.violations,
265
315
  {
266
316
  code: 'PLATFORM_GATE_EXIT_NON_ZERO',
267
317
  message:
@@ -270,16 +320,16 @@ export const runEnterpriseAiGateCheckAsync = async (params: {
270
320
  severity: 'ERROR' as const,
271
321
  },
272
322
  ]
273
- : evaluation.violations;
274
- const branch = evaluation.repo_state.git.branch;
275
- const timestamp = evaluation.evidence.source.generated_at;
323
+ : normalizedEvaluation.violations;
324
+ const branch = normalizedEvaluation.repo_state.git.branch;
325
+ const timestamp = normalizedEvaluation.evidence.source.generated_at;
276
326
  const learningContextFeature = resolveLearningContextExperimentalFeature();
277
327
  const learningContext = learningContextFeature.mode === 'off'
278
328
  ? null
279
329
  : readSddLearningContext({
280
330
  repoRoot: params.repoRoot,
281
331
  });
282
- const evaluationForHints = { ...evaluation, allowed, status, violations };
332
+ const evaluationForHints = { ...normalizedEvaluation, allowed, status, violations };
283
333
  const warnings = buildWarnings(evaluationForHints);
284
334
  const autoFixes = buildAutoFixes(evaluationForHints, learningContext);
285
335
  const message = buildMessage(evaluationForHints, platform);
@@ -295,16 +345,16 @@ export const runEnterpriseAiGateCheckAsync = async (params: {
295
345
  timestamp,
296
346
  branch,
297
347
  message,
298
- stage: evaluation.stage,
299
- policy: evaluation.policy,
348
+ stage: normalizedEvaluation.stage,
349
+ policy: normalizedEvaluation.policy,
300
350
  violations,
301
351
  warnings,
302
352
  auto_fixes: autoFixes,
303
353
  learning_context: learningContext,
304
- evidence: evaluation.evidence,
305
- mcp_receipt: evaluation.mcp_receipt,
306
- skills_contract: evaluation.skills_contract,
307
- repo_state: evaluation.repo_state,
354
+ evidence: normalizedEvaluation.evidence,
355
+ mcp_receipt: normalizedEvaluation.mcp_receipt,
356
+ skills_contract: normalizedEvaluation.skills_contract,
357
+ repo_state: normalizedEvaluation.repo_state,
308
358
  consistency_hint: buildConsistencyHint(evaluationForHints, platform),
309
359
  platform_gate_alignment: toPlatformGateAlignment(mode, platform),
310
360
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.87",
3
+ "version": "6.3.88",
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": {