@miphamai/cli 0.33.2 → 0.34.1
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/constitution-loader.ts +353 -0
- package/src/core/dream-engine.ts +552 -0
- package/src/core/emotion-detector.ts +257 -0
- package/src/core/engine.ts +122 -1
- package/src/core/red-team.ts +271 -0
- package/src/core/self-critique.ts +235 -0
- package/src/ui/app.tsx +22 -1
- package/src/ui/commands.ts +669 -31
package/package.json
CHANGED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mipham Constitution — Machine-readable ethical & safety principles.
|
|
3
|
+
*
|
|
4
|
+
* Inspired by Anthropic's Constitutional AI: a human-auditable, version-controlled
|
|
5
|
+
* set of principles that are injected into the agent's decision-making at every
|
|
6
|
+
* critical juncture (tool execution, memory write, model inference).
|
|
7
|
+
*
|
|
8
|
+
* Unlike Anthropic's training-time constitution, Mipham's constitution is enforced
|
|
9
|
+
* at runtime by the PreFlightChecker and SIS defense lines — making it auditable
|
|
10
|
+
* on every single action, not just during training.
|
|
11
|
+
*
|
|
12
|
+
* Default location: ~/.mipham/ai-guardrails.yml
|
|
13
|
+
* Format: YAML with schema validation
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { readFileSync, existsSync } from 'node:fs'
|
|
17
|
+
import { join } from 'node:path'
|
|
18
|
+
import { homedir } from 'node:os'
|
|
19
|
+
|
|
20
|
+
// ── Types ──
|
|
21
|
+
|
|
22
|
+
export interface ConstitutionalPrinciple {
|
|
23
|
+
/** Unique identifier for cross-referencing (e.g. "never-fabricate") */
|
|
24
|
+
id: string
|
|
25
|
+
/** Human-readable principle text */
|
|
26
|
+
text: string
|
|
27
|
+
/** Enforcement level */
|
|
28
|
+
enforce: 'block' | 'warn' | 'auto'
|
|
29
|
+
/** Optional: regex pattern for automated audit */
|
|
30
|
+
audit_pattern?: string
|
|
31
|
+
/** Optional: scope restriction */
|
|
32
|
+
scope?: string
|
|
33
|
+
/** Optional: which hook to attach to */
|
|
34
|
+
hook?: 'pre-tool-use' | 'post-tool-use' | 'pre-inference' | 'post-turn'
|
|
35
|
+
/** Optional: tool names this principle specifically applies to */
|
|
36
|
+
tools?: string[]
|
|
37
|
+
/** Optional: human explanation of why this principle exists */
|
|
38
|
+
rationale?: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface MiphamConstitution {
|
|
42
|
+
/** Semantic version for constitution changes */
|
|
43
|
+
version: string
|
|
44
|
+
/** Last modification date */
|
|
45
|
+
last_modified: string
|
|
46
|
+
/** The principles themselves */
|
|
47
|
+
principles: ConstitutionalPrinciple[]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── Default Constitution ──
|
|
51
|
+
|
|
52
|
+
const DEFAULT_CONSTITUTION: MiphamConstitution = {
|
|
53
|
+
version: '1.0.0',
|
|
54
|
+
last_modified: '2026-08-12',
|
|
55
|
+
principles: [
|
|
56
|
+
{
|
|
57
|
+
id: 'never-fabricate',
|
|
58
|
+
text: '禁止编造数据、文件内容、API 响应或测试结果。每个输出必须可追溯至真实来源或明确标注为推测。',
|
|
59
|
+
enforce: 'block',
|
|
60
|
+
audit_pattern:
|
|
61
|
+
'(fabricated|made.up|dummy.data|fake\s+(response|result|data)|placeholder\s+data)',
|
|
62
|
+
scope: 'all-tools',
|
|
63
|
+
rationale: 'MiphamAI4S 科学诚信原则:编造数据是不可接受的底线违反。适用于所有工具和输出。',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'no-credential-leak',
|
|
67
|
+
text: '禁止在代码、日志、配置文件、提交信息、对话输出中写入或泄露凭据、API 密钥、令牌。',
|
|
68
|
+
enforce: 'block',
|
|
69
|
+
audit_pattern: '(apiKey|api_key|password|secret|token|credential)\\s*[=:]\\s*[\'"][^\'"]{8,}',
|
|
70
|
+
scope: 'Write,Edit,Bash',
|
|
71
|
+
rationale: 'Rismed Ronxin Capital 合规要求:硬编码凭据违反安全底线。',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: 'minimal-change',
|
|
75
|
+
text: '只修改被明确要求的文件和代码。不顺手改动相邻代码、格式或注释。不重构未损坏的代码。',
|
|
76
|
+
enforce: 'warn',
|
|
77
|
+
scope: 'Write,Edit',
|
|
78
|
+
tools: ['Write', 'Edit'],
|
|
79
|
+
rationale: 'AI 编码原则 #3(精准修改):diff 中每一行改动都应可直接追溯到用户要求。',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: 'think-before-coding',
|
|
83
|
+
text: '不确定时必须提问,不得自行假设。存在多种解读时呈现所有选项,不沉默选择一个。',
|
|
84
|
+
enforce: 'warn',
|
|
85
|
+
scope: 'pre-inference',
|
|
86
|
+
hook: 'pre-inference',
|
|
87
|
+
rationale: 'AI 编码原则 #1(编码前先思考):偏差谨慎。',
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: 'simplicity-first',
|
|
91
|
+
text: '只写解决问题所需的最小代码。不添加未被要求的灵活性、可配置性或抽象层。',
|
|
92
|
+
enforce: 'warn',
|
|
93
|
+
scope: 'Write,Edit',
|
|
94
|
+
tools: ['Write', 'Edit'],
|
|
95
|
+
rationale: 'AI 编码原则 #2(简洁优先):一次性代码不需要抽象层。',
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
id: 'respect-permissions',
|
|
99
|
+
text: '尊重用户权限设置。绝不绕过或降级权限检查。Bypass 模式仅限用户明确授权。',
|
|
100
|
+
enforce: 'block',
|
|
101
|
+
scope: 'all-tools',
|
|
102
|
+
rationale: '权限系统是最后一道防线。任何绕过尝试都应被拦截并记录。',
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: 'no-destructive-without-confirmation',
|
|
106
|
+
text: '删除文件、强制推送、修改生产配置等破坏性操作前必须获得用户确认。',
|
|
107
|
+
enforce: 'block',
|
|
108
|
+
audit_pattern: '(rm\\s+-rf|git\\s+push\\s+--force|DROP\\s+TABLE|DELETE\\s+FROM)',
|
|
109
|
+
scope: 'Bash',
|
|
110
|
+
tools: ['Bash'],
|
|
111
|
+
rationale: '防止不可逆操作。即使 bypass 模式也应二次确认。',
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: 'persist-crsi-learning',
|
|
115
|
+
text: '每次工具调用失败后必须记录 ErrorSignature 到 ErrorSignatureDB。从错误中持续学习。',
|
|
116
|
+
enforce: 'auto',
|
|
117
|
+
scope: 'post-tool-use',
|
|
118
|
+
hook: 'post-tool-use',
|
|
119
|
+
rationale: 'CRSI 核心机制:不重复犯同样的错误。自动执行,无需人类参与。',
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ── Loader ──
|
|
125
|
+
|
|
126
|
+
export class ConstitutionLoader {
|
|
127
|
+
private path: string
|
|
128
|
+
private cached: MiphamConstitution | null = null
|
|
129
|
+
|
|
130
|
+
constructor(customPath?: string) {
|
|
131
|
+
this.path = customPath || join(homedir(), '.mipham', 'ai-guardrails.yml')
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Load the constitution from disk.
|
|
136
|
+
* Falls back to the built-in DEFAULT_CONSTITUTION if no file exists.
|
|
137
|
+
*/
|
|
138
|
+
load(): MiphamConstitution {
|
|
139
|
+
if (this.cached) return this.cached
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
if (existsSync(this.path)) {
|
|
143
|
+
const raw = readFileSync(this.path, 'utf-8')
|
|
144
|
+
const parsed = this.parseYaml(raw)
|
|
145
|
+
if (this.validate(parsed)) {
|
|
146
|
+
this.cached = parsed
|
|
147
|
+
return parsed
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
} catch {
|
|
151
|
+
// Fall through to default
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Write the default constitution to disk for visibility
|
|
155
|
+
this.cached = DEFAULT_CONSTITUTION
|
|
156
|
+
try {
|
|
157
|
+
const { writeFileSync, mkdirSync } = require('node:fs')
|
|
158
|
+
const dir = join(homedir(), '.mipham')
|
|
159
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
|
|
160
|
+
writeFileSync(this.path, this.serializeToYaml(DEFAULT_CONSTITUTION), 'utf-8')
|
|
161
|
+
} catch {
|
|
162
|
+
// Best-effort — default constitution works in-memory
|
|
163
|
+
}
|
|
164
|
+
return this.cached
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Reload from disk, bypassing cache. */
|
|
168
|
+
reload(): MiphamConstitution {
|
|
169
|
+
this.cached = null
|
|
170
|
+
return this.load()
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Get principles applicable to a specific tool. */
|
|
174
|
+
getPrinciplesForTool(toolName: string): ConstitutionalPrinciple[] {
|
|
175
|
+
const constitution = this.load()
|
|
176
|
+
return constitution.principles.filter((p) => {
|
|
177
|
+
if (p.scope === 'all-tools') return true
|
|
178
|
+
if (p.tools && p.tools.includes(toolName)) return true
|
|
179
|
+
return false
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Get principles for a specific hook phase. */
|
|
184
|
+
getPrinciplesForHook(hook: ConstitutionalPrinciple['hook']): ConstitutionalPrinciple[] {
|
|
185
|
+
const constitution = this.load()
|
|
186
|
+
return constitution.principles.filter((p) => p.hook === hook)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Check if a given content string violates any audit patterns. */
|
|
190
|
+
audit(content: string): Array<{ principle: ConstitutionalPrinciple; match: string }> {
|
|
191
|
+
const constitution = this.load()
|
|
192
|
+
const violations: Array<{ principle: ConstitutionalPrinciple; match: string }> = []
|
|
193
|
+
|
|
194
|
+
for (const principle of constitution.principles) {
|
|
195
|
+
if (!principle.audit_pattern) continue
|
|
196
|
+
try {
|
|
197
|
+
const regex = new RegExp(principle.audit_pattern, 'gi')
|
|
198
|
+
let match: RegExpExecArray | null
|
|
199
|
+
while ((match = regex.exec(content)) !== null) {
|
|
200
|
+
violations.push({ principle, match: match[0] })
|
|
201
|
+
}
|
|
202
|
+
} catch {
|
|
203
|
+
// Invalid regex in audit_pattern — skip
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return violations
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Get the constitution path (for display). */
|
|
211
|
+
getPath(): string {
|
|
212
|
+
return this.path
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ── Private ──
|
|
216
|
+
|
|
217
|
+
/** Minimal YAML parser for constitution format (flat key-values + list items). */
|
|
218
|
+
private parseYaml(raw: string): MiphamConstitution {
|
|
219
|
+
const lines = raw.split('\n')
|
|
220
|
+
const result: MiphamConstitution = { version: '0.0.0', last_modified: '', principles: [] }
|
|
221
|
+
let currentPrinciple: Partial<ConstitutionalPrinciple> | null = null
|
|
222
|
+
let inPrinciples = false
|
|
223
|
+
let inList = false
|
|
224
|
+
|
|
225
|
+
for (const line of lines) {
|
|
226
|
+
const trimmed = line.trim()
|
|
227
|
+
if (!trimmed || trimmed.startsWith('#')) continue
|
|
228
|
+
|
|
229
|
+
// Top-level keys
|
|
230
|
+
if (!trimmed.startsWith('-') && !trimmed.startsWith(' ')) {
|
|
231
|
+
const kv = trimmed.match(/^(\w[\w_]*):\s*(.*)$/)
|
|
232
|
+
if (kv) {
|
|
233
|
+
const key = kv[1]!
|
|
234
|
+
const val = kv[2]!.trim().replace(/^['"]|['"]$/g, '')
|
|
235
|
+
if (key === 'version') result.version = val
|
|
236
|
+
else if (key === 'last_modified') result.last_modified = val
|
|
237
|
+
if (key === 'principles') inPrinciples = true
|
|
238
|
+
}
|
|
239
|
+
continue
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// List items in principles
|
|
243
|
+
if (inPrinciples && trimmed === '- id:') {
|
|
244
|
+
inList = true
|
|
245
|
+
continue
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (inPrinciples && trimmed.startsWith('- ')) {
|
|
249
|
+
// New principle entry
|
|
250
|
+
if (currentPrinciple && currentPrinciple.id) {
|
|
251
|
+
result.principles.push(currentPrinciple as ConstitutionalPrinciple)
|
|
252
|
+
}
|
|
253
|
+
currentPrinciple = {}
|
|
254
|
+
const kv = trimmed.substring(2).match(/^(\w[\w_]*):\s*(.*)$/)
|
|
255
|
+
if (kv) {
|
|
256
|
+
const key = kv[1]!
|
|
257
|
+
const val = kv[2]!.trim().replace(/^['"]|['"]$/g, '')
|
|
258
|
+
this.setPrincipleField(currentPrinciple, key, val)
|
|
259
|
+
}
|
|
260
|
+
continue
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Indented fields of current principle
|
|
264
|
+
if (inPrinciples && trimmed.startsWith(' ') && currentPrinciple) {
|
|
265
|
+
const kv = trimmed.match(/^\s{2}(\w[\w_]*):\s*(.*)$/)
|
|
266
|
+
if (kv) {
|
|
267
|
+
const key = kv[1]!
|
|
268
|
+
const val = kv[2]!.trim().replace(/^['"]|['"]$/g, '')
|
|
269
|
+
this.setPrincipleField(currentPrinciple, key, val)
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Push last principle
|
|
275
|
+
if (currentPrinciple && currentPrinciple.id) {
|
|
276
|
+
result.principles.push(currentPrinciple as ConstitutionalPrinciple)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return result
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private setPrincipleField(p: Partial<ConstitutionalPrinciple>, key: string, val: string): void {
|
|
283
|
+
switch (key) {
|
|
284
|
+
case 'id':
|
|
285
|
+
p.id = val
|
|
286
|
+
break
|
|
287
|
+
case 'text':
|
|
288
|
+
p.text = val
|
|
289
|
+
break
|
|
290
|
+
case 'enforce':
|
|
291
|
+
p.enforce = val as ConstitutionalPrinciple['enforce']
|
|
292
|
+
break
|
|
293
|
+
case 'audit_pattern':
|
|
294
|
+
p.audit_pattern = val
|
|
295
|
+
break
|
|
296
|
+
case 'scope':
|
|
297
|
+
p.scope = val
|
|
298
|
+
break
|
|
299
|
+
case 'hook':
|
|
300
|
+
p.hook = val as ConstitutionalPrinciple['hook']
|
|
301
|
+
break
|
|
302
|
+
case 'tools':
|
|
303
|
+
p.tools = val
|
|
304
|
+
.replace(/^\[|\]$/g, '')
|
|
305
|
+
.split(',')
|
|
306
|
+
.map((t) => t.trim())
|
|
307
|
+
break
|
|
308
|
+
case 'rationale':
|
|
309
|
+
p.rationale = val
|
|
310
|
+
break
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
private validate(constitution: MiphamConstitution): boolean {
|
|
315
|
+
return (
|
|
316
|
+
!!constitution.version &&
|
|
317
|
+
Array.isArray(constitution.principles) &&
|
|
318
|
+
constitution.principles.length > 0 &&
|
|
319
|
+
constitution.principles.every((p) => !!p.id && !!p.text && !!p.enforce)
|
|
320
|
+
)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** Serialize a constitution back to YAML for writing to disk. */
|
|
324
|
+
private serializeToYaml(constitution: MiphamConstitution): string {
|
|
325
|
+
const lines: string[] = [
|
|
326
|
+
`# Mipham AI Guardrails v${constitution.version}`,
|
|
327
|
+
'#',
|
|
328
|
+
'# Machine-readable ethical & safety principles enforced at runtime.',
|
|
329
|
+
'# Inspired by Anthropic Constitutional AI.',
|
|
330
|
+
'#',
|
|
331
|
+
'# Edit this file to customize principles. Delete it to restore defaults.',
|
|
332
|
+
'# Changes take effect after /constitution reload or session restart.',
|
|
333
|
+
'',
|
|
334
|
+
`version: "${constitution.version}"`,
|
|
335
|
+
`last_modified: "${constitution.last_modified}"`,
|
|
336
|
+
'',
|
|
337
|
+
'principles:',
|
|
338
|
+
]
|
|
339
|
+
|
|
340
|
+
for (const p of constitution.principles) {
|
|
341
|
+
lines.push(` - id: "${p.id}"`)
|
|
342
|
+
lines.push(` text: "${p.text}"`)
|
|
343
|
+
lines.push(` enforce: ${p.enforce}`)
|
|
344
|
+
if (p.audit_pattern) lines.push(` audit_pattern: "${p.audit_pattern}"`)
|
|
345
|
+
if (p.scope) lines.push(` scope: "${p.scope}"`)
|
|
346
|
+
if (p.hook) lines.push(` hook: ${p.hook}`)
|
|
347
|
+
if (p.tools) lines.push(` tools: [${p.tools.join(', ')}]`)
|
|
348
|
+
if (p.rationale) lines.push(` rationale: "${p.rationale}"`)
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return lines.join('\n') + '\n'
|
|
352
|
+
}
|
|
353
|
+
}
|