pumuki 6.3.72 → 6.3.75

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.
Files changed (64) hide show
  1. package/docs/README.md +9 -7
  2. package/docs/operations/RELEASE_NOTES.md +0 -7
  3. package/docs/product/USAGE.md +2 -5
  4. package/docs/validation/README.md +3 -1
  5. package/integrations/evidence/buildEvidence.ts +14 -0
  6. package/integrations/evidence/repoState.ts +3 -0
  7. package/integrations/evidence/schema.ts +18 -0
  8. package/integrations/evidence/trackingContract.ts +146 -0
  9. package/integrations/evidence/writeEvidence.ts +14 -0
  10. package/integrations/gate/evaluateAiGate.ts +166 -3
  11. package/integrations/gate/governanceActionCatalog.ts +275 -0
  12. package/integrations/gate/remediationCatalog.ts +8 -0
  13. package/integrations/git/GitService.ts +0 -25
  14. package/integrations/git/aiGateRepoPolicyFindings.ts +4 -0
  15. package/integrations/git/runPlatformGate.ts +9 -1
  16. package/integrations/git/runPlatformGateFacts.ts +0 -7
  17. package/integrations/git/runPlatformGateOutput.ts +36 -27
  18. package/integrations/lifecycle/adapter.ts +24 -0
  19. package/integrations/lifecycle/bootstrapManifest.ts +248 -0
  20. package/integrations/lifecycle/cli.ts +45 -11
  21. package/integrations/lifecycle/cliSdd.ts +4 -3
  22. package/integrations/lifecycle/doctor.ts +49 -1
  23. package/integrations/lifecycle/governanceNextAction.ts +164 -0
  24. package/integrations/lifecycle/governanceObservationSnapshot.ts +315 -0
  25. package/integrations/lifecycle/install.ts +21 -0
  26. package/integrations/lifecycle/state.ts +8 -1
  27. package/integrations/lifecycle/status.ts +29 -2
  28. package/integrations/mcp/aiGateCheck.ts +140 -10
  29. package/integrations/mcp/alignedPlatformGate.ts +232 -0
  30. package/integrations/mcp/autoExecuteAiStart.ts +92 -85
  31. package/integrations/mcp/enterpriseServer.ts +6 -6
  32. package/integrations/mcp/preFlightCheck.ts +51 -5
  33. package/integrations/mcp/readMcpPrePushStdin.ts +7 -0
  34. package/integrations/policy/experimentalFeatures.ts +1 -1
  35. package/package.json +2 -4
  36. package/scripts/build-ruralgo-s1-evidence-pack.ts +85 -0
  37. package/scripts/consumer-menu-matrix-baseline-report-lib.ts +38 -13
  38. package/scripts/framework-menu-consumer-actions-lib.ts +4 -28
  39. package/scripts/framework-menu-consumer-preflight-hints.ts +2 -5
  40. package/scripts/framework-menu-consumer-preflight-render.ts +6 -0
  41. package/scripts/framework-menu-consumer-preflight-run.ts +19 -0
  42. package/scripts/framework-menu-consumer-preflight-types.ts +8 -0
  43. package/scripts/framework-menu-consumer-runtime-actions.ts +6 -86
  44. package/scripts/framework-menu-consumer-runtime-audit.ts +2 -36
  45. package/scripts/framework-menu-consumer-runtime-lib.ts +0 -2
  46. package/scripts/framework-menu-consumer-runtime-types.ts +1 -3
  47. package/scripts/framework-menu-evidence-summary-lib.ts +0 -1
  48. package/scripts/framework-menu-evidence-summary-read.ts +5 -57
  49. package/scripts/framework-menu-evidence-summary-severity.ts +1 -3
  50. package/scripts/framework-menu-evidence-summary-types.ts +0 -7
  51. package/scripts/framework-menu-gate-lib.ts +0 -9
  52. package/scripts/framework-menu-layout-data.ts +0 -5
  53. package/scripts/framework-menu-matrix-baseline-lib.ts +14 -15
  54. package/scripts/framework-menu-matrix-canary-lib.ts +1 -22
  55. package/scripts/framework-menu-matrix-evidence-lib.ts +0 -1
  56. package/scripts/framework-menu-matrix-evidence-types.ts +1 -13
  57. package/scripts/framework-menu-matrix-runner-lib.ts +0 -35
  58. package/scripts/framework-menu-system-notifications-macos.ts +0 -4
  59. package/scripts/framework-menu.ts +0 -3
  60. package/scripts/ruralgo-s1-evidence-pack-lib.ts +200 -0
  61. package/AGENTS.md +0 -269
  62. package/CHANGELOG.md +0 -666
  63. package/docs/tracking/plan-curso-pumuki-stack-my-architecture.md +0 -111
  64. package/scripts/framework-menu-consumer-runtime-evidence-classic.ts +0 -140
