@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miphamai/cli",
3
- "version": "0.33.2",
3
+ "version": "0.34.1",
4
4
  "description": "Mipham Code — Multi-model open-core intelligent coding terminal by MiphamAI",
5
5
  "keywords": [
6
6
  "ai",
@@ -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
+ }