pumuki 6.3.39 → 6.3.40
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/README.md +21 -12
- package/VERSION +1 -1
- package/core/gate/evaluateRules.test.ts +40 -0
- package/core/gate/evaluateRules.ts +7 -1
- package/core/rules/Consequence.ts +1 -0
- package/docs/CONFIGURATION.md +50 -0
- package/docs/INSTALLATION.md +38 -11
- package/docs/MCP_SERVERS.md +1 -1
- package/docs/README.md +1 -0
- package/docs/RELEASE_NOTES.md +44 -0
- package/docs/USAGE.md +191 -9
- package/docs/registro-maestro-de-seguimiento.md +2 -2
- package/docs/seguimiento-activo-pumuki-saas-supermercados.md +1592 -1
- package/docs/validation/README.md +2 -1
- package/docs/validation/ast-intelligence-roadmap.md +96 -0
- package/integrations/config/skillsCustomRules.ts +14 -0
- package/integrations/config/skillsDetectorRegistry.ts +11 -1
- package/integrations/config/skillsLock.ts +30 -0
- package/integrations/config/skillsMarkdownRules.ts +14 -3
- package/integrations/config/skillsRuleSet.ts +25 -3
- package/integrations/evidence/readEvidence.test.ts +3 -2
- package/integrations/evidence/readEvidence.ts +14 -4
- package/integrations/evidence/repoState.ts +10 -2
- package/integrations/evidence/schema.test.ts +3 -2
- package/integrations/evidence/schema.ts +3 -0
- package/integrations/evidence/writeEvidence.test.ts +3 -2
- package/integrations/gate/evaluateAiGate.ts +511 -2
- package/integrations/git/GitService.ts +5 -1
- package/integrations/git/astIntelligenceDualValidation.ts +275 -0
- package/integrations/git/gitAtomicity.ts +42 -9
- package/integrations/git/resolveGitRefs.ts +37 -0
- package/integrations/git/runPlatformGate.ts +228 -1
- package/integrations/git/runPlatformGateEvaluation.ts +4 -0
- package/integrations/git/stageRunners.ts +116 -2
- package/integrations/lifecycle/cli.ts +759 -22
- package/integrations/lifecycle/doctor.ts +62 -0
- package/integrations/lifecycle/index.ts +1 -0
- package/integrations/lifecycle/packageInfo.ts +25 -3
- package/integrations/lifecycle/policyReconcile.ts +304 -0
- package/integrations/lifecycle/preWriteAutomation.ts +42 -2
- package/integrations/lifecycle/watch.ts +365 -0
- package/integrations/mcp/aiGateCheck.ts +59 -2
- package/integrations/mcp/autoExecuteAiStart.ts +25 -1
- package/integrations/mcp/preFlightCheck.ts +13 -0
- package/integrations/sdd/evidenceScaffold.ts +223 -0
- package/integrations/sdd/index.ts +2 -0
- package/integrations/sdd/stateSync.ts +400 -0
- package/integrations/sdd/syncDocs.ts +97 -2
- package/package.json +4 -1
- package/scripts/backlog-action-reasons-lib.ts +38 -0
- package/scripts/backlog-id-issue-map-lib.ts +69 -0
- package/scripts/backlog-json-contract-lib.ts +3 -0
- package/scripts/framework-menu-consumer-preflight-lib.ts +6 -0
- package/scripts/package-install-smoke-command-resolution-lib.ts +64 -0
- package/scripts/package-install-smoke-consumer-npm-lib.ts +43 -0
- package/scripts/package-install-smoke-consumer-repo-setup-lib.ts +2 -0
- package/scripts/package-install-smoke-execution-steps-lib.ts +27 -9
- package/scripts/package-install-smoke-lifecycle-lib.ts +15 -4
- package/scripts/package-install-smoke-workspace-factory-lib.ts +4 -1
- package/scripts/reconcile-consumer-backlog-issues-lib.ts +651 -0
- package/scripts/reconcile-consumer-backlog-issues.ts +348 -0
- package/scripts/watch-consumer-backlog-lib.ts +465 -0
- package/scripts/watch-consumer-backlog.ts +326 -0
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { setTimeout as sleepTimer } from 'node:timers/promises';
|
|
3
|
+
import { isSeverityAtLeast, type Severity } from '../../core/rules/Severity';
|
|
4
|
+
import type { GateScope } from '../git/runPlatformGateFacts';
|
|
5
|
+
import { runPlatformGate } from '../git/runPlatformGate';
|
|
6
|
+
import { resolvePolicyForStage, type ResolvedStagePolicy } from '../gate/stagePolicies';
|
|
7
|
+
import { readEvidence } from '../evidence/readEvidence';
|
|
8
|
+
import type { AiEvidenceV2_1, SnapshotFinding } from '../evidence/schema';
|
|
9
|
+
import { GitService } from '../git/GitService';
|
|
10
|
+
import {
|
|
11
|
+
emitAuditSummaryNotificationFromEvidence,
|
|
12
|
+
emitGateBlockedNotification,
|
|
13
|
+
} from '../notifications/emitAuditSummaryNotification';
|
|
14
|
+
|
|
15
|
+
export type LifecycleWatchStage = 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
|
|
16
|
+
export type LifecycleWatchScope = 'workingTree' | 'staged' | 'repoAndStaged' | 'repo';
|
|
17
|
+
export type LifecycleWatchSeverityThreshold = 'critical' | 'high' | 'medium' | 'low';
|
|
18
|
+
|
|
19
|
+
export type LifecycleWatchTickResult = {
|
|
20
|
+
tick: number;
|
|
21
|
+
changed: boolean;
|
|
22
|
+
evaluated: boolean;
|
|
23
|
+
stage: LifecycleWatchStage;
|
|
24
|
+
scope: LifecycleWatchScope;
|
|
25
|
+
gateExitCode: number | null;
|
|
26
|
+
gateOutcome: 'BLOCK' | 'WARN' | 'ALLOW' | 'NO_EVIDENCE';
|
|
27
|
+
threshold: LifecycleWatchSeverityThreshold;
|
|
28
|
+
thresholdSeverity: Severity;
|
|
29
|
+
totalFindings: number;
|
|
30
|
+
findingsAtOrAboveThreshold: number;
|
|
31
|
+
topCodes: ReadonlyArray<string>;
|
|
32
|
+
notification:
|
|
33
|
+
| 'sent'
|
|
34
|
+
| 'disabled'
|
|
35
|
+
| 'below-threshold'
|
|
36
|
+
| 'suppressed-cooldown'
|
|
37
|
+
| 'suppressed-duplicate'
|
|
38
|
+
| 'not-delivered'
|
|
39
|
+
| 'not-evaluated';
|
|
40
|
+
notificationDeliveryReason?: string;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type LifecycleWatchResult = {
|
|
44
|
+
command: 'pumuki watch';
|
|
45
|
+
repoRoot: string;
|
|
46
|
+
stage: LifecycleWatchStage;
|
|
47
|
+
scope: LifecycleWatchScope;
|
|
48
|
+
intervalMs: number;
|
|
49
|
+
notifyCooldownMs: number;
|
|
50
|
+
severityThreshold: LifecycleWatchSeverityThreshold;
|
|
51
|
+
notifyEnabled: boolean;
|
|
52
|
+
ticks: number;
|
|
53
|
+
evaluations: number;
|
|
54
|
+
notificationsSent: number;
|
|
55
|
+
notificationsSuppressed: number;
|
|
56
|
+
lastTick: LifecycleWatchTickResult;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
type LifecycleWatchDependencies = {
|
|
60
|
+
resolveRepoRoot: () => string;
|
|
61
|
+
readChangeToken: (repoRoot: string) => string;
|
|
62
|
+
resolvePolicyForStage: (stage: LifecycleWatchStage) => ResolvedStagePolicy;
|
|
63
|
+
runPlatformGate: typeof runPlatformGate;
|
|
64
|
+
readEvidence: (repoRoot: string) => AiEvidenceV2_1 | undefined;
|
|
65
|
+
emitAuditSummaryNotificationFromEvidence: typeof emitAuditSummaryNotificationFromEvidence;
|
|
66
|
+
emitGateBlockedNotification: typeof emitGateBlockedNotification;
|
|
67
|
+
nowMs: () => number;
|
|
68
|
+
sleep: (ms: number) => Promise<void>;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const defaultGitService = new GitService();
|
|
72
|
+
|
|
73
|
+
const defaultDependencies: LifecycleWatchDependencies = {
|
|
74
|
+
resolveRepoRoot: () => defaultGitService.resolveRepoRoot(),
|
|
75
|
+
readChangeToken: (repoRoot) =>
|
|
76
|
+
defaultGitService.runGit(['status', '--porcelain=v1', '--untracked-files=all'], repoRoot),
|
|
77
|
+
resolvePolicyForStage: (stage) => resolvePolicyForStage(stage),
|
|
78
|
+
runPlatformGate,
|
|
79
|
+
readEvidence,
|
|
80
|
+
emitAuditSummaryNotificationFromEvidence,
|
|
81
|
+
emitGateBlockedNotification,
|
|
82
|
+
nowMs: () => Date.now(),
|
|
83
|
+
sleep: async (ms) => {
|
|
84
|
+
await sleepTimer(ms);
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const BLOCKED_REMEDIATION_BY_CODE: Readonly<Record<string, string>> = {
|
|
89
|
+
EVIDENCE_MISSING: 'Genera evidencia y vuelve a ejecutar el gate.',
|
|
90
|
+
EVIDENCE_INVALID: 'Regenera la evidencia antes de reintentar.',
|
|
91
|
+
EVIDENCE_CHAIN_INVALID: 'Regenera evidencia para reparar la cadena criptográfica.',
|
|
92
|
+
EVIDENCE_STALE: 'Refresca la evidencia y vuelve a intentarlo.',
|
|
93
|
+
EVIDENCE_BRANCH_MISMATCH: 'Regenera evidencia en esta rama y reintenta.',
|
|
94
|
+
EVIDENCE_REPO_ROOT_MISMATCH: 'Regenera evidencia desde este repositorio.',
|
|
95
|
+
PRE_PUSH_UPSTREAM_MISSING: 'Ejecuta: git push --set-upstream origin <branch>.',
|
|
96
|
+
SDD_SESSION_MISSING: 'Abre sesión SDD y vuelve a intentar.',
|
|
97
|
+
SDD_SESSION_INVALID: 'Refresca la sesión SDD y vuelve a intentar.',
|
|
98
|
+
OPENSPEC_MISSING: 'Instala OpenSpec y reintenta la validación.',
|
|
99
|
+
MCP_ENTERPRISE_RECEIPT_MISSING: 'Genera el receipt enterprise de MCP antes de continuar.',
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const THRESHOLD_TO_SEVERITY: Record<LifecycleWatchSeverityThreshold, Severity> = {
|
|
103
|
+
critical: 'CRITICAL',
|
|
104
|
+
high: 'ERROR',
|
|
105
|
+
medium: 'WARN',
|
|
106
|
+
low: 'INFO',
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const toGateScope = (scope: LifecycleWatchScope): GateScope => {
|
|
110
|
+
if (scope === 'staged') {
|
|
111
|
+
return { kind: 'staged' };
|
|
112
|
+
}
|
|
113
|
+
if (scope === 'repoAndStaged') {
|
|
114
|
+
return { kind: 'repoAndStaged' };
|
|
115
|
+
}
|
|
116
|
+
if (scope === 'repo') {
|
|
117
|
+
return { kind: 'repo' };
|
|
118
|
+
}
|
|
119
|
+
return { kind: 'workingTree' };
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const toNotificationSignature = (params: {
|
|
123
|
+
stage: LifecycleWatchStage;
|
|
124
|
+
gateOutcome: LifecycleWatchTickResult['gateOutcome'];
|
|
125
|
+
topCodes: ReadonlyArray<string>;
|
|
126
|
+
matchedFindings: number;
|
|
127
|
+
}): string =>
|
|
128
|
+
createHash('sha1')
|
|
129
|
+
.update(
|
|
130
|
+
`${params.stage}|${params.gateOutcome}|${params.topCodes.join(',')}|${params.matchedFindings}`,
|
|
131
|
+
'utf8'
|
|
132
|
+
)
|
|
133
|
+
.digest('hex');
|
|
134
|
+
|
|
135
|
+
const toTopCodes = (findings: ReadonlyArray<SnapshotFinding>): ReadonlyArray<string> => {
|
|
136
|
+
const seen = new Set<string>();
|
|
137
|
+
const codes: string[] = [];
|
|
138
|
+
for (const finding of findings) {
|
|
139
|
+
if (seen.has(finding.code)) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
seen.add(finding.code);
|
|
143
|
+
codes.push(finding.code);
|
|
144
|
+
if (codes.length >= 3) {
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return codes;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const toFirstCause = (params: {
|
|
152
|
+
evidence: AiEvidenceV2_1 | undefined;
|
|
153
|
+
matchedFindings: ReadonlyArray<SnapshotFinding>;
|
|
154
|
+
}): { code: string; message: string; remediation: string } => {
|
|
155
|
+
const firstFinding = params.matchedFindings[0];
|
|
156
|
+
const firstViolation = params.evidence?.ai_gate.violations[0];
|
|
157
|
+
const code = firstFinding?.code ?? firstViolation?.code ?? 'WATCH_GATE_BLOCKED';
|
|
158
|
+
const message =
|
|
159
|
+
firstFinding?.message ??
|
|
160
|
+
firstViolation?.message ??
|
|
161
|
+
`Watch gate bloqueado (${code}).`;
|
|
162
|
+
const remediation =
|
|
163
|
+
BLOCKED_REMEDIATION_BY_CODE[code] ??
|
|
164
|
+
'Corrige el bloqueo indicado y vuelve a evaluar.';
|
|
165
|
+
return {
|
|
166
|
+
code,
|
|
167
|
+
message,
|
|
168
|
+
remediation,
|
|
169
|
+
};
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
export const runLifecycleWatch = async (
|
|
173
|
+
params?: {
|
|
174
|
+
repoRoot?: string;
|
|
175
|
+
stage?: LifecycleWatchStage;
|
|
176
|
+
scope?: LifecycleWatchScope;
|
|
177
|
+
intervalMs?: number;
|
|
178
|
+
notifyCooldownMs?: number;
|
|
179
|
+
severityThreshold?: LifecycleWatchSeverityThreshold;
|
|
180
|
+
notifyEnabled?: boolean;
|
|
181
|
+
maxIterations?: number;
|
|
182
|
+
onTick?: (tick: LifecycleWatchTickResult) => void;
|
|
183
|
+
},
|
|
184
|
+
dependencies: Partial<LifecycleWatchDependencies> = {}
|
|
185
|
+
): Promise<LifecycleWatchResult> => {
|
|
186
|
+
const activeDependencies: LifecycleWatchDependencies = {
|
|
187
|
+
...defaultDependencies,
|
|
188
|
+
...dependencies,
|
|
189
|
+
};
|
|
190
|
+
const repoRoot = params?.repoRoot ?? activeDependencies.resolveRepoRoot();
|
|
191
|
+
const stage = params?.stage ?? 'PRE_COMMIT';
|
|
192
|
+
const scope = params?.scope ?? 'workingTree';
|
|
193
|
+
const intervalMs = Math.max(250, Math.trunc(params?.intervalMs ?? 3000));
|
|
194
|
+
const notifyCooldownMs = Math.max(0, Math.trunc(params?.notifyCooldownMs ?? 30_000));
|
|
195
|
+
const severityThreshold = params?.severityThreshold ?? 'high';
|
|
196
|
+
const thresholdSeverity = THRESHOLD_TO_SEVERITY[severityThreshold];
|
|
197
|
+
const notifyEnabled = params?.notifyEnabled !== false;
|
|
198
|
+
const maxIterations =
|
|
199
|
+
typeof params?.maxIterations === 'number' && params.maxIterations > 0
|
|
200
|
+
? Math.trunc(params.maxIterations)
|
|
201
|
+
: undefined;
|
|
202
|
+
|
|
203
|
+
let ticks = 0;
|
|
204
|
+
let evaluations = 0;
|
|
205
|
+
let notificationsSent = 0;
|
|
206
|
+
let notificationsSuppressed = 0;
|
|
207
|
+
let previousChangeToken: string | undefined;
|
|
208
|
+
let lastNotificationSignature: string | undefined;
|
|
209
|
+
let lastNotificationAtMs = 0;
|
|
210
|
+
let lastTick: LifecycleWatchTickResult = {
|
|
211
|
+
tick: 0,
|
|
212
|
+
changed: false,
|
|
213
|
+
evaluated: false,
|
|
214
|
+
stage,
|
|
215
|
+
scope,
|
|
216
|
+
gateExitCode: null,
|
|
217
|
+
gateOutcome: 'NO_EVIDENCE',
|
|
218
|
+
threshold: severityThreshold,
|
|
219
|
+
thresholdSeverity,
|
|
220
|
+
totalFindings: 0,
|
|
221
|
+
findingsAtOrAboveThreshold: 0,
|
|
222
|
+
topCodes: [],
|
|
223
|
+
notification: 'not-evaluated',
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
while (true) {
|
|
227
|
+
ticks += 1;
|
|
228
|
+
const changeToken = activeDependencies.readChangeToken(repoRoot);
|
|
229
|
+
const changed = previousChangeToken !== changeToken;
|
|
230
|
+
const shouldEvaluate = ticks === 1 || changed;
|
|
231
|
+
|
|
232
|
+
if (!shouldEvaluate) {
|
|
233
|
+
lastTick = {
|
|
234
|
+
...lastTick,
|
|
235
|
+
tick: ticks,
|
|
236
|
+
changed,
|
|
237
|
+
evaluated: false,
|
|
238
|
+
notification: 'not-evaluated',
|
|
239
|
+
};
|
|
240
|
+
params?.onTick?.(lastTick);
|
|
241
|
+
} else {
|
|
242
|
+
evaluations += 1;
|
|
243
|
+
const resolvedPolicy = activeDependencies.resolvePolicyForStage(stage);
|
|
244
|
+
const gateExitCode = await activeDependencies.runPlatformGate({
|
|
245
|
+
policy: resolvedPolicy.policy,
|
|
246
|
+
policyTrace: resolvedPolicy.trace,
|
|
247
|
+
scope: toGateScope(scope),
|
|
248
|
+
});
|
|
249
|
+
const evidence = activeDependencies.readEvidence(repoRoot);
|
|
250
|
+
const allFindings = evidence?.snapshot.findings ?? [];
|
|
251
|
+
const matchedFindings = allFindings.filter((finding) =>
|
|
252
|
+
isSeverityAtLeast(finding.severity, thresholdSeverity)
|
|
253
|
+
);
|
|
254
|
+
const rawGateOutcome =
|
|
255
|
+
evidence?.snapshot.outcome ??
|
|
256
|
+
(gateExitCode !== 0 ? 'BLOCK' : 'NO_EVIDENCE');
|
|
257
|
+
const gateOutcome =
|
|
258
|
+
rawGateOutcome === 'PASS' ? 'ALLOW' : rawGateOutcome;
|
|
259
|
+
const topCodes = toTopCodes(matchedFindings);
|
|
260
|
+
const signature = toNotificationSignature({
|
|
261
|
+
stage,
|
|
262
|
+
gateOutcome,
|
|
263
|
+
topCodes,
|
|
264
|
+
matchedFindings: matchedFindings.length,
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
let notification: LifecycleWatchTickResult['notification'] = 'below-threshold';
|
|
268
|
+
let notificationDeliveryReason: string | undefined;
|
|
269
|
+
|
|
270
|
+
if (!notifyEnabled) {
|
|
271
|
+
notification = 'disabled';
|
|
272
|
+
} else if (matchedFindings.length === 0) {
|
|
273
|
+
notification = 'below-threshold';
|
|
274
|
+
} else {
|
|
275
|
+
const nowMs = activeDependencies.nowMs();
|
|
276
|
+
const elapsed = nowMs - lastNotificationAtMs;
|
|
277
|
+
if (
|
|
278
|
+
notifyCooldownMs > 0 &&
|
|
279
|
+
typeof lastNotificationSignature === 'string' &&
|
|
280
|
+
elapsed < notifyCooldownMs
|
|
281
|
+
) {
|
|
282
|
+
notification =
|
|
283
|
+
signature === lastNotificationSignature
|
|
284
|
+
? 'suppressed-duplicate'
|
|
285
|
+
: 'suppressed-cooldown';
|
|
286
|
+
notificationsSuppressed += 1;
|
|
287
|
+
} else {
|
|
288
|
+
let notificationResult;
|
|
289
|
+
if (gateExitCode !== 0 || evidence?.ai_gate.status === 'BLOCKED') {
|
|
290
|
+
const cause = toFirstCause({
|
|
291
|
+
evidence,
|
|
292
|
+
matchedFindings,
|
|
293
|
+
});
|
|
294
|
+
notificationResult = activeDependencies.emitGateBlockedNotification({
|
|
295
|
+
repoRoot,
|
|
296
|
+
stage,
|
|
297
|
+
totalViolations: matchedFindings.length,
|
|
298
|
+
causeCode: cause.code,
|
|
299
|
+
causeMessage: cause.message,
|
|
300
|
+
remediation: cause.remediation,
|
|
301
|
+
});
|
|
302
|
+
} else {
|
|
303
|
+
notificationResult = activeDependencies.emitAuditSummaryNotificationFromEvidence({
|
|
304
|
+
repoRoot,
|
|
305
|
+
stage,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (notificationResult.delivered) {
|
|
310
|
+
notification = 'sent';
|
|
311
|
+
notificationsSent += 1;
|
|
312
|
+
} else {
|
|
313
|
+
notification = 'not-delivered';
|
|
314
|
+
notificationDeliveryReason = notificationResult.reason;
|
|
315
|
+
notificationsSuppressed += 1;
|
|
316
|
+
}
|
|
317
|
+
lastNotificationAtMs = nowMs;
|
|
318
|
+
lastNotificationSignature = signature;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
lastTick = {
|
|
323
|
+
tick: ticks,
|
|
324
|
+
changed,
|
|
325
|
+
evaluated: true,
|
|
326
|
+
stage,
|
|
327
|
+
scope,
|
|
328
|
+
gateExitCode,
|
|
329
|
+
gateOutcome,
|
|
330
|
+
threshold: severityThreshold,
|
|
331
|
+
thresholdSeverity,
|
|
332
|
+
totalFindings: allFindings.length,
|
|
333
|
+
findingsAtOrAboveThreshold: matchedFindings.length,
|
|
334
|
+
topCodes,
|
|
335
|
+
notification,
|
|
336
|
+
...(notificationDeliveryReason
|
|
337
|
+
? { notificationDeliveryReason }
|
|
338
|
+
: {}),
|
|
339
|
+
};
|
|
340
|
+
params?.onTick?.(lastTick);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
previousChangeToken = changeToken;
|
|
344
|
+
|
|
345
|
+
if (maxIterations && ticks >= maxIterations) {
|
|
346
|
+
return {
|
|
347
|
+
command: 'pumuki watch',
|
|
348
|
+
repoRoot,
|
|
349
|
+
stage,
|
|
350
|
+
scope,
|
|
351
|
+
intervalMs,
|
|
352
|
+
notifyCooldownMs,
|
|
353
|
+
severityThreshold,
|
|
354
|
+
notifyEnabled,
|
|
355
|
+
ticks,
|
|
356
|
+
evaluations,
|
|
357
|
+
notificationsSent,
|
|
358
|
+
notificationsSuppressed,
|
|
359
|
+
lastTick,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
await activeDependencies.sleep(intervalMs);
|
|
364
|
+
}
|
|
365
|
+
};
|
|
@@ -13,7 +13,58 @@ export type EnterpriseAiGateCheckResult = {
|
|
|
13
13
|
violations: ReturnType<typeof evaluateAiGate>['violations'];
|
|
14
14
|
evidence: ReturnType<typeof evaluateAiGate>['evidence'];
|
|
15
15
|
mcp_receipt: ReturnType<typeof evaluateAiGate>['mcp_receipt'];
|
|
16
|
+
skills_contract: ReturnType<typeof evaluateAiGate>['skills_contract'];
|
|
16
17
|
repo_state: ReturnType<typeof evaluateAiGate>['repo_state'];
|
|
18
|
+
consistency_hint?: {
|
|
19
|
+
comparable_with_hook_runner: boolean;
|
|
20
|
+
reason_code: 'HOOK_RUNNER_CAN_REFRESH_EVIDENCE' | null;
|
|
21
|
+
message: string;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const HOOK_STAGE_SET = new Set<AiGateStage>(['PRE_COMMIT', 'PRE_PUSH', 'CI']);
|
|
27
|
+
|
|
28
|
+
const isHookRefreshableEvidenceCode = (code: string): boolean =>
|
|
29
|
+
code.startsWith('EVIDENCE_');
|
|
30
|
+
|
|
31
|
+
type AiGateCheckDependencies = {
|
|
32
|
+
evaluateAiGate: typeof evaluateAiGate;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const defaultDependencies: AiGateCheckDependencies = {
|
|
36
|
+
evaluateAiGate,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const buildConsistencyHint = (
|
|
40
|
+
evaluation: ReturnType<typeof evaluateAiGate>
|
|
41
|
+
): EnterpriseAiGateCheckResult['result']['consistency_hint'] => {
|
|
42
|
+
if (!HOOK_STAGE_SET.has(evaluation.stage)) {
|
|
43
|
+
return {
|
|
44
|
+
comparable_with_hook_runner: true,
|
|
45
|
+
reason_code: null,
|
|
46
|
+
message: 'Stage is directly comparable with ai_gate_check semantics.',
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const hasRefreshableEvidenceViolation = evaluation.violations.some((violation) =>
|
|
51
|
+
isHookRefreshableEvidenceCode(violation.code)
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
if (!evaluation.allowed && hasRefreshableEvidenceViolation) {
|
|
55
|
+
return {
|
|
56
|
+
comparable_with_hook_runner: false,
|
|
57
|
+
reason_code: 'HOOK_RUNNER_CAN_REFRESH_EVIDENCE',
|
|
58
|
+
message:
|
|
59
|
+
'ai_gate_check is blocking on evidence integrity/freshness. ' +
|
|
60
|
+
'Hook stage runners may regenerate evidence before final verdict.',
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
comparable_with_hook_runner: true,
|
|
66
|
+
reason_code: null,
|
|
67
|
+
message: 'ai_gate_check verdict is directly comparable with hook stage runner output.',
|
|
17
68
|
};
|
|
18
69
|
};
|
|
19
70
|
|
|
@@ -21,8 +72,12 @@ export const runEnterpriseAiGateCheck = (params: {
|
|
|
21
72
|
repoRoot: string;
|
|
22
73
|
stage: AiGateStage;
|
|
23
74
|
requireMcpReceipt?: boolean;
|
|
24
|
-
}): EnterpriseAiGateCheckResult => {
|
|
25
|
-
const
|
|
75
|
+
}, dependencies: Partial<AiGateCheckDependencies> = {}): EnterpriseAiGateCheckResult => {
|
|
76
|
+
const activeDependencies: AiGateCheckDependencies = {
|
|
77
|
+
...defaultDependencies,
|
|
78
|
+
...dependencies,
|
|
79
|
+
};
|
|
80
|
+
const evaluation = activeDependencies.evaluateAiGate({
|
|
26
81
|
repoRoot: params.repoRoot,
|
|
27
82
|
stage: params.stage,
|
|
28
83
|
requireMcpReceipt: params.requireMcpReceipt ?? false,
|
|
@@ -41,7 +96,9 @@ export const runEnterpriseAiGateCheck = (params: {
|
|
|
41
96
|
violations: evaluation.violations,
|
|
42
97
|
evidence: evaluation.evidence,
|
|
43
98
|
mcp_receipt: evaluation.mcp_receipt,
|
|
99
|
+
skills_contract: evaluation.skills_contract,
|
|
44
100
|
repo_state: evaluation.repo_state,
|
|
101
|
+
consistency_hint: buildConsistencyHint(evaluation),
|
|
45
102
|
},
|
|
46
103
|
};
|
|
47
104
|
};
|
|
@@ -9,7 +9,14 @@ type AutoExecuteNextAction = {
|
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
const isEvidenceCode = (code: string): boolean =>
|
|
12
|
-
code === 'EVIDENCE_MISSING'
|
|
12
|
+
code === 'EVIDENCE_MISSING'
|
|
13
|
+
|| code === 'EVIDENCE_INVALID'
|
|
14
|
+
|| code === 'EVIDENCE_STALE'
|
|
15
|
+
|| code === 'EVIDENCE_SKILLS_CONTRACT_INCOMPLETE'
|
|
16
|
+
|| code === 'EVIDENCE_PREWRITE_WORKTREE_OVER_LIMIT'
|
|
17
|
+
|| code === 'EVIDENCE_PREWRITE_WORKTREE_WARN'
|
|
18
|
+
|| code === 'EVIDENCE_PLATFORM_SKILLS_SCOPE_INCOMPLETE'
|
|
19
|
+
|| code === 'EVIDENCE_PLATFORM_SKILLS_BUNDLES_MISSING';
|
|
13
20
|
|
|
14
21
|
const confidenceFromViolation = (violationCode: string | null): number => {
|
|
15
22
|
if (!violationCode) {
|
|
@@ -40,6 +47,23 @@ const nextActionFromViolation = (violation: AiGateViolation | undefined): AutoEx
|
|
|
40
47
|
message: 'Refresca evidencia y vuelve a evaluar PRE_WRITE.',
|
|
41
48
|
command: 'npx --yes --package pumuki@latest pumuki sdd validate --stage=PRE_WRITE --json',
|
|
42
49
|
};
|
|
50
|
+
case 'EVIDENCE_PLATFORM_SKILLS_SCOPE_INCOMPLETE':
|
|
51
|
+
case 'EVIDENCE_PLATFORM_SKILLS_BUNDLES_MISSING':
|
|
52
|
+
case 'EVIDENCE_SKILLS_CONTRACT_INCOMPLETE':
|
|
53
|
+
return {
|
|
54
|
+
kind: 'run_command',
|
|
55
|
+
message:
|
|
56
|
+
'Completa cobertura de skills por plataforma (prefijos + bundles) y revalida PRE_WRITE.',
|
|
57
|
+
command: 'npx --yes --package pumuki@latest pumuki sdd validate --stage=PRE_WRITE --json',
|
|
58
|
+
};
|
|
59
|
+
case 'EVIDENCE_PREWRITE_WORKTREE_OVER_LIMIT':
|
|
60
|
+
case 'EVIDENCE_PREWRITE_WORKTREE_WARN':
|
|
61
|
+
return {
|
|
62
|
+
kind: 'run_command',
|
|
63
|
+
message:
|
|
64
|
+
'Particiona el worktree en slices atómicos y revalida PRE_WRITE para continuar sin fricción.',
|
|
65
|
+
command: 'git status --short && git add -p',
|
|
66
|
+
};
|
|
43
67
|
case 'GITFLOW_PROTECTED_BRANCH':
|
|
44
68
|
return {
|
|
45
69
|
kind: 'run_command',
|
|
@@ -3,6 +3,7 @@ import { evaluateAiGate, type AiGateStage, type AiGateViolation } from '../gate/
|
|
|
3
3
|
const ACTIONABLE_HINTS_BY_CODE: Readonly<Record<string, string>> = {
|
|
4
4
|
EVIDENCE_MISSING: 'Ejecuta una auditoría (1/2/3/4) para regenerar .ai_evidence.json.',
|
|
5
5
|
EVIDENCE_INVALID: 'Regenera .ai_evidence.json desde una opción de auditoría.',
|
|
6
|
+
EVIDENCE_INTEGRITY_MISSING: 'Refresca evidencia para regenerar metadatos de integridad.',
|
|
6
7
|
EVIDENCE_STALE: 'Refresca evidencia antes de continuar con commit/push.',
|
|
7
8
|
EVIDENCE_TIMESTAMP_INVALID: 'Regenera evidencia para obtener un timestamp válido.',
|
|
8
9
|
EVIDENCE_GATE_BLOCKED: 'Corrige primero las violaciones bloqueantes y vuelve a auditar.',
|
|
@@ -14,6 +15,16 @@ const ACTIONABLE_HINTS_BY_CODE: Readonly<Record<string, string>> = {
|
|
|
14
15
|
EVIDENCE_RULES_COVERAGE_STAGE_MISMATCH: 'Reanuda auditoría en el stage correcto.',
|
|
15
16
|
EVIDENCE_RULES_COVERAGE_INCOMPLETE:
|
|
16
17
|
'Asegura unevaluated=0 y coverage_ratio=1 antes de continuar.',
|
|
18
|
+
EVIDENCE_PLATFORM_SKILLS_SCOPE_INCOMPLETE:
|
|
19
|
+
'Activa/evalúa reglas skills.<plataforma>. en la evidencia PRE_WRITE y vuelve a validar.',
|
|
20
|
+
EVIDENCE_PLATFORM_SKILLS_BUNDLES_MISSING:
|
|
21
|
+
'Carga los bundles de skills requeridos por plataforma detectada y regenera evidencia.',
|
|
22
|
+
EVIDENCE_SKILLS_CONTRACT_INCOMPLETE:
|
|
23
|
+
'Completa el contrato skills/policy para el stage solicitado y vuelve a validar.',
|
|
24
|
+
EVIDENCE_PREWRITE_WORKTREE_OVER_LIMIT:
|
|
25
|
+
'Reduce el worktree pendiente en slices atómicos y vuelve a ejecutar PRE_WRITE.',
|
|
26
|
+
EVIDENCE_PREWRITE_WORKTREE_WARN:
|
|
27
|
+
'Conviene particionar cambios ahora para evitar bloqueo tardío en commit/push.',
|
|
17
28
|
EVIDENCE_UNSUPPORTED_AUTO_RULES:
|
|
18
29
|
'Mapea todas las reglas AUTO a detectores AST antes de continuar.',
|
|
19
30
|
EVIDENCE_TIMESTAMP_FUTURE: 'Corrige la hora del sistema y regenera evidencia.',
|
|
@@ -65,6 +76,7 @@ export type EnterprisePreFlightCheckResult = {
|
|
|
65
76
|
violations: ReturnType<typeof evaluateAiGate>['violations'];
|
|
66
77
|
evidence: ReturnType<typeof evaluateAiGate>['evidence'];
|
|
67
78
|
mcp_receipt: ReturnType<typeof evaluateAiGate>['mcp_receipt'];
|
|
79
|
+
skills_contract: ReturnType<typeof evaluateAiGate>['skills_contract'];
|
|
68
80
|
repo_state: ReturnType<typeof evaluateAiGate>['repo_state'];
|
|
69
81
|
hints: ReadonlyArray<string>;
|
|
70
82
|
};
|
|
@@ -101,6 +113,7 @@ export const runEnterprisePreFlightCheck = (params: {
|
|
|
101
113
|
violations: evaluation.violations,
|
|
102
114
|
evidence: evaluation.evidence,
|
|
103
115
|
mcp_receipt: evaluation.mcp_receipt,
|
|
116
|
+
skills_contract: evaluation.skills_contract,
|
|
104
117
|
repo_state: evaluation.repo_state,
|
|
105
118
|
hints,
|
|
106
119
|
},
|