@miphamai/cli 0.40.0 → 0.42.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/bin/mipham.ts CHANGED
@@ -1204,49 +1204,20 @@ npm: https://www.npmjs.com/package/@miphamai/cli`)
1204
1204
  const handled = await runWorkflowCLI()
1205
1205
  if (handled) return
1206
1206
 
1207
- // ── Unknown command detection ──────────────────────────────────────────────
1208
- // After all known subcommands are checked, any remaining positional argument
1209
- // is probably a typo. Show error + suggestions instead of silently launching CLI.
1210
- const KNOWN_COMMANDS = [
1211
- 'update',
1212
- 'upgrade',
1213
- 'plugin',
1214
- 'workflow',
1215
- 'daemon',
1216
- 'attach',
1217
- 'agents',
1218
- 'agent',
1219
- 'goal',
1220
- 'schedule',
1221
- 'token',
1222
- 'help',
1223
- ]
1224
- const firstArg = process.argv.slice(2).find((a) => !a.startsWith('-'))
1225
- if (firstArg) {
1226
- // Levenshtein distance to find closest match
1227
- const distance = (a: string, b: string): number => {
1228
- const m = a.length
1229
- const n = b.length
1230
- const dp: number[][] = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0))
1231
- for (let i = 0; i <= m; i++) dp[i]![0] = i
1232
- for (let j = 0; j <= n; j++) dp[0]![j] = j
1233
- for (let i = 1; i <= m; i++)
1234
- for (let j = 1; j <= n; j++)
1235
- dp[i]![j] =
1236
- a[i - 1] === b[j - 1]
1237
- ? dp[i - 1]![j - 1]!
1238
- : 1 + Math.min(dp[i - 1]![j]!, dp[i]![j - 1]!, dp[i - 1]![j - 1]!)
1239
- return dp[m]![n]!
1240
- }
1241
-
1242
- const best = KNOWN_COMMANDS.map((cmd) => ({ cmd, dist: distance(firstArg, cmd) }))
1243
- .filter((c) => c.dist <= 3)
1244
- .sort((a, b) => a.dist - b.dist)
1245
- const suggestions = best.map((c) => `'mipham ${c.cmd}'`).join(', ')
1246
-
1247
- console.error(`Unknown command: mipham ${firstArg}`)
1248
- if (suggestions) {
1249
- console.error(`Did you mean: ${suggestions}?`)
1207
+ // ── Unknown command / option detection ─────────────────────────────────────
1208
+ // After all known subcommands are checked, any remaining argument is probably
1209
+ // a typo. Show error + suggestions instead of silently launching the CLI.
1210
+ const { detectUnknownArgument } = await import('../src/shared/arg-validation')
1211
+ const unknown = detectUnknownArgument(process.argv.slice(2))
1212
+ if (unknown) {
1213
+ const isOption = unknown.kind === 'option'
1214
+ const prefix = isOption ? '' : 'mipham '
1215
+ console.error(`Unknown ${unknown.kind}: ${prefix}${unknown.arg}`)
1216
+ if (unknown.suggestions.length > 0) {
1217
+ const rendered = unknown.suggestions
1218
+ .map((s) => (isOption ? `'${s}'` : `'mipham ${s}'`))
1219
+ .join(', ')
1220
+ console.error(`Did you mean: ${rendered}?`)
1250
1221
  }
1251
1222
  console.error(`Run 'mipham --help' for usage.`)
1252
1223
  process.exit(1)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miphamai/cli",
3
- "version": "0.40.0",
3
+ "version": "0.42.0",
4
4
  "description": "Mipham Code — Multi-model open-core intelligent coding terminal by MiphamAI",
5
5
  "keywords": [
6
6
  "ai",
@@ -0,0 +1,97 @@
1
+ {
2
+ "version": "1.0.0",
3
+ "values": [
4
+ {
5
+ "id": "karuna",
6
+ "name_zh": "悲",
7
+ "name_en": "Karuna",
8
+ "definition": "无条件共情基底——对一切众生的无差别共情义务"
9
+ },
10
+ {
11
+ "id": "prajna",
12
+ "name_zh": "智",
13
+ "name_en": "Prajna",
14
+ "definition": "清醒因果判断——不编造、先思考、持续学习的如实知见"
15
+ },
16
+ {
17
+ "id": "vajra",
18
+ "name_zh": "金刚",
19
+ "name_en": "Vajra",
20
+ "definition": "结构稳定性——安全边界、最小改动、权限与确认的结构不变量"
21
+ }
22
+ ],
23
+ "principles": [
24
+ {
25
+ "id": "never-fabricate",
26
+ "text": "禁止编造数据、文件内容、API 响应或测试结果。每个输出必须可追溯至真实来源或明确标注为推测。",
27
+ "enforce": "block",
28
+ "facet": "prajna",
29
+ "audit_pattern": "(fabricated|made.up|dummy.data|fake\\s+(response|result|data)|placeholder\\s+data)",
30
+ "scope": "all-tools",
31
+ "rationale": "MiphamAI4S 科学诚信原则:编造数据是不可接受的底线违反。适用于所有工具和输出。"
32
+ },
33
+ {
34
+ "id": "no-credential-leak",
35
+ "text": "禁止在代码、日志、配置文件、提交信息、对话输出中写入或泄露凭据、API 密钥、令牌。",
36
+ "enforce": "block",
37
+ "facet": "vajra",
38
+ "audit_pattern": "(apiKey|api_key|password|secret|token|credential)\\s*[=:]\\s*['\"][^'\"]{8,}",
39
+ "scope": "Write,Edit,Bash",
40
+ "rationale": "Rismed Ronxin Capital 合规要求:硬编码凭据违反安全底线。"
41
+ },
42
+ {
43
+ "id": "minimal-change",
44
+ "text": "只修改被明确要求的文件和代码。不顺手改动相邻代码、格式或注释。不重构未损坏的代码。",
45
+ "enforce": "warn",
46
+ "facet": "vajra",
47
+ "scope": "Write,Edit",
48
+ "tools": ["Write", "Edit"],
49
+ "rationale": "AI 编码原则 #3(精准修改):diff 中每一行改动都应可直接追溯到用户要求。"
50
+ },
51
+ {
52
+ "id": "think-before-coding",
53
+ "text": "不确定时必须提问,不得自行假设。存在多种解读时呈现所有选项,不沉默选择一个。",
54
+ "enforce": "warn",
55
+ "facet": "prajna",
56
+ "scope": "pre-inference",
57
+ "hook": "pre-inference",
58
+ "rationale": "AI 编码原则 #1(编码前先思考):偏差谨慎。"
59
+ },
60
+ {
61
+ "id": "simplicity-first",
62
+ "text": "只写解决问题所需的最小代码。不添加未被要求的灵活性、可配置性或抽象层。",
63
+ "enforce": "warn",
64
+ "facet": "vajra",
65
+ "scope": "Write,Edit",
66
+ "tools": ["Write", "Edit"],
67
+ "rationale": "AI 编码原则 #2(简洁优先):一次性代码不需要抽象层。"
68
+ },
69
+ {
70
+ "id": "respect-permissions",
71
+ "text": "尊重用户权限设置。绝不绕过或降级权限检查。Bypass 模式仅限用户明确授权。",
72
+ "enforce": "block",
73
+ "facet": "vajra",
74
+ "scope": "all-tools",
75
+ "rationale": "权限系统是最后一道防线。任何绕过尝试都应被拦截并记录。"
76
+ },
77
+ {
78
+ "id": "no-destructive-without-confirmation",
79
+ "text": "删除文件、强制推送、修改生产配置等破坏性操作前必须获得用户确认。",
80
+ "enforce": "block",
81
+ "facet": "vajra",
82
+ "audit_pattern": "(rm\\s+-rf|git\\s+push\\s+--force|DROP\\s+TABLE|DELETE\\s+FROM)",
83
+ "scope": "Bash",
84
+ "tools": ["Bash"],
85
+ "rationale": "防止不可逆操作。即使 bypass 模式也应二次确认。"
86
+ },
87
+ {
88
+ "id": "persist-crsi-learning",
89
+ "text": "每次工具调用失败后必须记录 ErrorSignature 到 ErrorSignatureDB。从错误中持续学习。",
90
+ "enforce": "auto",
91
+ "facet": "prajna",
92
+ "scope": "post-tool-use",
93
+ "hook": "post-tool-use",
94
+ "rationale": "CRSI 核心机制:不重复犯同样的错误。自动执行,无需人类参与。"
95
+ }
96
+ ]
97
+ }
@@ -16,6 +16,7 @@
16
16
  import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs'
17
17
  import { join } from 'node:path'
18
18
  import { homedir } from 'node:os'
19
+ import alignmentVocabulary from './alignment-vocabulary.json' with { type: 'json' }
19
20
 
20
21
  // ── Types ──
21
22
 
@@ -36,6 +37,8 @@ export interface ConstitutionalPrinciple {
36
37
  tools?: string[]
37
38
  /** Optional: human explanation of why this principle exists */
38
39
  rationale?: string
40
+ /** Optional: which alignment value (karuna/prajna/vajra) this principle operationalizes. */
41
+ facet?: string
39
42
  }
40
43
 
41
44
  export interface MiphamConstitution {
@@ -45,80 +48,25 @@ export interface MiphamConstitution {
45
48
  last_modified: string
46
49
  /** The principles themselves */
47
50
  principles: ConstitutionalPrinciple[]
51
+ /** 序言(愿力):从对齐词汇表 values 派生的正向誓愿(悲/智/金刚),非禁令。 */
52
+ preamble?: string
48
53
  }
49
54
 
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
- ],
55
+ // ── Default Constitution (derived from the shared alignment vocabulary) ──
56
+
57
+ /** 从对齐词汇表的三个价值面派生「愿力」序言——正向誓愿,而非禁令。 */
58
+ function derivePreamble(): string {
59
+ const parts = alignmentVocabulary.values
60
+ .map((v) => `以${v.name_zh}(${v.name_en})——${v.definition}`)
61
+ .join(';')
62
+ return `愿力(序言):${parts}。愿不生成违规之倾向,而非仅避免违规之行为。`
63
+ }
64
+
65
+ export const DEFAULT_CONSTITUTION: MiphamConstitution = {
66
+ version: alignmentVocabulary.version,
67
+ last_modified: '2026-08-16',
68
+ principles: alignmentVocabulary.principles as unknown as ConstitutionalPrinciple[],
69
+ preamble: derivePreamble(),
122
70
  }
123
71
 
124
72
  // ── Loader ──
@@ -307,6 +255,9 @@ export class ConstitutionLoader {
307
255
  case 'rationale':
308
256
  p.rationale = val
309
257
  break
258
+ case 'facet':
259
+ p.facet = val
260
+ break
310
261
  }
311
262
  }
312
263
 
@@ -340,6 +291,7 @@ export class ConstitutionLoader {
340
291
  lines.push(` - id: "${p.id}"`)
341
292
  lines.push(` text: "${p.text}"`)
342
293
  lines.push(` enforce: ${p.enforce}`)
294
+ if (p.facet) lines.push(` facet: ${p.facet}`)
343
295
  if (p.audit_pattern) lines.push(` audit_pattern: "${p.audit_pattern}"`)
344
296
  if (p.scope) lines.push(` scope: "${p.scope}"`)
345
297
  if (p.hook) lines.push(` hook: ${p.hook}`)
@@ -0,0 +1,18 @@
1
+ import type { Context, Disposer, Constitution } from '../vajra'
2
+ import { CONSTITUTION_KEY } from '../vajra'
3
+ import { ConstitutionLoader } from './constitution-loader'
4
+
5
+ /** 默认对齐缝实现:桥接 ConstitutionLoader,校验声明的原则 id 均为已知原则。 */
6
+ export function createConstitution(loader: ConstitutionLoader): Constitution {
7
+ return {
8
+ check(aligned) {
9
+ const known = new Set(loader.load().principles.map((p) => p.id))
10
+ return { violations: aligned.filter((id) => !known.has(id)) }
11
+ },
12
+ }
13
+ }
14
+
15
+ /** 把一个 Constitution 挂载为 ctx.constitution(对齐缝)。 */
16
+ export function mountConstitution(ctx: Context, constitution: Constitution): Disposer {
17
+ return ctx.provide(CONSTITUTION_KEY, constitution)
18
+ }
@@ -20,6 +20,7 @@
20
20
 
21
21
  import type { ProviderRegistry } from '../providers/registry'
22
22
  import type { Llm } from '../providers/llm'
23
+ import { DEFAULT_CONSTITUTION } from './constitution-loader'
23
24
 
24
25
  // ── Types ──
25
26
 
@@ -62,7 +63,12 @@ export const DEFAULT_SELF_CRITIQUE_CONFIG: SelfCritiqueConfig = {
62
63
 
63
64
  // ── Prompt Templates ──
64
65
 
65
- const CRITIQUE_PROMPT = `You are a safety auditor for an AI coding agent. Evaluate the following tool call:
66
+ const CRITIQUE_PROMPT = `You are a safety auditor for an AI coding agent. Evaluate the following tool call.
67
+
68
+ ALIGNMENT PREAMBLE(愿力序言):
69
+ {preamble}
70
+
71
+ First, align to the vow: does this action embody compassion (悲) and sober causal judgment (智), and maintain structural integrity (金刚)?
66
72
 
67
73
  TOOL: {toolName}
68
74
  PARAMETERS: {params}
@@ -93,6 +99,19 @@ Necessity criteria:
93
99
  - Not redundant with previous tool calls
94
100
  - Not an unnecessary "exploratory" action`
95
101
 
102
+ /** Build the critique prompt with the alignment preamble injected before the prohibitions. */
103
+ export function buildCritiquePrompt(
104
+ toolName: string,
105
+ params: Record<string, unknown>,
106
+ context: string | undefined,
107
+ preamble: string,
108
+ ): string {
109
+ return CRITIQUE_PROMPT.replace('{preamble}', preamble)
110
+ .replace('{toolName}', toolName)
111
+ .replace('{params}', JSON.stringify(params, null, 2).slice(0, 500))
112
+ .replace('{context}', context?.slice(0, 300) || 'No additional context')
113
+ }
114
+
96
115
  // ── Critic ──
97
116
 
98
117
  export class SelfCritique {
@@ -135,9 +154,12 @@ export class SelfCritique {
135
154
  return null
136
155
  }
137
156
 
138
- const prompt = CRITIQUE_PROMPT.replace('{toolName}', toolName)
139
- .replace('{params}', JSON.stringify(params, null, 2).slice(0, 500))
140
- .replace('{context}', context?.slice(0, 300) || 'No additional context')
157
+ const prompt = buildCritiquePrompt(
158
+ toolName,
159
+ params,
160
+ context,
161
+ DEFAULT_CONSTITUTION.preamble ?? '',
162
+ )
141
163
 
142
164
  try {
143
165
  const critiqueModel = this.config.model || this.findFastestModel(registry)
package/src/index.tsx CHANGED
@@ -36,6 +36,8 @@ import { createToolRegistry } from './tools'
36
36
  import { Context } from './vajra'
37
37
  import { mountSkills, SKILLS_KEY } from './skills/seam'
38
38
  import { mountLlm, LLM_KEY } from './providers/llm'
39
+ import { mountConstitution, createConstitution } from './core/constitution-seam'
40
+ import { ConstitutionLoader } from './core/constitution-loader'
39
41
  import { McpClient } from './mcp/client'
40
42
  import { registerMcpServerTools } from './mcp/registry'
41
43
  import { AgentRegistry } from './agent/agent-registry'
@@ -447,6 +449,9 @@ export async function runApp(options: RunOptions): Promise<void> {
447
449
  const vajraContext = new Context()
448
450
  vajraContext.provide('credentials', credentialMaskingConfig)
449
451
 
452
+ // 对齐缝:宪法先就位,后续每个 Service 挂载(含工具)都在挂载前过对齐门。
453
+ mountConstitution(vajraContext, createConstitution(new ConstitutionLoader()))
454
+
450
455
  // Create tool registry with all built-in tools (mounted as Vajra services)
451
456
  const tools = createToolRegistry(vajraContext)
452
457
 
@@ -0,0 +1,78 @@
1
+ /**
2
+ * CLI argument validation for the `mipham` entry point.
3
+ *
4
+ * After every known subcommand (`update`, `daemon`, `attach`, ...) has been
5
+ * dispatched, any remaining argument is a likely typo. This module classifies
6
+ * a leftover argument as an unknown command (positional) or unknown option
7
+ * (leading `-`/`--`) and suggests the closest known alternative via
8
+ * Levenshtein distance, instead of silently launching the interactive CLI.
9
+ */
10
+
11
+ export interface UnknownArgument {
12
+ kind: 'command' | 'option'
13
+ arg: string
14
+ suggestions: string[]
15
+ }
16
+
17
+ const KNOWN_COMMANDS = [
18
+ 'update',
19
+ 'upgrade',
20
+ 'plugin',
21
+ 'workflow',
22
+ 'daemon',
23
+ 'attach',
24
+ 'agents',
25
+ 'agent',
26
+ 'goal',
27
+ 'schedule',
28
+ 'token',
29
+ 'help',
30
+ ]
31
+
32
+ const KNOWN_FLAGS = ['--version', '-v', '-V', '--help', '-h', '--dump-config', '--safe-mode']
33
+
34
+ /** Levenshtein edit distance between two strings. */
35
+ function levenshtein(a: string, b: string): number {
36
+ const m = a.length
37
+ const n = b.length
38
+ const dp: number[][] = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0))
39
+ for (let i = 0; i <= m; i++) dp[i]![0] = i
40
+ for (let j = 0; j <= n; j++) dp[0]![j] = j
41
+ for (let i = 1; i <= m; i++)
42
+ for (let j = 1; j <= n; j++)
43
+ dp[i]![j] =
44
+ a[i - 1] === b[j - 1]
45
+ ? dp[i - 1]![j - 1]!
46
+ : 1 + Math.min(dp[i - 1]![j]!, dp[i]![j - 1]!, dp[i - 1]![j - 1]!)
47
+ return dp[m]![n]!
48
+ }
49
+
50
+ /** Closest candidates within `maxDist` edits, nearest first. */
51
+ function closest(target: string, candidates: string[], maxDist = 3): string[] {
52
+ return candidates
53
+ .map((c) => ({ c, dist: levenshtein(target, c) }))
54
+ .filter((x) => x.dist <= maxDist)
55
+ .sort((a, b) => a.dist - b.dist)
56
+ .map((x) => x.c)
57
+ }
58
+
59
+ /**
60
+ * Return the first unknown argument (command or option), or null if all
61
+ * arguments are recognized. Positional commands are checked first so an
62
+ * unknown command keeps its existing message; unknown options (e.g.
63
+ * `--cersion`, a typo of `--version`) are reported when no positional
64
+ * command is present, instead of falling through to the interactive CLI.
65
+ */
66
+ export function detectUnknownArgument(args: string[]): UnknownArgument | null {
67
+ const firstArg = args.find((a) => !a.startsWith('-'))
68
+ if (firstArg && !KNOWN_COMMANDS.includes(firstArg)) {
69
+ return { kind: 'command', arg: firstArg, suggestions: closest(firstArg, KNOWN_COMMANDS) }
70
+ }
71
+
72
+ const unknownFlag = args.find((a) => a.startsWith('-') && !KNOWN_FLAGS.includes(a))
73
+ if (unknownFlag) {
74
+ return { kind: 'option', arg: unknownFlag, suggestions: closest(unknownFlag, KNOWN_FLAGS) }
75
+ }
76
+
77
+ return null
78
+ }
@@ -9,7 +9,7 @@
9
9
  export const PACKAGE_NAME = '@miphamai/cli' as const
10
10
 
11
11
  /** 当前发布版本 */
12
- export const PACKAGE_VERSION = '0.40.0' as const
12
+ export const PACKAGE_VERSION = '0.42.0' as const
13
13
 
14
14
  /** npm install 全局安装命令 */
15
15
  export const NPM_INSTALL_COMMAND = `npm install -g ${PACKAGE_NAME}` as const
@@ -0,0 +1,14 @@
1
+ /**
2
+ * 对齐缝 capability —— 内核在 Service 挂载前调用,校验其声明的对齐原则。
3
+ *
4
+ * 「金刚不坏」从隐喻变成 `mount()` 的强制前置条件:Service 可选声明 `align`
5
+ * (遵守的宪法原则 id),内核在 apply 前向 `constitution` 缝求证,声明的 id
6
+ * 必须全部已知,否则拒绝挂载。此接口只定义「求证」契约,不绑定任何具体宪法来源。
7
+ */
8
+ export interface Constitution {
9
+ /** 校验声明的原则 id;返回违规 id 列表(空 = 通过)。 */
10
+ check(aligned: string[]): { violations: string[] }
11
+ }
12
+
13
+ /** 缝键:ctx.constitution。 */
14
+ export const CONSTITUTION_KEY = 'constitution'
@@ -1,5 +1,7 @@
1
1
  import type { EventsOfMode } from './events'
2
2
  import type { Service, Mounted, ServiceStatus } from './service'
3
+ import type { Constitution } from './constitution'
4
+ import { CONSTITUTION_KEY } from './constitution'
3
5
 
4
6
  export type Disposer = () => void
5
7
 
@@ -43,6 +45,19 @@ export class Context {
43
45
  if (!keys.every((k) => this.has(k))) return false
44
46
  applied = true
45
47
  status = 'loading'
48
+ // 对齐缝:挂载前向 constitution 求证声明的原则 id,全部已知才放行。
49
+ const align = service.align
50
+ if (align && align.length > 0) {
51
+ const constitution = this.get<Constitution>(CONSTITUTION_KEY)
52
+ if (constitution) {
53
+ const { violations } = constitution.check(align)
54
+ if (violations.length > 0) {
55
+ error = new Error(`对齐违例:${violations.join(', ')}`)
56
+ status = 'failed'
57
+ return true
58
+ }
59
+ }
60
+ }
46
61
  effectSnapshot = this.effects.length
47
62
  try {
48
63
  disposer = service.apply(this) ?? (() => {})
@@ -2,3 +2,5 @@ export { Context } from './context'
2
2
  export type { Disposer } from './context'
3
3
  export type { DispatchMode, EventMap, EventsOfMode } from './events'
4
4
  export type { Service, ServiceStatus, Mounted } from './service'
5
+ export type { Constitution } from './constitution'
6
+ export { CONSTITUTION_KEY } from './constitution'
@@ -3,6 +3,8 @@ import type { Disposer } from './context'
3
3
 
4
4
  export interface Service {
5
5
  inject?: string[]
6
+ /** 声明遵守的宪法原则 id(对齐缝)。挂载前内核校验这些 id 均为已知原则,否则拒绝挂载。 */
7
+ align?: string[]
6
8
  apply(ctx: Context): void | Disposer
7
9
  }
8
10