@@ -2,9 +2,17 @@ import { evaluateAiGate, type AiGateStage } from '../gate/evaluateAiGate';
2
2
  import { resolveRemediationHintForViolationCode } from '../gate/remediationCatalog';
3
3
  import { resolveLearningContextExperimentalFeature } from '../policy/experimentalFeatures';
4
4
  import { readSddLearningContext, type SddLearningContext } from '../sdd/learningInsights';
5
+ import { runMcpAlignedPlatformGate } from './alignedPlatformGate';
5
6
 
6
7
  const PROTECTED_BRANCHES = new Set(['main', 'master', 'develop', 'dev']);
7
8
 
9
+ type PlatformGateAlignment = {
10
+ mode: 'full' | 'policy';
11
+ exit_code: number;
12
+ aligned: boolean;
13
+ skip_reason: string | null;
14
+ };
15
+
8
16
  export type EnterpriseAiGateCheckResult = {
9
17
  tool: 'ai_gate_check';
10
18
  dryRun: true;
@@ -31,6 +39,7 @@ export type EnterpriseAiGateCheckResult = {
31
39
  reason_code: 'HOOK_RUNNER_CAN_REFRESH_EVIDENCE' | null;
32
40
  message: string;
33
41
  };
42
+ platform_gate_alignment?: PlatformGateAlignment;
34
43
  };
35
44
  };
36
45
 
@@ -41,23 +50,18 @@ const isHookRefreshableEvidenceCode = (code: string): boolean =>
41
50
 
42
51
  type AiGateCheckDependencies = {
43
52
  evaluateAiGate: typeof evaluateAiGate;
53
+ runMcpAlignedPlatformGate: typeof runMcpAlignedPlatformGate;
44
54
  };
45
55
 
46
56
  const defaultDependencies: AiGateCheckDependencies = {
47
57
  evaluateAiGate,
58
+ runMcpAlignedPlatformGate,
48
59
  };
49
60
 
50
61
  const buildConsistencyHint = (
51
- evaluation: ReturnType<typeof evaluateAiGate>
62
+ evaluation: ReturnType<typeof evaluateAiGate>,
63
+ platform?: { exitCode: number; aligned: boolean; skipReason: string | null }
52
64
  ): EnterpriseAiGateCheckResult['result']['consistency_hint'] => {
53
- if (!HOOK_STAGE_SET.has(evaluation.stage)) {
54
- return {
55
- comparable_with_hook_runner: true,
56
- reason_code: null,
57
- message: 'Stage is directly comparable with ai_gate_check semantics.',
58
- };
59
- }
60
-
61
65
  const hasRefreshableEvidenceViolation = evaluation.violations.some((violation) =>
62
66
  isHookRefreshableEvidenceCode(violation.code)
63
67
  );
@@ -72,6 +76,24 @@ const buildConsistencyHint = (
72
76
  };
73
77
  }
74
78
 
