pumuki 6.3.37 → 6.3.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/VERSION +1 -1
- package/docs/README.md +1 -1
- package/docs/RELEASE_NOTES.md +53 -0
- package/docs/registro-maestro-de-seguimiento.md +7 -6
- package/docs/seguimiento-activo-pumuki-saas-supermercados.md +129 -0
- package/docs/seguimiento-completo-validacion-ruralgo-03-03-2026.md +27 -3
- package/integrations/gate/evaluateAiGate.ts +20 -2
- package/integrations/git/GitService.ts +28 -1
- package/integrations/git/getCommitRangeFacts.ts +35 -5
- package/integrations/git/gitAtomicity.ts +274 -0
- package/integrations/git/runPlatformGate.ts +86 -0
- package/integrations/git/stageRunners.ts +193 -4
- package/integrations/lifecycle/adapter.templates.json +20 -20
- package/integrations/lifecycle/cli.ts +50 -1
- package/integrations/lifecycle/doctor.ts +17 -4
- package/integrations/lifecycle/hookBlock.ts +37 -11
- package/integrations/lifecycle/hookManager.ts +85 -1
- package/integrations/lifecycle/packageInfo.ts +27 -1
- package/integrations/lifecycle/status.ts +7 -2
- package/integrations/mcp/autoExecuteAiStart.ts +116 -0
- package/integrations/mcp/enterpriseServer.ts +56 -0
- package/integrations/mcp/preFlightCheck.ts +108 -0
- package/integrations/notifications/emitAuditSummaryNotification.ts +28 -0
- package/package.json +1 -1
- package/scripts/framework-menu-consumer-preflight-lib.ts +11 -0
- package/scripts/framework-menu-evidence-summary-lib.ts +1 -1
- package/scripts/framework-menu-system-notifications-lib.ts +281 -17
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { GitService, type IGitService } from './GitService';
|
|
4
|
+
|
|
5
|
+
export type GitAtomicityStage = 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
|
|
6
|
+
|
|
7
|
+
export type GitAtomicityViolation = {
|
|
8
|
+
code:
|
|
9
|
+
| 'GIT_ATOMICITY_TOO_MANY_FILES'
|
|
10
|
+
| 'GIT_ATOMICITY_TOO_MANY_SCOPES'
|
|
11
|
+
| 'GIT_ATOMICITY_COMMIT_MESSAGE_TRACEABILITY';
|
|
12
|
+
message: string;
|
|
13
|
+
remediation: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type GitAtomicityEvaluation = {
|
|
17
|
+
enabled: boolean;
|
|
18
|
+
allowed: boolean;
|
|
19
|
+
violations: ReadonlyArray<GitAtomicityViolation>;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type GitAtomicityConfig = {
|
|
23
|
+
enabled: boolean;
|
|
24
|
+
maxFiles: number;
|
|
25
|
+
maxScopes: number;
|
|
26
|
+
enforceCommitMessagePattern: boolean;
|
|
27
|
+
commitMessagePattern: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const ATOMICITY_CONFIG_FILE = '.pumuki/git-atomicity.json';
|
|
31
|
+
const DEFAULT_COMMIT_PATTERN =
|
|
32
|
+
'^(feat|fix|chore|refactor|docs|test|perf|build|ci|revert)(\\([^)]+\\))?:\\s.+$';
|
|
33
|
+
|
|
34
|
+
const defaultConfig: GitAtomicityConfig = {
|
|
35
|
+
enabled: true,
|
|
36
|
+
maxFiles: 25,
|
|
37
|
+
maxScopes: 2,
|
|
38
|
+
enforceCommitMessagePattern: true,
|
|
39
|
+
commitMessagePattern: DEFAULT_COMMIT_PATTERN,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const parseBooleanEnv = (value: string | undefined): boolean | undefined => {
|
|
43
|
+
if (typeof value !== 'string') {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
const normalized = value.trim().toLowerCase();
|
|
47
|
+
if (normalized.length === 0) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
if (normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on') {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
if (normalized === '0' || normalized === 'false' || normalized === 'no' || normalized === 'off') {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const parsePositiveIntegerEnv = (value: string | undefined): number | undefined => {
|
|
60
|
+
if (typeof value !== 'string') {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
const parsed = Number.parseInt(value.trim(), 10);
|
|
64
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
return parsed;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
71
|
+
typeof value === 'object' && value !== null;
|
|
72
|
+
|
|
73
|
+
const isNonEmptyString = (value: unknown): value is string =>
|
|
74
|
+
typeof value === 'string' && value.trim().length > 0;
|
|
75
|
+
|
|
76
|
+
const parseFileConfig = (repoRoot: string): Partial<GitAtomicityConfig> | undefined => {
|
|
77
|
+
const filePath = resolve(repoRoot, ATOMICITY_CONFIG_FILE);
|
|
78
|
+
if (!existsSync(filePath)) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const parsed: unknown = JSON.parse(readFileSync(filePath, 'utf8'));
|
|
83
|
+
if (!isRecord(parsed)) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
const fromFile: Partial<GitAtomicityConfig> = {};
|
|
87
|
+
if (typeof parsed.enabled === 'boolean') {
|
|
88
|
+
fromFile.enabled = parsed.enabled;
|
|
89
|
+
}
|
|
90
|
+
if (Number.isInteger(parsed.maxFiles) && Number(parsed.maxFiles) > 0) {
|
|
91
|
+
fromFile.maxFiles = Number(parsed.maxFiles);
|
|
92
|
+
}
|
|
93
|
+
if (Number.isInteger(parsed.maxScopes) && Number(parsed.maxScopes) > 0) {
|
|
94
|
+
fromFile.maxScopes = Number(parsed.maxScopes);
|
|
95
|
+
}
|
|
96
|
+
if (typeof parsed.enforceCommitMessagePattern === 'boolean') {
|
|
97
|
+
fromFile.enforceCommitMessagePattern = parsed.enforceCommitMessagePattern;
|
|
98
|
+
}
|
|
99
|
+
if (isNonEmptyString(parsed.commitMessagePattern)) {
|
|
100
|
+
fromFile.commitMessagePattern = parsed.commitMessagePattern;
|
|
101
|
+
}
|
|
102
|
+
return fromFile;
|
|
103
|
+
} catch {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const resolveConfig = (repoRoot: string): GitAtomicityConfig => {
|
|
109
|
+
const fromFile = parseFileConfig(repoRoot) ?? {};
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
enabled:
|
|
113
|
+
parseBooleanEnv(process.env.PUMUKI_GIT_ATOMICITY_ENABLED)
|
|
114
|
+
?? fromFile.enabled
|
|
115
|
+
?? defaultConfig.enabled,
|
|
116
|
+
maxFiles:
|
|
117
|
+
parsePositiveIntegerEnv(process.env.PUMUKI_GIT_ATOMICITY_MAX_FILES)
|
|
118
|
+
?? fromFile.maxFiles
|
|
119
|
+
?? defaultConfig.maxFiles,
|
|
120
|
+
maxScopes:
|
|
121
|
+
parsePositiveIntegerEnv(process.env.PUMUKI_GIT_ATOMICITY_MAX_SCOPES)
|
|
122
|
+
?? fromFile.maxScopes
|
|
123
|
+
?? defaultConfig.maxScopes,
|
|
124
|
+
enforceCommitMessagePattern:
|
|
125
|
+
parseBooleanEnv(process.env.PUMUKI_GIT_ATOMICITY_ENFORCE_COMMIT_PATTERN)
|
|
126
|
+
?? fromFile.enforceCommitMessagePattern
|
|
127
|
+
?? defaultConfig.enforceCommitMessagePattern,
|
|
128
|
+
commitMessagePattern:
|
|
129
|
+
process.env.PUMUKI_GIT_ATOMICITY_COMMIT_PATTERN?.trim()
|
|
130
|
+
|| fromFile.commitMessagePattern
|
|
131
|
+
|| defaultConfig.commitMessagePattern,
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const parseLines = (value: string): ReadonlyArray<string> =>
|
|
136
|
+
value
|
|
137
|
+
.split('\n')
|
|
138
|
+
.map((line) => line.trim())
|
|
139
|
+
.filter((line) => line.length > 0);
|
|
140
|
+
|
|
141
|
+
const resolveScopeKey = (filePath: string): string => {
|
|
142
|
+
const normalized = filePath.replace(/\\/g, '/').trim();
|
|
143
|
+
const segments = normalized.split('/').filter((segment) => segment.length > 0);
|
|
144
|
+
if (segments.length === 0) {
|
|
145
|
+
return '';
|
|
146
|
+
}
|
|
147
|
+
if (
|
|
148
|
+
(segments[0] === 'apps' || segments[0] === 'packages' || segments[0] === 'services')
|
|
149
|
+
&& segments.length >= 2
|
|
150
|
+
) {
|
|
151
|
+
return `${segments[0]}/${segments[1]}`;
|
|
152
|
+
}
|
|
153
|
+
return segments[0] ?? '';
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const collectChangedPaths = (params: {
|
|
157
|
+
git: IGitService;
|
|
158
|
+
repoRoot: string;
|
|
159
|
+
stage: GitAtomicityStage;
|
|
160
|
+
fromRef?: string;
|
|
161
|
+
toRef?: string;
|
|
162
|
+
}): ReadonlyArray<string> => {
|
|
163
|
+
if (params.stage === 'PRE_COMMIT') {
|
|
164
|
+
return parseLines(
|
|
165
|
+
params.git.runGit(['diff', '--cached', '--name-only', '--diff-filter=ACMR'], params.repoRoot)
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
if (!params.fromRef || !params.toRef) {
|
|
169
|
+
return [];
|
|
170
|
+
}
|
|
171
|
+
return parseLines(
|
|
172
|
+
params.git.runGit(
|
|
173
|
+
['diff', '--name-only', '--diff-filter=ACMR', `${params.fromRef}..${params.toRef}`],
|
|
174
|
+
params.repoRoot
|
|
175
|
+
)
|
|
176
|
+
);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const collectCommitSubjects = (params: {
|
|
180
|
+
git: IGitService;
|
|
181
|
+
repoRoot: string;
|
|
182
|
+
fromRef?: string;
|
|
183
|
+
toRef?: string;
|
|
184
|
+
}): ReadonlyArray<string> => {
|
|
185
|
+
if (!params.fromRef || !params.toRef) {
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
return parseLines(
|
|
189
|
+
params.git.runGit(['log', '--format=%s', `${params.fromRef}..${params.toRef}`], params.repoRoot)
|
|
190
|
+
);
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
export const evaluateGitAtomicity = (params: {
|
|
194
|
+
git?: IGitService;
|
|
195
|
+
repoRoot?: string;
|
|
196
|
+
stage: GitAtomicityStage;
|
|
197
|
+
fromRef?: string;
|
|
198
|
+
toRef?: string;
|
|
199
|
+
}): GitAtomicityEvaluation => {
|
|
200
|
+
const git = params.git ?? new GitService();
|
|
201
|
+
const repoRoot = params.repoRoot ? resolve(params.repoRoot) : git.resolveRepoRoot();
|
|
202
|
+
const config = resolveConfig(repoRoot);
|
|
203
|
+
if (!config.enabled) {
|
|
204
|
+
return {
|
|
205
|
+
enabled: false,
|
|
206
|
+
allowed: true,
|
|
207
|
+
violations: [],
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const violations: GitAtomicityViolation[] = [];
|
|
212
|
+
const changedPaths = collectChangedPaths({
|
|
213
|
+
git,
|
|
214
|
+
repoRoot,
|
|
215
|
+
stage: params.stage,
|
|
216
|
+
fromRef: params.fromRef,
|
|
217
|
+
toRef: params.toRef,
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
if (changedPaths.length > config.maxFiles) {
|
|
221
|
+
violations.push({
|
|
222
|
+
code: 'GIT_ATOMICITY_TOO_MANY_FILES',
|
|
223
|
+
message:
|
|
224
|
+
`Git atomicity guard blocked at ${params.stage}: changed_files=${changedPaths.length} exceeds max_files=${config.maxFiles}.`,
|
|
225
|
+
remediation: `Divide los cambios en commits más pequeños (máximo ${config.maxFiles} archivos por commit).`,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const scopeKeys = new Set(
|
|
230
|
+
changedPaths
|
|
231
|
+
.map((filePath) => resolveScopeKey(filePath))
|
|
232
|
+
.filter((scopeKey) => scopeKey.length > 0)
|
|
233
|
+
);
|
|
234
|
+
if (scopeKeys.size > config.maxScopes) {
|
|
235
|
+
violations.push({
|
|
236
|
+
code: 'GIT_ATOMICITY_TOO_MANY_SCOPES',
|
|
237
|
+
message:
|
|
238
|
+
`Git atomicity guard blocked at ${params.stage}: changed_scopes=${scopeKeys.size} exceeds max_scopes=${config.maxScopes}.`,
|
|
239
|
+
remediation: `Agrupa cambios por ámbito funcional (máximo ${config.maxScopes} scopes por commit).`,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (config.enforceCommitMessagePattern && params.stage !== 'PRE_COMMIT') {
|
|
244
|
+
let pattern: RegExp;
|
|
245
|
+
try {
|
|
246
|
+
pattern = new RegExp(config.commitMessagePattern);
|
|
247
|
+
} catch {
|
|
248
|
+
pattern = new RegExp(DEFAULT_COMMIT_PATTERN);
|
|
249
|
+
}
|
|
250
|
+
const subjects = collectCommitSubjects({
|
|
251
|
+
git,
|
|
252
|
+
repoRoot,
|
|
253
|
+
fromRef: params.fromRef,
|
|
254
|
+
toRef: params.toRef,
|
|
255
|
+
});
|
|
256
|
+
const invalidSubjects = subjects.filter((subject) => !pattern.test(subject));
|
|
257
|
+
if (invalidSubjects.length > 0) {
|
|
258
|
+
const sample = invalidSubjects.slice(0, 3).join(' | ');
|
|
259
|
+
violations.push({
|
|
260
|
+
code: 'GIT_ATOMICITY_COMMIT_MESSAGE_TRACEABILITY',
|
|
261
|
+
message:
|
|
262
|
+
`Git atomicity guard blocked at ${params.stage}: commit messages without traceable pattern detected (${invalidSubjects.length}). sample=[${sample}].`,
|
|
263
|
+
remediation:
|
|
264
|
+
'Reescribe los commits para cumplir el patrón de trazabilidad configurado (por ejemplo Conventional Commits).',
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
enabled: true,
|
|
271
|
+
allowed: violations.length === 0,
|
|
272
|
+
violations,
|
|
273
|
+
};
|
|
274
|
+
};
|
|
@@ -132,6 +132,13 @@ const defaultDependencies: GateDependencies = {
|
|
|
132
132
|
};
|
|
133
133
|
|
|
134
134
|
const resolveCurrentBranch = (git: IGitService, repoRoot: string): string | null => {
|
|
135
|
+
try {
|
|
136
|
+
const symbolicBranch = git.runGit(['symbolic-ref', '--short', 'HEAD'], repoRoot).trim();
|
|
137
|
+
if (symbolicBranch.length > 0) {
|
|
138
|
+
return symbolicBranch;
|
|
139
|
+
}
|
|
140
|
+
} catch {
|
|
141
|
+
}
|
|
135
142
|
try {
|
|
136
143
|
const branch = git.runGit(['rev-parse', '--abbrev-ref', 'HEAD'], repoRoot).trim();
|
|
137
144
|
if (branch.length === 0 || branch === 'HEAD') {
|
|
@@ -228,6 +235,10 @@ const PLATFORM_REQUIRED_SKILLS_BUNDLES: Record<
|
|
|
228
235
|
frontend: ['frontend-guidelines'],
|
|
229
236
|
};
|
|
230
237
|
|
|
238
|
+
const isCriticalProfileSeverity = (severity: string): boolean => {
|
|
239
|
+
return severity === 'CRITICAL' || severity === 'ERROR';
|
|
240
|
+
};
|
|
241
|
+
|
|
231
242
|
const toNormalizedPath = (value: string): string => value.replace(/\\/g, '/').trim();
|
|
232
243
|
|
|
233
244
|
const collectObservedPathsFromFacts = (
|
|
@@ -504,6 +515,66 @@ const toPlatformSkillsCoverageBlockingFinding = (params: {
|
|
|
504
515
|
};
|
|
505
516
|
};
|
|
506
517
|
|
|
518
|
+
const toCrossPlatformCriticalEnforcementBlockingFinding = (params: {
|
|
519
|
+
stage: 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
|
|
520
|
+
detectedPlatforms: DetectedPlatforms;
|
|
521
|
+
skillsRules: SkillsRuleSetLoadResult['rules'];
|
|
522
|
+
evaluatedRuleIds: ReadonlyArray<string>;
|
|
523
|
+
}): Finding | undefined => {
|
|
524
|
+
const detectedPlatformKeys = (
|
|
525
|
+
['ios', 'android', 'backend', 'frontend'] as const
|
|
526
|
+
).filter((platform) => params.detectedPlatforms[platform]?.detected === true);
|
|
527
|
+
|
|
528
|
+
if (detectedPlatformKeys.length === 0) {
|
|
529
|
+
return undefined;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const evaluatedRuleIds = new Set(params.evaluatedRuleIds);
|
|
533
|
+
const gaps: string[] = [];
|
|
534
|
+
|
|
535
|
+
for (const platform of detectedPlatformKeys) {
|
|
536
|
+
const rulePrefix = PLATFORM_SKILLS_RULE_PREFIXES[platform];
|
|
537
|
+
const criticalSkillRules = params.skillsRules
|
|
538
|
+
.filter(
|
|
539
|
+
(rule) =>
|
|
540
|
+
rule.id.startsWith(rulePrefix) &&
|
|
541
|
+
isCriticalProfileSeverity(rule.severity)
|
|
542
|
+
)
|
|
543
|
+
.map((rule) => rule.id)
|
|
544
|
+
.sort();
|
|
545
|
+
|
|
546
|
+
if (criticalSkillRules.length === 0) {
|
|
547
|
+
gaps.push(`${platform}{critical_profile_rules=missing}`);
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const evaluatedCriticalSkillRules = criticalSkillRules.filter((ruleId) =>
|
|
552
|
+
evaluatedRuleIds.has(ruleId)
|
|
553
|
+
);
|
|
554
|
+
if (evaluatedCriticalSkillRules.length === 0) {
|
|
555
|
+
gaps.push(
|
|
556
|
+
`${platform}{critical_profile_rules=${criticalSkillRules.length}; evaluated=0}`
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
if (gaps.length === 0) {
|
|
562
|
+
return undefined;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
return {
|
|
566
|
+
ruleId: 'governance.skills.cross-platform-critical.incomplete',
|
|
567
|
+
severity: 'ERROR',
|
|
568
|
+
code: 'SKILLS_CROSS_PLATFORM_CRITICAL_INCOMPLETE_S0',
|
|
569
|
+
message:
|
|
570
|
+
`Cross-platform critical enforcement incomplete at ${params.stage}: ${gaps.join(' | ')}. ` +
|
|
571
|
+
'Ensure each detected platform has critical-profile skill rules active and evaluated.',
|
|
572
|
+
filePath: '.ai_evidence.json',
|
|
573
|
+
matchedBy: 'SkillsCrossPlatformCriticalGuard',
|
|
574
|
+
source: 'skills-cross-platform-critical',
|
|
575
|
+
};
|
|
576
|
+
};
|
|
577
|
+
|
|
507
578
|
export async function runPlatformGate(params: {
|
|
508
579
|
policy: GatePolicy;
|
|
509
580
|
auditMode?: 'gate' | 'engine';
|
|
@@ -635,6 +706,17 @@ export async function runPlatformGate(params: {
|
|
|
635
706
|
evaluatedRuleIds: coverage?.evaluatedRuleIds ?? [],
|
|
636
707
|
})
|
|
637
708
|
: undefined;
|
|
709
|
+
const crossPlatformCriticalFinding =
|
|
710
|
+
params.policy.stage === 'PRE_COMMIT' ||
|
|
711
|
+
params.policy.stage === 'PRE_PUSH' ||
|
|
712
|
+
params.policy.stage === 'CI'
|
|
713
|
+
? toCrossPlatformCriticalEnforcementBlockingFinding({
|
|
714
|
+
stage: params.policy.stage,
|
|
715
|
+
detectedPlatforms,
|
|
716
|
+
skillsRules: skillsRuleSet.rules,
|
|
717
|
+
evaluatedRuleIds: coverage?.evaluatedRuleIds ?? [],
|
|
718
|
+
})
|
|
719
|
+
: undefined;
|
|
638
720
|
const skillsScopeComplianceFinding =
|
|
639
721
|
params.policy.stage === 'PRE_COMMIT' ||
|
|
640
722
|
params.policy.stage === 'PRE_PUSH' ||
|
|
@@ -713,6 +795,7 @@ export async function runPlatformGate(params: {
|
|
|
713
795
|
...(policyAsCodeBlockingFinding ? [policyAsCodeBlockingFinding] : []),
|
|
714
796
|
...(unsupportedSkillsMappingFinding ? [unsupportedSkillsMappingFinding] : []),
|
|
715
797
|
...(platformSkillsCoverageFinding ? [platformSkillsCoverageFinding] : []),
|
|
798
|
+
...(crossPlatformCriticalFinding ? [crossPlatformCriticalFinding] : []),
|
|
716
799
|
...(skillsScopeComplianceFinding ? [skillsScopeComplianceFinding] : []),
|
|
717
800
|
...(coverageBlockingFinding ? [coverageBlockingFinding] : []),
|
|
718
801
|
...tddBddEvaluation.findings,
|
|
@@ -720,6 +803,7 @@ export async function runPlatformGate(params: {
|
|
|
720
803
|
]
|
|
721
804
|
: unsupportedSkillsMappingFinding
|
|
722
805
|
|| platformSkillsCoverageFinding
|
|
806
|
+
|| crossPlatformCriticalFinding
|
|
723
807
|
|| skillsScopeComplianceFinding
|
|
724
808
|
|| coverageBlockingFinding
|
|
725
809
|
|| policyAsCodeBlockingFinding
|
|
@@ -730,6 +814,7 @@ export async function runPlatformGate(params: {
|
|
|
730
814
|
...(policyAsCodeBlockingFinding ? [policyAsCodeBlockingFinding] : []),
|
|
731
815
|
...(unsupportedSkillsMappingFinding ? [unsupportedSkillsMappingFinding] : []),
|
|
732
816
|
...(platformSkillsCoverageFinding ? [platformSkillsCoverageFinding] : []),
|
|
817
|
+
...(crossPlatformCriticalFinding ? [crossPlatformCriticalFinding] : []),
|
|
733
818
|
...(skillsScopeComplianceFinding ? [skillsScopeComplianceFinding] : []),
|
|
734
819
|
...(coverageBlockingFinding ? [coverageBlockingFinding] : []),
|
|
735
820
|
...tddBddEvaluation.findings,
|
|
@@ -743,6 +828,7 @@ export async function runPlatformGate(params: {
|
|
|
743
828
|
policyAsCodeBlockingFinding ||
|
|
744
829
|
unsupportedSkillsMappingFinding ||
|
|
745
830
|
platformSkillsCoverageFinding ||
|
|
831
|
+
crossPlatformCriticalFinding ||
|
|
746
832
|
skillsScopeComplianceFinding ||
|
|
747
833
|
coverageBlockingFinding ||
|
|
748
834
|
hasTddBddBlockingFinding
|