pumuki 6.3.113 → 6.3.114
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/CHANGELOG.md +51 -5
- package/README.md +4 -2
- package/VERSION +1 -1
- package/core/facts/detectors/typescript/index.test.ts +0 -229
- package/core/facts/detectors/typescript/index.ts +0 -278
- package/core/facts/extractHeuristicFacts.ts +0 -4
- package/core/rules/presets/heuristics/typescript.test.ts +1 -21
- package/core/rules/presets/heuristics/typescript.ts +0 -72
- package/docs/README.md +13 -9
- package/docs/codex-skills/backend-enterprise-rules.md +3 -3
- package/docs/operations/RELEASE_NOTES.md +40 -4
- package/docs/product/API_REFERENCE.md +1 -1
- package/docs/product/HOW_IT_WORKS.md +6 -0
- package/docs/product/INSTALLATION.md +1 -1
- package/docs/product/USAGE.md +42 -5
- package/docs/tracking/plan-curso-pumuki-stack-my-architecture.md +100 -44
- package/docs/validation/README.md +6 -3
- package/integrations/config/skillsDetectorRegistry.ts +0 -24
- package/integrations/config/skillsMarkdownRules.ts +0 -57
- package/integrations/evidence/buildEvidence.ts +0 -24
- package/integrations/evidence/repoState.ts +0 -3
- package/integrations/evidence/schema.ts +0 -18
- package/integrations/evidence/writeEvidence.ts +0 -24
- package/integrations/gate/evaluateAiGate.ts +8 -251
- package/integrations/gate/remediationCatalog.ts +0 -8
- package/integrations/git/GitService.ts +44 -5
- package/integrations/git/aiGateRepoPolicyFindings.ts +86 -17
- package/integrations/git/runPlatformGate.ts +1 -9
- package/integrations/git/runPlatformGateFacts.ts +19 -1
- package/integrations/git/runPlatformGateOutput.ts +41 -42
- package/integrations/lifecycle/adapter.templates.json +1 -0
- package/integrations/lifecycle/adapter.ts +0 -24
- package/integrations/lifecycle/audit.ts +101 -0
- package/integrations/lifecycle/cli.ts +120 -99
- package/integrations/lifecycle/cliSdd.ts +4 -26
- package/integrations/lifecycle/doctor.ts +40 -102
- package/integrations/lifecycle/index.ts +2 -0
- package/integrations/lifecycle/install.ts +0 -21
- package/integrations/lifecycle/packageInfo.ts +1 -118
- package/integrations/lifecycle/state.ts +1 -8
- package/integrations/lifecycle/status.ts +40 -59
- package/integrations/lifecycle/watch.ts +1 -1
- package/integrations/mcp/aiGateCheck.ts +10 -194
- package/integrations/mcp/autoExecuteAiStart.ts +116 -92
- package/integrations/mcp/enterpriseServer.ts +7 -23
- package/integrations/mcp/enterpriseStdioServer.cli.ts +4 -31
- package/integrations/mcp/preFlightCheck.ts +5 -67
- package/integrations/platform/detectPlatforms.ts +37 -0
- package/integrations/sdd/policy.ts +28 -20
- package/package.json +1 -1
- package/scripts/check-tracking-single-active.sh +1 -1
- package/scripts/consumer-menu-matrix-baseline-report-lib.ts +13 -38
- package/scripts/consumer-postinstall-resolve-args.cjs +44 -0
- package/scripts/consumer-postinstall.cjs +76 -21
- package/scripts/framework-menu-advanced-view-lib.ts +0 -49
- package/scripts/framework-menu-consumer-actions-lib.ts +28 -4
- package/scripts/framework-menu-consumer-preflight-hints.ts +5 -2
- package/scripts/framework-menu-consumer-preflight-render.ts +0 -10
- package/scripts/framework-menu-consumer-preflight-run.ts +0 -23
- package/scripts/framework-menu-consumer-preflight-types.ts +0 -12
- package/scripts/framework-menu-consumer-runtime-actions.ts +87 -17
- package/scripts/framework-menu-consumer-runtime-audit.ts +36 -2
- package/scripts/framework-menu-consumer-runtime-evidence-classic.ts +140 -0
- package/scripts/framework-menu-consumer-runtime-lib.ts +2 -38
- package/scripts/framework-menu-consumer-runtime-menu.ts +4 -31
- package/scripts/framework-menu-consumer-runtime-types.ts +3 -5
- package/scripts/framework-menu-evidence-summary-lib.ts +1 -0
- package/scripts/framework-menu-evidence-summary-read.ts +57 -5
- package/scripts/framework-menu-evidence-summary-severity.ts +3 -1
- package/scripts/framework-menu-evidence-summary-types.ts +7 -0
- package/scripts/framework-menu-gate-lib.ts +9 -0
- package/scripts/framework-menu-layout-data.ts +5 -0
- package/scripts/framework-menu-matrix-baseline-lib.ts +15 -14
- package/scripts/framework-menu-matrix-canary-lib.ts +22 -1
- package/scripts/framework-menu-matrix-evidence-lib.ts +1 -0
- package/scripts/framework-menu-matrix-evidence-types.ts +13 -1
- package/scripts/framework-menu-matrix-runner-lib.ts +35 -0
- package/scripts/framework-menu-system-notifications-cause.ts +0 -3
- package/scripts/framework-menu-system-notifications-macos-swift-source.ts +24 -204
- package/scripts/framework-menu-system-notifications-macos.ts +4 -0
- package/scripts/framework-menu-system-notifications-payloads-blocked.ts +1 -1
- package/scripts/framework-menu-system-notifications-text.ts +1 -7
- package/scripts/framework-menu.ts +3 -24
- package/scripts/package-install-smoke-consumer-git-repo-lib.ts +1 -10
- package/scripts/package-install-smoke-consumer-npm-lib.ts +9 -46
- package/scripts/pumuki-full-surface-smoke-lib.ts +37 -0
- package/scripts/pumuki-full-surface-smoke.ts +346 -0
- package/scripts/pumuki-smoke-installed-wrapper.cjs +31 -0
- package/integrations/evidence/trackingContract.ts +0 -17
- package/integrations/gate/governanceActionCatalog.ts +0 -275
- package/integrations/lifecycle/bootstrapManifest.ts +0 -248
- package/integrations/lifecycle/cliGovernanceConsole.ts +0 -69
- package/integrations/lifecycle/governanceNextAction.ts +0 -171
- package/integrations/lifecycle/governanceObservationSnapshot.ts +0 -369
- package/integrations/lifecycle/trackingState.ts +0 -403
- package/integrations/mcp/alignedPlatformGate.ts +0 -232
- package/integrations/mcp/readMcpPrePushStdin.ts +0 -7
- package/scripts/build-ruralgo-s1-evidence-pack.ts +0 -85
- package/scripts/ruralgo-s1-evidence-pack-lib.ts +0 -200
|
@@ -1,20 +1,10 @@
|
|
|
1
1
|
import { evaluateAiGate, type AiGateStage } from '../gate/evaluateAiGate';
|
|
2
2
|
import { resolveRemediationHintForViolationCode } from '../gate/remediationCatalog';
|
|
3
|
-
import { readLifecyclePolicyValidationSnapshot } from '../lifecycle/policyValidationSnapshot';
|
|
4
3
|
import { resolveLearningContextExperimentalFeature } from '../policy/experimentalFeatures';
|
|
5
|
-
import { resolvePreWriteEnforcement } from '../policy/preWriteEnforcement';
|
|
6
4
|
import { readSddLearningContext, type SddLearningContext } from '../sdd/learningInsights';
|
|
7
|
-
import { runMcpAlignedPlatformGate } from './alignedPlatformGate';
|
|
8
5
|
|
|
9
6
|
const PROTECTED_BRANCHES = new Set(['main', 'master', 'develop', 'dev']);
|
|
10
7
|
|
|
11
|
-
type PlatformGateAlignment = {
|
|
12
|
-
mode: 'full' | 'policy';
|
|
13
|
-
exit_code: number;
|
|
14
|
-
aligned: boolean;
|
|
15
|
-
skip_reason: string | null;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
8
|
export type EnterpriseAiGateCheckResult = {
|
|
19
9
|
tool: 'ai_gate_check';
|
|
20
10
|
dryRun: true;
|
|
@@ -26,19 +16,6 @@ export type EnterpriseAiGateCheckResult = {
|
|
|
26
16
|
timestamp: string | null;
|
|
27
17
|
branch: string | null;
|
|
28
18
|
message: string;
|
|
29
|
-
reason_code: string;
|
|
30
|
-
instruction: string;
|
|
31
|
-
prewrite_effective: {
|
|
32
|
-
mode: ReturnType<typeof resolvePreWriteEnforcement>['mode'];
|
|
33
|
-
source: ReturnType<typeof resolvePreWriteEnforcement>['source'];
|
|
34
|
-
blocking: boolean;
|
|
35
|
-
strict_policy: boolean;
|
|
36
|
-
};
|
|
37
|
-
next_action: {
|
|
38
|
-
kind: 'info';
|
|
39
|
-
reason: string;
|
|
40
|
-
message: string;
|
|
41
|
-
};
|
|
42
19
|
stage: ReturnType<typeof evaluateAiGate>['stage'];
|
|
43
20
|
policy: ReturnType<typeof evaluateAiGate>['policy'];
|
|
44
21
|
violations: ReturnType<typeof evaluateAiGate>['violations'];
|
|
@@ -54,7 +31,6 @@ export type EnterpriseAiGateCheckResult = {
|
|
|
54
31
|
reason_code: 'HOOK_RUNNER_CAN_REFRESH_EVIDENCE' | null;
|
|
55
32
|
message: string;
|
|
56
33
|
};
|
|
57
|
-
platform_gate_alignment?: PlatformGateAlignment;
|
|
58
34
|
};
|
|
59
35
|
};
|
|
60
36
|
|
|
@@ -65,18 +41,23 @@ const isHookRefreshableEvidenceCode = (code: string): boolean =>
|
|
|
65
41
|
|
|
66
42
|
type AiGateCheckDependencies = {
|
|
67
43
|
evaluateAiGate: typeof evaluateAiGate;
|
|
68
|
-
runMcpAlignedPlatformGate: typeof runMcpAlignedPlatformGate;
|
|
69
44
|
};
|
|
70
45
|
|
|
71
46
|
const defaultDependencies: AiGateCheckDependencies = {
|
|
72
47
|
evaluateAiGate,
|
|
73
|
-
runMcpAlignedPlatformGate,
|
|
74
48
|
};
|
|
75
49
|
|
|
76
50
|
const buildConsistencyHint = (
|
|
77
|
-
evaluation: ReturnType<typeof evaluateAiGate
|
|
78
|
-
platform?: { exitCode: number; aligned: boolean; skipReason: string | null }
|
|
51
|
+
evaluation: ReturnType<typeof evaluateAiGate>
|
|
79
52
|
): 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
|
+
|
|
80
61
|
const hasRefreshableEvidenceViolation = evaluation.violations.some((violation) =>
|
|
81
62
|
isHookRefreshableEvidenceCode(violation.code)
|
|
82
63
|
);
|
|
@@ -91,24 +72,6 @@ const buildConsistencyHint = (
|
|
|
91
72
|
};
|
|
92
73
|
}
|
|
93
74
|
|
|
94
|
-
if (platform?.aligned) {
|
|
95
|
-
return {
|
|
96
|
-
comparable_with_hook_runner: true,
|
|
97
|
-
reason_code: null,
|
|
98
|
-
message:
|
|
99
|
-
`ai_gate_check ejecutó runPlatformGate después de leer la evidencia actual (exit_code=${platform.exitCode}); ` +
|
|
100
|
-
'alineación hook-like habilitada explícitamente para este stage.',
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (!HOOK_STAGE_SET.has(evaluation.stage)) {
|
|
105
|
-
return {
|
|
106
|
-
comparable_with_hook_runner: true,
|
|
107
|
-
reason_code: null,
|
|
108
|
-
message: 'Stage is directly comparable with ai_gate_check semantics.',
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
75
|
return {
|
|
113
76
|
comparable_with_hook_runner: true,
|
|
114
77
|
reason_code: null,
|
|
@@ -155,31 +118,7 @@ const buildAutoFixes = (
|
|
|
155
118
|
return fixes;
|
|
156
119
|
};
|
|
157
120
|
|
|
158
|
-
const
|
|
159
|
-
evaluation.violations[0]?.code ?? 'AI_GATE_ALLOWED';
|
|
160
|
-
|
|
161
|
-
const buildInstruction = (evaluation: ReturnType<typeof evaluateAiGate>): string =>
|
|
162
|
-
evaluation.allowed
|
|
163
|
-
? 'Continúa con el stage actual manteniendo el vocabulario canónico de governance.'
|
|
164
|
-
: 'Corrige el bloqueante primario y vuelve a ejecutar ai_gate_check en el mismo stage.';
|
|
165
|
-
|
|
166
|
-
const buildNextAction = (
|
|
167
|
-
evaluation: ReturnType<typeof evaluateAiGate>,
|
|
168
|
-
autoFixes: ReadonlyArray<string>
|
|
169
|
-
): EnterpriseAiGateCheckResult['result']['next_action'] => ({
|
|
170
|
-
kind: 'info',
|
|
171
|
-
reason: buildReasonCode(evaluation),
|
|
172
|
-
message: autoFixes[0] ?? buildInstruction(evaluation),
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
const buildMessage = (
|
|
176
|
-
evaluation: ReturnType<typeof evaluateAiGate>,
|
|
177
|
-
platform?: { exitCode: number; skipReason: string | null }
|
|
178
|
-
): string => {
|
|
179
|
-
if (platform && platform.exitCode !== 0) {
|
|
180
|
-
const suffix = platform.skipReason ? ` (${platform.skipReason})` : '';
|
|
181
|
-
return `🔴 runPlatformGate exit_code=${platform.exitCode}${suffix}.`;
|
|
182
|
-
}
|
|
121
|
+
const buildMessage = (evaluation: ReturnType<typeof evaluateAiGate>): string => {
|
|
183
122
|
if (evaluation.allowed) {
|
|
184
123
|
return `✅ Gate ${evaluation.stage} ALLOWED.`;
|
|
185
124
|
}
|
|
@@ -190,26 +129,6 @@ const buildMessage = (
|
|
|
190
129
|
return `🔴 ${firstViolation.code}: ${firstViolation.message}`;
|
|
191
130
|
};
|
|
192
131
|
|
|
193
|
-
const resolveAiGateCheckMode = (): PlatformGateAlignment['mode'] => {
|
|
194
|
-
const raw = process.env.PUMUKI_MCP_AI_GATE_CHECK_MODE?.trim().toLowerCase();
|
|
195
|
-
return raw === 'full' || raw === 'aligned' ? 'full' : 'policy';
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
const toPlatformGateAlignment = (
|
|
199
|
-
mode: PlatformGateAlignment['mode'],
|
|
200
|
-
platform?: { exitCode: number; aligned: boolean; skipReason: string | null }
|
|
201
|
-
): PlatformGateAlignment | undefined => {
|
|
202
|
-
if (mode !== 'full' || !platform) {
|
|
203
|
-
return undefined;
|
|
204
|
-
}
|
|
205
|
-
return {
|
|
206
|
-
mode,
|
|
207
|
-
exit_code: platform.exitCode,
|
|
208
|
-
aligned: platform.aligned,
|
|
209
|
-
skip_reason: platform.skipReason,
|
|
210
|
-
};
|
|
211
|
-
};
|
|
212
|
-
|
|
213
132
|
export const runEnterpriseAiGateCheck = (params: {
|
|
214
133
|
repoRoot: string;
|
|
215
134
|
stage: AiGateStage;
|
|
@@ -232,8 +151,6 @@ export const runEnterpriseAiGateCheck = (params: {
|
|
|
232
151
|
: readSddLearningContext({
|
|
233
152
|
repoRoot: params.repoRoot,
|
|
234
153
|
});
|
|
235
|
-
const preWriteEnforcement = resolvePreWriteEnforcement();
|
|
236
|
-
const policyValidation = readLifecyclePolicyValidationSnapshot(params.repoRoot);
|
|
237
154
|
const warnings = buildWarnings(evaluation);
|
|
238
155
|
const autoFixes = buildAutoFixes(evaluation, learningContext);
|
|
239
156
|
const message = buildMessage(evaluation);
|
|
@@ -249,15 +166,6 @@ export const runEnterpriseAiGateCheck = (params: {
|
|
|
249
166
|
timestamp,
|
|
250
167
|
branch,
|
|
251
168
|
message,
|
|
252
|
-
reason_code: buildReasonCode(evaluation),
|
|
253
|
-
instruction: buildInstruction(evaluation),
|
|
254
|
-
prewrite_effective: {
|
|
255
|
-
mode: preWriteEnforcement.mode,
|
|
256
|
-
source: preWriteEnforcement.source,
|
|
257
|
-
blocking: preWriteEnforcement.blocking,
|
|
258
|
-
strict_policy: policyValidation.stages.PRE_WRITE.strict,
|
|
259
|
-
},
|
|
260
|
-
next_action: buildNextAction(evaluation, autoFixes),
|
|
261
169
|
stage: evaluation.stage,
|
|
262
170
|
policy: evaluation.policy,
|
|
263
171
|
violations: evaluation.violations,
|
|
@@ -272,95 +180,3 @@ export const runEnterpriseAiGateCheck = (params: {
|
|
|
272
180
|
},
|
|
273
181
|
};
|
|
274
182
|
};
|
|
275
|
-
|
|
276
|
-
export const runEnterpriseAiGateCheckAsync = async (params: {
|
|
277
|
-
repoRoot: string;
|
|
278
|
-
stage: AiGateStage;
|
|
279
|
-
requireMcpReceipt?: boolean;
|
|
280
|
-
}, dependencies: Partial<AiGateCheckDependencies> = {}): Promise<EnterpriseAiGateCheckResult> => {
|
|
281
|
-
const mode = resolveAiGateCheckMode();
|
|
282
|
-
const activeDependencies: AiGateCheckDependencies = {
|
|
283
|
-
...defaultDependencies,
|
|
284
|
-
...dependencies,
|
|
285
|
-
};
|
|
286
|
-
const evaluation = activeDependencies.evaluateAiGate({
|
|
287
|
-
repoRoot: params.repoRoot,
|
|
288
|
-
stage: params.stage,
|
|
289
|
-
requireMcpReceipt: params.requireMcpReceipt ?? false,
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
let platform:
|
|
293
|
-
| { exitCode: number; aligned: boolean; skipReason: string | null }
|
|
294
|
-
| undefined;
|
|
295
|
-
if (mode === 'full') {
|
|
296
|
-
platform = await activeDependencies.runMcpAlignedPlatformGate({
|
|
297
|
-
repoRoot: params.repoRoot,
|
|
298
|
-
stage: params.stage,
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const platformBlocks = Boolean(platform && platform.exitCode !== 0);
|
|
303
|
-
const allowed = evaluation.allowed && !platformBlocks;
|
|
304
|
-
const status: 'ALLOWED' | 'BLOCKED' = allowed ? 'ALLOWED' : 'BLOCKED';
|
|
305
|
-
const violations = platformBlocks && platform
|
|
306
|
-
? [
|
|
307
|
-
...evaluation.violations,
|
|
308
|
-
{
|
|
309
|
-
code: 'PLATFORM_GATE_EXIT_NON_ZERO',
|
|
310
|
-
message:
|
|
311
|
-
`runPlatformGate devolvió exit_code=${platform.exitCode}` +
|
|
312
|
-
(platform.skipReason ? ` (${platform.skipReason})` : ''),
|
|
313
|
-
severity: 'ERROR' as const,
|
|
314
|
-
},
|
|
315
|
-
]
|
|
316
|
-
: evaluation.violations;
|
|
317
|
-
const branch = evaluation.repo_state.git.branch;
|
|
318
|
-
const timestamp = evaluation.evidence.source.generated_at;
|
|
319
|
-
const learningContextFeature = resolveLearningContextExperimentalFeature();
|
|
320
|
-
const learningContext = learningContextFeature.mode === 'off'
|
|
321
|
-
? null
|
|
322
|
-
: readSddLearningContext({
|
|
323
|
-
repoRoot: params.repoRoot,
|
|
324
|
-
});
|
|
325
|
-
const evaluationForHints = { ...evaluation, allowed, status, violations };
|
|
326
|
-
const warnings = buildWarnings(evaluationForHints);
|
|
327
|
-
const autoFixes = buildAutoFixes(evaluationForHints, learningContext);
|
|
328
|
-
const message = buildMessage(evaluationForHints, platform);
|
|
329
|
-
const preWriteEnforcement = resolvePreWriteEnforcement();
|
|
330
|
-
const policyValidation = readLifecyclePolicyValidationSnapshot(params.repoRoot);
|
|
331
|
-
|
|
332
|
-
return {
|
|
333
|
-
tool: 'ai_gate_check',
|
|
334
|
-
dryRun: true,
|
|
335
|
-
executed: true,
|
|
336
|
-
success: allowed,
|
|
337
|
-
result: {
|
|
338
|
-
allowed,
|
|
339
|
-
status,
|
|
340
|
-
timestamp,
|
|
341
|
-
branch,
|
|
342
|
-
message,
|
|
343
|
-
reason_code: buildReasonCode(evaluationForHints),
|
|
344
|
-
instruction: buildInstruction(evaluationForHints),
|
|
345
|
-
prewrite_effective: {
|
|
346
|
-
mode: preWriteEnforcement.mode,
|
|
347
|
-
source: preWriteEnforcement.source,
|
|
348
|
-
blocking: preWriteEnforcement.blocking,
|
|
349
|
-
strict_policy: policyValidation.stages.PRE_WRITE.strict,
|
|
350
|
-
},
|
|
351
|
-
next_action: buildNextAction(evaluationForHints, autoFixes),
|
|
352
|
-
stage: evaluation.stage,
|
|
353
|
-
policy: evaluation.policy,
|
|
354
|
-
violations,
|
|
355
|
-
warnings,
|
|
356
|
-
auto_fixes: autoFixes,
|
|
357
|
-
learning_context: learningContext,
|
|
358
|
-
evidence: evaluation.evidence,
|
|
359
|
-
mcp_receipt: evaluation.mcp_receipt,
|
|
360
|
-
skills_contract: evaluation.skills_contract,
|
|
361
|
-
repo_state: evaluation.repo_state,
|
|
362
|
-
consistency_hint: buildConsistencyHint(evaluationForHints, platform),
|
|
363
|
-
platform_gate_alignment: toPlatformGateAlignment(mode, platform),
|
|
364
|
-
},
|
|
365
|
-
};
|
|
366
|
-
};
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
buildGovernanceValidateCommand,
|
|
3
|
-
resolveGovernanceCatalogAction,
|
|
4
|
-
} from '../gate/governanceActionCatalog';
|
|
5
1
|
import { evaluateAiGate, type AiGateStage, type AiGateViolation } from '../gate/evaluateAiGate';
|
|
6
2
|
import { collectWorktreeAtomicSlices } from '../git/worktreeAtomicSlices';
|
|
3
|
+
import { getCurrentPumukiVersion } from '../lifecycle/packageInfo';
|
|
7
4
|
import { resolveLearningContextExperimentalFeature } from '../policy/experimentalFeatures';
|
|
8
5
|
import { readSddLearningContext, type SddLearningContext } from '../sdd/learningInsights';
|
|
9
6
|
|
|
@@ -54,98 +51,126 @@ const confidenceFromViolation = (violationCode: string | null): number => {
|
|
|
54
51
|
if (isEvidenceCode(violationCode)) {
|
|
55
52
|
return 65;
|
|
56
53
|
}
|
|
57
|
-
if (
|
|
58
|
-
violationCode === 'GITFLOW_PROTECTED_BRANCH'
|
|
59
|
-
|| violationCode === 'GITFLOW_BRANCH_NAMING_INVALID'
|
|
60
|
-
) {
|
|
54
|
+
if (violationCode === 'GITFLOW_PROTECTED_BRANCH') {
|
|
61
55
|
return 40;
|
|
62
56
|
}
|
|
63
57
|
return 50;
|
|
64
58
|
};
|
|
65
59
|
|
|
66
|
-
const
|
|
67
|
-
switch (code) {
|
|
68
|
-
case 'EVIDENCE_INVALID':
|
|
69
|
-
case 'EVIDENCE_CHAIN_INVALID':
|
|
70
|
-
return 'EVIDENCE_INVALID_OR_CHAIN';
|
|
71
|
-
case 'GITFLOW_PROTECTED_BRANCH':
|
|
72
|
-
return 'GITFLOW_PROTECTED_BRANCH_CONTEXT';
|
|
73
|
-
case 'GITFLOW_BRANCH_NAMING_INVALID':
|
|
74
|
-
return 'GITFLOW_BRANCH_NAMING_INVALID_CONTEXT';
|
|
75
|
-
default:
|
|
76
|
-
return code;
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
const resolveAutoExecuteRemediation = (params: {
|
|
81
|
-
violation: AiGateViolation | undefined;
|
|
60
|
+
const buildPinnedPumukiNpxCommand = (params: {
|
|
82
61
|
repoRoot: string;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
stage: params.stage,
|
|
93
|
-
});
|
|
62
|
+
executableAndArgs: string;
|
|
63
|
+
}): string =>
|
|
64
|
+
`npx --yes --package pumuki@${getCurrentPumukiVersion({ repoRoot: params.repoRoot })} ${params.executableAndArgs}`;
|
|
65
|
+
|
|
66
|
+
const nextActionFromViolation = (
|
|
67
|
+
violation: AiGateViolation | undefined,
|
|
68
|
+
repoRoot: string
|
|
69
|
+
): AutoExecuteNextAction => {
|
|
70
|
+
if (!violation) {
|
|
94
71
|
return {
|
|
95
|
-
|
|
96
|
-
|
|
72
|
+
kind: 'info',
|
|
73
|
+
message: 'Gate listo. Puedes continuar con implementación.',
|
|
97
74
|
};
|
|
98
75
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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];
|
|
76
|
+
switch (violation.code) {
|
|
77
|
+
case 'EVIDENCE_MISSING':
|
|
78
|
+
case 'EVIDENCE_INVALID':
|
|
79
|
+
case 'EVIDENCE_CHAIN_INVALID':
|
|
80
|
+
case 'EVIDENCE_STALE':
|
|
112
81
|
return {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
? undefined
|
|
120
|
-
: `${firstSlice?.staged_command ?? 'git add -p'} && ${validateCommand}`,
|
|
121
|
-
},
|
|
82
|
+
kind: 'run_command',
|
|
83
|
+
message: 'Regenera o refresca evidencia y vuelve a evaluar PRE_WRITE.',
|
|
84
|
+
command: buildPinnedPumukiNpxCommand({
|
|
85
|
+
repoRoot,
|
|
86
|
+
executableAndArgs: 'pumuki sdd validate --stage=PRE_WRITE --json',
|
|
87
|
+
}),
|
|
122
88
|
};
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
89
|
+
case 'EVIDENCE_ACTIVE_RULE_IDS_EMPTY_FOR_CODE_CHANGES':
|
|
90
|
+
return {
|
|
91
|
+
kind: 'run_command',
|
|
92
|
+
message:
|
|
93
|
+
'No hay active_rule_ids para plataforma de código detectada. Reconciliación strict de policy/skills y revalidación PRE_WRITE.',
|
|
94
|
+
command:
|
|
95
|
+
`${buildPinnedPumukiNpxCommand({
|
|
96
|
+
repoRoot,
|
|
97
|
+
executableAndArgs: 'pumuki policy reconcile --strict --json',
|
|
98
|
+
})} && ${buildPinnedPumukiNpxCommand({
|
|
99
|
+
repoRoot,
|
|
100
|
+
executableAndArgs: 'pumuki sdd validate --stage=PRE_WRITE --json',
|
|
101
|
+
})}`,
|
|
102
|
+
};
|
|
103
|
+
case 'EVIDENCE_PLATFORM_SKILLS_SCOPE_INCOMPLETE':
|
|
104
|
+
case 'EVIDENCE_PLATFORM_SKILLS_BUNDLES_MISSING':
|
|
105
|
+
case 'EVIDENCE_SKILLS_CONTRACT_INCOMPLETE':
|
|
106
|
+
return {
|
|
107
|
+
kind: 'run_command',
|
|
108
|
+
message:
|
|
109
|
+
'Completa cobertura de skills por plataforma (prefijos + bundles) y revalida PRE_WRITE.',
|
|
110
|
+
command: buildPinnedPumukiNpxCommand({
|
|
111
|
+
repoRoot,
|
|
112
|
+
executableAndArgs: 'pumuki sdd validate --stage=PRE_WRITE --json',
|
|
113
|
+
}),
|
|
114
|
+
};
|
|
115
|
+
case 'EVIDENCE_PLATFORM_CRITICAL_SKILLS_RULES_MISSING':
|
|
116
|
+
case 'EVIDENCE_CROSS_PLATFORM_CRITICAL_ENFORCEMENT_INCOMPLETE':
|
|
117
|
+
return {
|
|
118
|
+
kind: 'run_command',
|
|
119
|
+
message:
|
|
120
|
+
'Reconcilia policy/skills en modo estricto (incluida skills.ios.critical-test-quality cuando aplique) y revalida PRE_WRITE.',
|
|
121
|
+
command:
|
|
122
|
+
`${buildPinnedPumukiNpxCommand({
|
|
123
|
+
repoRoot,
|
|
124
|
+
executableAndArgs: 'pumuki policy reconcile --strict --json',
|
|
125
|
+
})} && ${buildPinnedPumukiNpxCommand({
|
|
126
|
+
repoRoot,
|
|
127
|
+
executableAndArgs: 'pumuki sdd validate --stage=PRE_WRITE --json',
|
|
128
|
+
})}`,
|
|
129
|
+
};
|
|
130
|
+
case 'EVIDENCE_PREWRITE_WORKTREE_OVER_LIMIT':
|
|
131
|
+
case 'EVIDENCE_PREWRITE_WORKTREE_WARN':
|
|
132
|
+
{
|
|
133
|
+
const plan = collectWorktreeAtomicSlices({
|
|
134
|
+
repoRoot,
|
|
135
|
+
maxSlices: 3,
|
|
136
|
+
maxFilesPerSlice: 4,
|
|
137
|
+
});
|
|
138
|
+
if (plan.slices.length > 0) {
|
|
139
|
+
const firstSlice = plan.slices[0];
|
|
140
|
+
return {
|
|
141
|
+
kind: 'run_command',
|
|
142
|
+
message:
|
|
143
|
+
`Particiona el worktree en slices atómicos por scope. Primer lote sugerido: ${firstSlice?.scope ?? 'scope-desconocido'}.`,
|
|
144
|
+
command:
|
|
145
|
+
`${firstSlice?.staged_command ?? 'git add -p'} && ${buildPinnedPumukiNpxCommand({
|
|
146
|
+
repoRoot,
|
|
147
|
+
executableAndArgs: 'pumuki sdd validate --stage=PRE_WRITE --json',
|
|
148
|
+
})}`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
146
151
|
}
|
|
147
|
-
|
|
148
|
-
|
|
152
|
+
return {
|
|
153
|
+
kind: 'run_command',
|
|
154
|
+
message:
|
|
155
|
+
'Particiona el worktree en slices atómicos y revalida PRE_WRITE para continuar sin fricción.',
|
|
156
|
+
command:
|
|
157
|
+
`git status --short && git add -p && ${buildPinnedPumukiNpxCommand({
|
|
158
|
+
repoRoot,
|
|
159
|
+
executableAndArgs: 'pumuki sdd validate --stage=PRE_WRITE --json',
|
|
160
|
+
})}`,
|
|
161
|
+
};
|
|
162
|
+
case 'GITFLOW_PROTECTED_BRANCH':
|
|
163
|
+
return {
|
|
164
|
+
kind: 'run_command',
|
|
165
|
+
message: 'Cambia a una rama feature/* antes de continuar.',
|
|
166
|
+
command: 'git checkout -b feature/<descripcion-kebab-case>',
|
|
167
|
+
};
|
|
168
|
+
default:
|
|
169
|
+
return {
|
|
170
|
+
kind: 'info',
|
|
171
|
+
message: 'Corrige la violación bloqueante y vuelve a ejecutar auto_execute_ai_start.',
|
|
172
|
+
};
|
|
173
|
+
}
|
|
149
174
|
};
|
|
150
175
|
|
|
151
176
|
export type EnterpriseAutoExecuteAiStartResult = {
|
|
@@ -196,20 +221,19 @@ export const runEnterpriseAutoExecuteAiStart = (params: {
|
|
|
196
221
|
const action: AutoExecuteAction = evaluation.allowed ? 'proceed' : 'ask';
|
|
197
222
|
const phase = toAutoExecutePhase(action);
|
|
198
223
|
const confidencePct = confidenceFromViolation(firstViolation?.code ?? null);
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const nextAction = remediation.nextAction;
|
|
224
|
+
const nextAction = evaluation.allowed
|
|
225
|
+
? {
|
|
226
|
+
kind: 'info' as const,
|
|
227
|
+
message: 'Gate en verde. Continúa con la implementación.',
|
|
228
|
+
}
|
|
229
|
+
: nextActionFromViolation(firstViolation, params.repoRoot);
|
|
206
230
|
|
|
207
231
|
let message = toHumanMessage({
|
|
208
232
|
action,
|
|
209
233
|
confidencePct,
|
|
210
234
|
reasonCode,
|
|
211
235
|
});
|
|
212
|
-
let instruction =
|
|
236
|
+
let instruction = nextAction.message;
|
|
213
237
|
if (learningContext?.recommended_actions[0]) {
|
|
214
238
|
message = `${message} Learning: ${learningContext.recommended_actions[0]}`;
|
|
215
239
|
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 {
|
|
12
|
+
import { runEnterpriseAiGateCheck } from './aiGateCheck';
|
|
13
13
|
import { runEnterprisePreFlightCheck } from './preFlightCheck';
|
|
14
14
|
import { runEnterpriseAutoExecuteAiStart } from './autoExecuteAiStart';
|
|
15
15
|
import { writeMcpAiGateReceipt } from './aiGateReceipt';
|
|
@@ -39,14 +39,6 @@ type EnterpriseStatusPayload = {
|
|
|
39
39
|
evidence: ReturnType<typeof toStatusPayload>;
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
-
type EnterpriseHealthPayload = {
|
|
43
|
-
status: 'ok';
|
|
44
|
-
repoRoot: string;
|
|
45
|
-
experimentalFeatures: {
|
|
46
|
-
mcp_enterprise: ReturnType<typeof resolveMcpEnterpriseExperimentalFeature>;
|
|
47
|
-
};
|
|
48
|
-
};
|
|
49
|
-
|
|
50
42
|
const ENTERPRISE_RESOURCES = [
|
|
51
43
|
'evidence://status',
|
|
52
44
|
'gitflow://state',
|
|
@@ -390,16 +382,16 @@ const evaluateCriticalToolGuard = (
|
|
|
390
382
|
}
|
|
391
383
|
};
|
|
392
384
|
|
|
393
|
-
const executeEnterpriseTool =
|
|
385
|
+
const executeEnterpriseTool = (
|
|
394
386
|
repoRoot: string,
|
|
395
387
|
toolName: EnterpriseToolName,
|
|
396
388
|
args: Record<string, string | number | boolean | bigint | symbol | null | Date | object>,
|
|
397
389
|
dryRun: boolean
|
|
398
|
-
):
|
|
390
|
+
): EnterpriseToolExecution => {
|
|
399
391
|
switch (toolName) {
|
|
400
392
|
case 'ai_gate_check': {
|
|
401
393
|
const stage = toSddStage(args.stage, 'PRE_COMMIT');
|
|
402
|
-
const execution =
|
|
394
|
+
const execution = runEnterpriseAiGateCheck({
|
|
403
395
|
repoRoot,
|
|
404
396
|
stage,
|
|
405
397
|
});
|
|
@@ -658,14 +650,6 @@ const buildStatusPayload = (repoRoot: string): EnterpriseStatusPayload => ({
|
|
|
658
650
|
evidence: toStatusPayload(repoRoot),
|
|
659
651
|
});
|
|
660
652
|
|
|
661
|
-
const buildHealthPayload = (repoRoot: string): EnterpriseHealthPayload => ({
|
|
662
|
-
status: 'ok',
|
|
663
|
-
repoRoot,
|
|
664
|
-
experimentalFeatures: {
|
|
665
|
-
mcp_enterprise: readMcpEnterpriseExperimentalState(),
|
|
666
|
-
},
|
|
667
|
-
});
|
|
668
|
-
|
|
669
653
|
export const startEnterpriseMcpServer = (
|
|
670
654
|
options: EnterpriseServerOptions = {}
|
|
671
655
|
): EnterpriseServerHandle => {
|
|
@@ -696,7 +680,7 @@ export const startEnterpriseMcpServer = (
|
|
|
696
680
|
sendJson(res, 405, { error: 'Method not allowed' });
|
|
697
681
|
return;
|
|
698
682
|
}
|
|
699
|
-
sendJson(res, 200,
|
|
683
|
+
sendJson(res, 200, { status: 'ok' });
|
|
700
684
|
return;
|
|
701
685
|
}
|
|
702
686
|
|
|
@@ -757,7 +741,7 @@ export const startEnterpriseMcpServer = (
|
|
|
757
741
|
return;
|
|
758
742
|
}
|
|
759
743
|
void readJsonBody(req)
|
|
760
|
-
.then(
|
|
744
|
+
.then((body) => {
|
|
761
745
|
if (typeof body !== 'object' || body === null) {
|
|
762
746
|
sendJson(res, 400, {
|
|
763
747
|
error: 'Invalid request body.',
|
|
@@ -830,7 +814,7 @@ export const startEnterpriseMcpServer = (
|
|
|
830
814
|
}
|
|
831
815
|
let result: EnterpriseToolExecution;
|
|
832
816
|
try {
|
|
833
|
-
result =
|
|
817
|
+
result = executeEnterpriseTool(
|
|
834
818
|
repoRoot,
|
|
835
819
|
toolName,
|
|
836
820
|
args,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Socket, createServer } from 'node:net';
|
|
2
2
|
import { startEnterpriseMcpServer } from './enterpriseServer';
|
|
3
|
-
import { resolveMcpEnterpriseExperimentalFeature } from '../policy/experimentalFeatures';
|
|
4
3
|
|
|
5
4
|
type JsonRpcId = string | number | null;
|
|
6
5
|
|
|
@@ -99,32 +98,11 @@ const fetchJson = async (url: string, options?: RequestInit): Promise<unknown> =
|
|
|
99
98
|
}
|
|
100
99
|
};
|
|
101
100
|
|
|
102
|
-
type EnterpriseHealthPayload = {
|
|
103
|
-
status?: string;
|
|
104
|
-
repoRoot?: string;
|
|
105
|
-
experimentalFeatures?: {
|
|
106
|
-
mcp_enterprise?: {
|
|
107
|
-
mode?: string;
|
|
108
|
-
};
|
|
109
|
-
};
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
const canReuseEnterpriseHttp = (
|
|
113
|
-
health: EnterpriseHealthPayload,
|
|
114
|
-
repoRoot: string,
|
|
115
|
-
expectedMcpMode: string
|
|
116
|
-
): boolean =>
|
|
117
|
-
health.status === 'ok'
|
|
118
|
-
&& health.repoRoot === repoRoot
|
|
119
|
-
&& health.experimentalFeatures?.mcp_enterprise?.mode === expectedMcpMode;
|
|
120
|
-
|
|
121
101
|
const startOrReuseEnterpriseHttp = async (): Promise<{
|
|
122
102
|
host: string;
|
|
123
103
|
port: number;
|
|
124
104
|
startedByThisProcess: boolean;
|
|
125
105
|
}> => {
|
|
126
|
-
const repoRoot = process.cwd();
|
|
127
|
-
const expectedMcpMode = resolveMcpEnterpriseExperimentalFeature().mode;
|
|
128
106
|
const host = process.env.PUMUKI_ENTERPRISE_MCP_HOST ?? '127.0.0.1';
|
|
129
107
|
const parsedPort = Number.parseInt(process.env.PUMUKI_ENTERPRISE_MCP_PORT ?? '', 10);
|
|
130
108
|
const preferredPort = Number.isFinite(parsedPort) ? parsedPort : 7391;
|
|
@@ -132,8 +110,8 @@ const startOrReuseEnterpriseHttp = async (): Promise<{
|
|
|
132
110
|
|
|
133
111
|
const healthUrl = `http://${host}:${requestedPort}/health`;
|
|
134
112
|
try {
|
|
135
|
-
const health = (await fetchJson(healthUrl)) as
|
|
136
|
-
if (
|
|
113
|
+
const health = (await fetchJson(healthUrl)) as { status?: string };
|
|
114
|
+
if (health.status === 'ok') {
|
|
137
115
|
return {
|
|
138
116
|
host,
|
|
139
117
|
port: requestedPort,
|
|
@@ -141,12 +119,7 @@ const startOrReuseEnterpriseHttp = async (): Promise<{
|
|
|
141
119
|
};
|
|
142
120
|
}
|
|
143
121
|
} catch (error) {
|
|
144
|
-
|
|
145
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
146
|
-
process.stderr.write(
|
|
147
|
-
`[pumuki-mcp-enterprise-stdio] health probe reuse skipped: ${message}\n`
|
|
148
|
-
);
|
|
149
|
-
}
|
|
122
|
+
void error;
|
|
150
123
|
}
|
|
151
124
|
|
|
152
125
|
const portInUse = await isPortInUse(host, requestedPort);
|
|
@@ -154,7 +127,7 @@ const startOrReuseEnterpriseHttp = async (): Promise<{
|
|
|
154
127
|
startEnterpriseMcpServer({
|
|
155
128
|
host,
|
|
156
129
|
port: resolvedPort,
|
|
157
|
-
repoRoot,
|
|
130
|
+
repoRoot: process.cwd(),
|
|
158
131
|
});
|
|
159
132
|
|
|
160
133
|
return {
|