@miphamai/cli 0.22.0 → 0.24.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/README.md +1 -1
- package/package.json +1 -1
- package/skills/workflows/audit.js +66 -18
- package/src/agent/agent-context.ts +20 -57
- package/src/agent/agent-experience.ts +8 -0
- package/src/agent/cross-session/discovery.ts +99 -0
- package/src/agent/cross-session/file-inbox.ts +118 -0
- package/src/agent/cross-session/transport.ts +17 -0
- package/src/agent/effectiveness-tracker.ts +149 -0
- package/src/agent/experience-rules.ts +188 -0
- package/src/agent/message-router.ts +83 -0
- package/src/agent/pattern-analyzer.ts +154 -0
- package/src/agent/sub-agent.ts +67 -0
- package/src/agent/types.ts +2 -0
- package/src/config/defaults.ts +12 -0
- package/src/config/loader.ts +33 -0
- package/src/core/credential-masker/env-filter.ts +39 -0
- package/src/core/credential-masker/index.ts +35 -0
- package/src/core/credential-masker/matcher.ts +106 -0
- package/src/core/credential-masker/output-scrub.ts +27 -0
- package/src/core/credential-masker/pipeline.ts +67 -0
- package/src/core/credential-masker/strategies/aws.ts +50 -0
- package/src/core/credential-masker/strategies/extract.ts +106 -0
- package/src/core/credential-masker/strategies/full.ts +15 -0
- package/src/core/credential-masker/strategies/jwt.ts +75 -0
- package/src/core/credential-masker/types.ts +16 -0
- package/src/core/credential-masker.ts +12 -187
- package/src/core/engine.ts +114 -10
- package/src/core/rule-engine.ts +179 -0
- package/src/core/session-store.ts +15 -2
- package/src/index.tsx +47 -2
- package/src/shared/types.ts +69 -5
- package/src/tools/agent/agent.ts +7 -1
- package/src/tools/agent/list-agents.ts +67 -0
- package/src/tools/agent/send-message.ts +21 -24
- package/src/tools/exec/bash.ts +67 -3
- package/src/tools/index.ts +2 -0
- package/src/ui/commands.ts +133 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { ToolDefinition } from '../../shared/index.ts'
|
|
2
|
+
import { discoverSessions } from '../../agent/cross-session/discovery'
|
|
3
|
+
|
|
4
|
+
export const listAgentsTool: ToolDefinition = {
|
|
5
|
+
name: 'ListAgents',
|
|
6
|
+
description:
|
|
7
|
+
'Discover active Mipham Code sessions running on this machine. ' +
|
|
8
|
+
'Returns session ID, name, machine, working directory, provider, and model for each session. ' +
|
|
9
|
+
'Use with SendMessage to communicate across sessions.',
|
|
10
|
+
category: 'agent',
|
|
11
|
+
permission: 'auto',
|
|
12
|
+
parameters: {
|
|
13
|
+
type: 'object',
|
|
14
|
+
properties: {
|
|
15
|
+
scope: {
|
|
16
|
+
type: 'string',
|
|
17
|
+
enum: ['local', 'all'],
|
|
18
|
+
default: 'local',
|
|
19
|
+
description:
|
|
20
|
+
'Discovery scope. "local" scans this machine only. "all" is reserved for future network discovery.',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
async execute(params) {
|
|
25
|
+
const scope = (params.scope as string) || 'local'
|
|
26
|
+
|
|
27
|
+
if (scope === 'all') {
|
|
28
|
+
return {
|
|
29
|
+
success: true,
|
|
30
|
+
content:
|
|
31
|
+
'Network discovery is not yet available. Showing local sessions only.\n\n' +
|
|
32
|
+
formatSessionList(discoverSessions()),
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const sessions = discoverSessions()
|
|
37
|
+
|
|
38
|
+
if (sessions.length === 0) {
|
|
39
|
+
return {
|
|
40
|
+
success: true,
|
|
41
|
+
content: 'No active Mipham Code sessions found on this machine.',
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
success: true,
|
|
47
|
+
content: formatSessionList(sessions),
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function formatSessionList(sessions: import('../../shared/types').SessionInfo[]): string {
|
|
53
|
+
const lines: string[] = [`${sessions.length} active session(s):\n`]
|
|
54
|
+
|
|
55
|
+
for (const s of sessions) {
|
|
56
|
+
const modelInfo = s.model ? ` · ${s.model}` : ''
|
|
57
|
+
const providerInfo = s.provider ? ` (${s.provider})` : ''
|
|
58
|
+
lines.push(` ${s.id}`)
|
|
59
|
+
lines.push(` Name: ${s.name}`)
|
|
60
|
+
lines.push(` Machine: ${s.machine}${modelInfo}${providerInfo}`)
|
|
61
|
+
lines.push(` PID: ${s.pid} · Started: ${s.startedAt}`)
|
|
62
|
+
if (s.cwd) lines.push(` CWD: ${s.cwd}`)
|
|
63
|
+
lines.push('')
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return lines.join('\n').trim()
|
|
67
|
+
}
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import type { ToolDefinition } from '../../shared/index.ts'
|
|
2
|
-
import {
|
|
2
|
+
import { getMessageRouter } from '../../agent/message-router'
|
|
3
3
|
|
|
4
4
|
export const sendMessageTool: ToolDefinition = {
|
|
5
5
|
name: 'SendMessage',
|
|
6
6
|
description:
|
|
7
|
-
'Send a message to another agent or
|
|
8
|
-
'Use "main"
|
|
9
|
-
'or a
|
|
10
|
-
'Messages are stored in the AgentMessageBus and can be polled by the recipient.',
|
|
7
|
+
'Send a message to another agent or session. ' +
|
|
8
|
+
'Use "main" for the parent conversation, a background task ID for same-process agents, ' +
|
|
9
|
+
'or a session ID for cross-session messaging (use ListAgents to discover sessions).',
|
|
11
10
|
category: 'agent',
|
|
12
11
|
permission: 'auto',
|
|
13
12
|
parameters: {
|
|
@@ -16,7 +15,7 @@ export const sendMessageTool: ToolDefinition = {
|
|
|
16
15
|
to: {
|
|
17
16
|
type: 'string',
|
|
18
17
|
description:
|
|
19
|
-
'Recipient: "main" for the parent conversation, a background task ID, or
|
|
18
|
+
'Recipient: "main" for the parent conversation, a background task ID, or a session ID for cross-session messaging.',
|
|
20
19
|
},
|
|
21
20
|
summary: {
|
|
22
21
|
type: 'string',
|
|
@@ -34,34 +33,32 @@ export const sendMessageTool: ToolDefinition = {
|
|
|
34
33
|
const summary = (params.summary as string) || '(no subject)'
|
|
35
34
|
const message = params.message as string
|
|
36
35
|
|
|
37
|
-
// Determine sender: use sessionId or 'main'
|
|
38
36
|
const from =
|
|
39
37
|
ctx.sessionId === 'sub-agent'
|
|
40
38
|
? `sub-agent-${Date.now().toString(36)}`
|
|
41
39
|
: ctx.sessionId || 'main'
|
|
42
40
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const msgId = bus.post(from, to, summary, message)
|
|
41
|
+
const router = getMessageRouter()
|
|
42
|
+
const result = await router.route(from, to, summary, message)
|
|
46
43
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return {
|
|
50
|
-
success: true,
|
|
51
|
-
content:
|
|
52
|
-
`── Message Sent ──\n\n` +
|
|
53
|
-
`ID: ${msgId}\n` +
|
|
54
|
-
`From: ${from}\n` +
|
|
55
|
-
`To: ${to}\n` +
|
|
56
|
-
`Summary: ${summary.slice(0, 100)}\n\n` +
|
|
57
|
-
`The recipient has ${unreadForRecipient} unread message(s).`,
|
|
58
|
-
}
|
|
59
|
-
} catch (err) {
|
|
44
|
+
if (!result.success) {
|
|
60
45
|
return {
|
|
61
46
|
success: false,
|
|
62
47
|
content: '',
|
|
63
|
-
error: `Failed to send message: ${
|
|
48
|
+
error: `Failed to send message: ${result.error}`,
|
|
64
49
|
}
|
|
65
50
|
}
|
|
51
|
+
|
|
52
|
+
const routedLabel = result.routedTo === 'bus' ? 'in-process' : 'cross-session'
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
success: true,
|
|
56
|
+
content:
|
|
57
|
+
`── Message Sent (${routedLabel}) ──\n\n` +
|
|
58
|
+
`ID: ${result.messageId}\n` +
|
|
59
|
+
`From: ${from}\n` +
|
|
60
|
+
`To: ${to}\n` +
|
|
61
|
+
`Summary: ${summary.slice(0, 100)}`,
|
|
62
|
+
}
|
|
66
63
|
},
|
|
67
64
|
}
|
package/src/tools/exec/bash.ts
CHANGED
|
@@ -122,6 +122,57 @@ function isBlocked(command: string): string | null {
|
|
|
122
122
|
return null // safe
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Detect sandbox violations from stderr output.
|
|
127
|
+
* Parses common OS-level error patterns indicating denied access.
|
|
128
|
+
*/
|
|
129
|
+
export function detectViolations(stderr: string): string[] {
|
|
130
|
+
const violations: string[] = []
|
|
131
|
+
|
|
132
|
+
// File access violations
|
|
133
|
+
const accessPatterns = /(?:Permission denied|EACCES|EPERM|Operation not permitted)/gi
|
|
134
|
+
const accessMatches = stderr.match(accessPatterns)
|
|
135
|
+
if (accessMatches && accessMatches.length > 0) {
|
|
136
|
+
// Extract file paths from error messages
|
|
137
|
+
const pathPattern =
|
|
138
|
+
/(?:Permission denied|EACCES|EPERM|Operation not permitted).*?['"]?(\/[^\s'"]+)['"]?/gi
|
|
139
|
+
const paths: string[] = []
|
|
140
|
+
let match: RegExpExecArray | null
|
|
141
|
+
while ((match = pathPattern.exec(stderr)) !== null) {
|
|
142
|
+
paths.push(match[1]!)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (paths.length > 0) {
|
|
146
|
+
violations.push(` File access denied: ${paths.join(', ')}`)
|
|
147
|
+
} else {
|
|
148
|
+
violations.push(` File access denied (${accessMatches.length} occurrence(s))`)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Network access violations
|
|
153
|
+
const netPatterns =
|
|
154
|
+
/(?:Network is unreachable|Connection refused|ECONNREFUSED|ENETUNREACH|Could not resolve host|Name or service not known|ETIMEDOUT|Connection timed out)/gi
|
|
155
|
+
const netMatches = stderr.match(netPatterns)
|
|
156
|
+
if (netMatches && netMatches.length > 0) {
|
|
157
|
+
// Extract host:port from error messages
|
|
158
|
+
const hostPattern =
|
|
159
|
+
/(?:connect to|Could not resolve host|Failed to connect to)\s+([^\s:]+(?::\d+)?)/gi
|
|
160
|
+
const hosts: string[] = []
|
|
161
|
+
let match: RegExpExecArray | null
|
|
162
|
+
while ((match = hostPattern.exec(stderr)) !== null) {
|
|
163
|
+
hosts.push(match[1]!)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (hosts.length > 0) {
|
|
167
|
+
violations.push(` Network access denied: ${hosts.join(', ')}`)
|
|
168
|
+
} else {
|
|
169
|
+
violations.push(` Network access denied (${netMatches.length} occurrence(s))`)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return violations
|
|
174
|
+
}
|
|
175
|
+
|
|
125
176
|
export const bashTool: ToolDefinition = {
|
|
126
177
|
name: 'Bash',
|
|
127
178
|
description:
|
|
@@ -173,6 +224,9 @@ export const bashTool: ToolDefinition = {
|
|
|
173
224
|
const exitCode = await proc.exited
|
|
174
225
|
clearTimeout(timer)
|
|
175
226
|
|
|
227
|
+
// Read stderr for violation detection and error reporting
|
|
228
|
+
const rawStderr = await new Response(proc.stderr).text()
|
|
229
|
+
|
|
176
230
|
// ── Credential masking: scrub output ──
|
|
177
231
|
let output = rawOutput
|
|
178
232
|
if (credentialConfig?.enabled && credentialConfig.output_scrubbing.enabled) {
|
|
@@ -180,20 +234,30 @@ export const bashTool: ToolDefinition = {
|
|
|
180
234
|
output = maskOutput(rawOutput, credentialConfig)
|
|
181
235
|
}
|
|
182
236
|
|
|
237
|
+
// ── Sandbox violation detection ──
|
|
238
|
+
const violations = detectViolations(rawStderr)
|
|
239
|
+
|
|
183
240
|
if (exitCode !== 0) {
|
|
184
|
-
const rawStderr = await new Response(proc.stderr).text()
|
|
185
241
|
const stderr =
|
|
186
242
|
credentialConfig?.enabled && credentialConfig.output_scrubbing.enabled
|
|
187
243
|
? (await import('../../core/credential-masker')).maskOutput(rawStderr, credentialConfig)
|
|
188
244
|
: rawStderr
|
|
245
|
+
let errorContent = output.slice(0, 5_000)
|
|
246
|
+
if (violations.length > 0) {
|
|
247
|
+
errorContent += '\n\n── Sandbox Violations ──\n' + violations.join('\n')
|
|
248
|
+
}
|
|
189
249
|
return {
|
|
190
250
|
success: false,
|
|
191
|
-
content:
|
|
251
|
+
content: errorContent,
|
|
192
252
|
error: `Exit code ${exitCode}: ${stderr.slice(0, 1_000)}`,
|
|
193
253
|
}
|
|
194
254
|
}
|
|
195
255
|
|
|
196
|
-
|
|
256
|
+
let successContent = output.slice(0, 100_000) || '(no output)'
|
|
257
|
+
if (violations.length > 0) {
|
|
258
|
+
successContent += '\n\n── Sandbox Violations ──\n' + violations.join('\n')
|
|
259
|
+
}
|
|
260
|
+
return { success: true, content: successContent }
|
|
197
261
|
} catch (err) {
|
|
198
262
|
return {
|
|
199
263
|
success: false,
|
package/src/tools/index.ts
CHANGED
|
@@ -29,6 +29,7 @@ import { toolSearchTool } from './system/tool-search'
|
|
|
29
29
|
import { artifactTool } from './artifact/artifact'
|
|
30
30
|
import { reportFindingsTool } from './agent/report-findings'
|
|
31
31
|
import { sendMessageTool } from './agent/send-message'
|
|
32
|
+
import { listAgentsTool } from './agent/list-agents'
|
|
32
33
|
import { computerUseTool } from './computer/computer-use'
|
|
33
34
|
import { scheduleWakeupTool } from './scheduling/schedule-wakeup.js'
|
|
34
35
|
import { cronCreateTool, cronDeleteTool, cronListTool } from './scheduling/cron.js'
|
|
@@ -145,6 +146,7 @@ export function createToolRegistry(): Map<string, ToolDefinition> {
|
|
|
145
146
|
withValidation(workflowTool),
|
|
146
147
|
withValidation(reportFindingsTool),
|
|
147
148
|
withValidation(sendMessageTool),
|
|
149
|
+
withValidation(listAgentsTool),
|
|
148
150
|
// Network tools
|
|
149
151
|
withValidation(webFetchTool),
|
|
150
152
|
withValidation(webSearchTool),
|
package/src/ui/commands.ts
CHANGED
|
@@ -446,6 +446,124 @@ const skillsCmd: CommandHandler = (ctx) => {
|
|
|
446
446
|
return { content: lines.join('\n') }
|
|
447
447
|
}
|
|
448
448
|
|
|
449
|
+
// ═══════════════════════════════════════════════════════════════
|
|
450
|
+
// CRSI (Conversational Rule Self-Improvement)
|
|
451
|
+
// ═══════════════════════════════════════════════════════════════
|
|
452
|
+
|
|
453
|
+
const crsiRulesCmd: CommandHandler = (ctx) => {
|
|
454
|
+
const engine = ctx.engine.getRuleEngine()
|
|
455
|
+
if (!engine) {
|
|
456
|
+
return { content: 'CRSI rule engine is not available.' }
|
|
457
|
+
}
|
|
458
|
+
const rules = engine.getActiveRules()
|
|
459
|
+
if (rules.length === 0) {
|
|
460
|
+
return { content: 'No active CRSI rules.' }
|
|
461
|
+
}
|
|
462
|
+
const lines: string[] = ['## Active CRSI Rules', '']
|
|
463
|
+
for (const r of rules) {
|
|
464
|
+
const status = r.enabled ? '✅' : '⛔'
|
|
465
|
+
lines.push(`- \`${r.id}\` [${r.category}] ${r.toolName} — ${r.source} ${status}`)
|
|
466
|
+
}
|
|
467
|
+
lines.push('', `Total: ${rules.length} active rules`)
|
|
468
|
+
lines.push('', 'Use `/crsi disable <rule-id>` to disable a rule.')
|
|
469
|
+
return { content: lines.join('\n') }
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const crsiDisableCmd: CommandHandler = (ctx, args) => {
|
|
473
|
+
const engine = ctx.engine.getRuleEngine()
|
|
474
|
+
if (!engine) {
|
|
475
|
+
return { content: 'CRSI rule engine is not available.' }
|
|
476
|
+
}
|
|
477
|
+
const ruleId = args[0]?.trim()
|
|
478
|
+
if (!ruleId) {
|
|
479
|
+
return { content: 'Usage: /crsi disable <rule-id>' }
|
|
480
|
+
}
|
|
481
|
+
engine.setRuleEnabled(ruleId, false)
|
|
482
|
+
return {
|
|
483
|
+
content: `Rule \`${ruleId}\` has been disabled. Use \`/crsi restore ${ruleId}\` to re-enable.`,
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const crsiAnalyzeCmd: CommandHandler = async (ctx) => {
|
|
488
|
+
const analyzer = ctx.engine.getPatternAnalyzer()
|
|
489
|
+
const engine = ctx.engine.getRuleEngine()
|
|
490
|
+
if (!engine) {
|
|
491
|
+
return { content: 'CRSI system is not available.' }
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const patterns = analyzer.analyzeAllAgents()
|
|
495
|
+
if (patterns.length === 0) {
|
|
496
|
+
return { content: 'No failure patterns found across agents.' }
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
let registered = 0
|
|
500
|
+
for (const pattern of patterns) {
|
|
501
|
+
const toolRule = analyzer.toToolRule(pattern)
|
|
502
|
+
engine.register(toolRule)
|
|
503
|
+
registered++
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const lines: string[] = [
|
|
507
|
+
'## CRSI Analysis Complete',
|
|
508
|
+
'',
|
|
509
|
+
`Found ${patterns.length} patterns, ${registered} rules registered.`,
|
|
510
|
+
'',
|
|
511
|
+
]
|
|
512
|
+
for (const p of patterns) {
|
|
513
|
+
lines.push(
|
|
514
|
+
`- [${p.category}] \`${p.agentName}\` — ${p.frequency} failures (${p.confidence} confidence)`,
|
|
515
|
+
)
|
|
516
|
+
}
|
|
517
|
+
return { content: lines.join('\n') }
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const crsiRestoreCmd: CommandHandler = (ctx, args) => {
|
|
521
|
+
const engine = ctx.engine.getRuleEngine()
|
|
522
|
+
if (!engine) {
|
|
523
|
+
return { content: 'CRSI rule engine is not available.' }
|
|
524
|
+
}
|
|
525
|
+
const ruleId = args.join(' ').trim()
|
|
526
|
+
if (!ruleId) {
|
|
527
|
+
return { content: 'Usage: /crsi restore <rule-id>' }
|
|
528
|
+
}
|
|
529
|
+
engine.setRuleEnabled(ruleId, true)
|
|
530
|
+
return { content: `Rule \`${ruleId}\` has been re-enabled.` }
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const crsiStatsCmd: CommandHandler = async (ctx) => {
|
|
534
|
+
const engine = ctx.engine.getRuleEngine()
|
|
535
|
+
const tracker = ctx.engine.getEffectivenessTracker()
|
|
536
|
+
if (!engine) {
|
|
537
|
+
return { content: 'CRSI rule engine is not available.' }
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const rules = engine.getActiveRules()
|
|
541
|
+
const lines: string[] = ['## CRSI Statistics', '']
|
|
542
|
+
lines.push(`Total active rules: ${rules.length}`)
|
|
543
|
+
lines.push(`Builtin: ${rules.filter((r) => r.source === 'builtin').length}`)
|
|
544
|
+
lines.push(`Auto-generated: ${rules.filter((r) => r.source === 'pattern-analyzer').length}`)
|
|
545
|
+
lines.push(`Manual: ${rules.filter((r) => r.source === 'manual').length}`)
|
|
546
|
+
|
|
547
|
+
if (tracker) {
|
|
548
|
+
let totalInterceptions = 0
|
|
549
|
+
let totalSuccesses = 0
|
|
550
|
+
for (const r of rules) {
|
|
551
|
+
const eff = tracker.getEffectiveness(r.id)
|
|
552
|
+
if (eff) {
|
|
553
|
+
totalInterceptions += eff.appliedCount
|
|
554
|
+
totalSuccesses += eff.successAfterCount
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
lines.push('')
|
|
558
|
+
lines.push(`Total interceptions: ${totalInterceptions}`)
|
|
559
|
+
lines.push(
|
|
560
|
+
`Success rate after rules: ${totalInterceptions > 0 ? Math.round((totalSuccesses / totalInterceptions) * 100) : 0}%`,
|
|
561
|
+
)
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
return { content: lines.join('\n') }
|
|
565
|
+
}
|
|
566
|
+
|
|
449
567
|
// ═══════════════════════════════════════════════════════════════
|
|
450
568
|
// Workflow
|
|
451
569
|
// ═══════════════════════════════════════════════════════════════
|
|
@@ -3072,6 +3190,11 @@ const commandsListCmd: CommandHandler = () => {
|
|
|
3072
3190
|
'/plugin-enable': 'Plugins',
|
|
3073
3191
|
'/plugin-disable': 'Plugins',
|
|
3074
3192
|
'/commands': 'Tools & Skills',
|
|
3193
|
+
'/crsi rules': 'Tools & Skills',
|
|
3194
|
+
'/crsi disable': 'Tools & Skills',
|
|
3195
|
+
'/crsi analyze': 'Tools & Skills',
|
|
3196
|
+
'/crsi restore': 'Tools & Skills',
|
|
3197
|
+
'/crsi stats': 'Tools & Skills',
|
|
3075
3198
|
'/plan': 'Workflow',
|
|
3076
3199
|
'/no-plan': 'Workflow',
|
|
3077
3200
|
'/tdd': 'Workflow',
|
|
@@ -3209,6 +3332,11 @@ registry.set('/remove-plugin', removePluginCmd)
|
|
|
3209
3332
|
registry.set('/plugin-enable', pluginEnableCmd)
|
|
3210
3333
|
registry.set('/plugin-disable', pluginDisableCmd)
|
|
3211
3334
|
registry.set('/commands', commandsListCmd)
|
|
3335
|
+
registry.set('/crsi rules', crsiRulesCmd)
|
|
3336
|
+
registry.set('/crsi disable', crsiDisableCmd)
|
|
3337
|
+
registry.set('/crsi analyze', crsiAnalyzeCmd)
|
|
3338
|
+
registry.set('/crsi restore', crsiRestoreCmd)
|
|
3339
|
+
registry.set('/crsi stats', crsiStatsCmd)
|
|
3212
3340
|
|
|
3213
3341
|
// Workflow
|
|
3214
3342
|
registry.set('/plan', planCmd)
|
|
@@ -3359,6 +3487,11 @@ const COMMAND_DESCRIPTIONS: Record<string, string> = {
|
|
|
3359
3487
|
'/remove-plugin': 'Remove an installed plugin',
|
|
3360
3488
|
'/plugin-enable': 'Enable a disabled plugin',
|
|
3361
3489
|
'/plugin-disable': 'Disable an enabled plugin',
|
|
3490
|
+
'/crsi rules': 'List all active CRSI rules with their status',
|
|
3491
|
+
'/crsi disable': 'Disable a CRSI rule by ID',
|
|
3492
|
+
'/crsi analyze': 'Manually trigger CRSI pattern analysis across all agents',
|
|
3493
|
+
'/crsi restore': 'Restore a disabled or degraded CRSI rule',
|
|
3494
|
+
'/crsi stats': 'Show CRSI overall effectiveness statistics',
|
|
3362
3495
|
'/plan': 'Enter plan mode',
|
|
3363
3496
|
'/no-plan': 'Exit plan mode',
|
|
3364
3497
|
'/tdd': 'Test-Driven Development workflow (RED → GREEN → REFACTOR)',
|