79
+ if (platform?.aligned) {
80
+ return {
81
+ comparable_with_hook_runner: true,
82
+ reason_code: null,
83
+ message:
84
+ `ai_gate_check ejecutó runPlatformGate después de leer la evidencia actual (exit_code=${platform.exitCode}); ` +
85
+ 'alineación hook-like habilitada explícitamente para este stage.',
86
+ };
87
+ }
88
+
89
+ if (!HOOK_STAGE_SET.has(evaluation.stage)) {
90
+ return {
91
+ comparable_with_hook_runner: true,
92
+ reason_code: null,
93
+ message: 'Stage is directly comparable with ai_gate_check semantics.',
94
+ };
95
+ }
96
+
75
97
  return {
76
98
  comparable_with_hook_runner: true,
77
99
  reason_code: null,
@@ -118,7 +140,14 @@ const buildAutoFixes = (
118
140
  return fixes;
119
141
  };
120
142
 
121
- const buildMessage = (evaluation: ReturnType<typeof evaluateAiGate>): string => {
143
+ const buildMessage = (
144
+ evaluation: ReturnType<typeof evaluateAiGate>,
145
+ platform?: { exitCode: number; skipReason: string | null }
146
+ ): string => {
147
+ if (platform && platform.exitCode !== 0) {
148
+ const suffix = platform.skipReason ? ` (${platform.skipReason})` : '';
149
+ return `🔴 runPlatformGate exit_code=${platform.exitCode}${suffix}.`;
150
+ }
122
151
  if (evaluation.allowed) {
123
152
  return `✅ Gate ${evaluation.stage} ALLOWED.`;
124
153
  }
@@ -129,6 +158,26 @@ const buildMessage = (evaluation: ReturnType<typeof evaluateAiGate>): string =>
129
158
  return `🔴 ${firstViolation.code}: ${firstViolation.message}`;
130
159
  };
131
160
 
161
+ const resolveAiGateCheckMode = (): PlatformGateAlignment['mode'] => {
162
+ const raw = process.env.PUMUKI_MCP_AI_GATE_CHECK_MODE?.trim().toLowerCase();
163
+ return raw === 'full' || raw === 'aligned' ? 'full' : 'policy';
164
+ };
165
+
166
+ const toPlatformGateAlignment = (
167
+ mode: PlatformGateAlignment['mode'],
168
+ platform?: { exitCode: number; aligned: boolean; skipReason: string | null }
169
+ ): PlatformGateAlignment | undefined => {
170
+ if (mode !== 'full' || !platform) {
171
+ return undefined;
172
+ }
173
+ return {
174
+ mode,
175
+ exit_code: platform.exitCode,
176
+ aligned: platform.aligned,
177
+ skip_reason: platform.skipReason,
178
+ };
179
+ };
180
+
132
181
  export const runEnterpriseAiGateCheck = (params: {
133
182
  repoRoot: string;
134
183
  stage: AiGateStage;
@@ -180,3 +229,84 @@ export const runEnterpriseAiGateCheck = (params: {
180
229
  },
181
230
  };
182
231
  };
232
+
233
+ export const runEnterpriseAiGateCheckAsync = async (params: {
234
+ repoRoot: string;
235
+ stage: AiGateStage;
236
+ requireMcpReceipt?: boolean;
237
+ }, dependencies: Partial<AiGateCheckDependencies> = {}): Promise<EnterpriseAiGateCheckResult> => {
238
+ const mode = resolveAiGateCheckMode();
239
+ const activeDependencies: AiGateCheckDependencies = {
240
+ ...defaultDependencies,
241
+ ...dependencies,
242
+ };
243
+ const evaluation = activeDependencies.evaluateAiGate({
244
+ repoRoot: params.repoRoot,
245
+ stage: params.stage,
246
+ requireMcpReceipt: params.requireMcpReceipt ?? false,
247
+ });
248
+
249
+ let platform:
250
+ | { exitCode: number; aligned: boolean; skipReason: string | null }
251
+ | undefined;
252
+ if (mode === 'full') {
253
+ platform = await activeDependencies.runMcpAlignedPlatformGate({
254
+ repoRoot: params.repoRoot,
255
+ stage: params.stage,
256
+ });
257
+ }
258
+
259
+ const platformBlocks = Boolean(platform && platform.exitCode !== 0);
260
+ const allowed = evaluation.allowed && !platformBlocks;
261
+ const status: 'ALLOWED' | 'BLOCKED' = allowed ? 'ALLOWED' : 'BLOCKED';
262
+ const violations = platformBlocks && platform
263
+ ? [
264
+ ...evaluation.violations,
265
+ {
266
+ code: 'PLATFORM_GATE_EXIT_NON_ZERO',
267
+ message:
268
+ `runPlatformGate devolvió exit_code=${platform.exitCode}` +
269
+ (platform.skipReason ? ` (${platform.skipReason})` : ''),
270
+ severity: 'ERROR' as const,
271
+ },
272
+ ]
273
+ : evaluation.violations;
274
+ const branch = evaluation.repo_state.git.branch;
275
+ const timestamp = evaluation.evidence.source.generated_at;
276
+ const learningContextFeature = resolveLearningContextExperimentalFeature();
277
+ const learningContext = learningContextFeature.mode === 'off'
278
+ ? null
279
+ : readSddLearningContext({
280
+ repoRoot: params.repoRoot,
281
+ });
282
+ const evaluationForHints = { ...evaluation, allowed, status, violations };
283
+ const warnings = buildWarnings(evaluationForHints);
284
+ const autoFixes = buildAutoFixes(evaluationForHints, learningContext);
285
+ const message = buildMessage(evaluationForHints, platform);
286
+
287
+ return {
288
+ tool: 'ai_gate_check',
289
+ dryRun: true,
290
+ executed: true,
291
+ success: allowed,
292
+ result: {
293
+ allowed,
294
+ status,
295
+ timestamp,
296
+ branch,
297
+ message,
298
+ stage: evaluation.stage,
299
+ policy: evaluation.policy,
300
+ violations,
301
+ warnings,
302
+ auto_fixes: autoFixes,
303
+ 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,
308
+ consistency_hint: buildConsistencyHint(evaluationForHints, platform),
309
+ platform_gate_alignment: toPlatformGateAlignment(mode, platform),
310
+ },
311
+ };
312
+ };
@@ -0,0 +1,232 @@
1
+ import type { AiGateStage } from '../gate/evaluateAiGate';
2
+ import { resolvePolicyForStage } from '../gate/stagePolicies';
3
+ import type { SddDecision } from '../sdd';
4
+ import { GitService } from '../git/GitService';
5
+ import { runPlatformGate } from '../git/runPlatformGate';
6
+ import type { GateScope } from '../git/runPlatformGateFacts';
7
+ import { readMcpPrePushStdin } from './readMcpPrePushStdin';
8
+
9
+ const ZERO_HASH = /^0+$/;
10
+
11
+ const runGit = (repoRoot: string, args: ReadonlyArray<string>): string | null => {
12
+ try {
13
+ return new GitService().runGit(args, repoRoot).trim();
14
+ } catch {
15
+ return null;
16
+ }
17
+ };
18
+
19
+ const resolveUpstreamRefInRepo = (repoRoot: string): string | null =>
20
+ runGit(repoRoot, ['rev-parse', '@{u}']);
21
+
22
+ const resolveHeadOidInRepo = (repoRoot: string): string | null =>
23
+ runGit(repoRoot, ['rev-parse', 'HEAD']);
24
+
25
+ const resolveCiBaseRefInRepo = (repoRoot: string): string => {
26
+ const fromEnv = process.env.GITHUB_BASE_REF?.trim();
27
+ if (fromEnv) {
28
+ if (runGit(repoRoot, ['rev-parse', '--verify', fromEnv])) {
29
+ return fromEnv;
30
+ }
31
+ const remoteRef = `origin/${fromEnv}`;
32
+ if (runGit(repoRoot, ['rev-parse', '--verify', remoteRef])) {
33
+ return remoteRef;
34
+ }
35
+ }
36
+
37
+ for (const candidate of ['origin/main', 'main', 'HEAD']) {
38
+ if (runGit(repoRoot, ['rev-parse', '--verify', candidate])) {
39
+ return candidate;
40
+ }
41
+ }
42
+
43
+ return 'HEAD';
44
+ };
45
+
46
+ const resolvePrePushBootstrapBaseRefInRepo = (repoRoot: string): string => {
47
+ const candidates = ['origin/develop', 'develop', resolveCiBaseRefInRepo(repoRoot)];
48
+ for (const candidate of candidates) {
49
+ if (runGit(repoRoot, ['rev-parse', '--verify', candidate])) {
50
+ return candidate;
51
+ }
52
+ }
53
+
54
+ return 'HEAD';
55
+ };
56
+
57
+ const shouldAllowBootstrapPrePush = (rawInput: string): boolean => {
58
+ const lines = rawInput
59
+ .split('\n')
60
+ .map((line) => line.trim())
61
+ .filter((line) => line.length > 0);
62
+
63
+ for (const line of lines) {
64
+ const [localRef, localOid, remoteRef, remoteOid] = line.split(/\s+/);
65
+ if (!localRef || !localOid || !remoteRef || !remoteOid) {
66
+ continue;
67
+ }
68
+ const localIsBranch = localRef.startsWith('refs/heads/');
69
+ const remoteIsBranch = remoteRef.startsWith('refs/heads/');
70
+ const localIsDeletion = ZERO_HASH.test(localOid);
71
+ const remoteIsNewBranch = ZERO_HASH.test(remoteOid);
72
+
73
+ if (localIsBranch && remoteIsBranch && !localIsDeletion && remoteIsNewBranch) {
74
+ return true;
75
+ }
76
+ }
77
+
78
+ return false;
79
+ };
80
+
81
+ const resolveExplicitPrePushRange = (
82
+ rawInput: string
83
+ ): { fromRef: string; toRef: string } | undefined => {
84
+ const lines = rawInput
85
+ .split('\n')
86
+ .map((line) => line.trim())
87
+ .filter((line) => line.length > 0);
88
+
89
+ const eligibleRanges = lines
90
+ .map((line) => {
91
+ const [localRef, localOid, remoteRef, remoteOid] = line.split(/\s+/);
92
+ if (!localRef || !localOid || !remoteRef || !remoteOid) {
93
+ return undefined;
94
+ }
95
+ const localIsDeletion = ZERO_HASH.test(localOid);
96
+ const remoteIsNewBranch = ZERO_HASH.test(remoteOid);
97
+ if (localIsDeletion || remoteIsNewBranch) {
98
+ return undefined;
99
+ }
100
+ return {
101
+ fromRef: remoteOid,
102
+ toRef: localOid,
103
+ };
104
+ })
105
+ .filter((value): value is { fromRef: string; toRef: string } => Boolean(value));
106
+
107
+ if (eligibleRanges.length !== 1) {
108
+ return undefined;
109
+ }
110
+
111
+ return eligibleRanges[0];
112
+ };
113
+
114
+ type PrePushScopeResolution =
115
+ | { kind: 'scope'; scope: GateScope; sddDecisionOverride?: Pick<SddDecision, 'allowed' | 'code' | 'message'> }
116
+ | { kind: 'upstream_missing' };
117
+
118
+ const resolvePrePushScopeForMcp = (params: { repoRoot: string }): PrePushScopeResolution => {
119
+ const prePushInput = readMcpPrePushStdin();
120
+ const upstreamRef = resolveUpstreamRefInRepo(params.repoRoot);
121
+ if (!upstreamRef) {
122
+ const bootstrapBaseRef = resolvePrePushBootstrapBaseRefInRepo(params.repoRoot);
123
+ const bootstrapByPrePushStdIn = shouldAllowBootstrapPrePush(prePushInput);
124
+ const bootstrapByFallbackBase = !bootstrapByPrePushStdIn && bootstrapBaseRef !== 'HEAD';
125
+ const manualInvocationFallback =
126
+ !bootstrapByPrePushStdIn &&
127
+ !bootstrapByFallbackBase &&
128
+ prePushInput.trim().length === 0;
129
+ if (bootstrapByPrePushStdIn || bootstrapByFallbackBase) {
130
+ return {
131
+ kind: 'scope',
132
+ scope: {
133
+ kind: 'range',
134
+ fromRef: bootstrapBaseRef,
135
+ toRef: 'HEAD',
136
+ },
137
+ };
138
+ }
139
+ if (manualInvocationFallback) {
140
+ return { kind: 'scope', scope: { kind: 'workingTree' } };
141
+ }
142
+ return { kind: 'upstream_missing' };
143
+ }
144
+ const explicitPrePushRange = resolveExplicitPrePushRange(prePushInput);
145
+ const prePushFromRef = explicitPrePushRange?.fromRef ?? upstreamRef;
146
+ const prePushToRef = explicitPrePushRange?.toRef ?? 'HEAD';
147
+ const headOid = resolveHeadOidInRepo(params.repoRoot);
148
+ const sddDecisionOverride =
149
+ explicitPrePushRange && headOid && explicitPrePushRange.toRef !== headOid
150
+ ? ({
151
+ allowed: true,
152
+ code: 'ALLOWED',
153
+ message:
154
+ `SDD enforcement suspended for PRE_PUSH historical publish targeting ${explicitPrePushRange.toRef.slice(0, 12)} ` +
155
+ `instead of current HEAD ${headOid.slice(0, 12)}.`,
156
+ } as Pick<SddDecision, 'allowed' | 'code' | 'message'>)
157
+ : undefined;
158
+ return {
159
+ kind: 'scope',
160
+ scope: {
161
+ kind: 'range',
162
+ fromRef: prePushFromRef,
163
+ toRef: prePushToRef,
164
+ },
165
+ sddDecisionOverride,
166
+ };
167
+ };
168
+
169
+ type RunAlignedParams = {
170
+ repoRoot: string;
171
+ stage: AiGateStage;
172
+ };
173
+
174
+ export const runMcpAlignedPlatformGate = async (
175
+ params: RunAlignedParams
176
+ ): Promise<{ exitCode: number; aligned: boolean; skipReason: string | null }> => {
177
+ const git = new GitService();
178
+ const resolved = resolvePolicyForStage(params.stage, params.repoRoot);
179
+ if (params.stage === 'PRE_WRITE') {
180
+ const exitCode = await runPlatformGate({
181
+ policy: resolved.policy,
182
+ policyTrace: resolved.trace,
183
+ scope: { kind: 'workingTree' },
184
+ silent: true,
185
+ services: { git },
186
+ });
187
+ return { exitCode, aligned: true, skipReason: null };
188
+ }
189
+ if (params.stage === 'PRE_COMMIT') {
190
+ const exitCode = await runPlatformGate({
191
+ policy: resolved.policy,
192
+ policyTrace: resolved.trace,
193
+ scope: { kind: 'staged' },
194
+ silent: true,
195
+ services: { git },
196
+ });
197
+ return { exitCode, aligned: true, skipReason: null };
198
+ }
199
+ if (params.stage === 'CI') {
200
+ const ciBaseRef = resolveCiBaseRefInRepo(params.repoRoot);
201
+ const exitCode = await runPlatformGate({
202
+ policy: resolved.policy,
203
+ policyTrace: resolved.trace,
204
+ scope: {
205
+ kind: 'range',
206
+ fromRef: ciBaseRef,
207
+ toRef: 'HEAD',
208
+ },
209
+ silent: true,
210
+ services: { git },
211
+ });
212
+ return { exitCode, aligned: true, skipReason: null };
213
+ }
214
+ if (params.stage === 'PRE_PUSH') {
215
+ const scopeResolution = resolvePrePushScopeForMcp({ repoRoot: params.repoRoot });
216
+ if (scopeResolution.kind === 'upstream_missing') {
217
+ return { exitCode: 1, aligned: false, skipReason: 'PRE_PUSH_UPSTREAM_MISSING' };
218
+ }
219
+ const exitCode = await runPlatformGate({
220
+ policy: resolved.policy,
221
+ policyTrace: resolved.trace,
222
+ scope: scopeResolution.scope,
223
+ silent: true,
224
+ services: { git },
225
+ ...(scopeResolution.sddDecisionOverride
226
+ ? { sddDecisionOverride: scopeResolution.sddDecisionOverride }
227
+ : {}),
228
+ });
229
+ return { exitCode, aligned: true, skipReason: null };
230
+ }
231
+ throw new Error(`Unsupported MCP aligned stage: ${String(params.stage)}`);
232
+ };
@@ -1,3 +1,7 @@
1
+ import {
2
+ buildGovernanceValidateCommand,
3
+ resolveGovernanceCatalogAction,
4
+ } from '../gate/governanceActionCatalog';
1
5
  import { evaluateAiGate, type AiGateStage, type AiGateViolation } from '../gate/evaluateAiGate';
2
6
  import { collectWorktreeAtomicSlices } from '../git/worktreeAtomicSlices';
3
7
  import { resolveLearningContextExperimentalFeature } from '../policy/experimentalFeatures';
@@ -50,96 +54,98 @@ const confidenceFromViolation = (violationCode: string | null): number => {
50
54
  if (isEvidenceCode(violationCode)) {
51
55
  return 65;
52
56
  }
53
- if (violationCode === 'GITFLOW_PROTECTED_BRANCH') {
57
+ if (
58
+ violationCode === 'GITFLOW_PROTECTED_BRANCH'
59
+ || violationCode === 'GITFLOW_BRANCH_NAMING_INVALID'
60
+ ) {
54
61
  return 40;
55
62
  }
56
63
  return 50;
57
64
  };
58
65
 
59
- const nextActionFromViolation = (
60
- violation: AiGateViolation | undefined,
61
- repoRoot: string
62
- ): AutoExecuteNextAction => {
63
- if (!violation) {
64
- return {
65
- kind: 'info',
66
- message: 'Gate listo. Puedes continuar con implementación.',
67
- };
68
- }
69
- switch (violation.code) {
70
- case 'EVIDENCE_MISSING':
66
+ const normalizeGovernanceCatalogCode = (code: string): string => {
67
+ switch (code) {
71
68
  case 'EVIDENCE_INVALID':
72
69
  case 'EVIDENCE_CHAIN_INVALID':
73
- case 'EVIDENCE_STALE':
74
- return {
75
- kind: 'run_command',
76
- message: 'Regenera o refresca evidencia y vuelve a evaluar PRE_WRITE.',
77
- command: 'npx --yes --package pumuki@latest pumuki sdd validate --stage=PRE_WRITE --json',
78
- };
79
- case 'EVIDENCE_ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES':
80
- return {
81
- kind: 'run_command',
82
- message:
83
- 'No hay active_rule_ids para plataforma de código detectada. Reconciliación strict de policy/skills y revalidación PRE_WRITE.',
84
- command:
85
- 'npx --yes --package pumuki@latest pumuki policy reconcile --strict --json && npx --yes --package pumuki@latest pumuki sdd validate --stage=PRE_WRITE --json',
86
- };
87
- case 'EVIDENCE_PLATFORM_SKILLS_SCOPE_INCOMPLETE':
88
- case 'EVIDENCE_PLATFORM_SKILLS_BUNDLES_MISSING':
89
- case 'EVIDENCE_SKILLS_CONTRACT_INCOMPLETE':
90
- return {
91
- kind: 'run_command',
92
- message:
93
- 'Completa cobertura de skills por plataforma (prefijos + bundles) y revalida PRE_WRITE.',
94
- command: 'npx --yes --package pumuki@latest pumuki sdd validate --stage=PRE_WRITE --json',
95
- };
96
- case 'EVIDENCE_PLATFORM_CRITICAL_SKILLS_RULES_MISSING':
97
- case 'EVIDENCE_CROSS_PLATFORM_CRITICAL_ENFORCEMENT_INCOMPLETE':
98
- return {
99
- kind: 'run_command',
100
- message:
101
- 'Reconcilia policy/skills en modo estricto (incluida skills.ios.critical-test-quality cuando aplique) y revalida PRE_WRITE.',
102
- command:
103
- 'npx --yes --package pumuki@latest pumuki policy reconcile --strict --json && npx --yes --package pumuki@latest pumuki sdd validate --stage=PRE_WRITE --json',
104
- };
105
- case 'EVIDENCE_PREWRITE_WORKTREE_OVER_LIMIT':
106
- case 'EVIDENCE_PREWRITE_WORKTREE_WARN':
107
- {
108
- const plan = collectWorktreeAtomicSlices({
109
- repoRoot,
110
- maxSlices: 3,
111
- maxFilesPerSlice: 4,
112
- });
113
- if (plan.slices.length > 0) {
114
- const firstSlice = plan.slices[0];
115
- return {
116
- kind: 'run_command',
117
- message:
118
- `Particiona el worktree en slices atómicos por scope. Primer lote sugerido: ${firstSlice?.scope ?? 'scope-desconocido'}.`,
119
- command:
120
- `${firstSlice?.staged_command ?? 'git add -p'} && npx --yes --package pumuki@latest pumuki sdd validate --stage=PRE_WRITE --json`,
121
- };
122
- }
123
- }
124
- return {
125
- kind: 'run_command',
126
- message:
127
- 'Particiona el worktree en slices atómicos y revalida PRE_WRITE para continuar sin fricción.',
128
- command:
129
- 'git status --short && git add -p && npx --yes --package pumuki@latest pumuki sdd validate --stage=PRE_WRITE --json',
130
- };
70
+ return 'EVIDENCE_INVALID_OR_CHAIN';
131
71
  case 'GITFLOW_PROTECTED_BRANCH':
132
- return {
133
- kind: 'run_command',
134
- message: 'Cambia a una rama feature/* antes de continuar.',
135
- command: 'git checkout -b feature/<descripcion-kebab-case>',
136
- };
72
+ return 'GITFLOW_PROTECTED_BRANCH_CONTEXT';
73
+ case 'GITFLOW_BRANCH_NAMING_INVALID':
74
+ return 'GITFLOW_BRANCH_NAMING_INVALID_CONTEXT';
137
75
  default:
76
+ return code;
77
+ }
78
+ };
79
+
80
+ const resolveAutoExecuteRemediation = (params: {
81
+ violation: AiGateViolation | undefined;
82
+ repoRoot: string;
83
+ stage: AiGateStage;
84
+ allowed: boolean;
85
+ }): {
86
+ instruction: string;
87
+ nextAction: AutoExecuteNextAction;
88
+ } => {
89
+ if (!params.violation) {
90
+ const readyAction = resolveGovernanceCatalogAction({
91
+ code: 'READY',
92
+ stage: params.stage,
93
+ });
94
+ return {
95
+ instruction: readyAction.instruction,
96
+ nextAction: readyAction.next_action,
97
+ };
98
+ }
99
+
100
+ if (
101
+ params.violation.code === 'EVIDENCE_PREWRITE_WORKTREE_OVER_LIMIT'
102
+ || params.violation.code === 'EVIDENCE_PREWRITE_WORKTREE_WARN'
103
+ ) {
104
+ const validateCommand = buildGovernanceValidateCommand(params.stage);
105
+ const plan = collectWorktreeAtomicSlices({
106
+ repoRoot: params.repoRoot,
107
+ maxSlices: 3,
108
+ maxFilesPerSlice: 4,
109
+ });
110
+ if (plan.slices.length > 0) {
111
+ const firstSlice = plan.slices[0];
138
112
  return {
139
- kind: 'info',
140
- message: 'Corrige la violación bloqueante y vuelve a ejecutar auto_execute_ai_start.',
113
+ instruction: 'Particiona el worktree en slices atómicos antes de continuar.',
114
+ nextAction: {
115
+ kind: params.allowed ? 'info' : 'run_command',
116
+ message:
117
+ `Particiona el worktree en slices atómicos por scope. Primer lote sugerido: ${firstSlice?.scope ?? 'scope-desconocido'}.`,
118
+ command: params.allowed
119
+ ? undefined
120
+ : `${firstSlice?.staged_command ?? 'git add -p'} && ${validateCommand}`,
121
+ },
141
122
  };
123
+ }
124
+ return {
125
+ instruction: 'Particiona el worktree en slices atómicos antes de continuar.',
126
+ nextAction: {
127
+ kind: params.allowed ? 'info' : 'run_command',
128
+ message: 'Particiona el worktree en slices atómicos y revalida PRE_WRITE para continuar sin fricción.',
129
+ command: params.allowed
130
+ ? undefined
131
+ : `git status --short && git add -p && ${validateCommand}`,
132
+ },
133
+ };
142
134
  }
135
+
136
+ const governanceAction = resolveGovernanceCatalogAction({
137
+ code: normalizeGovernanceCatalogCode(params.violation.code),
138
+ stage: params.stage,
139
+ });
140
+ return {
141
+ instruction: governanceAction.instruction,
142
+ nextAction: params.allowed
143
+ ? {
144
+ kind: 'info',
145
+ message: governanceAction.next_action.message,
146
+ }
147
+ : governanceAction.next_action,
148
+ };
143
149
  };
144
150
 
145
151
  export type EnterpriseAutoExecuteAiStartResult = {
@@ -190,19 +196,20 @@ export const runEnterpriseAutoExecuteAiStart = (params: {
190
196
  const action: AutoExecuteAction = evaluation.allowed ? 'proceed' : 'ask';
191
197
  const phase = toAutoExecutePhase(action);
192
198
  const confidencePct = confidenceFromViolation(firstViolation?.code ?? null);
193
- const nextAction = evaluation.allowed
194
- ? {
195
- kind: 'info' as const,
196
- message: 'Gate en verde. Continúa con la implementación.',
197
- }
198
- : nextActionFromViolation(firstViolation, params.repoRoot);
199
+ const remediation = resolveAutoExecuteRemediation({
200
+ violation: firstViolation,
201
+ repoRoot: params.repoRoot,
202
+ stage,
203
+ allowed: evaluation.allowed,
204
+ });
205
+ const nextAction = remediation.nextAction;
199
206
 
200
207
  let message = toHumanMessage({
201
208
  action,
202
209
  confidencePct,
203
210
  reasonCode,
204
211
  });
205
- let instruction = nextAction.message;
212
+ let instruction = remediation.instruction;
206
213
  if (learningContext?.recommended_actions[0]) {
207
214
  message = `${message} Learning: ${learningContext.recommended_actions[0]}`;
208
215
  instruction = `${instruction} Learning: ${learningContext.recommended_actions[0]}`;
@@ -9,7 +9,7 @@ import { resolveMcpEnterpriseExperimentalFeature } from '../policy/experimentalF
9
9
  import { evaluateSddPolicy, readSddStatus } from '../sdd';
10
10
  import type { SddStage } from '../sdd';
11
11
  import { toStatusPayload } from './evidencePayloads';
12
- import { runEnterpriseAiGateCheck } from './aiGateCheck';
12
+ import { runEnterpriseAiGateCheckAsync } from './aiGateCheck';
13
13
  import { runEnterprisePreFlightCheck } from './preFlightCheck';
14
14
  import { runEnterpriseAutoExecuteAiStart } from './autoExecuteAiStart';
15
15
  import { writeMcpAiGateReceipt } from './aiGateReceipt';
@@ -382,16 +382,16 @@ const evaluateCriticalToolGuard = (
382
382
  }
383
383
  };
384
384
 
385
- const executeEnterpriseTool = (
385
+ const executeEnterpriseTool = async (
386
386
  repoRoot: string,
387
387
  toolName: EnterpriseToolName,
388
388
  args: Record<string, string | number | boolean | bigint | symbol | null | Date | object>,
389
389
  dryRun: boolean
390
- ): EnterpriseToolExecution => {
390
+ ): Promise<EnterpriseToolExecution> => {
391
391
  switch (toolName) {
392
392
  case 'ai_gate_check': {
393
393
  const stage = toSddStage(args.stage, 'PRE_COMMIT');
394
- const execution = runEnterpriseAiGateCheck({
394
+ const execution = await runEnterpriseAiGateCheckAsync({
395
395
  repoRoot,
396
396
  stage,
397
397
  });
@@ -741,7 +741,7 @@ export const startEnterpriseMcpServer = (
741
741
  return;
742
742
  }
743
743
  void readJsonBody(req)
744
- .then((body) => {
744
+ .then(async (body) => {
745
745
  if (typeof body !== 'object' || body === null) {
746
746
  sendJson(res, 400, {
747
747
  error: 'Invalid request body.',
@@ -814,7 +814,7 @@ export const startEnterpriseMcpServer = (
814
814
  }
815
815
  let result: EnterpriseToolExecution;
816
816
  try {
817
- result = executeEnterpriseTool(
817
+ result = await executeEnterpriseTool(
818
818
  repoRoot,
819
819
  toolName,
820
820
  args,