pumuki 6.3.270 → 6.3.271
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 +4 -0
- package/VERSION +1 -1
- package/core/facts/detectors/text/android.test.ts +538 -0
- package/core/facts/detectors/text/android.ts +436 -0
- package/core/facts/detectors/text/ios.test.ts +328 -1
- package/core/facts/detectors/text/ios.ts +241 -0
- package/core/facts/detectors/typescript/index.test.ts +393 -0
- package/core/facts/detectors/typescript/index.ts +316 -0
- package/core/facts/extractHeuristicFacts.ts +70 -1
- package/core/rules/presets/heuristics/android.test.ts +91 -1
- package/core/rules/presets/heuristics/android.ts +360 -0
- package/core/rules/presets/heuristics/ios.test.ts +54 -1
- package/core/rules/presets/heuristics/ios.ts +243 -2
- package/core/rules/presets/heuristics/typescript.test.ts +50 -2
- package/core/rules/presets/heuristics/typescript.ts +162 -0
- package/docs/operations/RELEASE_NOTES.md +4 -0
- package/integrations/config/skillsDetectorRegistry.ts +501 -0
- package/integrations/config/skillsRuleClassification.ts +127 -3
- package/integrations/git/runPlatformGate.ts +4 -1
- package/integrations/lifecycle/preWriteAutomation.ts +1 -0
- package/integrations/lifecycle/preWriteLease.ts +41 -4
- package/package.json +1 -1
- package/scripts/classify-skills-rules.ts +2 -2
- package/scripts/framework-menu-consumer-actions-lib.ts +9 -9
- package/scripts/framework-menu-consumer-runtime-actions.ts +53 -117
- package/scripts/framework-menu-consumer-runtime-audit.ts +66 -0
- package/scripts/framework-menu-consumer-runtime-menu.ts +4 -4
- package/scripts/framework-menu-gate-lib.ts +86 -1
- package/scripts/framework-menu-layout-data.ts +3 -3
- package/scripts/framework-menu-legacy-audit-render-sections.ts +6 -0
- package/scripts/framework-menu.ts +10 -6
- package/scripts/package-install-smoke-consumer-npm-lib.ts +10 -4
- package/scripts/package-install-smoke-lifecycle-lib.ts +19 -0
|
@@ -3,7 +3,7 @@ import { resolveMappedHeuristicRuleIdsForCompiledRule } from './skillsDetectorRe
|
|
|
3
3
|
|
|
4
4
|
export type SkillsRuleClassificationStatus =
|
|
5
5
|
| 'AST_IMPLEMENTED'
|
|
6
|
-
| '
|
|
6
|
+
| 'IMPLEMENTAR_DETECTOR'
|
|
7
7
|
| 'REQUIERE_ESTUDIO'
|
|
8
8
|
| 'NO_ES_REGLA_DE_CODIGO';
|
|
9
9
|
|
|
@@ -28,7 +28,7 @@ export type SkillsRuleClassificationSummary = {
|
|
|
28
28
|
|
|
29
29
|
const emptyStatusCounts = (): Record<SkillsRuleClassificationStatus, number> => ({
|
|
30
30
|
AST_IMPLEMENTED: 0,
|
|
31
|
-
|
|
31
|
+
IMPLEMENTAR_DETECTOR: 0,
|
|
32
32
|
REQUIERE_ESTUDIO: 0,
|
|
33
33
|
NO_ES_REGLA_DE_CODIGO: 0,
|
|
34
34
|
});
|
|
@@ -55,11 +55,106 @@ const NON_CODE_RULE_PATTERNS: ReadonlyArray<RegExp> = [
|
|
|
55
55
|
/\bci\/cd\b/,
|
|
56
56
|
];
|
|
57
57
|
|
|
58
|
+
const CODE_RULE_WITH_DIRECT_DETECTOR_PATTERNS: ReadonlyArray<RegExp> = [
|
|
59
|
+
/\baccessibility\b/,
|
|
60
|
+
/\bcontentdescription\b/,
|
|
61
|
+
/\bsemantic\b/,
|
|
62
|
+
/\btest tag\b/,
|
|
63
|
+
/\basynctask\b/,
|
|
64
|
+
/\bcallback\b/,
|
|
65
|
+
/\bglobal ?scope\b/,
|
|
66
|
+
/\brunblocking\b/,
|
|
67
|
+
/\bthread\.?sleep\b/,
|
|
68
|
+
/\bdispatch(queue|group|semaphore)\b/,
|
|
69
|
+
/\bforce\b.*\b(unwrap|try|cast)\b/,
|
|
70
|
+
/\btry!\b/,
|
|
71
|
+
/\bas!\b/,
|
|
72
|
+
/\bany\b/,
|
|
73
|
+
/\bconsole\b/,
|
|
74
|
+
/\blog\b/,
|
|
75
|
+
/\bprint\b/,
|
|
76
|
+
/\bsecret\b/,
|
|
77
|
+
/\btoken\b/,
|
|
78
|
+
/\bpassword\b/,
|
|
79
|
+
/\bapi key\b/,
|
|
80
|
+
/\bhardcoded\b/,
|
|
81
|
+
/\bmagic number\b/,
|
|
82
|
+
/\bcolor\b/,
|
|
83
|
+
/\bdimension\b/,
|
|
84
|
+
/\bgod\b/,
|
|
85
|
+
/\blarge\b.*\b(class|component|view|service|function|file)\b/,
|
|
86
|
+
/\bsolid\b/,
|
|
87
|
+
/\bsrp\b/,
|
|
88
|
+
/\bdependency\b.*\binjection\b/,
|
|
89
|
+
/\bconstructor\b.*\b(param|dependency)/,
|
|
90
|
+
/\bsharedpreferences\b/,
|
|
91
|
+
/\buserdefaults\b/,
|
|
92
|
+
/\bappstorage\b/,
|
|
93
|
+
/\bkeychain\b/,
|
|
94
|
+
/\braw sql\b/,
|
|
95
|
+
/\bsql\b.*\b(template|injection)\b/,
|
|
96
|
+
/\bempty catch\b/,
|
|
97
|
+
/\bcatch\b.*\b(empty|silenc)/,
|
|
98
|
+
/\bmock\b/,
|
|
99
|
+
/\bspy\b/,
|
|
100
|
+
/\bjunit4\b/,
|
|
101
|
+
/\bxctassert\b/,
|
|
102
|
+
/\bxctunwrap\b/,
|
|
103
|
+
/\bxctest\b/,
|
|
104
|
+
/\bquick\b/,
|
|
105
|
+
/\bnimble\b/,
|
|
106
|
+
/\bwaitforexpectations\b/,
|
|
107
|
+
/\bexpectation\(description\b/,
|
|
108
|
+
/\bnavigationview\b/,
|
|
109
|
+
/\bgeometryreader\b/,
|
|
110
|
+
/\banyview\b/,
|
|
111
|
+
/\bforegroundcolor\b/,
|
|
112
|
+
/\bcornerradius\b/,
|
|
113
|
+
/\bsheet\b.*\bispresented\b/,
|
|
114
|
+
/\bscrollview\b.*\bshowsindicators\b/,
|
|
115
|
+
/\bforeach\b.*\b(indices|index)\b/,
|
|
116
|
+
/\bonchange\b/,
|
|
117
|
+
/\bontapgesture\b/,
|
|
118
|
+
/\buiscreen\.main\.bounds\b/,
|
|
119
|
+
/\bstring\(format\b/,
|
|
120
|
+
/\bobservableobject\b/,
|
|
121
|
+
/\bstateobject\b/,
|
|
122
|
+
/\bobservable\b/,
|
|
123
|
+
/\blivedata\b/,
|
|
124
|
+
/\bstateflow\b/,
|
|
125
|
+
/\bsharedflow\b/,
|
|
126
|
+
/\bremember\b/,
|
|
127
|
+
/\blaunched(effect)?\b/,
|
|
128
|
+
/\blazy(column|row|vstack|hstack)\b/,
|
|
129
|
+
/\bwindow ?size ?class\b/,
|
|
130
|
+
/\bpadding\b/,
|
|
131
|
+
/\bframe\b/,
|
|
132
|
+
/\bfont\b/,
|
|
133
|
+
/\broute\b/,
|
|
134
|
+
/\bnavigation\b/,
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
const STUDY_BEFORE_DETECTOR_RULE_IDS = new Set<string>([
|
|
138
|
+
'skills.android.guideline.android.adaptive-layouts-responsive-design-windowsizeclass',
|
|
139
|
+
'skills.android.guideline.android.color-contrast-wcag-aa-mi-nimo',
|
|
140
|
+
'skills.android.guideline.android.play-console-production-deployment',
|
|
141
|
+
'skills.backend.guideline.backend.dependency-injection-injectable-inject-providers-array',
|
|
142
|
+
'skills.backend.guideline.backend.event-store-log-de-eventos-para-auditori-a',
|
|
143
|
+
]);
|
|
144
|
+
|
|
145
|
+
const requiresStudyBeforeDetector = (rule: SkillsCompiledRule): boolean =>
|
|
146
|
+
STUDY_BEFORE_DETECTOR_RULE_IDS.has(rule.id);
|
|
147
|
+
|
|
58
148
|
const isNonCodeRule = (rule: SkillsCompiledRule): boolean => {
|
|
59
149
|
const haystack = normalizeText(`${rule.id} ${rule.description} ${rule.sourceSkill}`);
|
|
60
150
|
return NON_CODE_RULE_PATTERNS.some((pattern) => pattern.test(haystack));
|
|
61
151
|
};
|
|
62
152
|
|
|
153
|
+
const hasDirectCodeDetectorCandidate = (rule: SkillsCompiledRule): boolean => {
|
|
154
|
+
const haystack = normalizeText(`${rule.id} ${rule.description} ${rule.sourceSkill}`);
|
|
155
|
+
return CODE_RULE_WITH_DIRECT_DETECTOR_PATTERNS.some((pattern) => pattern.test(haystack));
|
|
156
|
+
};
|
|
157
|
+
|
|
63
158
|
const classifyRule = (rule: SkillsCompiledRule): ClassifiedSkillsRule => {
|
|
64
159
|
const evaluationMode = rule.evaluationMode ?? 'AUTO';
|
|
65
160
|
const astNodeIds = resolveMappedHeuristicRuleIdsForCompiledRule(rule);
|
|
@@ -86,12 +181,27 @@ const classifyRule = (rule: SkillsCompiledRule): ClassifiedSkillsRule => {
|
|
|
86
181
|
sourcePath: rule.sourcePath,
|
|
87
182
|
evaluationMode,
|
|
88
183
|
severity: rule.severity,
|
|
89
|
-
status: '
|
|
184
|
+
status: 'IMPLEMENTAR_DETECTOR',
|
|
90
185
|
reason: 'AUTO rule without AST/nodal detector binding; implement detector mapping.',
|
|
91
186
|
astNodeIds,
|
|
92
187
|
};
|
|
93
188
|
}
|
|
94
189
|
|
|
190
|
+
if (requiresStudyBeforeDetector(rule)) {
|
|
191
|
+
return {
|
|
192
|
+
ruleId: rule.id,
|
|
193
|
+
platform: rule.platform,
|
|
194
|
+
sourceSkill: rule.sourceSkill,
|
|
195
|
+
sourcePath: rule.sourcePath,
|
|
196
|
+
evaluationMode,
|
|
197
|
+
severity: rule.severity,
|
|
198
|
+
status: 'REQUIERE_ESTUDIO',
|
|
199
|
+
reason:
|
|
200
|
+
'Rule has code relevance, but the AST/nodal signal needs explicit design before implementation to avoid poor umbrella detectors.',
|
|
201
|
+
astNodeIds,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
95
205
|
if (isNonCodeRule(rule)) {
|
|
96
206
|
return {
|
|
97
207
|
ruleId: rule.id,
|
|
@@ -106,6 +216,20 @@ const classifyRule = (rule: SkillsCompiledRule): ClassifiedSkillsRule => {
|
|
|
106
216
|
};
|
|
107
217
|
}
|
|
108
218
|
|
|
219
|
+
if (hasDirectCodeDetectorCandidate(rule)) {
|
|
220
|
+
return {
|
|
221
|
+
ruleId: rule.id,
|
|
222
|
+
platform: rule.platform,
|
|
223
|
+
sourceSkill: rule.sourceSkill,
|
|
224
|
+
sourcePath: rule.sourcePath,
|
|
225
|
+
evaluationMode,
|
|
226
|
+
severity: rule.severity,
|
|
227
|
+
status: 'IMPLEMENTAR_DETECTOR',
|
|
228
|
+
reason: 'Code rule has a direct AST/nodal detection surface; implement detector and blocking evidence.',
|
|
229
|
+
astNodeIds,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
109
233
|
return {
|
|
110
234
|
ruleId: rule.id,
|
|
111
235
|
platform: rule.platform,
|
|
@@ -286,6 +286,9 @@ const toSkillsDeclarativeRulesClassificationFinding = (params: {
|
|
|
286
286
|
declarativeRuleIds: ReadonlyArray<string>;
|
|
287
287
|
facts: ReadonlyArray<Fact>;
|
|
288
288
|
}): Finding | undefined => {
|
|
289
|
+
if (process.env.PUMUKI_ENFORCE_DECLARATIVE_RULE_CLASSIFICATION !== '1') {
|
|
290
|
+
return undefined;
|
|
291
|
+
}
|
|
289
292
|
if (params.declarativeRuleIds.length === 0) {
|
|
290
293
|
return undefined;
|
|
291
294
|
}
|
|
@@ -302,7 +305,7 @@ const toSkillsDeclarativeRulesClassificationFinding = (params: {
|
|
|
302
305
|
message:
|
|
303
306
|
`Skills declarative rules require AST/nodal classification at ${params.stage}: ` +
|
|
304
307
|
`total=${params.declarativeRuleIds.length} sample_rule_ids=[${sampleRuleIds}]. ` +
|
|
305
|
-
'Classify every rule as
|
|
308
|
+
'Classify every rule as IMPLEMENTAR_DETECTOR, REQUIERE_ESTUDIO, or NO_ES_REGLA_DE_CODIGO; code skills cannot remain as hidden declarative/advisory rules.',
|
|
306
309
|
filePath: 'skills.lock.json',
|
|
307
310
|
matchedBy: 'SkillsDeclarativeRulesClassificationGuard',
|
|
308
311
|
source: 'skills-declarative-rules-classification',
|
|
@@ -250,6 +250,7 @@ export const buildPreWriteAutomationTrace = async (params: {
|
|
|
250
250
|
const leaseResult = activeDependencies.writePreWriteLease({
|
|
251
251
|
repoRoot: params.repoRoot,
|
|
252
252
|
git,
|
|
253
|
+
allowExistingCodeChanges: true,
|
|
253
254
|
});
|
|
254
255
|
trace.actions.push({
|
|
255
256
|
action: 'write_prewrite_lease',
|
|
@@ -8,6 +8,7 @@ import { DEFAULT_FACT_FILE_EXTENSIONS } from '../git/runPlatformGateFacts';
|
|
|
8
8
|
export type PreWriteLease = {
|
|
9
9
|
version: '1';
|
|
10
10
|
kind: 'pumuki-pre-write-lease';
|
|
11
|
+
validation_mode?: 'clean-prewrite' | 'validated-diff';
|
|
11
12
|
repo_root: string;
|
|
12
13
|
head: string;
|
|
13
14
|
branch: string | null;
|
|
@@ -119,6 +120,10 @@ const parseLease = (raw: string): PreWriteLease | undefined => {
|
|
|
119
120
|
return {
|
|
120
121
|
version: '1',
|
|
121
122
|
kind: 'pumuki-pre-write-lease',
|
|
123
|
+
validation_mode:
|
|
124
|
+
value.validation_mode === 'validated-diff' || value.validation_mode === 'clean-prewrite'
|
|
125
|
+
? value.validation_mode
|
|
126
|
+
: undefined,
|
|
122
127
|
repo_root: value.repo_root,
|
|
123
128
|
head: value.head,
|
|
124
129
|
branch: typeof value.branch === 'string' ? value.branch : null,
|
|
@@ -186,6 +191,33 @@ export const readPreWriteLeaseStatus = (params: {
|
|
|
186
191
|
};
|
|
187
192
|
}
|
|
188
193
|
|
|
194
|
+
if (lease.validation_mode === 'validated-diff') {
|
|
195
|
+
const expectedPaths = [...lease.pre_change_code_paths].sort((left, right) => left.localeCompare(right));
|
|
196
|
+
const currentPaths = [...changedCodePaths].sort((left, right) => left.localeCompare(right));
|
|
197
|
+
if (
|
|
198
|
+
lease.pre_change_code_changes_count !== expectedPaths.length ||
|
|
199
|
+
expectedPaths.length !== currentPaths.length ||
|
|
200
|
+
expectedPaths.some((path, index) => path !== currentPaths[index])
|
|
201
|
+
) {
|
|
202
|
+
return {
|
|
203
|
+
valid: false,
|
|
204
|
+
code: 'PRE_WRITE_LEASE_DIRTY_AT_ISSUE',
|
|
205
|
+
path,
|
|
206
|
+
lease,
|
|
207
|
+
changedCodePaths,
|
|
208
|
+
message: 'PRE_WRITE lease was issued for a different validated code diff.',
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
valid: true,
|
|
214
|
+
code: 'PRE_WRITE_LEASE_VALID',
|
|
215
|
+
path,
|
|
216
|
+
lease,
|
|
217
|
+
changedCodePaths,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
189
221
|
if (lease.pre_change_code_changes_count !== 0 || lease.pre_change_code_paths.length !== 0) {
|
|
190
222
|
return {
|
|
191
223
|
valid: false,
|
|
@@ -210,10 +242,11 @@ export const writePreWriteLease = (params: {
|
|
|
210
242
|
repoRoot: string;
|
|
211
243
|
git: Pick<IGitService, 'runGit'>;
|
|
212
244
|
now?: Date;
|
|
245
|
+
allowExistingCodeChanges?: boolean;
|
|
213
246
|
}): PreWriteLeaseWriteResult => {
|
|
214
247
|
const path = resolvePreWriteLeasePath(params.repoRoot);
|
|
215
248
|
const changedCodePaths = collectPreWriteCodeChangePaths(params);
|
|
216
|
-
if (changedCodePaths.length > 0) {
|
|
249
|
+
if (changedCodePaths.length > 0 && !params.allowExistingCodeChanges) {
|
|
217
250
|
return {
|
|
218
251
|
path,
|
|
219
252
|
written: false,
|
|
@@ -229,13 +262,14 @@ export const writePreWriteLease = (params: {
|
|
|
229
262
|
const lease: PreWriteLease = {
|
|
230
263
|
version: '1',
|
|
231
264
|
kind: 'pumuki-pre-write-lease',
|
|
265
|
+
validation_mode: changedCodePaths.length > 0 ? 'validated-diff' : 'clean-prewrite',
|
|
232
266
|
repo_root: params.repoRoot,
|
|
233
267
|
head: resolveHead(params),
|
|
234
268
|
branch: resolveBranch(params),
|
|
235
269
|
issued_at: issuedAt.toISOString(),
|
|
236
270
|
expires_at: new Date(issuedAt.getTime() + PRE_WRITE_LEASE_TTL_MS).toISOString(),
|
|
237
|
-
pre_change_code_changes_count:
|
|
238
|
-
pre_change_code_paths:
|
|
271
|
+
pre_change_code_changes_count: changedCodePaths.length,
|
|
272
|
+
pre_change_code_paths: changedCodePaths,
|
|
239
273
|
};
|
|
240
274
|
mkdirSync(dirname(path), { recursive: true });
|
|
241
275
|
writeFileSync(path, `${JSON.stringify(lease, null, 2)}\n`, 'utf8');
|
|
@@ -245,7 +279,10 @@ export const writePreWriteLease = (params: {
|
|
|
245
279
|
valid: true,
|
|
246
280
|
code: 'PRE_WRITE_LEASE_WRITTEN',
|
|
247
281
|
changedCodePaths,
|
|
248
|
-
message:
|
|
282
|
+
message:
|
|
283
|
+
changedCodePaths.length > 0
|
|
284
|
+
? 'PRE_WRITE lease written for validated code diff.'
|
|
285
|
+
: 'PRE_WRITE lease written for clean code state.',
|
|
249
286
|
lease,
|
|
250
287
|
};
|
|
251
288
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pumuki",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.271",
|
|
4
4
|
"description": "Enterprise-grade AST Intelligence System with multi-platform support (iOS, Android, Backend, Frontend) and Feature-First + DDD + Clean Architecture enforcement. Includes dynamic violations API for intelligent querying.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -19,8 +19,8 @@ process.stdout.write(`${JSON.stringify({
|
|
|
19
19
|
AST_IMPLEMENTED: summary.rules
|
|
20
20
|
.filter((rule) => rule.status === 'AST_IMPLEMENTED')
|
|
21
21
|
.slice(0, 12),
|
|
22
|
-
|
|
23
|
-
.filter((rule) => rule.status === '
|
|
22
|
+
IMPLEMENTAR_DETECTOR: summary.rules
|
|
23
|
+
.filter((rule) => rule.status === 'IMPLEMENTAR_DETECTOR')
|
|
24
24
|
.slice(0, 12),
|
|
25
25
|
REQUIERE_ESTUDIO: summary.rules
|
|
26
26
|
.filter((rule) => rule.status === 'REQUIERE_ESTUDIO')
|
|
@@ -26,22 +26,22 @@ export const createConsumerLegacyMenuActions = (
|
|
|
26
26
|
return [
|
|
27
27
|
{
|
|
28
28
|
id: '1',
|
|
29
|
-
label: '
|
|
29
|
+
label: 'Full audit (repo analysis)',
|
|
30
30
|
execute: params.runFullAudit,
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
id: '2',
|
|
34
|
-
label: '
|
|
34
|
+
label: 'Strict REPO+STAGING (CI/CD)',
|
|
35
35
|
execute: params.runStrictRepoAndStaged,
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
38
|
id: '3',
|
|
39
|
-
label: '
|
|
39
|
+
label: 'Strict STAGING only (dev)',
|
|
40
40
|
execute: params.runStrictStagedOnly,
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
id: '4',
|
|
44
|
-
label: '
|
|
44
|
+
label: 'Standard CRITICAL/HIGH',
|
|
45
45
|
execute: params.runStandardCriticalHigh,
|
|
46
46
|
},
|
|
47
47
|
{
|
|
@@ -66,27 +66,27 @@ export const createConsumerLegacyMenuActions = (
|
|
|
66
66
|
},
|
|
67
67
|
{
|
|
68
68
|
id: '5',
|
|
69
|
-
label: '
|
|
69
|
+
label: 'Pattern checks',
|
|
70
70
|
execute: params.runPatternChecks,
|
|
71
71
|
},
|
|
72
72
|
{
|
|
73
73
|
id: '6',
|
|
74
|
-
label: '
|
|
74
|
+
label: 'ESLint Admin+Web',
|
|
75
75
|
execute: params.runEslintAudit,
|
|
76
76
|
},
|
|
77
77
|
{
|
|
78
78
|
id: '7',
|
|
79
|
-
label: '
|
|
79
|
+
label: 'AST Intelligence',
|
|
80
80
|
execute: params.runAstIntelligence,
|
|
81
81
|
},
|
|
82
82
|
{
|
|
83
83
|
id: '8',
|
|
84
|
-
label: 'Export
|
|
84
|
+
label: 'Export Markdown',
|
|
85
85
|
execute: params.runExportMarkdown,
|
|
86
86
|
},
|
|
87
87
|
{
|
|
88
88
|
id: '9',
|
|
89
|
-
label: '
|
|
89
|
+
label: 'File diagnostics',
|
|
90
90
|
execute: params.runFileDiagnostics,
|
|
91
91
|
},
|
|
92
92
|
{
|
|
@@ -12,7 +12,12 @@ import {
|
|
|
12
12
|
renderConsumerRuntimePatternChecks,
|
|
13
13
|
renderConsumerRuntimeSummary,
|
|
14
14
|
} from './framework-menu-consumer-runtime-audit';
|
|
15
|
-
import type {
|
|
15
|
+
import type {
|
|
16
|
+
ConsumerAction,
|
|
17
|
+
ConsumerRuntimeEmitNotification,
|
|
18
|
+
ConsumerRuntimeGateResult,
|
|
19
|
+
ConsumerRuntimeWrite,
|
|
20
|
+
} from './framework-menu-consumer-runtime-types';
|
|
16
21
|
|
|
17
22
|
type ConsumerRuntimeActionDependencies = {
|
|
18
23
|
repoRoot: string;
|
|
@@ -61,49 +66,54 @@ const runConsumerRuntimePreflight = async (
|
|
|
61
66
|
);
|
|
62
67
|
};
|
|
63
68
|
|
|
69
|
+
const renderSummaryAfterGate = (
|
|
70
|
+
dependencies: Pick<
|
|
71
|
+
ConsumerRuntimeActionDependencies,
|
|
72
|
+
| 'clearSummaryOverride'
|
|
73
|
+
| 'emitNotification'
|
|
74
|
+
| 'getSummaryOverride'
|
|
75
|
+
| 'repoRoot'
|
|
76
|
+
| 'setSummaryOverride'
|
|
77
|
+
| 'useColor'
|
|
78
|
+
| 'write'
|
|
79
|
+
>,
|
|
80
|
+
gateResult: ConsumerRuntimeGateResult | void
|
|
81
|
+
) => {
|
|
82
|
+
if (gateResult?.blocked) {
|
|
83
|
+
dependencies.setSummaryOverride(
|
|
84
|
+
buildConsumerRuntimeBlockedSummary(gateResult.blocked)
|
|
85
|
+
);
|
|
86
|
+
} else {
|
|
87
|
+
dependencies.clearSummaryOverride();
|
|
88
|
+
}
|
|
89
|
+
const summary = renderConsumerRuntimeSummary({
|
|
90
|
+
repoRoot: dependencies.repoRoot,
|
|
91
|
+
write: dependencies.write,
|
|
92
|
+
useColor: dependencies.useColor,
|
|
93
|
+
summaryOverride: dependencies.getSummaryOverride(),
|
|
94
|
+
});
|
|
95
|
+
notifyConsumerRuntimeAuditSummary(
|
|
96
|
+
{
|
|
97
|
+
emitNotification: dependencies.emitNotification,
|
|
98
|
+
repoRoot: dependencies.repoRoot,
|
|
99
|
+
},
|
|
100
|
+
summary
|
|
101
|
+
);
|
|
102
|
+
return summary;
|
|
103
|
+
};
|
|
104
|
+
|
|
64
105
|
export const createConsumerRuntimeActions = (
|
|
65
106
|
dependencies: ConsumerRuntimeActionDependencies
|
|
66
107
|
): ReadonlyArray<ConsumerAction> =>
|
|
67
108
|
createConsumerLegacyMenuActions({
|
|
68
109
|
runFullAudit: async () => {
|
|
69
110
|
await runConsumerRuntimePreflight(dependencies, 'PRE_COMMIT');
|
|
70
|
-
await dependencies.runRepoGate();
|
|
71
|
-
dependencies.clearSummaryOverride();
|
|
72
|
-
notifyConsumerRuntimeAuditSummary(
|
|
73
|
-
{
|
|
74
|
-
emitNotification: dependencies.emitNotification,
|
|
75
|
-
repoRoot: dependencies.repoRoot,
|
|
76
|
-
},
|
|
77
|
-
renderConsumerRuntimeSummary({
|
|
78
|
-
repoRoot: dependencies.repoRoot,
|
|
79
|
-
write: dependencies.write,
|
|
80
|
-
useColor: dependencies.useColor,
|
|
81
|
-
})
|
|
82
|
-
);
|
|
111
|
+
renderSummaryAfterGate(dependencies, await dependencies.runRepoGate());
|
|
83
112
|
},
|
|
84
113
|
runStrictRepoAndStaged: async () => {
|
|
85
114
|
await runConsumerRuntimePreflight(dependencies, 'PRE_PUSH');
|
|
86
115
|
const gateResult = await dependencies.runRepoAndStagedGate();
|
|
87
|
-
|
|
88
|
-
dependencies.setSummaryOverride(
|
|
89
|
-
buildConsumerRuntimeBlockedSummary(gateResult.blocked)
|
|
90
|
-
);
|
|
91
|
-
} else {
|
|
92
|
-
dependencies.clearSummaryOverride();
|
|
93
|
-
}
|
|
94
|
-
const summary = renderConsumerRuntimeSummary({
|
|
95
|
-
repoRoot: dependencies.repoRoot,
|
|
96
|
-
write: dependencies.write,
|
|
97
|
-
useColor: dependencies.useColor,
|
|
98
|
-
summaryOverride: dependencies.getSummaryOverride(),
|
|
99
|
-
});
|
|
100
|
-
notifyConsumerRuntimeAuditSummary(
|
|
101
|
-
{
|
|
102
|
-
emitNotification: dependencies.emitNotification,
|
|
103
|
-
repoRoot: dependencies.repoRoot,
|
|
104
|
-
},
|
|
105
|
-
summary
|
|
106
|
-
);
|
|
116
|
+
const summary = renderSummaryAfterGate(dependencies, gateResult);
|
|
107
117
|
if (
|
|
108
118
|
!gateResult?.blocked
|
|
109
119
|
&& (summary.outcome === 'PASS' || summary.outcome === 'WARN')
|
|
@@ -113,109 +123,35 @@ export const createConsumerRuntimeActions = (
|
|
|
113
123
|
},
|
|
114
124
|
runStrictStagedOnly: async () => {
|
|
115
125
|
await runConsumerRuntimePreflight(dependencies, 'PRE_COMMIT');
|
|
116
|
-
await dependencies.runStagedGate();
|
|
117
|
-
dependencies.clearSummaryOverride();
|
|
118
|
-
const summary = renderConsumerRuntimeSummary({
|
|
119
|
-
repoRoot: dependencies.repoRoot,
|
|
120
|
-
write: dependencies.write,
|
|
121
|
-
useColor: dependencies.useColor,
|
|
122
|
-
});
|
|
123
|
-
notifyConsumerRuntimeAuditSummary(
|
|
124
|
-
{
|
|
125
|
-
emitNotification: dependencies.emitNotification,
|
|
126
|
-
repoRoot: dependencies.repoRoot,
|
|
127
|
-
},
|
|
128
|
-
summary
|
|
129
|
-
);
|
|
126
|
+
const summary = renderSummaryAfterGate(dependencies, await dependencies.runStagedGate());
|
|
130
127
|
printConsumerRuntimeEmptyScopeHint({ write: dependencies.write }, summary, 'staged');
|
|
131
128
|
},
|
|
132
129
|
runStandardCriticalHigh: async () => {
|
|
133
130
|
await runConsumerRuntimePreflight(dependencies, 'PRE_PUSH');
|
|
134
|
-
await dependencies.runWorkingTreeGate();
|
|
135
|
-
dependencies
|
|
136
|
-
const summary = renderConsumerRuntimeSummary({
|
|
137
|
-
repoRoot: dependencies.repoRoot,
|
|
138
|
-
write: dependencies.write,
|
|
139
|
-
useColor: dependencies.useColor,
|
|
140
|
-
});
|
|
141
|
-
notifyConsumerRuntimeAuditSummary(
|
|
142
|
-
{
|
|
143
|
-
emitNotification: dependencies.emitNotification,
|
|
144
|
-
repoRoot: dependencies.repoRoot,
|
|
145
|
-
},
|
|
146
|
-
summary
|
|
147
|
-
);
|
|
131
|
+
const gateResult = await dependencies.runWorkingTreeGate();
|
|
132
|
+
const summary = renderSummaryAfterGate(dependencies, gateResult);
|
|
148
133
|
if (summary.outcome === 'PASS' || summary.outcome === 'WARN') {
|
|
149
134
|
printPrePushTrackedEvidenceDiskHint({ write: dependencies.write });
|
|
150
135
|
}
|
|
151
136
|
printConsumerRuntimeEmptyScopeHint({ write: dependencies.write }, summary, 'workingTree');
|
|
152
137
|
},
|
|
153
138
|
runEngineStagedNoPreflight: async () => {
|
|
154
|
-
await dependencies.runStagedGate();
|
|
155
|
-
dependencies.clearSummaryOverride();
|
|
156
|
-
const summary = renderConsumerRuntimeSummary({
|
|
157
|
-
repoRoot: dependencies.repoRoot,
|
|
158
|
-
write: dependencies.write,
|
|
159
|
-
useColor: dependencies.useColor,
|
|
160
|
-
});
|
|
161
|
-
notifyConsumerRuntimeAuditSummary(
|
|
162
|
-
{
|
|
163
|
-
emitNotification: dependencies.emitNotification,
|
|
164
|
-
repoRoot: dependencies.repoRoot,
|
|
165
|
-
},
|
|
166
|
-
summary
|
|
167
|
-
);
|
|
139
|
+
const summary = renderSummaryAfterGate(dependencies, await dependencies.runStagedGate());
|
|
168
140
|
printConsumerRuntimeEmptyScopeHint({ write: dependencies.write }, summary, 'staged');
|
|
169
141
|
},
|
|
170
142
|
runEngineUnstagedNoPreflight: async () => {
|
|
171
|
-
await dependencies.runUnstagedGate();
|
|
172
|
-
dependencies.clearSummaryOverride();
|
|
173
|
-
const summary = renderConsumerRuntimeSummary({
|
|
174
|
-
repoRoot: dependencies.repoRoot,
|
|
175
|
-
write: dependencies.write,
|
|
176
|
-
useColor: dependencies.useColor,
|
|
177
|
-
});
|
|
178
|
-
notifyConsumerRuntimeAuditSummary(
|
|
179
|
-
{
|
|
180
|
-
emitNotification: dependencies.emitNotification,
|
|
181
|
-
repoRoot: dependencies.repoRoot,
|
|
182
|
-
},
|
|
183
|
-
summary
|
|
184
|
-
);
|
|
143
|
+
const summary = renderSummaryAfterGate(dependencies, await dependencies.runUnstagedGate());
|
|
185
144
|
printConsumerRuntimeEmptyScopeHint({ write: dependencies.write }, summary, 'unstaged');
|
|
186
145
|
},
|
|
187
146
|
runEngineStagedAndUnstagedNoPreflight: async () => {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
repoRoot: dependencies.repoRoot,
|
|
192
|
-
write: dependencies.write,
|
|
193
|
-
useColor: dependencies.useColor,
|
|
194
|
-
});
|
|
195
|
-
notifyConsumerRuntimeAuditSummary(
|
|
196
|
-
{
|
|
197
|
-
emitNotification: dependencies.emitNotification,
|
|
198
|
-
repoRoot: dependencies.repoRoot,
|
|
199
|
-
},
|
|
200
|
-
summary
|
|
147
|
+
const summary = renderSummaryAfterGate(
|
|
148
|
+
dependencies,
|
|
149
|
+
await dependencies.runWorkingTreePreCommitGate()
|
|
201
150
|
);
|
|
202
151
|
printConsumerRuntimeEmptyScopeHint({ write: dependencies.write }, summary, 'workingTree');
|
|
203
152
|
},
|
|
204
153
|
runEngineFullRepoNoPreflight: async () => {
|
|
205
|
-
await dependencies.runRepoGate();
|
|
206
|
-
dependencies.clearSummaryOverride();
|
|
207
|
-
const summary = renderConsumerRuntimeSummary({
|
|
208
|
-
repoRoot: dependencies.repoRoot,
|
|
209
|
-
write: dependencies.write,
|
|
210
|
-
useColor: dependencies.useColor,
|
|
211
|
-
});
|
|
212
|
-
notifyConsumerRuntimeAuditSummary(
|
|
213
|
-
{
|
|
214
|
-
emitNotification: dependencies.emitNotification,
|
|
215
|
-
repoRoot: dependencies.repoRoot,
|
|
216
|
-
},
|
|
217
|
-
summary
|
|
218
|
-
);
|
|
154
|
+
renderSummaryAfterGate(dependencies, await dependencies.runRepoGate());
|
|
219
155
|
},
|
|
220
156
|
runPatternChecks: async () => {
|
|
221
157
|
dependencies.write(`\n${renderConsumerRuntimePatternChecks(dependencies.repoRoot)}\n`);
|
|
@@ -33,6 +33,9 @@ export const renderConsumerRuntimeSummary = (
|
|
|
33
33
|
dependencies: ConsumerRuntimeSummaryDependencies
|
|
34
34
|
): FrameworkMenuEvidenceSummary => {
|
|
35
35
|
const summary = dependencies.summaryOverride ?? readEvidenceSummaryForMenu(dependencies.repoRoot);
|
|
36
|
+
const canonicalSummary = dependencies.summaryOverride
|
|
37
|
+
? readEvidenceSummaryForMenu(dependencies.repoRoot)
|
|
38
|
+
: null;
|
|
36
39
|
const lines = [
|
|
37
40
|
formatEvidenceSummaryForMenu(summary),
|
|
38
41
|
'',
|
|
@@ -41,6 +44,13 @@ export const renderConsumerRuntimeSummary = (
|
|
|
41
44
|
`Files affected: ${summary.filesAffected}`,
|
|
42
45
|
];
|
|
43
46
|
|
|
47
|
+
const driftLines = canonicalSummary
|
|
48
|
+
? buildConsumerRuntimeEvidenceDriftLines(summary, canonicalSummary)
|
|
49
|
+
: [];
|
|
50
|
+
if (driftLines.length > 0) {
|
|
51
|
+
lines.push('', ...driftLines);
|
|
52
|
+
}
|
|
53
|
+
|
|
44
54
|
if (summary.status === 'ok' && summary.topFindings.length > 0) {
|
|
45
55
|
const primaryFinding = summary.topFindings[0];
|
|
46
56
|
lines.push('', `Primary block: ${primaryFinding.ruleId}`);
|
|
@@ -74,6 +84,62 @@ export const renderConsumerRuntimeSummary = (
|
|
|
74
84
|
return summary;
|
|
75
85
|
};
|
|
76
86
|
|
|
87
|
+
const toPlatformMap = (
|
|
88
|
+
summary: FrameworkMenuEvidenceSummary
|
|
89
|
+
): Map<string, number> =>
|
|
90
|
+
new Map((summary.platformAuditRows ?? []).map((row) => [row.platform, row.violations]));
|
|
91
|
+
|
|
92
|
+
const buildConsumerRuntimeEvidenceDriftLines = (
|
|
93
|
+
gateSummary: FrameworkMenuEvidenceSummary,
|
|
94
|
+
canonicalSummary: FrameworkMenuEvidenceSummary
|
|
95
|
+
): string[] => {
|
|
96
|
+
const drifts: string[] = [];
|
|
97
|
+
if (canonicalSummary.status !== 'ok') {
|
|
98
|
+
return [
|
|
99
|
+
`⚠ Menu/gate evidence drift: canonical_evidence=${canonicalSummary.status}; using gate blocked summary`,
|
|
100
|
+
];
|
|
101
|
+
}
|
|
102
|
+
if (gateSummary.stage !== canonicalSummary.stage) {
|
|
103
|
+
drifts.push(`stage gate=${gateSummary.stage ?? 'null'} evidence=${canonicalSummary.stage ?? 'null'}`);
|
|
104
|
+
}
|
|
105
|
+
if (gateSummary.outcome !== canonicalSummary.outcome) {
|
|
106
|
+
drifts.push(`outcome gate=${gateSummary.outcome ?? 'null'} evidence=${canonicalSummary.outcome ?? 'null'}`);
|
|
107
|
+
}
|
|
108
|
+
if (gateSummary.totalFindings !== canonicalSummary.totalFindings) {
|
|
109
|
+
drifts.push(`totalFindings gate=${gateSummary.totalFindings} evidence=${canonicalSummary.totalFindings}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const severityKeys = ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW'] as const;
|
|
113
|
+
for (const key of severityKeys) {
|
|
114
|
+
const gateValue = gateSummary.byEnterpriseSeverity?.[key] ?? 0;
|
|
115
|
+
const evidenceValue = canonicalSummary.byEnterpriseSeverity?.[key] ?? 0;
|
|
116
|
+
if (gateValue !== evidenceValue) {
|
|
117
|
+
drifts.push(`severity.${key} gate=${gateValue} evidence=${evidenceValue}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const gatePlatforms = toPlatformMap(gateSummary);
|
|
122
|
+
const evidencePlatforms = toPlatformMap(canonicalSummary);
|
|
123
|
+
const platformNames = [...new Set([...gatePlatforms.keys(), ...evidencePlatforms.keys()])].sort();
|
|
124
|
+
for (const platform of platformNames) {
|
|
125
|
+
const gateValue = gatePlatforms.get(platform) ?? 0;
|
|
126
|
+
const evidenceValue = evidencePlatforms.get(platform) ?? 0;
|
|
127
|
+
if (gateValue !== evidenceValue) {
|
|
128
|
+
drifts.push(`platform.${platform} gate=${gateValue} evidence=${evidenceValue}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (drifts.length === 0) {
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
return [
|
|
136
|
+
'⚠ Menu/gate evidence drift detected',
|
|
137
|
+
'Diagnostic code: MENU_GATE_EVIDENCE_DRIFT',
|
|
138
|
+
'Diagnostic severity: HIGH',
|
|
139
|
+
...drifts.map((drift) => `- ${drift}`),
|
|
140
|
+
];
|
|
141
|
+
};
|
|
142
|
+
|
|
77
143
|
export const printPrePushTrackedEvidenceDiskHint = (params: {
|
|
78
144
|
write: ConsumerRuntimeSummaryDependencies['write'];
|
|
79
145
|
}): void => {
|