pumuki 6.3.267 → 6.3.269
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/core/gate/evaluateGate.test.ts +13 -5
- package/core/gate/evaluateGate.ts +4 -4
- package/docs/governance/CONTRIBUTING.md +1 -1
- package/integrations/config/coreSkillsLock.ts +2 -1
- package/integrations/config/skillsDetectorRegistry.ts +18 -0
- package/integrations/config/skillsRuleClassification.ts +147 -0
- package/integrations/git/astIntelligenceDualValidation.ts +1 -0
- package/integrations/git/runPlatformGate.ts +125 -33
- package/integrations/git/stageRunners.ts +4 -10
- package/integrations/lifecycle/cli.ts +94 -4
- package/integrations/lifecycle/doctor.ts +33 -0
- package/integrations/lifecycle/install.ts +26 -4
- package/integrations/lifecycle/notificationStatus.ts +54 -0
- package/integrations/lifecycle/preWriteAutomation.ts +35 -5
- package/integrations/lifecycle/preWriteLease.ts +271 -0
- package/integrations/lifecycle/status.ts +40 -2
- package/integrations/lifecycle/watch.ts +2 -1
- package/integrations/mcp/evidenceStdioServer.cli.ts +2 -2
- package/integrations/policy/skillsEnforcement.ts +3 -14
- package/integrations/policy/tddBddEnforcement.ts +3 -2
- package/package.json +2 -1
- package/scripts/classify-skills-rules.ts +32 -0
- package/scripts/framework-menu-system-notifications-cause.ts +11 -1
- package/scripts/package-install-smoke-consumer-git-payload-lib.ts +2 -2
- package/scripts/package-install-smoke-consumer-git-repo-lib.ts +1 -1
- package/scripts/package-install-smoke-execution-lib.ts +3 -1
- package/scripts/package-install-smoke-gate-lib.ts +3 -0
- package/scripts/package-install-smoke-lifecycle-lib.ts +29 -0
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
v6.3.
|
|
1
|
+
v6.3.269
|
|
@@ -25,12 +25,14 @@ test('evaluateGate devuelve WARN cuando hay warnings sin bloqueantes', () => {
|
|
|
25
25
|
severity: 'WARN',
|
|
26
26
|
code: 'RULE_WARN',
|
|
27
27
|
message: 'Warn finding',
|
|
28
|
+
blocking: false,
|
|
28
29
|
},
|
|
29
30
|
{
|
|
30
31
|
ruleId: 'rule.info',
|
|
31
32
|
severity: 'INFO',
|
|
32
33
|
code: 'RULE_INFO',
|
|
33
34
|
message: 'Info finding',
|
|
35
|
+
blocking: false,
|
|
34
36
|
},
|
|
35
37
|
];
|
|
36
38
|
|
|
@@ -42,7 +44,7 @@ test('evaluateGate devuelve WARN cuando hay warnings sin bloqueantes', () => {
|
|
|
42
44
|
assert.equal(result.warnings[0]?.ruleId, 'rule.warn');
|
|
43
45
|
});
|
|
44
46
|
|
|
45
|
-
test('evaluateGate devuelve BLOCK
|
|
47
|
+
test('evaluateGate devuelve BLOCK para cualquier finding runtime salvo blocking=false', () => {
|
|
46
48
|
const findings: Finding[] = [
|
|
47
49
|
{
|
|
48
50
|
ruleId: 'rule.error',
|
|
@@ -62,18 +64,24 @@ test('evaluateGate devuelve BLOCK cuando existe al menos un finding bloqueante',
|
|
|
62
64
|
code: 'RULE_WARN',
|
|
63
65
|
message: 'Warn finding',
|
|
64
66
|
},
|
|
67
|
+
{
|
|
68
|
+
ruleId: 'rule.info.advisory',
|
|
69
|
+
severity: 'INFO',
|
|
70
|
+
code: 'RULE_INFO_ADVISORY',
|
|
71
|
+
message: 'Info advisory finding',
|
|
72
|
+
blocking: false,
|
|
73
|
+
},
|
|
65
74
|
];
|
|
66
75
|
|
|
67
76
|
const result = evaluateGate(findings, defaultPolicy);
|
|
68
77
|
|
|
69
78
|
assert.equal(result.outcome, 'BLOCK');
|
|
70
|
-
assert.equal(result.blocking.length,
|
|
79
|
+
assert.equal(result.blocking.length, 3);
|
|
71
80
|
assert.deepEqual(
|
|
72
81
|
result.blocking.map((finding) => finding.ruleId),
|
|
73
|
-
['rule.error', 'rule.critical']
|
|
82
|
+
['rule.error', 'rule.critical', 'rule.warn']
|
|
74
83
|
);
|
|
75
|
-
assert.
|
|
76
|
-
assert.equal(result.warnings[0]?.ruleId, 'rule.warn');
|
|
84
|
+
assert.deepEqual(result.warnings, []);
|
|
77
85
|
});
|
|
78
86
|
|
|
79
87
|
test('evaluateGate bloquea cualquier severidad cuando la policy zero-violations usa INFO', () => {
|
|
@@ -3,16 +3,16 @@ import type { GateOutcome } from './GateOutcome';
|
|
|
3
3
|
import type { GatePolicy } from './GatePolicy';
|
|
4
4
|
import { isSeverityAtLeast } from '../rules/Severity';
|
|
5
5
|
|
|
6
|
+
const isBlockingFinding = (finding: Finding): boolean => finding.blocking !== false;
|
|
7
|
+
|
|
6
8
|
export function evaluateGate(
|
|
7
9
|
findings: Finding[],
|
|
8
10
|
policy: GatePolicy
|
|
9
11
|
): { outcome: GateOutcome; blocking: Finding[]; warnings: Finding[] } {
|
|
10
|
-
const blocking = findings.filter(
|
|
11
|
-
isSeverityAtLeast(finding.severity, policy.blockOnOrAbove)
|
|
12
|
-
);
|
|
12
|
+
const blocking = findings.filter(isBlockingFinding);
|
|
13
13
|
const warnings = findings.filter(
|
|
14
14
|
(finding) =>
|
|
15
|
-
!
|
|
15
|
+
!isBlockingFinding(finding) &&
|
|
16
16
|
isSeverityAtLeast(finding.severity, policy.warnOnOrAbove)
|
|
17
17
|
);
|
|
18
18
|
|
|
@@ -89,4 +89,4 @@ npm run test:heuristics
|
|
|
89
89
|
## Notes for automation contributors
|
|
90
90
|
|
|
91
91
|
This repository still contains legacy hooks/scripts for compatibility.
|
|
92
|
-
|
|
92
|
+
Framework refactor commits must run through the managed hooks; bypassing verification is not an accepted maintainer workflow.
|
|
@@ -427,6 +427,16 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
427
427
|
'skills.frontend.no-god-classes': heuristicDetector('typescript.god-class', [
|
|
428
428
|
'heuristics.ts.god-class-large-class.ast',
|
|
429
429
|
]),
|
|
430
|
+
'skills.ios.guideline.ios.verificar-que-no-viole-solid-srp-ocp-lsp-isp-dip': heuristicDetector(
|
|
431
|
+
'ios.solid',
|
|
432
|
+
[
|
|
433
|
+
'heuristics.ios.solid.srp.presentation-mixed-responsibilities.ast',
|
|
434
|
+
'heuristics.ios.solid.ocp.discriminator-switch.ast',
|
|
435
|
+
'heuristics.ios.solid.dip.concrete-framework-dependency.ast',
|
|
436
|
+
'heuristics.ios.solid.isp.fat-protocol-dependency.ast',
|
|
437
|
+
'heuristics.ios.solid.lsp.narrowed-precondition.ast',
|
|
438
|
+
]
|
|
439
|
+
),
|
|
430
440
|
'skills.android.no-thread-sleep': heuristicDetector('android.thread-sleep', [
|
|
431
441
|
'heuristics.android.thread-sleep.ast',
|
|
432
442
|
]),
|
|
@@ -505,6 +515,14 @@ const registryByRuleId: Record<string, SkillsDetectorBinding> = {
|
|
|
505
515
|
'heuristics.android.solid.isp.fat-interface-dependency.ast',
|
|
506
516
|
'heuristics.android.solid.lsp.narrowed-precondition.ast',
|
|
507
517
|
]),
|
|
518
|
+
'skills.android.guideline.android.verificar-que-no-viole-solid-srp-ocp-lsp-isp-dip':
|
|
519
|
+
heuristicDetector('android.solid', [
|
|
520
|
+
'heuristics.android.solid.srp.presentation-mixed-responsibilities.ast',
|
|
521
|
+
'heuristics.android.solid.ocp.discriminator-branching.ast',
|
|
522
|
+
'heuristics.android.solid.dip.concrete-framework-dependency.ast',
|
|
523
|
+
'heuristics.android.solid.isp.fat-interface-dependency.ast',
|
|
524
|
+
'heuristics.android.solid.lsp.narrowed-precondition.ast',
|
|
525
|
+
]),
|
|
508
526
|
};
|
|
509
527
|
|
|
510
528
|
export const listSkillsDetectorBindings = (): ReadonlyArray<{
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type { SkillsCompiledRule, SkillsLockV1 } from './skillsLock';
|
|
2
|
+
import { resolveMappedHeuristicRuleIdsForCompiledRule } from './skillsDetectorRegistry';
|
|
3
|
+
|
|
4
|
+
export type SkillsRuleClassificationStatus =
|
|
5
|
+
| 'AST_IMPLEMENTED'
|
|
6
|
+
| 'IMPLEMENTABLE_AHORA'
|
|
7
|
+
| 'REQUIERE_ESTUDIO'
|
|
8
|
+
| 'NO_ES_REGLA_DE_CODIGO';
|
|
9
|
+
|
|
10
|
+
export type ClassifiedSkillsRule = {
|
|
11
|
+
ruleId: string;
|
|
12
|
+
platform: SkillsCompiledRule['platform'];
|
|
13
|
+
sourceSkill: string;
|
|
14
|
+
sourcePath: string;
|
|
15
|
+
evaluationMode: 'AUTO' | 'DECLARATIVE';
|
|
16
|
+
severity: SkillsCompiledRule['severity'];
|
|
17
|
+
status: SkillsRuleClassificationStatus;
|
|
18
|
+
reason: string;
|
|
19
|
+
astNodeIds: ReadonlyArray<string>;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type SkillsRuleClassificationSummary = {
|
|
23
|
+
total: number;
|
|
24
|
+
byStatus: Record<SkillsRuleClassificationStatus, number>;
|
|
25
|
+
byPlatform: Record<string, number>;
|
|
26
|
+
rules: ReadonlyArray<ClassifiedSkillsRule>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const emptyStatusCounts = (): Record<SkillsRuleClassificationStatus, number> => ({
|
|
30
|
+
AST_IMPLEMENTED: 0,
|
|
31
|
+
IMPLEMENTABLE_AHORA: 0,
|
|
32
|
+
REQUIERE_ESTUDIO: 0,
|
|
33
|
+
NO_ES_REGLA_DE_CODIGO: 0,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const normalizeText = (value: string): string =>
|
|
37
|
+
value
|
|
38
|
+
.normalize('NFD')
|
|
39
|
+
.replace(/\p{Diacritic}/gu, '')
|
|
40
|
+
.toLowerCase();
|
|
41
|
+
|
|
42
|
+
const NON_CODE_RULE_PATTERNS: ReadonlyArray<RegExp> = [
|
|
43
|
+
/\bactua como\b/,
|
|
44
|
+
/\bsiempre responder\b/,
|
|
45
|
+
/\bflujo bdd\b/,
|
|
46
|
+
/\bfeature files\b/,
|
|
47
|
+
/\bspecs\b.*\bimplementacion\b/,
|
|
48
|
+
/\bcomprobar que compile\b/,
|
|
49
|
+
/\banalizar estructura existente\b/,
|
|
50
|
+
/\bandroid profiler\b/,
|
|
51
|
+
/\bxcode instruments\b/,
|
|
52
|
+
/\bdocumentation\b/,
|
|
53
|
+
/\bdocumentacion\b/,
|
|
54
|
+
/\breadme\b/,
|
|
55
|
+
/\bci\/cd\b/,
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
const isNonCodeRule = (rule: SkillsCompiledRule): boolean => {
|
|
59
|
+
const haystack = normalizeText(`${rule.id} ${rule.description} ${rule.sourceSkill}`);
|
|
60
|
+
return NON_CODE_RULE_PATTERNS.some((pattern) => pattern.test(haystack));
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const classifyRule = (rule: SkillsCompiledRule): ClassifiedSkillsRule => {
|
|
64
|
+
const evaluationMode = rule.evaluationMode ?? 'AUTO';
|
|
65
|
+
const astNodeIds = resolveMappedHeuristicRuleIdsForCompiledRule(rule);
|
|
66
|
+
|
|
67
|
+
if (astNodeIds.length > 0) {
|
|
68
|
+
return {
|
|
69
|
+
ruleId: rule.id,
|
|
70
|
+
platform: rule.platform,
|
|
71
|
+
sourceSkill: rule.sourceSkill,
|
|
72
|
+
sourcePath: rule.sourcePath,
|
|
73
|
+
evaluationMode,
|
|
74
|
+
severity: rule.severity,
|
|
75
|
+
status: 'AST_IMPLEMENTED',
|
|
76
|
+
reason: `Mapped to AST/nodal detector(s): ${astNodeIds.join(', ')}`,
|
|
77
|
+
astNodeIds,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (evaluationMode === 'AUTO') {
|
|
82
|
+
return {
|
|
83
|
+
ruleId: rule.id,
|
|
84
|
+
platform: rule.platform,
|
|
85
|
+
sourceSkill: rule.sourceSkill,
|
|
86
|
+
sourcePath: rule.sourcePath,
|
|
87
|
+
evaluationMode,
|
|
88
|
+
severity: rule.severity,
|
|
89
|
+
status: 'IMPLEMENTABLE_AHORA',
|
|
90
|
+
reason: 'AUTO rule without AST/nodal detector binding; implement detector mapping.',
|
|
91
|
+
astNodeIds,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (isNonCodeRule(rule)) {
|
|
96
|
+
return {
|
|
97
|
+
ruleId: rule.id,
|
|
98
|
+
platform: rule.platform,
|
|
99
|
+
sourceSkill: rule.sourceSkill,
|
|
100
|
+
sourcePath: rule.sourcePath,
|
|
101
|
+
evaluationMode,
|
|
102
|
+
severity: rule.severity,
|
|
103
|
+
status: 'NO_ES_REGLA_DE_CODIGO',
|
|
104
|
+
reason: 'Process, documentation, validation workflow, or operating rule; keep outside code AST runtime.',
|
|
105
|
+
astNodeIds,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
ruleId: rule.id,
|
|
111
|
+
platform: rule.platform,
|
|
112
|
+
sourceSkill: rule.sourceSkill,
|
|
113
|
+
sourcePath: rule.sourcePath,
|
|
114
|
+
evaluationMode,
|
|
115
|
+
severity: rule.severity,
|
|
116
|
+
status: 'REQUIERE_ESTUDIO',
|
|
117
|
+
reason: 'Declarative skill rule without AST/nodal detector. Study exact code pattern, AST node, and closure criterion.',
|
|
118
|
+
astNodeIds,
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export const classifySkillsRules = (
|
|
123
|
+
lock: SkillsLockV1
|
|
124
|
+
): SkillsRuleClassificationSummary => {
|
|
125
|
+
const rules = lock.bundles
|
|
126
|
+
.flatMap((bundle) => bundle.rules)
|
|
127
|
+
.filter((rule) => rule.id.startsWith('skills.'))
|
|
128
|
+
.map(classifyRule)
|
|
129
|
+
.sort((left, right) => left.ruleId.localeCompare(right.ruleId));
|
|
130
|
+
|
|
131
|
+
const byStatus = emptyStatusCounts();
|
|
132
|
+
const byPlatform: Record<string, number> = {};
|
|
133
|
+
|
|
134
|
+
for (const rule of rules) {
|
|
135
|
+
byStatus[rule.status] += 1;
|
|
136
|
+
byPlatform[rule.platform] = (byPlatform[rule.platform] ?? 0) + 1;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
total: rules.length,
|
|
141
|
+
byStatus,
|
|
142
|
+
byPlatform: Object.fromEntries(
|
|
143
|
+
Object.entries(byPlatform).sort(([left], [right]) => left.localeCompare(right))
|
|
144
|
+
),
|
|
145
|
+
rules,
|
|
146
|
+
};
|
|
147
|
+
};
|
|
@@ -188,6 +188,7 @@ const toDualValidationFinding = (params: {
|
|
|
188
188
|
return {
|
|
189
189
|
ruleId: 'governance.ast-intelligence.dual-validation.shadow',
|
|
190
190
|
severity: 'INFO',
|
|
191
|
+
blocking: false,
|
|
191
192
|
code: 'AST_INTELLIGENCE_DUAL_VALIDATION_SHADOW',
|
|
192
193
|
message:
|
|
193
194
|
`AST Intelligence dual validation shadow at ${params.stage}: ` +
|
|
@@ -39,6 +39,12 @@ import {
|
|
|
39
39
|
filterFactsByPathPrefixes,
|
|
40
40
|
resolveGateScopePathPrefixesFromEnv,
|
|
41
41
|
} from './filterFactsByPathPrefixes';
|
|
42
|
+
import {
|
|
43
|
+
readPreWriteLeaseStatus,
|
|
44
|
+
toPreWriteEnforcementGapFinding,
|
|
45
|
+
} from '../lifecycle/preWriteLease';
|
|
46
|
+
import { evaluateContextGate, type ContextGateResult } from '../context/contextGate';
|
|
47
|
+
import { emitGateBlockedNotification } from '../notifications/emitAuditSummaryNotification';
|
|
42
48
|
|
|
43
49
|
export type OperationalMemoryShadowRecommendation = {
|
|
44
50
|
recommendedOutcome: 'ALLOW' | 'WARN' | 'BLOCK';
|
|
@@ -73,6 +79,8 @@ export type GateDependencies = {
|
|
|
73
79
|
stage: 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
|
|
74
80
|
branch: string | null;
|
|
75
81
|
}) => GateWaiverResult;
|
|
82
|
+
evaluateContextGate: typeof evaluateContextGate;
|
|
83
|
+
notifyGateBlocked: typeof emitGateBlockedNotification;
|
|
76
84
|
};
|
|
77
85
|
|
|
78
86
|
const defaultServices: GateServices = {
|
|
@@ -105,20 +113,8 @@ const normalizeScopedRuleEngineFindings = (params: {
|
|
|
105
113
|
findings: ReadonlyArray<Finding>;
|
|
106
114
|
scope: GateScope;
|
|
107
115
|
}): ReadonlyArray<Finding> => {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return params.findings.filter((finding) => {
|
|
113
|
-
if (
|
|
114
|
-
!finding.filePath ||
|
|
115
|
-
hasActionableFindingLocation(finding) ||
|
|
116
|
-
(!finding.ruleId.startsWith('skills.') && !finding.ruleId.startsWith('heuristics.'))
|
|
117
|
-
) {
|
|
118
|
-
return true;
|
|
119
|
-
}
|
|
120
|
-
return false;
|
|
121
|
-
});
|
|
116
|
+
void params.scope;
|
|
117
|
+
return params.findings;
|
|
122
118
|
};
|
|
123
119
|
|
|
124
120
|
const buildDefaultMemoryShadowRecommendation = (params: {
|
|
@@ -195,6 +191,8 @@ const defaultDependencies: GateDependencies = {
|
|
|
195
191
|
stage,
|
|
196
192
|
branch,
|
|
197
193
|
}),
|
|
194
|
+
evaluateContextGate,
|
|
195
|
+
notifyGateBlocked: emitGateBlockedNotification,
|
|
198
196
|
};
|
|
199
197
|
|
|
200
198
|
const readSymbolicBranchRef = (git: IGitService, repoRoot: string): string | null => {
|
|
@@ -283,6 +281,34 @@ const toSkillsUnsupportedAutoRulesBlockingFinding = (params: {
|
|
|
283
281
|
};
|
|
284
282
|
};
|
|
285
283
|
|
|
284
|
+
const toSkillsDeclarativeRulesClassificationFinding = (params: {
|
|
285
|
+
stage: 'PRE_COMMIT' | 'PRE_PUSH' | 'CI';
|
|
286
|
+
declarativeRuleIds: ReadonlyArray<string>;
|
|
287
|
+
facts: ReadonlyArray<Fact>;
|
|
288
|
+
}): Finding | undefined => {
|
|
289
|
+
if (params.declarativeRuleIds.length === 0) {
|
|
290
|
+
return undefined;
|
|
291
|
+
}
|
|
292
|
+
if (collectObservedCodePathsFromFacts(params.facts).length === 0) {
|
|
293
|
+
return undefined;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const sampleRuleIds = [...params.declarativeRuleIds].sort().slice(0, 12).join(', ');
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
ruleId: 'governance.skills.declarative-rules.classification-required',
|
|
300
|
+
severity: 'ERROR',
|
|
301
|
+
code: 'SKILLS_DECLARATIVE_RULES_CLASSIFICATION_REQUIRED',
|
|
302
|
+
message:
|
|
303
|
+
`Skills declarative rules require AST/nodal classification at ${params.stage}: ` +
|
|
304
|
+
`total=${params.declarativeRuleIds.length} sample_rule_ids=[${sampleRuleIds}]. ` +
|
|
305
|
+
'Classify every rule as IMPLEMENTABLE_AHORA, REQUIERE_ESTUDIO, or NO_ES_REGLA_DE_CODIGO; code skills cannot remain as hidden declarative/advisory rules.',
|
|
306
|
+
filePath: 'skills.lock.json',
|
|
307
|
+
matchedBy: 'SkillsDeclarativeRulesClassificationGuard',
|
|
308
|
+
source: 'skills-declarative-rules-classification',
|
|
309
|
+
};
|
|
310
|
+
};
|
|
311
|
+
|
|
286
312
|
const PLATFORM_SKILLS_RULE_PREFIXES: Record<
|
|
287
313
|
'ios' | 'android' | 'backend' | 'frontend',
|
|
288
314
|
string
|
|
@@ -801,9 +827,18 @@ const shouldBlockFromFinding = (finding: Finding | undefined): boolean => {
|
|
|
801
827
|
if (!finding) {
|
|
802
828
|
return false;
|
|
803
829
|
}
|
|
804
|
-
return finding.
|
|
830
|
+
return finding.blocking !== false;
|
|
805
831
|
};
|
|
806
832
|
|
|
833
|
+
const resolvePrimaryBlockingFinding = (
|
|
834
|
+
findings: ReadonlyArray<Finding>
|
|
835
|
+
): Finding | undefined => (
|
|
836
|
+
findings.find((finding) => shouldBlockFromFinding(finding) && finding.severity === 'CRITICAL') ??
|
|
837
|
+
findings.find((finding) => shouldBlockFromFinding(finding) && finding.severity === 'ERROR') ??
|
|
838
|
+
findings.find((finding) => shouldBlockFromFinding(finding)) ??
|
|
839
|
+
findings[0]
|
|
840
|
+
);
|
|
841
|
+
|
|
807
842
|
const applySkillsFindingEnforcement = (
|
|
808
843
|
finding: Finding | undefined
|
|
809
844
|
): Finding | undefined => {
|
|
@@ -817,6 +852,7 @@ const applySkillsFindingEnforcement = (
|
|
|
817
852
|
return {
|
|
818
853
|
...finding,
|
|
819
854
|
severity: 'WARN',
|
|
855
|
+
blocking: false,
|
|
820
856
|
};
|
|
821
857
|
};
|
|
822
858
|
|
|
@@ -825,21 +861,9 @@ const toSoftPreCommitSkillsFinding = (params: {
|
|
|
825
861
|
enabled: boolean;
|
|
826
862
|
observedCodePaths: ReadonlyArray<string>;
|
|
827
863
|
}): Finding | undefined => {
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
if (!params.enabled || !shouldBlockFromFinding(params.finding)) {
|
|
832
|
-
return params.finding;
|
|
833
|
-
}
|
|
834
|
-
return {
|
|
835
|
-
...params.finding,
|
|
836
|
-
severity: 'WARN',
|
|
837
|
-
code: `${params.finding.code}_SOFT_PRECOMMIT`,
|
|
838
|
-
message:
|
|
839
|
-
`${params.finding.message} ` +
|
|
840
|
-
`Soft-enforced at PRE_COMMIT for low-risk scope (observed_code_paths=${params.observedCodePaths.length}). ` +
|
|
841
|
-
'Strict enforcement remains active at PRE_PUSH/CI.',
|
|
842
|
-
};
|
|
864
|
+
void params.enabled;
|
|
865
|
+
void params.observedCodePaths;
|
|
866
|
+
return params.finding;
|
|
843
867
|
};
|
|
844
868
|
|
|
845
869
|
export async function runPlatformGate(params: {
|
|
@@ -862,6 +886,11 @@ export async function runPlatformGate(params: {
|
|
|
862
886
|
const repoRoot = git.resolveRepoRoot();
|
|
863
887
|
const auditMode = params.auditMode ?? 'gate';
|
|
864
888
|
const shouldShortCircuitSdd = params.sddShortCircuit ?? false;
|
|
889
|
+
const contextGate: ContextGateResult = dependencies.evaluateContextGate({
|
|
890
|
+
repoRoot,
|
|
891
|
+
stage: params.policy.stage,
|
|
892
|
+
});
|
|
893
|
+
const contextBlockingFinding = contextGate.finding;
|
|
865
894
|
let sddDecision:
|
|
866
895
|
| Pick<SddDecision, 'allowed' | 'code' | 'message'>
|
|
867
896
|
| undefined;
|
|
@@ -939,6 +968,18 @@ export async function runPlatformGate(params: {
|
|
|
939
968
|
: facts;
|
|
940
969
|
const filesScanned = countScannedFilesFromFacts(factsForPlatformEvaluation);
|
|
941
970
|
const observedCodePaths = collectObservedCodePathsFromFacts(facts);
|
|
971
|
+
const preWriteLeaseFinding =
|
|
972
|
+
params.policy.stage === 'PRE_COMMIT' ||
|
|
973
|
+
params.policy.stage === 'PRE_PUSH' ||
|
|
974
|
+
params.policy.stage === 'CI'
|
|
975
|
+
? toPreWriteEnforcementGapFinding({
|
|
976
|
+
stage: params.policy.stage,
|
|
977
|
+
status: readPreWriteLeaseStatus({
|
|
978
|
+
repoRoot,
|
|
979
|
+
git,
|
|
980
|
+
}),
|
|
981
|
+
})
|
|
982
|
+
: undefined;
|
|
942
983
|
|
|
943
984
|
const platformEvaluation = dependencies.evaluatePlatformGateFindings({
|
|
944
985
|
facts: factsForPlatformEvaluation,
|
|
@@ -998,6 +1039,16 @@ export async function runPlatformGate(params: {
|
|
|
998
1039
|
unsupportedAutoRuleIds: skillsRuleSet.unsupportedAutoRuleIds ?? [],
|
|
999
1040
|
})
|
|
1000
1041
|
: undefined;
|
|
1042
|
+
const declarativeRulesClassificationFinding =
|
|
1043
|
+
params.policy.stage === 'PRE_COMMIT' ||
|
|
1044
|
+
params.policy.stage === 'PRE_PUSH' ||
|
|
1045
|
+
params.policy.stage === 'CI'
|
|
1046
|
+
? toSkillsDeclarativeRulesClassificationFinding({
|
|
1047
|
+
stage: params.policy.stage,
|
|
1048
|
+
declarativeRuleIds: skillsRuleSet.registryCoverage?.declarativeRuleIds ?? [],
|
|
1049
|
+
facts: factsForPlatformEvaluation,
|
|
1050
|
+
})
|
|
1051
|
+
: undefined;
|
|
1001
1052
|
const effectiveUnsupportedSkillsMappingInput = applySkillsFindingEnforcement(
|
|
1002
1053
|
unsupportedSkillsMappingFinding
|
|
1003
1054
|
);
|
|
@@ -1184,12 +1235,12 @@ export async function runPlatformGate(params: {
|
|
|
1184
1235
|
? tddBddEvaluation.snapshot
|
|
1185
1236
|
: undefined;
|
|
1186
1237
|
const hasTddBddBlockingFinding = tddBddEvaluation.findings.some(
|
|
1187
|
-
(finding) => finding
|
|
1238
|
+
(finding) => shouldBlockFromFinding(finding)
|
|
1188
1239
|
);
|
|
1189
1240
|
const hasNativeBlockingFinding = findings.some(
|
|
1190
|
-
(finding) => finding
|
|
1241
|
+
(finding) => shouldBlockFromFinding(finding)
|
|
1191
1242
|
);
|
|
1192
|
-
const preCommitSoftSkillsEnabled = process.env.PUMUKI_PRE_COMMIT_SOFT_SKILLS
|
|
1243
|
+
const preCommitSoftSkillsEnabled = process.env.PUMUKI_PRE_COMMIT_SOFT_SKILLS === '1';
|
|
1193
1244
|
const lowRiskPreCommitWindow = observedCodePaths.length > 0 && observedCodePaths.length <= 3;
|
|
1194
1245
|
const shouldSoftEnforceSkillsFindings =
|
|
1195
1246
|
params.policy.stage === 'PRE_COMMIT'
|
|
@@ -1199,6 +1250,7 @@ export async function runPlatformGate(params: {
|
|
|
1199
1250
|
&& !degradedModeBlocks
|
|
1200
1251
|
&& !shouldBlockFromFinding(policyAsCodeBlockingFinding)
|
|
1201
1252
|
&& !shouldBlockFromFinding(coverageBlockingFinding)
|
|
1253
|
+
&& !shouldBlockFromFinding(declarativeRulesClassificationFinding)
|
|
1202
1254
|
&& !shouldBlockFromFinding(activeRulesEmptyForCodeChangesFinding)
|
|
1203
1255
|
&& !shouldBlockFromFinding(effectiveIosTestsQualityFinding)
|
|
1204
1256
|
&& !shouldBlockFromFinding(astIntelligenceDualFinding)
|
|
@@ -1226,8 +1278,10 @@ export async function runPlatformGate(params: {
|
|
|
1226
1278
|
});
|
|
1227
1279
|
const effectiveFindings = sddBlockingFinding
|
|
1228
1280
|
? [
|
|
1281
|
+
...(contextBlockingFinding ? [contextBlockingFinding] : []),
|
|
1229
1282
|
sddBlockingFinding,
|
|
1230
1283
|
...(degradedModeFinding ? [degradedModeFinding] : []),
|
|
1284
|
+
...(preWriteLeaseFinding ? [preWriteLeaseFinding] : []),
|
|
1231
1285
|
...(policyAsCodeBlockingFinding ? [policyAsCodeBlockingFinding] : []),
|
|
1232
1286
|
...(effectiveUnsupportedSkillsMappingFinding ? [effectiveUnsupportedSkillsMappingFinding] : []),
|
|
1233
1287
|
...(effectivePlatformSkillsCoverageFinding ? [effectivePlatformSkillsCoverageFinding] : []),
|
|
@@ -1237,6 +1291,7 @@ export async function runPlatformGate(params: {
|
|
|
1237
1291
|
...(effectiveIosTestsQualityFinding ? [effectiveIosTestsQualityFinding] : []),
|
|
1238
1292
|
...(astIntelligenceDualFinding ? [astIntelligenceDualFinding] : []),
|
|
1239
1293
|
...(coverageBlockingFinding ? [coverageBlockingFinding] : []),
|
|
1294
|
+
...(declarativeRulesClassificationFinding ? [declarativeRulesClassificationFinding] : []),
|
|
1240
1295
|
...brownfieldHotspotFindings,
|
|
1241
1296
|
...tddBddEvaluation.findings,
|
|
1242
1297
|
...findings,
|
|
@@ -1245,16 +1300,21 @@ export async function runPlatformGate(params: {
|
|
|
1245
1300
|
|| effectivePlatformSkillsCoverageFinding
|
|
1246
1301
|
|| effectiveCrossPlatformCriticalFinding
|
|
1247
1302
|
|| effectiveSkillsScopeComplianceFinding
|
|
1303
|
+
|| preWriteLeaseFinding
|
|
1248
1304
|
|| activeRulesEmptyForCodeChangesFinding
|
|
1249
1305
|
|| effectiveIosTestsQualityFinding
|
|
1250
1306
|
|| astIntelligenceDualFinding
|
|
1251
1307
|
|| coverageBlockingFinding
|
|
1308
|
+
|| declarativeRulesClassificationFinding
|
|
1252
1309
|
|| brownfieldHotspotFindings.length > 0
|
|
1253
1310
|
|| policyAsCodeBlockingFinding
|
|
1311
|
+
|| contextBlockingFinding
|
|
1254
1312
|
|| degradedModeFinding
|
|
1255
1313
|
|| tddBddEvaluation.findings.length > 0
|
|
1256
1314
|
? [
|
|
1315
|
+
...(contextBlockingFinding ? [contextBlockingFinding] : []),
|
|
1257
1316
|
...(degradedModeFinding ? [degradedModeFinding] : []),
|
|
1317
|
+
...(preWriteLeaseFinding ? [preWriteLeaseFinding] : []),
|
|
1258
1318
|
...(policyAsCodeBlockingFinding ? [policyAsCodeBlockingFinding] : []),
|
|
1259
1319
|
...(effectiveUnsupportedSkillsMappingFinding ? [effectiveUnsupportedSkillsMappingFinding] : []),
|
|
1260
1320
|
...(effectivePlatformSkillsCoverageFinding ? [effectivePlatformSkillsCoverageFinding] : []),
|
|
@@ -1264,6 +1324,7 @@ export async function runPlatformGate(params: {
|
|
|
1264
1324
|
...(effectiveIosTestsQualityFinding ? [effectiveIosTestsQualityFinding] : []),
|
|
1265
1325
|
...(astIntelligenceDualFinding ? [astIntelligenceDualFinding] : []),
|
|
1266
1326
|
...(coverageBlockingFinding ? [coverageBlockingFinding] : []),
|
|
1327
|
+
...(declarativeRulesClassificationFinding ? [declarativeRulesClassificationFinding] : []),
|
|
1267
1328
|
...brownfieldHotspotFindings,
|
|
1268
1329
|
...tddBddEvaluation.findings,
|
|
1269
1330
|
...findings,
|
|
@@ -1283,7 +1344,9 @@ export async function runPlatformGate(params: {
|
|
|
1283
1344
|
);
|
|
1284
1345
|
const baseGateOutcome =
|
|
1285
1346
|
sddBlockingFinding ||
|
|
1347
|
+
shouldBlockFromFinding(contextBlockingFinding) ||
|
|
1286
1348
|
degradedModeBlocks ||
|
|
1349
|
+
shouldBlockFromFinding(preWriteLeaseFinding) ||
|
|
1287
1350
|
shouldBlockFromFinding(policyAsCodeBlockingFinding) ||
|
|
1288
1351
|
shouldBlockFromFinding(effectiveUnsupportedSkillsMappingFinding) ||
|
|
1289
1352
|
shouldBlockFromFinding(effectivePlatformSkillsCoverageFinding) ||
|
|
@@ -1293,6 +1356,7 @@ export async function runPlatformGate(params: {
|
|
|
1293
1356
|
shouldBlockFromFinding(effectiveIosTestsQualityFinding) ||
|
|
1294
1357
|
hasAstIntelligenceBlockingFinding ||
|
|
1295
1358
|
shouldBlockFromFinding(coverageBlockingFinding) ||
|
|
1359
|
+
shouldBlockFromFinding(declarativeRulesClassificationFinding) ||
|
|
1296
1360
|
brownfieldHotspotFindings.some((finding) => shouldBlockFromFinding(finding)) ||
|
|
1297
1361
|
hasTddBddBlockingFinding
|
|
1298
1362
|
? 'BLOCK'
|
|
@@ -1410,6 +1474,34 @@ export async function runPlatformGate(params: {
|
|
|
1410
1474
|
});
|
|
1411
1475
|
|
|
1412
1476
|
if (gateOutcome === 'BLOCK') {
|
|
1477
|
+
const primaryBlockingFinding = resolvePrimaryBlockingFinding(findingsWithWaiver);
|
|
1478
|
+
if (
|
|
1479
|
+
primaryBlockingFinding &&
|
|
1480
|
+
(
|
|
1481
|
+
params.policy.stage === 'PRE_COMMIT' ||
|
|
1482
|
+
params.policy.stage === 'PRE_PUSH' ||
|
|
1483
|
+
params.policy.stage === 'CI'
|
|
1484
|
+
)
|
|
1485
|
+
) {
|
|
1486
|
+
const notificationResult = dependencies.notifyGateBlocked({
|
|
1487
|
+
repoRoot,
|
|
1488
|
+
stage: params.policy.stage,
|
|
1489
|
+
totalViolations: findingsWithWaiver.length,
|
|
1490
|
+
causeCode: primaryBlockingFinding.code ?? primaryBlockingFinding.ruleId,
|
|
1491
|
+
causeMessage: primaryBlockingFinding.message,
|
|
1492
|
+
remediation:
|
|
1493
|
+
primaryBlockingFinding.expected_fix ??
|
|
1494
|
+
'Corrige la causa bloqueante y reejecuta el gate.',
|
|
1495
|
+
});
|
|
1496
|
+
process.stderr.write(
|
|
1497
|
+
`[pumuki][blocked] code=${primaryBlockingFinding.code ?? primaryBlockingFinding.ruleId} ` +
|
|
1498
|
+
`stage=${params.policy.stage} reason=${primaryBlockingFinding.message}\n`
|
|
1499
|
+
);
|
|
1500
|
+
process.stderr.write(
|
|
1501
|
+
`[pumuki][notification] delivered=${notificationResult.delivered ? 'yes' : 'no'} ` +
|
|
1502
|
+
`reason=${notificationResult.reason}\n`
|
|
1503
|
+
);
|
|
1504
|
+
}
|
|
1413
1505
|
if (params.silent !== true) {
|
|
1414
1506
|
dependencies.printGateFindings(findingsWithWaiver);
|
|
1415
1507
|
}
|
|
@@ -818,7 +818,7 @@ export async function runPreCommitStage(
|
|
|
818
818
|
repoRoot,
|
|
819
819
|
stage: 'PRE_COMMIT',
|
|
820
820
|
scope: {
|
|
821
|
-
kind: '
|
|
821
|
+
kind: 'workingTree',
|
|
822
822
|
},
|
|
823
823
|
});
|
|
824
824
|
if (
|
|
@@ -898,9 +898,7 @@ export async function runPrePushStage(
|
|
|
898
898
|
repoRoot,
|
|
899
899
|
stage: 'PRE_PUSH',
|
|
900
900
|
scope: {
|
|
901
|
-
kind: '
|
|
902
|
-
fromRef: bootstrapBaseRef,
|
|
903
|
-
toRef: 'HEAD',
|
|
901
|
+
kind: 'repoAndStaged',
|
|
904
902
|
},
|
|
905
903
|
});
|
|
906
904
|
if (
|
|
@@ -1006,9 +1004,7 @@ export async function runPrePushStage(
|
|
|
1006
1004
|
repoRoot,
|
|
1007
1005
|
stage: 'PRE_PUSH',
|
|
1008
1006
|
scope: {
|
|
1009
|
-
kind: '
|
|
1010
|
-
fromRef: prePushFromRef,
|
|
1011
|
-
toRef: prePushToRef,
|
|
1007
|
+
kind: 'repoAndStaged',
|
|
1012
1008
|
},
|
|
1013
1009
|
sddDecisionOverride: historicalPrePushSddOverride,
|
|
1014
1010
|
});
|
|
@@ -1064,9 +1060,7 @@ export async function runCiStage(
|
|
|
1064
1060
|
policy: resolved.policy,
|
|
1065
1061
|
policyTrace: resolved.trace,
|
|
1066
1062
|
scope: {
|
|
1067
|
-
kind: '
|
|
1068
|
-
fromRef: ciBaseRef,
|
|
1069
|
-
toRef: 'HEAD',
|
|
1063
|
+
kind: 'repo',
|
|
1070
1064
|
},
|
|
1071
1065
|
});
|
|
1072
1066
|
if (exitCode !== 0) {
|