@miphamai/cli 0.32.6 → 0.33.0
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/package.json +1 -1
- package/src/core/auto-corrector.ts +220 -0
- package/src/core/auto-memory.ts +29 -0
- package/src/core/engine.ts +97 -1
- package/src/core/error-signature-db.ts +324 -0
- package/src/core/immune-memory-gc.ts +187 -0
- package/src/core/instructions.ts +14 -0
- package/src/core/meta-rule-engine.ts +602 -0
- package/src/core/preflight-checker.ts +218 -0
- package/src/tools/file/grep.ts +9 -1
- package/src/ui/command-picker.tsx +34 -1
- package/src/ui/commands.ts +268 -0
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SIS Phase 3 (RSI Level 3): Meta-Rule Engine — Rules About Rules
|
|
3
|
+
*
|
|
4
|
+
* The recursive core of CRSI: analyzes accumulated SIS data to
|
|
5
|
+
* improve the rule-generation system itself.
|
|
6
|
+
*
|
|
7
|
+
* Meta-rule types:
|
|
8
|
+
* 1. Threshold Tuning — adjust confidence thresholds based on outcomes
|
|
9
|
+
* 2. Strategy Optimization — which fixStrategy works best per category?
|
|
10
|
+
* 3. Cross-Rule Patterns — discover relationships across unrelated rules
|
|
11
|
+
* 4. Category Emergence — detect new error categories not in the predefined set
|
|
12
|
+
*
|
|
13
|
+
* Flow:
|
|
14
|
+
* ErrorSignatureDB + EffectivenessTracker data
|
|
15
|
+
* → MetaRuleEngine.analyze()
|
|
16
|
+
* → MetaRule[] (actionable suggestions for rule system improvement)
|
|
17
|
+
* → /crsi meta displays + optionally auto-applies safe meta-rules
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { ErrorSignatureDB, ErrorSignature } from './error-signature-db.js'
|
|
21
|
+
import type { EffectivenessTracker } from '../agent/effectiveness-tracker.js'
|
|
22
|
+
|
|
23
|
+
// ── Types ──
|
|
24
|
+
|
|
25
|
+
export type MetaRuleCategory =
|
|
26
|
+
'threshold-tuning' | 'strategy-optimization' | 'cross-rule-pattern' | 'category-emergence'
|
|
27
|
+
|
|
28
|
+
export type MetaRuleConfidence = 'high' | 'medium' | 'low'
|
|
29
|
+
|
|
30
|
+
export interface MetaRule {
|
|
31
|
+
/** Unique identifier */
|
|
32
|
+
id: string
|
|
33
|
+
/** Type of meta-rule */
|
|
34
|
+
category: MetaRuleCategory
|
|
35
|
+
/** Human-readable title */
|
|
36
|
+
title: string
|
|
37
|
+
/** Detailed explanation */
|
|
38
|
+
description: string
|
|
39
|
+
/** Suggested action */
|
|
40
|
+
recommendation: string
|
|
41
|
+
/** Whether this can be auto-applied safely */
|
|
42
|
+
autoApplicable: boolean
|
|
43
|
+
/** Statistical evidence supporting this meta-rule */
|
|
44
|
+
evidence: MetaEvidence
|
|
45
|
+
/** Confidence level */
|
|
46
|
+
confidence: MetaRuleConfidence
|
|
47
|
+
/** When this meta-rule was generated */
|
|
48
|
+
generatedAt: string
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface MetaEvidence {
|
|
52
|
+
/** Number of data points supporting this conclusion */
|
|
53
|
+
sampleSize: number
|
|
54
|
+
/** Key metrics */
|
|
55
|
+
metrics: Record<string, number>
|
|
56
|
+
/** Supporting rule/signature IDs */
|
|
57
|
+
relatedIds: string[]
|
|
58
|
+
/** Raw summary for display */
|
|
59
|
+
summary: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface MetaAnalysis {
|
|
63
|
+
/** Discovered meta-rules */
|
|
64
|
+
metaRules: MetaRule[]
|
|
65
|
+
/** Overall system health assessment */
|
|
66
|
+
systemHealth: SystemHealth
|
|
67
|
+
/** When the analysis was performed */
|
|
68
|
+
analyzedAt: string
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface SystemHealth {
|
|
72
|
+
/** Overall CRSI health score (0-100) */
|
|
73
|
+
score: number
|
|
74
|
+
/** Component-level scores */
|
|
75
|
+
components: {
|
|
76
|
+
errorSignatures: number // 0-100
|
|
77
|
+
preflightPrevention: number
|
|
78
|
+
autoCorrection: number
|
|
79
|
+
immuneMemory: number
|
|
80
|
+
ruleEffectiveness: number
|
|
81
|
+
}
|
|
82
|
+
/** Top-level assessment */
|
|
83
|
+
assessment: string
|
|
84
|
+
/** Recommended actions */
|
|
85
|
+
recommendations: string[]
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ── Constants ──
|
|
89
|
+
|
|
90
|
+
/** Minimum data points needed to generate a meta-rule */
|
|
91
|
+
const MIN_SAMPLE_SIZE = 5
|
|
92
|
+
|
|
93
|
+
// ── Engine ──
|
|
94
|
+
|
|
95
|
+
export class MetaRuleEngine {
|
|
96
|
+
private errorDB?: ErrorSignatureDB
|
|
97
|
+
private tracker?: EffectivenessTracker
|
|
98
|
+
|
|
99
|
+
constructor(errorDB?: ErrorSignatureDB, tracker?: EffectivenessTracker) {
|
|
100
|
+
this.errorDB = errorDB
|
|
101
|
+
this.tracker = tracker
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Set or update the data sources after construction. */
|
|
105
|
+
setErrorDB(db: ErrorSignatureDB): void {
|
|
106
|
+
this.errorDB = db
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
setTracker(tracker: EffectivenessTracker): void {
|
|
110
|
+
this.tracker = tracker
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Run a full meta-analysis across all available data sources.
|
|
115
|
+
*
|
|
116
|
+
* @returns MetaAnalysis with discovered meta-rules and system health
|
|
117
|
+
*/
|
|
118
|
+
analyze(): MetaAnalysis {
|
|
119
|
+
const metaRules: MetaRule[] = []
|
|
120
|
+
|
|
121
|
+
// Phase 1: Threshold tuning recommendations
|
|
122
|
+
metaRules.push(...this.analyzeThresholds())
|
|
123
|
+
|
|
124
|
+
// Phase 2: Strategy effectiveness per category
|
|
125
|
+
metaRules.push(...this.analyzeStrategies())
|
|
126
|
+
|
|
127
|
+
// Phase 3: Cross-rule pattern discovery
|
|
128
|
+
metaRules.push(...this.discoverCrossRulePatterns())
|
|
129
|
+
|
|
130
|
+
// Phase 4: Emergent category detection
|
|
131
|
+
metaRules.push(...this.detectEmergentCategories())
|
|
132
|
+
|
|
133
|
+
// Sort by confidence (high → medium → low), then by category severity
|
|
134
|
+
metaRules.sort((a, b) => {
|
|
135
|
+
const confOrder = { high: 0, medium: 1, low: 2 }
|
|
136
|
+
const confDiff = confOrder[a.confidence] - confOrder[b.confidence]
|
|
137
|
+
if (confDiff !== 0) return confDiff
|
|
138
|
+
return b.evidence.sampleSize - a.evidence.sampleSize
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
const systemHealth = this.computeSystemHealth(metaRules)
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
metaRules,
|
|
145
|
+
systemHealth,
|
|
146
|
+
analyzedAt: new Date().toISOString(),
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ── Phase 1: Threshold Tuning ──
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Analyze whether current thresholds (AUTO_RETRY_THRESHOLD, ZERO_SUCCESS_THRESHOLD,
|
|
154
|
+
* SIMILARITY_THRESHOLD) are optimal based on observed outcomes.
|
|
155
|
+
*/
|
|
156
|
+
private analyzeThresholds(): MetaRule[] {
|
|
157
|
+
const rules: MetaRule[] = []
|
|
158
|
+
const sigs = this.errorDB?.getActive() || []
|
|
159
|
+
if (sigs.length < MIN_SAMPLE_SIZE) return rules
|
|
160
|
+
|
|
161
|
+
// Check if many signatures plateau just below AUTO_RETRY_THRESHOLD (0.7)
|
|
162
|
+
const nearThreshold = sigs.filter(
|
|
163
|
+
(s) => s.successRate >= 0.5 && s.successRate < 0.7 && s.occurrences >= 5,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
if (nearThreshold.length >= 3) {
|
|
167
|
+
const avgRate =
|
|
168
|
+
nearThreshold.reduce((sum, s) => sum + s.successRate, 0) / nearThreshold.length
|
|
169
|
+
rules.push({
|
|
170
|
+
id: `meta-threshold-retry-${Date.now().toString(36)}`,
|
|
171
|
+
category: 'threshold-tuning',
|
|
172
|
+
title: '降低自动重试阈值建议',
|
|
173
|
+
description: `${nearThreshold.length} 个错误签名成功率在 50%-70% 之间(平均 ${Math.round(avgRate * 100)}%),但不足以触发自动重试。如果阈值从 70% 降到 60%,可自动修复其中 ${Math.round(nearThreshold.filter((s) => s.successRate >= 0.6).length)} 个签名。`,
|
|
174
|
+
recommendation: `考虑将 AUTO_RETRY_THRESHOLD 从 0.7 降至 0.6,或对特定类别(如 timeout)使用差异化阈值`,
|
|
175
|
+
autoApplicable: false, // threshold changes need human review
|
|
176
|
+
evidence: {
|
|
177
|
+
sampleSize: nearThreshold.length,
|
|
178
|
+
metrics: {
|
|
179
|
+
avgSuccessRate: Math.round(avgRate * 100) / 100,
|
|
180
|
+
belowThreshold: nearThreshold.length,
|
|
181
|
+
totalSignatures: sigs.length,
|
|
182
|
+
},
|
|
183
|
+
relatedIds: nearThreshold.map((s) => s.id),
|
|
184
|
+
summary: `${nearThreshold.length}/${sigs.length} 签名接近但未达到自动重试阈值`,
|
|
185
|
+
},
|
|
186
|
+
confidence: nearThreshold.length >= 5 ? 'high' : 'medium',
|
|
187
|
+
generatedAt: new Date().toISOString(),
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Check if ZERO_SUCCESS_THRESHOLD (10) is appropriate
|
|
192
|
+
const zeroSuccess = sigs.filter((s) => s.successCount === 0 && s.occurrences >= 3)
|
|
193
|
+
if (zeroSuccess.length >= 5) {
|
|
194
|
+
rules.push({
|
|
195
|
+
id: `meta-threshold-zero-${Date.now().toString(36)}`,
|
|
196
|
+
category: 'threshold-tuning',
|
|
197
|
+
title: '加速零成功率签名淘汰',
|
|
198
|
+
description: `${zeroSuccess.length} 个签名成功率始终为 0,其中 ${zeroSuccess.filter((s) => s.occurrences >= 5).length} 个已发生 ≥5 次。当前零成功淘汰阈值是 10 次,可考虑降至 5-7 次以加速清理无效签名。`,
|
|
199
|
+
recommendation: '将 ZERO_SUCCESS_THRESHOLD 从 10 降至 7,加速淘汰无效签名',
|
|
200
|
+
autoApplicable: false,
|
|
201
|
+
evidence: {
|
|
202
|
+
sampleSize: zeroSuccess.length,
|
|
203
|
+
metrics: {
|
|
204
|
+
zeroSuccessCount: zeroSuccess.length,
|
|
205
|
+
avgOccurrences:
|
|
206
|
+
Math.round(
|
|
207
|
+
(zeroSuccess.reduce((sum, s) => sum + s.occurrences, 0) / zeroSuccess.length) * 10,
|
|
208
|
+
) / 10,
|
|
209
|
+
wouldRetireAt7: zeroSuccess.filter((s) => s.occurrences >= 7).length,
|
|
210
|
+
},
|
|
211
|
+
relatedIds: zeroSuccess.map((s) => s.id),
|
|
212
|
+
summary: `${zeroSuccess.length} 签名零成功率,可加速淘汰`,
|
|
213
|
+
},
|
|
214
|
+
confidence: zeroSuccess.length >= 8 ? 'high' : 'medium',
|
|
215
|
+
generatedAt: new Date().toISOString(),
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return rules
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ── Phase 2: Strategy Optimization ──
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Analyze which fixStrategy works best for each error category.
|
|
226
|
+
* e.g., "replace outperforms warn for timeout errors"
|
|
227
|
+
*/
|
|
228
|
+
private analyzeStrategies(): MetaRule[] {
|
|
229
|
+
const rules: MetaRule[] = []
|
|
230
|
+
const sigs = this.errorDB?.getActive() || []
|
|
231
|
+
if (sigs.length < MIN_SAMPLE_SIZE) return rules
|
|
232
|
+
|
|
233
|
+
// Group by category + fixStrategy
|
|
234
|
+
const byCatStrategy = new Map<
|
|
235
|
+
string,
|
|
236
|
+
{ successRates: number[]; occurrences: number[]; ids: string[] }
|
|
237
|
+
>()
|
|
238
|
+
|
|
239
|
+
for (const sig of sigs) {
|
|
240
|
+
if (sig.occurrences < 3) continue // too new to evaluate
|
|
241
|
+
const key = `${sig.category}:${sig.fixStrategy}`
|
|
242
|
+
const entry = byCatStrategy.get(key) || { successRates: [], occurrences: [], ids: [] }
|
|
243
|
+
entry.successRates.push(sig.successRate)
|
|
244
|
+
entry.occurrences.push(sig.occurrences)
|
|
245
|
+
entry.ids.push(sig.id)
|
|
246
|
+
byCatStrategy.set(key, entry)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// For each category, compare strategies
|
|
250
|
+
const categories = new Set(sigs.map((s) => s.category))
|
|
251
|
+
for (const cat of categories) {
|
|
252
|
+
const strategies = new Map<
|
|
253
|
+
string,
|
|
254
|
+
{ avgRate: number; count: number; totalOcc: number; ids: string[] }
|
|
255
|
+
>()
|
|
256
|
+
|
|
257
|
+
for (const [key, data] of byCatStrategy) {
|
|
258
|
+
if (!key.startsWith(cat + ':')) continue
|
|
259
|
+
const strategy = key.split(':')[1]!
|
|
260
|
+
if (!strategy) continue
|
|
261
|
+
const avgRate = data.successRates.reduce((a, b) => a + b, 0) / data.successRates.length
|
|
262
|
+
const totalOcc = data.occurrences.reduce((a, b) => a + b, 0)
|
|
263
|
+
strategies.set(strategy, {
|
|
264
|
+
avgRate,
|
|
265
|
+
count: data.successRates.length,
|
|
266
|
+
totalOcc,
|
|
267
|
+
ids: data.ids,
|
|
268
|
+
})
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (strategies.size < 2) continue
|
|
272
|
+
|
|
273
|
+
// Find best and worst strategy for this category
|
|
274
|
+
const sorted = [...strategies.entries()].sort((a, b) => b[1].avgRate - a[1].avgRate)
|
|
275
|
+
const best = sorted[0]!
|
|
276
|
+
const worst = sorted[sorted.length - 1]!
|
|
277
|
+
if (!best || !worst) continue
|
|
278
|
+
|
|
279
|
+
if (best[1].avgRate - worst[1].avgRate > 0.2 && best[1].count >= 2) {
|
|
280
|
+
rules.push({
|
|
281
|
+
id: `meta-strategy-${cat}-${Date.now().toString(36)}`,
|
|
282
|
+
category: 'strategy-optimization',
|
|
283
|
+
title: `${cat} 类别最优策略: ${best[0]}`,
|
|
284
|
+
description: `在 ${cat} 类别中,\`${best[0]}\` 策略平均成功率 ${Math.round(best[1].avgRate * 100)}%(${best[1].count} 个签名),\`${worst[0]}\` 仅 ${Math.round(worst[1].avgRate * 100)}%(${worst[1].count} 个签名),差距 ${Math.round((best[1].avgRate - worst[1].avgRate) * 100)} 个百分点。`,
|
|
285
|
+
recommendation: `将 ${cat} 类别的新签名默认策略设为 \`${best[0]}\`,并考虑将现有 \`${worst[0]}\` 策略签名迁移为 \`${best[0]}\``,
|
|
286
|
+
autoApplicable: best[1].avgRate > 0.85 && best[1].count >= 5,
|
|
287
|
+
evidence: {
|
|
288
|
+
sampleSize: best[1].count + worst[1].count,
|
|
289
|
+
metrics: {
|
|
290
|
+
bestStrategyAvg: Math.round(best[1].avgRate * 100) / 100,
|
|
291
|
+
worstStrategyAvg: Math.round(worst[1].avgRate * 100) / 100,
|
|
292
|
+
gap: Math.round((best[1].avgRate - worst[1].avgRate) * 100) / 100,
|
|
293
|
+
bestCount: best[1].count,
|
|
294
|
+
worstCount: worst[1].count,
|
|
295
|
+
},
|
|
296
|
+
relatedIds: [...best[1].ids, ...worst[1].ids],
|
|
297
|
+
summary: `${best[0]} 比 ${worst[0]} 高 ${Math.round((best[1].avgRate - worst[1].avgRate) * 100)}%`,
|
|
298
|
+
},
|
|
299
|
+
confidence: best[1].count >= 5 ? 'high' : 'medium',
|
|
300
|
+
generatedAt: new Date().toISOString(),
|
|
301
|
+
})
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return rules
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// ── Phase 3: Cross-Rule Pattern Discovery ──
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Discover patterns that span multiple unrelated rules.
|
|
312
|
+
* e.g., "all rules targeting Bash with high success share the pattern
|
|
313
|
+
* of increasing timeout or adding --no-* flags"
|
|
314
|
+
*/
|
|
315
|
+
private discoverCrossRulePatterns(): MetaRule[] {
|
|
316
|
+
const rules: MetaRule[] = []
|
|
317
|
+
const sigs = this.errorDB?.getActive() || []
|
|
318
|
+
if (sigs.length < 8) return rules
|
|
319
|
+
|
|
320
|
+
// Pattern 1: Check if high-success signatures cluster by tool
|
|
321
|
+
const highSuccess = sigs.filter((s) => s.successRate >= 0.8 && s.occurrences >= 5)
|
|
322
|
+
const byTool = new Map<string, ErrorSignature[]>()
|
|
323
|
+
for (const s of highSuccess) {
|
|
324
|
+
const list = byTool.get(s.toolName) || []
|
|
325
|
+
list.push(s)
|
|
326
|
+
byTool.set(s.toolName, list)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// If one tool dominates high-success signatures
|
|
330
|
+
const toolEntries = [...byTool.entries()].sort((a, b) => b[1].length - a[1].length)
|
|
331
|
+
const dominant = toolEntries[0]
|
|
332
|
+
const runnerUp = toolEntries[1]
|
|
333
|
+
if (
|
|
334
|
+
dominant &&
|
|
335
|
+
runnerUp &&
|
|
336
|
+
toolEntries.length >= 2 &&
|
|
337
|
+
dominant[1].length > runnerUp[1].length * 2
|
|
338
|
+
) {
|
|
339
|
+
rules.push({
|
|
340
|
+
id: `meta-cross-tool-${Date.now().toString(36)}`,
|
|
341
|
+
category: 'cross-rule-pattern',
|
|
342
|
+
title: `${dominant[0]} 工具规则最成熟`,
|
|
343
|
+
description: `${dominant[0]} 工具有 ${dominant[1].length} 个高成功率签名(≥80%),远超第二名 ${runnerUp[0]}(${runnerUp[1].length} 个)。SIS 对 ${dominant[0]} 类错误的免疫力最强。`,
|
|
344
|
+
recommendation: `优先在其他工具(${toolEntries
|
|
345
|
+
.slice(1)
|
|
346
|
+
.map((e) => e[0])
|
|
347
|
+
.join(', ')})上扩展签名覆盖,追赶 ${dominant[0]} 的成熟度`,
|
|
348
|
+
autoApplicable: false,
|
|
349
|
+
evidence: {
|
|
350
|
+
sampleSize: highSuccess.length,
|
|
351
|
+
metrics: {
|
|
352
|
+
dominantCount: dominant[1].length,
|
|
353
|
+
runnerUpCount: runnerUp[1].length,
|
|
354
|
+
totalHighSuccess: highSuccess.length,
|
|
355
|
+
},
|
|
356
|
+
relatedIds: dominant[1].map((s) => s.id),
|
|
357
|
+
summary: `${dominant[0]} 领先 ${runnerUp[1].length}→${dominant[1].length}`,
|
|
358
|
+
},
|
|
359
|
+
confidence: dominant[1].length >= 10 ? 'high' : 'medium',
|
|
360
|
+
generatedAt: new Date().toISOString(),
|
|
361
|
+
})
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Pattern 2: Detect fix action patterns (common substrings in fix actions)
|
|
365
|
+
const fixActions = sigs.filter((s) => s.fixAction && s.fixAction.length > 0)
|
|
366
|
+
if (fixActions.length >= 5) {
|
|
367
|
+
const commonPrefixes = new Map<string, number>()
|
|
368
|
+
for (const s of fixActions) {
|
|
369
|
+
const words = s.fixAction.split(/\s+/)
|
|
370
|
+
if (words.length >= 2) {
|
|
371
|
+
const bigram = words.slice(0, 2).join(' ')
|
|
372
|
+
commonPrefixes.set(bigram, (commonPrefixes.get(bigram) || 0) + 1)
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const topBigrams = [...commonPrefixes.entries()]
|
|
377
|
+
.filter(([, count]) => count >= 3)
|
|
378
|
+
.sort((a, b) => b[1] - a[1])
|
|
379
|
+
|
|
380
|
+
for (const [bigram, count] of topBigrams.slice(0, 3)) {
|
|
381
|
+
rules.push({
|
|
382
|
+
id: `meta-cross-bigram-${Date.now().toString(36)}`,
|
|
383
|
+
category: 'cross-rule-pattern',
|
|
384
|
+
title: `修复模式复用: "${bigram}..."`,
|
|
385
|
+
description: `${count} 个签名使用以 "${bigram}" 开头的修复动作。这是高频修复模式,可能值得提升为内置规则模板。`,
|
|
386
|
+
recommendation: `为 "${bigram}" 模式创建快速修复模板,加速新签名创建`,
|
|
387
|
+
autoApplicable: false,
|
|
388
|
+
evidence: {
|
|
389
|
+
sampleSize: count,
|
|
390
|
+
metrics: { count, totalFixActions: fixActions.length },
|
|
391
|
+
relatedIds: fixActions.filter((s) => s.fixAction.startsWith(bigram)).map((s) => s.id),
|
|
392
|
+
summary: `${count}/${fixActions.length} 修复动作以 "${bigram}" 开头`,
|
|
393
|
+
},
|
|
394
|
+
confidence: count >= 5 ? 'high' : 'low',
|
|
395
|
+
generatedAt: new Date().toISOString(),
|
|
396
|
+
})
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return rules
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// ── Phase 4: Emergent Category Detection ──
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Detect error categories that don't fit the predefined set.
|
|
407
|
+
* e.g., if many errors contain "rate limit" or "quota exceeded",
|
|
408
|
+
* we may need a new 'rate-limit' category.
|
|
409
|
+
*/
|
|
410
|
+
private detectEmergentCategories(): MetaRule[] {
|
|
411
|
+
const rules: MetaRule[] = []
|
|
412
|
+
const sigs = this.errorDB?.getActive() || []
|
|
413
|
+
if (sigs.length < 10) return rules
|
|
414
|
+
|
|
415
|
+
// Look for signature clusters that don't clearly fit known categories
|
|
416
|
+
// by analyzing pattern keywords
|
|
417
|
+
const keywords = new Map<string, { count: number; sigs: ErrorSignature[] }>()
|
|
418
|
+
|
|
419
|
+
const candidateKeywords = [
|
|
420
|
+
'rate limit',
|
|
421
|
+
'quota',
|
|
422
|
+
'network',
|
|
423
|
+
'connection',
|
|
424
|
+
'auth',
|
|
425
|
+
'token',
|
|
426
|
+
'memory',
|
|
427
|
+
'disk',
|
|
428
|
+
'parse',
|
|
429
|
+
'syntax',
|
|
430
|
+
'version',
|
|
431
|
+
'conflict',
|
|
432
|
+
]
|
|
433
|
+
|
|
434
|
+
for (const sig of sigs) {
|
|
435
|
+
const pattern = sig.pattern.toLowerCase()
|
|
436
|
+
for (const kw of candidateKeywords) {
|
|
437
|
+
if (pattern.includes(kw)) {
|
|
438
|
+
const entry = keywords.get(kw) || { count: 0, sigs: [] }
|
|
439
|
+
entry.count++
|
|
440
|
+
entry.sigs.push(sig)
|
|
441
|
+
keywords.set(kw, entry)
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
for (const [kw, data] of keywords) {
|
|
447
|
+
if (data.count >= 3) {
|
|
448
|
+
// Check if these signatures' current categories are mostly 'semantic'
|
|
449
|
+
// (meaning the system couldn't classify them well)
|
|
450
|
+
const semanticRatio = data.sigs.filter((s) => s.category === 'semantic').length / data.count
|
|
451
|
+
|
|
452
|
+
rules.push({
|
|
453
|
+
id: `meta-emergent-${kw.replace(/\s+/g, '-')}-${Date.now().toString(36)}`,
|
|
454
|
+
category: 'category-emergence',
|
|
455
|
+
title: `新错误类别候选: "${kw}"`,
|
|
456
|
+
description: `${data.count} 个签名包含关键词 "${kw}",其中 ${Math.round(semanticRatio * 100)}% 被归类为 'semantic'(无法精细分类)。这可能是一个值得独立追踪的新错误类别。`,
|
|
457
|
+
recommendation:
|
|
458
|
+
semanticRatio > 0.5
|
|
459
|
+
? `创建新类别 \`${kw.replace(/\s+/g, '-')}\`,将 ${data.count} 个签名重新分类`
|
|
460
|
+
: `监控 "${kw}" 相关签名增长趋势,若持续增长则创建独立类别`,
|
|
461
|
+
autoApplicable: false,
|
|
462
|
+
evidence: {
|
|
463
|
+
sampleSize: data.count,
|
|
464
|
+
metrics: {
|
|
465
|
+
count: data.count,
|
|
466
|
+
semanticRatio: Math.round(semanticRatio * 100) / 100,
|
|
467
|
+
avgSuccessRate:
|
|
468
|
+
Math.round(
|
|
469
|
+
(data.sigs.reduce((sum, s) => sum + s.successRate, 0) / data.count) * 100,
|
|
470
|
+
) / 100,
|
|
471
|
+
},
|
|
472
|
+
relatedIds: data.sigs.map((s) => s.id),
|
|
473
|
+
summary: `${data.count} 签名匹配 "${kw}",${Math.round(semanticRatio * 100)}% 为 semantic`,
|
|
474
|
+
},
|
|
475
|
+
confidence: data.count >= 5 ? 'high' : data.count >= 4 ? 'medium' : 'low',
|
|
476
|
+
generatedAt: new Date().toISOString(),
|
|
477
|
+
})
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
return rules
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// ── System Health Computation ──
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Compute overall CRSI system health from meta-analysis results.
|
|
488
|
+
*/
|
|
489
|
+
private computeSystemHealth(metaRules: MetaRule[]): SystemHealth {
|
|
490
|
+
const sigs = this.errorDB?.getActive() || []
|
|
491
|
+
const stats = this.errorDB?.getStats() || { total: 0, active: 0, avgSuccessRate: 0 }
|
|
492
|
+
|
|
493
|
+
// ── Error Signatures health ──
|
|
494
|
+
let errorSignaturesScore = 50 // baseline
|
|
495
|
+
if (sigs.length > 0) {
|
|
496
|
+
const coverageBonus = Math.min(sigs.length * 2, 30) // up to 30 for volume
|
|
497
|
+
const successBonus = Math.round(stats.avgSuccessRate * 20) // up to 20 for quality
|
|
498
|
+
errorSignaturesScore = Math.min(50 + coverageBonus + successBonus, 100)
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// ── Preflight Prevention health ──
|
|
502
|
+
const fixSigs = sigs.filter(
|
|
503
|
+
(s) =>
|
|
504
|
+
s.fixStrategy === 'replace' || s.fixStrategy === 'prepend' || s.fixStrategy === 'append',
|
|
505
|
+
).length
|
|
506
|
+
let preflightScore = 50
|
|
507
|
+
if (sigs.length > 0) {
|
|
508
|
+
const fixRatio = fixSigs / sigs.length
|
|
509
|
+
preflightScore = Math.min(50 + Math.round(fixRatio * 50), 100)
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// ── Auto Correction health ──
|
|
513
|
+
const highConfSigs = sigs.filter((s) => s.successRate >= 0.7).length
|
|
514
|
+
let autoCorrectionScore = 50
|
|
515
|
+
if (sigs.length > 0) {
|
|
516
|
+
autoCorrectionScore = Math.min(50 + Math.round((highConfSigs / sigs.length) * 50), 100)
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// ── Immune Memory health ──
|
|
520
|
+
let immuneScore = 50
|
|
521
|
+
if (stats.total > 0) {
|
|
522
|
+
const activeRatio = stats.active / stats.total
|
|
523
|
+
immuneScore = Math.min(50 + Math.round(activeRatio * 50), 100)
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// ── Rule Effectiveness health (from tracker) ──
|
|
527
|
+
let ruleEffectivenessScore = 50 // default if no tracker
|
|
528
|
+
if (this.tracker) {
|
|
529
|
+
const allEff = this.tracker.allRules
|
|
530
|
+
if (allEff.length > 0) {
|
|
531
|
+
const avgSuccess =
|
|
532
|
+
allEff.reduce((sum, e) => {
|
|
533
|
+
const rate = e.appliedCount > 0 ? e.successAfterCount / e.appliedCount : 0
|
|
534
|
+
return sum + rate
|
|
535
|
+
}, 0) / allEff.length
|
|
536
|
+
ruleEffectivenessScore = Math.min(50 + Math.round(avgSuccess * 50), 100)
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// ── Composite Score ──
|
|
541
|
+
const score = Math.round(
|
|
542
|
+
errorSignaturesScore * 0.25 +
|
|
543
|
+
preflightScore * 0.2 +
|
|
544
|
+
autoCorrectionScore * 0.25 +
|
|
545
|
+
immuneScore * 0.15 +
|
|
546
|
+
ruleEffectivenessScore * 0.15,
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
// ── Assessment ──
|
|
550
|
+
let assessment: string
|
|
551
|
+
if (score >= 80) assessment = '🟢 SIS 免疫系统运行良好。错误覆盖率、自动修复率均处于健康水平。'
|
|
552
|
+
else if (score >= 60)
|
|
553
|
+
assessment = '🟡 SIS 免疫系统基本正常。部分组件需要关注,建议按下方推荐优化。'
|
|
554
|
+
else if (score >= 40) assessment = '🟠 SIS 免疫系统有待加强。错误签名覆盖不足或自动修复率偏低。'
|
|
555
|
+
else assessment = '🔴 SIS 免疫系统薄弱。需要大量积累错误签名并提升修复成功率。'
|
|
556
|
+
|
|
557
|
+
// ── Recommendations ──
|
|
558
|
+
const recommendations: string[] = []
|
|
559
|
+
if (errorSignaturesScore < 60) recommendations.push('增加错误签名积累:让系统经历更多错误场景')
|
|
560
|
+
if (preflightScore < 60) recommendations.push('提升预防能力:将更多 warn 策略升级为 fix 策略')
|
|
561
|
+
if (autoCorrectionScore < 60)
|
|
562
|
+
recommendations.push('提升自动修复率:验证并提升现有签名的成功率至 70%+')
|
|
563
|
+
if (immuneScore < 60) recommendations.push('清理免疫记忆:运行 /sis cleanup 移除过期和无效签名')
|
|
564
|
+
if (ruleEffectivenessScore < 60)
|
|
565
|
+
recommendations.push('提升规则有效性:检查 EffectivenessTracker 中低效规则')
|
|
566
|
+
|
|
567
|
+
// Add meta-rule driven recommendations
|
|
568
|
+
const highConfMeta = metaRules.filter((m) => m.confidence === 'high')
|
|
569
|
+
if (highConfMeta.length > 0) {
|
|
570
|
+
recommendations.push(`发现 ${highConfMeta.length} 个高置信度元规则,建议审查后应用`)
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const autoApplicable = metaRules.filter((m) => m.autoApplicable)
|
|
574
|
+
if (autoApplicable.length > 0) {
|
|
575
|
+
recommendations.push(`${autoApplicable.length} 个元规则可自动应用,执行 /crsi meta --apply`)
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
if (recommendations.length === 0) {
|
|
579
|
+
recommendations.push('系统运行良好,无需立即行动。持续监控即可。')
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
return {
|
|
583
|
+
score,
|
|
584
|
+
components: {
|
|
585
|
+
errorSignatures: errorSignaturesScore,
|
|
586
|
+
preflightPrevention: preflightScore,
|
|
587
|
+
autoCorrection: autoCorrectionScore,
|
|
588
|
+
immuneMemory: immuneScore,
|
|
589
|
+
ruleEffectiveness: ruleEffectivenessScore,
|
|
590
|
+
},
|
|
591
|
+
assessment,
|
|
592
|
+
recommendations,
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Get a summary of auto-applicable meta-rules for safe auto-application.
|
|
598
|
+
*/
|
|
599
|
+
getAutoApplicable(metaRules: MetaRule[]): MetaRule[] {
|
|
600
|
+
return metaRules.filter((m) => m.autoApplicable && m.confidence === 'high')
|
|
601
|
+
}
|
|
602
|
+
}
|