@miphamai/cli 0.68.0 → 0.70.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/commands/loop-scaffold.ts +15 -44
- package/src/config/loader.ts +56 -1
- package/src/core/claude-md-fix.ts +28 -0
- package/src/core/crsi-modify.ts +10 -1
- package/src/core/crsi-producer.ts +13 -0
- package/src/core/engine.ts +31 -0
- package/src/core/eval-harness.ts +94 -2
- package/src/core/fix-code.ts +167 -0
- package/src/core/fix.ts +176 -0
- package/src/core/hooks-config.ts +10 -5
- package/src/core/hooks-executor.ts +84 -5
- package/src/core/instructions.ts +2 -2
- package/src/core/memory/memory-manager.ts +205 -19
- package/src/core/post-flight-checker.ts +102 -0
- package/src/core/reward-fn.ts +3 -1
- package/src/core/session-log.ts +3 -1
- package/src/core/working-memory.ts +140 -0
- package/src/i18n-core/locales/en-US.json +30 -2
- package/src/i18n-core/locales/zh-CN.json +30 -2
- package/src/index.tsx +12 -0
- package/src/shared/package-info.ts +1 -1
- package/src/shared/types.ts +2 -0
- package/src/skills/community-registry.json +16 -0
- package/src/skills/marketplace.ts +233 -0
- package/src/skills/registry.ts +77 -63
- package/src/tools/agent/memory.ts +5 -1
- package/src/tools/exec/task.ts +21 -2
- package/src/ui/commands.ts +354 -34
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostFlightChecker — 事后检查器(Recuris C 组件)。
|
|
3
|
+
*
|
|
4
|
+
* Recuris M=(E,W,ρ,C) 的 C:检查器。核心原则「调用技能/尝试工具 ≠ 完成证据;
|
|
5
|
+
* 只有工具/env 的观察结果支撑才算」。`PreFlightChecker` 是事前拦截(ErrorSignatureDB
|
|
6
|
+
* + RuleEngine),本模块是事后验证:把「完成谓词 (observation) => boolean」抽象出来,
|
|
7
|
+
* 对工具结果核对,决策写进会话轨迹(供 ① 组件归因 + eval 打分)。
|
|
8
|
+
*
|
|
9
|
+
* 设计约束:
|
|
10
|
+
* - 默认 no-checker 静默、rejected 只记录不阻塞(第一阶段只产证据,不强制拦截)。
|
|
11
|
+
* - 确定性、无 LLM、可单测。
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { existsSync, readFileSync, statSync } from 'node:fs'
|
|
15
|
+
import type { ToolResult } from '../shared/types'
|
|
16
|
+
|
|
17
|
+
/** 工具事后观察:工具实际用的参数 + 工具返回结果。 */
|
|
18
|
+
export interface ToolObservation {
|
|
19
|
+
params: Record<string, unknown>
|
|
20
|
+
result: ToolResult
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type CheckerDecision =
|
|
24
|
+
| { verdict: 'supported'; checkerId: string }
|
|
25
|
+
| { verdict: 'rejected'; checkerId: string; reason: string }
|
|
26
|
+
| { verdict: 'no-checker' }
|
|
27
|
+
|
|
28
|
+
/** 完成谓词:工具/env 观察是否支撑「该变更已生效」。绝不信任模型自称成功。 */
|
|
29
|
+
export type Checker = (observation: ToolObservation) => boolean
|
|
30
|
+
|
|
31
|
+
export class PostFlightChecker {
|
|
32
|
+
private checkers = new Map<string, { id: string; predicate: Checker }>()
|
|
33
|
+
|
|
34
|
+
register(checkerId: string, toolName: string, predicate: Checker): void {
|
|
35
|
+
this.checkers.set(toolName, { id: checkerId, predicate })
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** 事后核对一次工具结果。无匹配 checker 时返回 no-checker(不阻塞)。 */
|
|
39
|
+
check(toolName: string, observation: ToolObservation): CheckerDecision {
|
|
40
|
+
const entry = this.checkers.get(toolName)
|
|
41
|
+
if (!entry) return { verdict: 'no-checker' }
|
|
42
|
+
try {
|
|
43
|
+
return entry.predicate(observation)
|
|
44
|
+
? { verdict: 'supported', checkerId: entry.id }
|
|
45
|
+
: {
|
|
46
|
+
verdict: 'rejected',
|
|
47
|
+
checkerId: entry.id,
|
|
48
|
+
reason: `post-condition failed (${entry.id})`,
|
|
49
|
+
}
|
|
50
|
+
} catch {
|
|
51
|
+
return { verdict: 'rejected', checkerId: entry.id, reason: `checker threw (${entry.id})` }
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ── 首批 checker(最小、确定性、可测) ──
|
|
57
|
+
|
|
58
|
+
/** Bash → exit code 0(对齐 detectViolations 语义,不重复实现)。 */
|
|
59
|
+
function bashExit(o: ToolObservation): boolean {
|
|
60
|
+
return o.result.success === true
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Write → 文件存在且长度一致。 */
|
|
64
|
+
function writeExists(o: ToolObservation): boolean {
|
|
65
|
+
if (!o.result.success) return false
|
|
66
|
+
const filePath = String(o.params.file_path ?? '')
|
|
67
|
+
if (!filePath) return false
|
|
68
|
+
try {
|
|
69
|
+
if (!existsSync(filePath)) return false
|
|
70
|
+
const expected = Buffer.byteLength(String(o.params.content ?? ''), 'utf-8')
|
|
71
|
+
return statSync(filePath).size === expected
|
|
72
|
+
} catch {
|
|
73
|
+
return false
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Edit → 工具返回 applied 且 new_string 已生效(退化为「old_string 无残留」)。 */
|
|
78
|
+
function editApplied(o: ToolObservation): boolean {
|
|
79
|
+
if (!o.result.success) return false
|
|
80
|
+
const filePath = String(o.params.file_path ?? '')
|
|
81
|
+
const newString = String(o.params.new_string ?? '')
|
|
82
|
+
const oldString = String(o.params.old_string ?? '')
|
|
83
|
+
if (!filePath) return false
|
|
84
|
+
try {
|
|
85
|
+
if (!existsSync(filePath)) return false
|
|
86
|
+
const text = readFileSync(filePath, 'utf-8')
|
|
87
|
+
if (newString) return text.includes(newString)
|
|
88
|
+
if (oldString) return !text.includes(oldString)
|
|
89
|
+
return true
|
|
90
|
+
} catch {
|
|
91
|
+
return false
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** 默认 checker:Bash/Write/Edit 三件套(工具作用域后置条件)。 */
|
|
96
|
+
export function createDefaultPostFlightChecker(): PostFlightChecker {
|
|
97
|
+
const c = new PostFlightChecker()
|
|
98
|
+
c.register('bash-exit', 'Bash', bashExit)
|
|
99
|
+
c.register('write-exists', 'Write', writeExists)
|
|
100
|
+
c.register('edit-applied', 'Edit', editApplied)
|
|
101
|
+
return c
|
|
102
|
+
}
|
package/src/core/reward-fn.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// 统一「给一个 policy 打分」:机制哨兵(runEval)与任务表现(runTaskPerformance)
|
|
3
3
|
// 都 conform 成 RewardFn,自改进环的 verify 阶段可对任意奖励源比分数、判退化。
|
|
4
4
|
import type { Llm } from '../providers/llm'
|
|
5
|
-
import { runEval } from './eval-harness'
|
|
5
|
+
import { runEval, type EvalResult } from './eval-harness'
|
|
6
6
|
import { runTaskPerformance } from './task-performance'
|
|
7
7
|
|
|
8
8
|
/** 奖励函数统一输出的「分数」形状——所有 RewardFn 的 evaluate 都产出它。 */
|
|
@@ -11,6 +11,8 @@ export interface ScoreReport {
|
|
|
11
11
|
passed: number
|
|
12
12
|
score: number // 0-100
|
|
13
13
|
failures: string[]
|
|
14
|
+
/** 逐契约结果(机制哨兵 runEval 天然携带;任务表现等 reward 可缺省)。 */
|
|
15
|
+
results?: EvalResult[]
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
/** 奖励函数(reward function = policy→feedback):一个具名、可替换的打分器。 */
|
package/src/core/session-log.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { appendFileSync, readFileSync, mkdirSync, existsSync } from 'node:fs'
|
|
|
2
2
|
import { join } from 'node:path'
|
|
3
3
|
import { createHash } from 'node:crypto'
|
|
4
4
|
import type { Message, ToolUseContent, ToolResultContent, ToolResult } from '../shared/types'
|
|
5
|
+
import type { CheckerDecision } from './post-flight-checker'
|
|
5
6
|
|
|
6
7
|
export type SessionEvent =
|
|
7
8
|
| {
|
|
@@ -20,6 +21,7 @@ export type SessionEvent =
|
|
|
20
21
|
| { type: 'context/inject'; at: number; source: string; text: string }
|
|
21
22
|
| { type: 'compaction/summary'; at: number; summary: string; replacedCount: number }
|
|
22
23
|
| { type: 'compaction/rewrite'; at: number; messages: Message[] }
|
|
24
|
+
| { type: 'checker/decision'; at: number; toolName: string; decision: CheckerDecision }
|
|
23
25
|
|
|
24
26
|
export function messageToEvents(msg: Message, at = 0): SessionEvent[] {
|
|
25
27
|
if (msg.role === 'user') {
|
|
@@ -94,7 +96,7 @@ export function deriveMessages(events: SessionEvent[]): Message[] {
|
|
|
94
96
|
// 快照替换:整个投影重建(微压缩/截断等结构性编辑的字节级复现)
|
|
95
97
|
out = structuredClone(e.messages)
|
|
96
98
|
}
|
|
97
|
-
// 'session/start' →
|
|
99
|
+
// 'session/start' / 'checker/decision' → 无消息(决策仅记录证据,不进投影,保字节级可逆)
|
|
98
100
|
}
|
|
99
101
|
return out
|
|
100
102
|
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Working Memory — 用「已验证的当前任务状态」接地记忆召回(借鉴 Recuris 的 W,仅借概念)。
|
|
3
|
+
*
|
|
4
|
+
* 问题(治「前存后忘」):MemoryManager.recall 用 TF-IDF 去和「刚说的 query」比,
|
|
5
|
+
* 历史越长,旧但相关的记忆越容易被埋掉。Recuris 的解法是把召回绑定到「当前还剩
|
|
6
|
+
* 什么没做」(pending/in_progress),而不是只绑定到「刚说了什么」。
|
|
7
|
+
*
|
|
8
|
+
* 本文件分两层:
|
|
9
|
+
* - Phase 1(纯只读):`renderWorkingState(tasks)` 把「现有任务追踪」的未完成任务
|
|
10
|
+
* 渲染成紧凑接地串,拼进 recall 的 query(状态接地召回)。
|
|
11
|
+
* - Phase 2(证据接地状态机):`WorkingMemory` 类复现 Recuris「w_t → ρ → E_t → (a,o)
|
|
12
|
+
* → C → w_{t+1}」环——`done` 只能由 PostFlightChecker 的 `supported` 决策推进,
|
|
13
|
+
* `blocked` 由 `rejected` 置位;**没有「模型自称 done」的入口**(不信任模型叙事,
|
|
14
|
+
* 只信任工具/env 观察)。
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { CheckerDecision } from './post-flight-checker'
|
|
18
|
+
|
|
19
|
+
export interface WorkingGoal {
|
|
20
|
+
subject: string
|
|
21
|
+
status: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** 渲染「当前仍未完成」的任务主题为紧凑接地串;无未完成任务返回空串。 */
|
|
25
|
+
export function renderWorkingState(tasks: ReadonlyArray<WorkingGoal>): string {
|
|
26
|
+
const open = tasks.filter((t) => t.status === 'pending' || t.status === 'in_progress')
|
|
27
|
+
if (open.length === 0) return ''
|
|
28
|
+
return open
|
|
29
|
+
.map((t) => t.subject.trim())
|
|
30
|
+
.filter(Boolean)
|
|
31
|
+
.join('\n')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ── Phase 2:证据接地状态机 ──
|
|
35
|
+
|
|
36
|
+
export type GoalStatus = 'pending' | 'done' | 'blocked'
|
|
37
|
+
|
|
38
|
+
export interface GoalState {
|
|
39
|
+
id: string
|
|
40
|
+
content: string
|
|
41
|
+
status: GoalStatus
|
|
42
|
+
/** 推进/置位时固化的证据(checkerId + reason),供审计「谁证明了这个状态」。 */
|
|
43
|
+
evidence: string[]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** 从现有任务追踪(Task 工具的 Task 列表)同步的最小结构,避免 import Task 造成循环依赖。 */
|
|
47
|
+
export interface TaskLike {
|
|
48
|
+
id: string
|
|
49
|
+
subject: string
|
|
50
|
+
status: string
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export class WorkingMemory {
|
|
54
|
+
private goals = new Map<string, GoalState>()
|
|
55
|
+
|
|
56
|
+
/** 注册一个待办目标(新目标默认 pending)。重复 id 只更新 content,不回退已验证的状态。 */
|
|
57
|
+
setGoal(id: string, content: string): void {
|
|
58
|
+
const existing = this.goals.get(id)
|
|
59
|
+
if (existing) {
|
|
60
|
+
existing.content = content
|
|
61
|
+
} else {
|
|
62
|
+
this.goals.set(id, { id, content, status: 'pending', evidence: [] })
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 证据接地推进:`done` 只能由 checker `supported` 决策推进;`rejected` 置 `blocked`;
|
|
68
|
+
* `no-checker` 不改状态(模型自称不算)。已 `done` 的目标不再回退。
|
|
69
|
+
*/
|
|
70
|
+
observe(goalId: string, decision: CheckerDecision): void {
|
|
71
|
+
const g = this.goals.get(goalId)
|
|
72
|
+
if (!g || g.status === 'done') return
|
|
73
|
+
if (decision.verdict === 'supported') {
|
|
74
|
+
g.status = 'done'
|
|
75
|
+
g.evidence.push(`supported:${decision.checkerId}`)
|
|
76
|
+
} else if (decision.verdict === 'rejected') {
|
|
77
|
+
g.status = 'blocked'
|
|
78
|
+
g.evidence.push(`rejected:${decision.checkerId}:${decision.reason}`)
|
|
79
|
+
}
|
|
80
|
+
// no-checker:证据不足,状态不变
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
getGoal(id: string): GoalState | undefined {
|
|
84
|
+
return this.goals.get(id)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** 从现有任务追踪(Task 工具的 Task 列表)重建目标状态——TaskList 是唯一真源。
|
|
88
|
+
* pending/in_progress → pending;completed → done;failed → blocked;deleted 跳过。 */
|
|
89
|
+
syncFromTasks(tasks: ReadonlyArray<TaskLike>): void {
|
|
90
|
+
this.goals.clear()
|
|
91
|
+
for (const t of tasks) {
|
|
92
|
+
if (t.status === 'deleted') continue
|
|
93
|
+
const status: GoalStatus =
|
|
94
|
+
t.status === 'completed' ? 'done' : t.status === 'failed' ? 'blocked' : 'pending'
|
|
95
|
+
this.goals.set(t.id, { id: t.id, content: t.subject, status, evidence: [] })
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** 紧凑三态块(检索/系统提示用);无任何目标时返回空串。 */
|
|
100
|
+
renderWorkingState(): string {
|
|
101
|
+
if (this.goals.size === 0) return ''
|
|
102
|
+
const byStatus: Record<GoalStatus, string[]> = { pending: [], done: [], blocked: [] }
|
|
103
|
+
for (const g of this.goals.values()) {
|
|
104
|
+
byStatus[g.status].push(g.content.trim())
|
|
105
|
+
}
|
|
106
|
+
return (['pending', 'done', 'blocked'] as const)
|
|
107
|
+
.map((s) => {
|
|
108
|
+
const items = byStatus[s].filter(Boolean)
|
|
109
|
+
return `[WORKING] ${s}: ${items.length > 0 ? items.join(', ') : '(none)'}`
|
|
110
|
+
})
|
|
111
|
+
.join('\n')
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ── 证据账本(engine post-flight 后写入;Task 完成门读取) ──
|
|
116
|
+
|
|
117
|
+
export interface ToolEvidence {
|
|
118
|
+
toolName: string
|
|
119
|
+
decision: CheckerDecision
|
|
120
|
+
at: number
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** 会话级证据账本(对齐 Task 工具的 module-level `tasks` Map,session 生命周期)。 */
|
|
124
|
+
const evidenceLog: ToolEvidence[] = []
|
|
125
|
+
|
|
126
|
+
/** 记录一次工具事后决策。no-checker 不产证据(忽略)。engine post-flight 后调用。 */
|
|
127
|
+
export function recordToolEvidence(toolName: string, decision: CheckerDecision): void {
|
|
128
|
+
if (decision.verdict === 'no-checker') return
|
|
129
|
+
evidenceLog.push({ toolName, decision, at: Date.now() })
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** 某时间点之后是否有 supported 决策(用于任务完成证据门)。 */
|
|
133
|
+
export function hasSupportedEvidenceSince(sinceAt: number): boolean {
|
|
134
|
+
return evidenceLog.some((e) => e.decision.verdict === 'supported' && e.at >= sinceAt)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** 测试用:清空证据账本(避免跨测试污染)。 */
|
|
138
|
+
export function clearEvidenceLog(): void {
|
|
139
|
+
evidenceLog.length = 0
|
|
140
|
+
}
|
|
@@ -407,7 +407,15 @@
|
|
|
407
407
|
"error": "─ Memory ─\n\nCould not read memory directory.",
|
|
408
408
|
"location": "Location: {path}",
|
|
409
409
|
"total": "Total: {count} memor{plural}",
|
|
410
|
-
"footer": "Memories are used by the AI to provide personalized context across sessions.\nEach file in ~/.mipham/memory/ represents one remembered fact or preference."
|
|
410
|
+
"footer": "Memories are used by the AI to provide personalized context across sessions.\nEach file in ~/.mipham/memory/ represents one remembered fact or preference.",
|
|
411
|
+
"gc_title": "─ Memory GC ─",
|
|
412
|
+
"gc_none": "Nothing to archive. All auto-* memories are recent or have been recalled.",
|
|
413
|
+
"gc_archived": "Archived ({count}):",
|
|
414
|
+
"gc_candidates": "Manual review candidates ({count}):",
|
|
415
|
+
"gc_done": "Done. Archived files moved to ~/.mipham/memory/archive/",
|
|
416
|
+
"consolidate_title": "─ Memory Consolidate ─",
|
|
417
|
+
"consolidate_none": "No auto-* memories to consolidate.",
|
|
418
|
+
"consolidate_result": "Consolidated: {removed} auto-* removed, {merged} lessons written."
|
|
411
419
|
},
|
|
412
420
|
"upgrade": {
|
|
413
421
|
"uptodate": "── Upgrade Mipham Code ──\n\nCurrent version: v{current}\nLatest: v{latest}\n\n✓ Already up to date.\n\nTo check manually: https://www.npmjs.com/package/@miphamai/cli",
|
|
@@ -571,7 +579,7 @@
|
|
|
571
579
|
},
|
|
572
580
|
"hooks": {
|
|
573
581
|
"title": "── Hooks ──",
|
|
574
|
-
"no_hooks": "── Hooks ──\n\nNo hooks configured.\n\
|
|
582
|
+
"no_hooks": "── Hooks ──\n\nNo hooks configured.\n\nConfigure hooks in .mipham/settings.json under the hooks section:\n PreToolUse — before each tool\n PostToolUse — after each tool\n Stop — on session end\n\nCommand hooks read JSON from stdin; exit 2 blocks (stderr as reason).\nUse /loop init to scaffold settings.json.",
|
|
575
583
|
"location": "Location: {path}",
|
|
576
584
|
"found": "{count} hook(s) found.",
|
|
577
585
|
"error": "── Hooks ──\n\nCould not read hooks directory."
|
|
@@ -601,6 +609,26 @@
|
|
|
601
609
|
"audit_clean": " ✓ no code-derivable sections found",
|
|
602
610
|
"audit_found": "Add the sections above to the `prompt-exclude` frontmatter to stop sending them to the model."
|
|
603
611
|
},
|
|
612
|
+
"fix": {
|
|
613
|
+
"title": "── Fix ──",
|
|
614
|
+
"dryrun_banner": "[dry-run] preview — no changes written",
|
|
615
|
+
"doctor_clean": "✓ no code-derivable CLAUDE.md sections to fix",
|
|
616
|
+
"doctor_fixed": "✓ {path}: added prompt-exclude [{added}]",
|
|
617
|
+
"config_clean": "✓ config healthy, no disabled hooks",
|
|
618
|
+
"config_corrupt": "⚠ {path}: config corrupt",
|
|
619
|
+
"config_restored": "✓ restored config from backup: {path}",
|
|
620
|
+
"hook_disabled": "⚠ hook disabled: {key}",
|
|
621
|
+
"hook_reenabled": "✓ re-enabled hook: {key}",
|
|
622
|
+
"cache_clean": "✓ no corrupt cache lines",
|
|
623
|
+
"cache_found": "⚠ {path}: {count} corrupt line(s)",
|
|
624
|
+
"cache_cleaned": "✓ cleaned {path}: removed {count} line(s)",
|
|
625
|
+
"cache_hint": "Run /fix cache --apply to remove corrupt lines",
|
|
626
|
+
"test_usage": "Usage: /fix test <test-file> [--apply]\n\nLLM-driven repair of a failing test (test frozen, source only). Dry-run by default; --apply writes the fix.",
|
|
627
|
+
"test_no_llm": "No LLM available in this session — cannot generate a fix.",
|
|
628
|
+
"test_fixed": "✓ fixed {test} (source {source}, {attempts} attempt(s))",
|
|
629
|
+
"test_failed": "✗ could not fix {test}: {detail}",
|
|
630
|
+
"test_hint": "Run /fix test <file> --apply to write the fix."
|
|
631
|
+
},
|
|
604
632
|
"plugins": {
|
|
605
633
|
"unavailable": "PluginManager not available in this session.",
|
|
606
634
|
"title": "── Installed Plugins ──",
|
|
@@ -407,7 +407,15 @@
|
|
|
407
407
|
"error": "─ 记忆 ─\n\n无法读取记忆目录。",
|
|
408
408
|
"location": "位置: {path}",
|
|
409
409
|
"total": "总计: {count} 条记忆",
|
|
410
|
-
"footer": "AI 使用记忆提供跨会话的个性化上下文。\n~/.mipham/memory/ 中每个文件代表一条已记住的事实或偏好。"
|
|
410
|
+
"footer": "AI 使用记忆提供跨会话的个性化上下文。\n~/.mipham/memory/ 中每个文件代表一条已记住的事实或偏好。",
|
|
411
|
+
"gc_title": "─ 记忆 GC ─",
|
|
412
|
+
"gc_none": "无需归档。所有 auto-* 记忆要么较新、要么已被召回。",
|
|
413
|
+
"gc_archived": "已归档({count} 条):",
|
|
414
|
+
"gc_candidates": "待人工确认({count} 条):",
|
|
415
|
+
"gc_done": "完成。归档文件已移到 ~/.mipham/memory/archive/",
|
|
416
|
+
"consolidate_title": "─ 记忆合并 ─",
|
|
417
|
+
"consolidate_none": "没有需要合并的 auto-* 记忆。",
|
|
418
|
+
"consolidate_result": "合并完成:删除 {removed} 条 auto-*,写入 {merged} 条 lesson-*。"
|
|
411
419
|
},
|
|
412
420
|
"upgrade": {
|
|
413
421
|
"uptodate": "── 升级 Mipham Code ──\n\n当前版本: v{current}\n最新版本: v{latest}\n\n✓ 已是最新版本。\n\n手动检查: https://www.npmjs.com/package/@miphamai/cli",
|
|
@@ -571,7 +579,7 @@
|
|
|
571
579
|
},
|
|
572
580
|
"hooks": {
|
|
573
581
|
"title": "── 钩子 ──",
|
|
574
|
-
"no_hooks": "── 钩子 ──\n\n未配置钩子。\n\n在 .mipham/hooks
|
|
582
|
+
"no_hooks": "── 钩子 ──\n\n未配置钩子。\n\n在 .mipham/settings.json 的 hooks 段配置钩子:\n PreToolUse — 工具执行前\n PostToolUse — 工具执行后\n Stop — 会话结束\n\n命令钩子从 stdin 读 JSON,exit 2 拦截(stderr 作理由)。\n使用 /loop init 初始化 settings.json。",
|
|
575
583
|
"location": "位置: {path}",
|
|
576
584
|
"found": "找到 {count} 个钩子。",
|
|
577
585
|
"error": "── 钩子 ──\n\n无法读取钩子目录。"
|
|
@@ -601,6 +609,26 @@
|
|
|
601
609
|
"audit_clean": " ✓ 未发现可从代码推断的冗余章节",
|
|
602
610
|
"audit_found": "将上述章节加入 `prompt-exclude` frontmatter,可停止发送给模型、节省 token。"
|
|
603
611
|
},
|
|
612
|
+
"fix": {
|
|
613
|
+
"title": "── 修复 ──",
|
|
614
|
+
"dryrun_banner": "[dry-run] 预览模式 — 不落盘",
|
|
615
|
+
"doctor_clean": "✓ CLAUDE.md 无需要修复的冗余章节",
|
|
616
|
+
"doctor_fixed": "✓ {path}: 已加 prompt-exclude [{added}]",
|
|
617
|
+
"config_clean": "✓ 配置无损坏、hooks 无禁用",
|
|
618
|
+
"config_corrupt": "⚠ {path}: 配置损坏",
|
|
619
|
+
"config_restored": "✓ 已从备份恢复 config: {path}",
|
|
620
|
+
"hook_disabled": "⚠ hook 已禁用: {key}",
|
|
621
|
+
"hook_reenabled": "✓ 已重新启用 hook: {key}",
|
|
622
|
+
"cache_clean": "✓ 缓存无损坏行",
|
|
623
|
+
"cache_found": "⚠ {path}: {count} 行损坏",
|
|
624
|
+
"cache_cleaned": "✓ 已清理 {path}: 移除 {count} 行",
|
|
625
|
+
"cache_hint": "使用 /fix cache --apply 清理损坏行",
|
|
626
|
+
"test_usage": "用法: /fix test <测试文件> [--apply]\n\nLLM 自动修复失败测试(测试冻结,只改源码)。默认 dry-run,--apply 落盘。",
|
|
627
|
+
"test_no_llm": "当前会话无可用 LLM,无法生成修复。",
|
|
628
|
+
"test_fixed": "✓ 已修复 {test}(源码 {source},{attempts} 次尝试)",
|
|
629
|
+
"test_failed": "✗ 未能修复 {test}: {detail}",
|
|
630
|
+
"test_hint": "使用 /fix test <文件> --apply 落盘修复。"
|
|
631
|
+
},
|
|
604
632
|
"plugins": {
|
|
605
633
|
"unavailable": "当前会话中 PluginManager 不可用。",
|
|
606
634
|
"title": "── 已安装插件 ──",
|
package/src/index.tsx
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
loadInferenceHookConfig,
|
|
11
11
|
loadCredentialMaskingConfig,
|
|
12
12
|
loadCrossSessionConfig,
|
|
13
|
+
loadSettingsJson,
|
|
13
14
|
} from './config/loader'
|
|
14
15
|
import {
|
|
15
16
|
registerActiveSession,
|
|
@@ -44,6 +45,7 @@ import { registerMcpServerTools, syncMcpToolsOnChange } from './mcp/registry'
|
|
|
44
45
|
import { formatMcpConnectFailures, type McpConnectFailure } from './mcp/connect-failures'
|
|
45
46
|
import { AgentRegistry } from './agent/agent-registry'
|
|
46
47
|
import { HookEngine } from './core/hooks'
|
|
48
|
+
import { loadHookConfigs } from './core/hooks-config'
|
|
47
49
|
import { ArtifactServer } from './artifacts/server'
|
|
48
50
|
import { getMetrics } from './core/metrics'
|
|
49
51
|
import { getWorkspaceTrust } from './core/workspace-trust'
|
|
@@ -514,6 +516,12 @@ export async function runApp(options: RunOptions): Promise<void> {
|
|
|
514
516
|
}
|
|
515
517
|
}
|
|
516
518
|
|
|
519
|
+
// Register settings.json hooks (Claude Code convention — additive across levels)
|
|
520
|
+
const settingsJson = loadSettingsJson(process.cwd())
|
|
521
|
+
for (const def of loadHookConfigs(settingsJson.hooks)) {
|
|
522
|
+
hookEngine.register(def)
|
|
523
|
+
}
|
|
524
|
+
|
|
517
525
|
// Start artifact server (lazy — first artifact creation triggers listening)
|
|
518
526
|
const artifactsDir = join(process.cwd(), MIPHAM_DIR, ARTIFACTS_DIR)
|
|
519
527
|
const artifactServer = new ArtifactServer(artifactsDir, ARTIFACT_PORT)
|
|
@@ -547,6 +555,10 @@ export async function runApp(options: RunOptions): Promise<void> {
|
|
|
547
555
|
for (const rule of config.permissionRules.deny ?? []) engine.getPermission().deny(rule)
|
|
548
556
|
}
|
|
549
557
|
|
|
558
|
+
// Apply settings.json permissions (allow/deny) — Claude Code convention
|
|
559
|
+
for (const rule of settingsJson.permissions.allow) engine.getPermission().allow(rule)
|
|
560
|
+
for (const rule of settingsJson.permissions.deny) engine.getPermission().deny(rule)
|
|
561
|
+
|
|
550
562
|
// Initialize agent registry and load plugin agents/skills/MCP/hooks
|
|
551
563
|
const agentRegistry = new AgentRegistry()
|
|
552
564
|
agentRegistry.loadUserAgents()
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
export const PACKAGE_NAME = '@miphamai/cli' as const
|
|
10
10
|
|
|
11
11
|
/** 当前发布版本 */
|
|
12
|
-
export const PACKAGE_VERSION = '0.
|
|
12
|
+
export const PACKAGE_VERSION = '0.70.0' as const
|
|
13
13
|
|
|
14
14
|
/** npm install 全局安装命令 */
|
|
15
15
|
export const NPM_INSTALL_COMMAND = `npm install -g ${PACKAGE_NAME}` as const
|
package/src/shared/types.ts
CHANGED
|
@@ -77,5 +77,21 @@
|
|
|
77
77
|
"file": "frontend-design.SKILL.md",
|
|
78
78
|
"category": "Design",
|
|
79
79
|
"author": "Anthropic"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"name": "grill-me",
|
|
83
|
+
"description": "A relentless one-question-at-a-time interview that stress-tests your plan or design",
|
|
84
|
+
"url": "https://github.com/mattpocock/skills",
|
|
85
|
+
"file": "skills/productivity/grill-me/SKILL.md",
|
|
86
|
+
"category": "Development",
|
|
87
|
+
"author": "Matt Pocock"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"name": "eli5",
|
|
91
|
+
"description": "Explain any topic like the user knows nothing about it, using an HTML artifact with big pictures and few words",
|
|
92
|
+
"url": "https://github.com/anthropics/claude-plugins-community",
|
|
93
|
+
"file": "eli5/skills/eli5/SKILL.md",
|
|
94
|
+
"category": "Communication",
|
|
95
|
+
"author": "Anthropic"
|
|
80
96
|
}
|
|
81
97
|
]
|