@miphamai/cli 0.56.0 → 0.58.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
@@ -1057,6 +1057,36 @@ async function runTokenCLI(): Promise<boolean> {
1057
1057
  process.exit(1)
1058
1058
  }
1059
1059
 
1060
+ // ── mipham init [dir] — 生成规范项目文档 (CLAUDE.md/MIPHAM.md/README.md) ──
1061
+ async function runInitCLI(): Promise<boolean> {
1062
+ const args = process.argv.slice(2)
1063
+ if (args[0] !== 'init') return false
1064
+
1065
+ const { resolve } = await import('node:path')
1066
+ const { homedir } = await import('node:os')
1067
+ const { scaffoldProject } = await import('../src/core/project-scaffold')
1068
+
1069
+ const rawTarget = args.slice(1).find((a) => !a.startsWith('--'))
1070
+ const targetDir = rawTarget ? resolve(rawTarget.replace(/^~/, homedir())) : process.cwd()
1071
+
1072
+ const gitInit = !args.includes('--no-git')
1073
+ const result = scaffoldProject(targetDir, { gitInit })
1074
+
1075
+ const lines: string[] = ['', `✅ Mipham Code 项目已初始化:${targetDir}`, '']
1076
+ if (result.created.length > 0) {
1077
+ lines.push('已创建:')
1078
+ for (const c of result.created) lines.push(` ✅ ${c}`)
1079
+ }
1080
+ if (result.skipped.length > 0) {
1081
+ lines.push('已存在(跳过):')
1082
+ for (const s of result.skipped) lines.push(` ⏭ ${s}`)
1083
+ }
1084
+ if (result.gitInitialized) lines.push(' ✅ git init')
1085
+ lines.push('', '下一步:', ' 1. 填写各文档中的 [占位符]', ' 2. 运行 mipham 开始协作')
1086
+ console.log(lines.join('\n'))
1087
+ process.exit(0)
1088
+ }
1089
+
1060
1090
  async function main() {
1061
1091
  // ── Deleted-cwd guard ──────────────────────────────────────────────────
1062
1092
  // `process.cwd()` throws ENOENT when the directory the process was launched
@@ -1129,6 +1159,7 @@ async function main() {
1129
1159
 
1130
1160
  Usage:
1131
1161
  mipham Launch interactive CLI
1162
+ mipham init [dir] Scaffold a project (CLAUDE.md/MIPHAM.md/README.md)
1132
1163
  mipham update Update to the latest version
1133
1164
  mipham upgrade Same as 'mipham update'
1134
1165
  mipham daemon <cmd> Daemon lifecycle (start, stop, status, restart)
@@ -1183,6 +1214,10 @@ npm: https://www.npmjs.com/package/@miphamai/cli`)
1183
1214
  const handledUpdate = await runUpdate()
1184
1215
  if (handledUpdate) return
1185
1216
 
1217
+ // ── Init command ─────────────────────────────────────────────────────────
1218
+ const handledInit = await runInitCLI()
1219
+ if (handledInit) return
1220
+
1186
1221
  // ── Daemon commands ───────────────────────────────────────────────────────
1187
1222
  const handledDaemon = await runDaemonCLI()
1188
1223
  if (handledDaemon) return
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miphamai/cli",
3
- "version": "0.56.0",
3
+ "version": "0.58.0",
4
4
  "description": "Mipham Code — Multi-model open-core intelligent coding terminal by MiphamAI",
5
5
  "keywords": [
6
6
  "ai",
@@ -77,7 +77,7 @@ Project: [✅/⬜] .mipham/ [✅/⬜] config.yml [✅/⬜] MIPHAM.md
77
77
  User Config: [✅/⬜] ~/.mipham/config.yml
78
78
  API Keys: [N] set (list names or "none")
79
79
  Skills: [N] installed
80
- Permissions: [mode] (default/acceptEdits/plan/auto/dontAsk/bypass)
80
+ Permissions: [mode] (default/acceptEdits/plan/bypassPermissions)
81
81
  Trust: [✅/⬜] workspace trusted
82
82
  ```
83
83
 
@@ -336,8 +336,6 @@ Or directly:
336
336
  | `default` | Prompt for each tool | Normal development (recommended) |
337
337
  | `acceptEdits` | Auto-allow edits, prompt others | Active coding sessions |
338
338
  | `plan` | Plan-only, no tool execution | Design & architecture work |
339
- | `auto` | Auto-allow all | Trusted, frequent use |
340
- | `dontAsk` | Never auto-allow | CI/CD safety |
341
339
  | `bypassPermissions` | Skip all checks | ⚠️ Only for fully trusted codebases |
342
340
 
343
341
  ### 6.2 — Configure
@@ -345,7 +343,7 @@ Or directly:
345
343
  In `.mipham/config.yml`:
346
344
 
347
345
  ```yaml
348
- permission: auto
346
+ permission: default
349
347
  ```
350
348
 
351
349
  Or via slash command:
@@ -357,11 +355,9 @@ Or via slash command:
357
355
 
358
356
  ### 6.3 — CI/CD Safety
359
357
 
360
- For CI/CD environments, use `dontAsk` mode to prevent the AI from executing tools without explicit approval:
361
-
362
- ```yaml
363
- permission: dontAsk
364
- ```
358
+ For CI/CD environments, use the `default` mode (the daemon default): headless
359
+ sessions never prompt, so `ask`-level tools (Bash/Write/Edit) are blocked rather
360
+ than auto-approved.
365
361
 
366
362
  ### ✅ Verification
367
363
 
@@ -210,9 +210,6 @@ export class SubAgent {
210
210
  ? agentDef.permissionMode
211
211
  : 'inherit'
212
212
  subPermission = this.permission.createSubAgentPermission(agentPermMode)
213
- // P0-5: Enable sub-agent safety mode — auto mode returns 'ask' for
214
- // Bash/Write/Edit so hooks remain the safety gate in background agents.
215
- subPermission.setSubAgentMode(true)
216
213
  }
217
214
 
218
215
  // Resolve execution directory: worktree isolation or process cwd
@@ -10,8 +10,7 @@ export interface AgentFrontmatter {
10
10
  tools?: string // comma-separated allowlist
11
11
  disallowedTools?: string
12
12
  model?: string // 'sonnet' | 'opus' | 'haiku' | 'inherit' | full model ID
13
- permissionMode?:
14
- 'default' | 'acceptEdits' | 'auto' | 'bypass' | 'plan' | 'bypassPermissions' | 'dontAsk'
13
+ permissionMode?: 'default' | 'acceptEdits' | 'bypass' | 'plan' | 'bypassPermissions'
15
14
  maxTurns?: number
16
15
  skills?: string
17
16
  background?: boolean
@@ -111,21 +111,22 @@ const permissionsCmd: CommandHandler = (ctx) => {
111
111
  return {
112
112
  content: `─ Permission Settings ─
113
113
 
114
- Mode: ${ctx.config.permission}
114
+ Mode: ${ctx.engine.getPermission().getMode()}
115
115
  Messages: ${msgs.length} in context
116
116
  Tools: ${ctx.engine.getTools().size} available
117
117
 
118
- Permission levels:
119
- auto — run tools automatically without asking
120
- ask — prompt before each tool execution (default)
121
- bypass — skip all permission checks (use with caution)
118
+ Switch mode with Shift+Tab. Modes (least → most permissive):
119
+ default — per-tool defaults (Bash/Write/Edit ask first)
120
+ acceptEdits — reads + edits free; Bash auto-runs read/check commands
121
+ plan — reads only (Read/Grep/Glob); nothing writes or runs
122
+ bypassPermissions — skip ALL permission checks (⚠️ use with caution)
122
123
 
123
- Change with /config permission <level>.
124
+ To let Bash run without asking: press Shift+Tab until the status line shows
125
+ "acceptEdits", then send your message again.
124
126
 
125
127
  Current directory permissions:
126
128
  CWD: ${process.cwd()}
127
129
 
128
- To add a directory: use /add-dir (coming soon).
129
130
  Tool execution is sandboxed to the project directory by default.`,
130
131
  }
131
132
  }
@@ -12,7 +12,7 @@ export const DEFAULT_CONFIG: MiphamConfig = {
12
12
  version: PACKAGE_VERSION,
13
13
  defaultProvider: 'deepseek',
14
14
  defaultModel: 'deepseek-v4-pro',
15
- permission: 'auto',
15
+ permission: 'default',
16
16
  // Org 级权限限制(可选):forbiddenModes 禁指定模式 / maxAllowedMode 封顶层级;
17
17
  // 请求被禁模式时 fail-closed 降级(如 forbiddenModes:['bypassPermissions'])。
18
18
  // permissionRestrictions: { forbiddenModes: ['bypassPermissions'] },
@@ -80,6 +80,47 @@ export function buildLessonContent(signal: CrsiSignal, timestamp: string): strin
80
80
  return lines.join('\n')
81
81
  }
82
82
 
83
+ /** 教训精华(标题 + 建议),用于运行时召回注入系统提示。 */
84
+ export interface CrsiLessonSummary {
85
+ title: string
86
+ suggestion: string
87
+ }
88
+
89
+ /**
90
+ * 从 crsi-lessons.md 提取每条教训的「精华」(标题 + 建议),跳过证据段落。
91
+ * 这是「只写不读」缺口 → 「写后召回」的读取侧。
92
+ */
93
+ export function extractCrsiLessonSummaries(content: string): CrsiLessonSummary[] {
94
+ const out: CrsiLessonSummary[] = []
95
+ let title = ''
96
+ for (const line of content.split('\n')) {
97
+ const h = line.match(/^##\s+(.+?)\s*$/)
98
+ if (h) {
99
+ title = h[1]!.trim()
100
+ continue
101
+ }
102
+ const s = line.match(/^-\s*建议[::]\s*(.+)$/)
103
+ if (s && title) {
104
+ out.push({ title, suggestion: s[1]!.trim() })
105
+ title = ''
106
+ }
107
+ }
108
+ return out
109
+ }
110
+
111
+ /** 把教训精华渲染为系统提示召回块。无教训时返回空串。 */
112
+ export function buildCrsiLessonsBlock(summaries: CrsiLessonSummary[]): string {
113
+ if (summaries.length === 0) return ''
114
+ const items = summaries.map((s, i) => `${i + 1}. **${s.title}**\n ${s.suggestion}`).join('\n\n')
115
+ return `## CRSI Lessons (Self-Improvement Recall)
116
+
117
+ These are hard-won lessons consolidated by the CRSI self-improvement
118
+ loop from past sessions. Apply them proactively — do not repeat these
119
+ mistakes:
120
+
121
+ ${items}`
122
+ }
123
+
83
124
  /** 产出教训文件变更候选。无合格信号时返回 null。 */
84
125
  export function produceCrsiProposal(
85
126
  insights: CrsiInsight[],
@@ -764,10 +764,26 @@ export class QueryEngine {
764
764
  }
765
765
 
766
766
  private async *continueWithTools(signal?: AbortSignal): AsyncGenerator<StreamChunk> {
767
- const MAX_TURNS = 20
767
+ // Tool-calling round cap. 20 was too low for real multi-step tasks — the model
768
+ // hit "max turns" and dropped pending tools mid-task. 100 stays bounded.
769
+ const MAX_TURNS = 100
770
+ // Task-level stall guard: a turn that produces no text or tool result for
771
+ // this long is considered stalled and stopped (prevents ~40-min idle spins).
772
+ const TURN_TIMEOUT_MS = 15 * 60 * 1000
773
+ let lastActivity = Date.now()
768
774
  const toolDefs = this.getToolDefinitions()
769
775
 
770
776
  for (let turn = 0; turn < MAX_TURNS; turn++) {
777
+ if (Date.now() - lastActivity > TURN_TIMEOUT_MS) {
778
+ yield {
779
+ type: 'warning',
780
+ content: t('errors.turn_timeout', {
781
+ minutes: String(Math.round(TURN_TIMEOUT_MS / 60000)),
782
+ }),
783
+ }
784
+ yield { type: 'stop' }
785
+ return
786
+ }
771
787
  const systemPrompt = this.context.getSystemPrompt()
772
788
  const messages = this.context.getMessages()
773
789
 
@@ -812,6 +828,7 @@ export class QueryEngine {
812
828
  if (chunk.type === 'text' && chunk.content) {
813
829
  assistantContent += chunk.content
814
830
  this.context.recordChunk(chunk.content)
831
+ lastActivity = Date.now()
815
832
  }
816
833
 
817
834
  if (chunk.reasoning_content) {
@@ -911,6 +928,7 @@ export class QueryEngine {
911
928
  // Execute tools and feed results back to the model for the next turn
912
929
  for (const toolUse of toolUses) {
913
930
  const result = await this.executeTool(toolUse.name, toolUse.input)
931
+ lastActivity = Date.now()
914
932
  yield {
915
933
  type: 'tool_result',
916
934
  tool_use_id: toolUse.id,
@@ -1,8 +1,15 @@
1
1
  import { readFileSync, existsSync } from 'node:fs'
2
2
  import { join } from 'node:path'
3
+ import { execSync } from 'node:child_process'
3
4
  import { parse as parseYaml } from 'yaml'
4
5
  import type { InstructionFile } from '../shared/index.ts'
5
6
  import { COAUTHOR_TRAILER } from '../shared/index.ts'
7
+ import {
8
+ LESSONS_FILE,
9
+ extractCrsiLessonSummaries,
10
+ buildCrsiLessonsBlock,
11
+ type CrsiLessonSummary,
12
+ } from './crsi-producer'
6
13
 
7
14
  interface FrontmatterResult {
8
15
  data: Record<string, unknown>
@@ -54,6 +61,7 @@ export function parsePromptExclude(value: unknown): string[] {
54
61
 
55
62
  export class InstructionsLoader {
56
63
  private instructions: InstructionFile[] = []
64
+ private crsiLessonSummaries: CrsiLessonSummary[] = []
57
65
 
58
66
  loadAll(cwd: string): void {
59
67
  this.instructions = []
@@ -74,6 +82,9 @@ export class InstructionsLoader {
74
82
  // Tier 3: User-level ~/.mipham/USER.md
75
83
  const home = process.env.HOME || '~'
76
84
  this.tryLoad(join(home, '.mipham', 'USER.md'), 'user')
85
+
86
+ // CRSI 教训召回:读 crsi-lessons.md 提取精华,注入系统提示(只写不读 → 写后召回)
87
+ this.crsiLessonSummaries = this.loadCrsiLessons(cwd)
77
88
  }
78
89
 
79
90
  buildSystemPrompt(permissionMode?: string): string {
@@ -206,6 +217,10 @@ its live CRSI / SIS / constitution state. Report the numbers you read
206
217
  from it as live counts; if it shows a subsystem as 未初始化 (uninitialized),
207
218
  say so explicitly instead of claiming it exists.`)
208
219
 
220
+ // CRSI 教训召回 — 把 crsi-lessons.md 的教训精华注入,让模型「写后召回」而非只写不读
221
+ const lessonsBlock = buildCrsiLessonsBlock(this.crsiLessonSummaries)
222
+ if (lessonsBlock) parts.push(lessonsBlock)
223
+
209
224
  // AI 署名披露:提交时附带 Co-Authored-By 署名(与 Undercover 式隐瞒相反)
210
225
  parts.push(`## Commit Attribution
211
226
 
@@ -219,6 +234,26 @@ Never omit it or present the work as purely human-authored.`)
219
234
  return parts.join('\n\n---\n\n')
220
235
  }
221
236
 
237
+ /** 读 crsi-lessons.md(按仓库根定位)提取教训精华。读不到则返回空。 */
238
+ private loadCrsiLessons(cwd: string): CrsiLessonSummary[] {
239
+ let root = cwd
240
+ try {
241
+ root = execSync('git rev-parse --show-toplevel', {
242
+ cwd,
243
+ timeout: 5000,
244
+ encoding: 'utf-8',
245
+ }).trim()
246
+ } catch {
247
+ // 非 git 目录 → 回退 cwd
248
+ }
249
+ try {
250
+ const content = readFileSync(join(root, LESSONS_FILE), 'utf-8')
251
+ return extractCrsiLessonSummaries(content)
252
+ } catch {
253
+ return []
254
+ }
255
+ }
256
+
222
257
  /**
223
258
  * P2-2: Build a permission-mode context block for the system prompt.
224
259
  * Tells the model its current permission level and what to expect.
@@ -230,9 +265,6 @@ Never omit it or present the work as purely human-authored.`)
230
265
  acceptEdits:
231
266
  'You are in **acceptEdits** mode. File reads and edits are allowed; Bash requires approval.',
232
267
  plan: 'You are in **plan** mode. Only Read/Grep/Glob are allowed — no file modifications or command execution.',
233
- auto: 'You are in **auto** mode. Most tools run without approval. If a tool is blocked by security policy or hooks, try a different approach instead of retrying.',
234
- dontAsk:
235
- 'You are in **dontAsk** mode. All tools blocked unless explicitly allowlisted. Check your allow rules before acting.',
236
268
  bypassPermissions:
237
269
  'You are in **bypassPermissions** mode. All tools are allowed. Use this power responsibly.',
238
270
  }
@@ -27,8 +27,6 @@ export const PERMISSION_MODE_HIERARCHY: PermissionMode[] = [
27
27
  'default',
28
28
  'acceptEdits',
29
29
  'plan',
30
- 'auto',
31
- 'dontAsk',
32
30
  'bypassPermissions',
33
31
  ]
34
32
 
@@ -9,20 +9,6 @@ import type { PermissionRuleEntry } from '../shared/index.ts'
9
9
  import { matchBashRule, compileRule } from './permission-rules'
10
10
  import { loadPermissionConfig, nextMode, clampMode, MODE_CYCLE } from './permission-config'
11
11
 
12
- /**
13
- * Check if a Bash command is a git/gh operation using a destructive flag
14
- * (--force, --amend, --no-verify, --hard) that must never be auto-approved.
15
- * v2.1.229 alignment: these rewrite history / force-push, so they fall back
16
- * to explicit user confirmation even in auto mode.
17
- */
18
- function hasDangerousGitFlags(input: Record<string, unknown>): boolean {
19
- const cmd = (input.command as string) || ''
20
- // Detect a git/gh invocation anywhere in the command (not just `^git`), so
21
- // `echo ok && git push --force` is still caught.
22
- if (!/(?:^|[\s;&|])(?:sudo\s+)?(?:git|gh)\s+/.test(cmd)) return false
23
- return /--force(?!-with-lease)\b|--amend\b|--no-verify\b|--hard\b/.test(cmd)
24
- }
25
-
26
12
  /**
27
13
  * Check if a Bash command is a "verification-only" command that should be
28
14
  * auto-approved in acceptEdits mode. These are non-destructive read/check
@@ -94,9 +80,6 @@ export class PermissionSystem {
94
80
  // ── Org-level restrictions (P0 security) ──
95
81
  private restrictions: PermissionRestrictions | undefined = undefined
96
82
 
97
- // ── P0-5: Sub-agent mode (auto mode hardening for background tasks) ──
98
- private isSubAgent = false
99
-
100
83
  // ── P1-4: Consecutive block counter (prevents infinite retry loops) ──
101
84
  private consecutiveBlockCount = 0
102
85
  private static readonly MAX_CONSECUTIVE_BLOCKS = 3
@@ -149,16 +132,6 @@ export class PermissionSystem {
149
132
  return this.restrictions
150
133
  }
151
134
 
152
- /**
153
- * P0-5 (v2.1.222 alignment): Enable sub-agent safety mode.
154
- * When true, auto mode returns 'ask' for Bash/Write/Edit tools instead of
155
- * 'bypass', ensuring PreToolUse hooks remain the safety gate in background agents.
156
- */
157
- setSubAgentMode(enabled: boolean): void {
158
- this.isSubAgent = enabled
159
- this.invalidateCache()
160
- }
161
-
162
135
  /**
163
136
  * P1-4 (v2.1.225 alignment): Increment the consecutive block counter
164
137
  * when a tool is denied. Safety-filter refusals should NOT count.
@@ -216,8 +189,6 @@ export class PermissionSystem {
216
189
 
217
190
  const modeMap: Record<string, PermissionMode> = {
218
191
  bypassPermissions: 'bypassPermissions',
219
- dontAsk: 'dontAsk',
220
- auto: 'auto',
221
192
  plan: 'plan',
222
193
  acceptEdits: 'acceptEdits',
223
194
  default: 'default',
@@ -418,30 +389,6 @@ export class PermissionSystem {
418
389
  ? 'bypass'
419
390
  : 'ask'
420
391
 
421
- case 'auto':
422
- // Safety checks handled by hook layer (PreToolUse hooks).
423
- // Bypass the static permission system so hooks are the sole gate.
424
- // Exception: SendMessage always goes through the permission classifier
425
- // so deny/allow rules are honored for cross-session messages.
426
- if (tool.name === 'SendMessage') {
427
- return 'mode-baseline'
428
- }
429
- // P0-5: In sub-agent context, enforce 'ask' for destructive tools
430
- // so hooks remain the sole safety gate. Without hooks, these tools
431
- // would otherwise run completely un-gated in auto mode.
432
- if (this.isSubAgent && ['Bash', 'Write', 'Edit'].includes(tool.name)) {
433
- return 'ask'
434
- }
435
- // v2.1.229 alignment: destructive git/gh flags are never auto-approved.
436
- if (tool.name === 'Bash' && input && hasDangerousGitFlags(input)) {
437
- return 'ask'
438
- }
439
- return 'bypass'
440
-
441
- case 'dontAsk':
442
- // Only allowlisted tools free (already handled above); everything else requires approval
443
- return 'ask'
444
-
445
392
  case 'bypassPermissions':
446
393
  return 'bypass'
447
394
 
@@ -453,11 +400,10 @@ export class PermissionSystem {
453
400
  // ── Legacy compatibility ──
454
401
 
455
402
  setDefaultLevel(level: PermissionLevel): void {
456
- // Map legacy 3-level to new mode
457
- let newMode: PermissionMode
458
- if (level === 'auto') newMode = 'auto'
459
- else if (level === 'bypass') newMode = 'bypassPermissions'
460
- else newMode = 'default'
403
+ // Map legacy 3-level (auto/ask/bypass) to new 4-level mode.
404
+ // Legacy 'auto'/'ask' = "let each tool self-decide" → 'default'.
405
+ // Legacy 'bypass' → 'bypassPermissions'.
406
+ const newMode: PermissionMode = level === 'bypass' ? 'bypassPermissions' : 'default'
461
407
  this.mode = clampMode(newMode, this.restrictions)
462
408
  this.invalidateCache()
463
409
  }
@@ -465,8 +411,7 @@ export class PermissionSystem {
465
411
  getDefaultLevel(): PermissionLevel {
466
412
  // Legacy constructor fallback takes priority
467
413
  if (this.legacyDefaultFallback) return this.legacyDefaultFallback
468
- if (this.mode === 'auto' || this.mode === 'bypassPermissions' || this.mode === 'dontAsk')
469
- return 'bypass'
414
+ if (this.mode === 'bypassPermissions') return 'bypass'
470
415
  if (this.mode === 'plan') return 'ask'
471
416
  return 'auto'
472
417
  }
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Mipham Code — Project Scaffold
3
+ *
4
+ * `mipham init` 生成规范的项目文档(CLAUDE.md + MIPHAM.md + README.md),
5
+ * 避免用户「冷建立文件」。模板遵循 One Mipham Corporation 规范。
6
+ */
7
+ import { existsSync, mkdirSync, writeFileSync, readdirSync } from 'node:fs'
8
+ import { join, basename } from 'node:path'
9
+ import { execSync } from 'node:child_process'
10
+
11
+ export interface ScaffoldResult {
12
+ created: string[]
13
+ skipped: string[]
14
+ gitInitialized: boolean
15
+ }
16
+
17
+ export function renderMiphamMd(projectName: string): string {
18
+ return `---
19
+ model: mipham-code
20
+ version: 1.0.0
21
+ privacy: project
22
+ language: zh-CN
23
+ ---
24
+
25
+ # MIPHAM.md — ${projectName}
26
+
27
+ > 本文件定义 ${projectName} 项目中 AI 助手的交互人格和项目规范。
28
+ > 继承自 One Mipham Corporation 集团 MIPHAM.md。
29
+
30
+ ---
31
+
32
+ ## 项目概述
33
+
34
+ [简要描述项目目的和定位]
35
+
36
+ ## 技术栈
37
+
38
+ [列出主要技术栈]
39
+
40
+ ## 项目规范
41
+
42
+ - [添加项目特有的编码规则]
43
+ - [添加团队约定]
44
+
45
+ ## AI 交互偏好
46
+
47
+ - 回复语言:[中文/英文]
48
+ - 代码风格:[偏好]
49
+ - 注释语言:[中文/英文]
50
+ `
51
+ }
52
+
53
+ export function renderClaudeMd(projectName: string): string {
54
+ return `# CLAUDE.md
55
+
56
+ > 项目:${projectName}
57
+ > 版本:0.1.0
58
+ > 初始化:\`mipham init\`
59
+
60
+ ## 项目概述
61
+
62
+ [简要描述项目目的和定位]
63
+
64
+ ## 技术栈
65
+
66
+ [列出主要技术栈]
67
+
68
+ ## 开发命令
69
+
70
+ - 构建:[\`填入 build 命令\`]
71
+ - 测试:[\`填入 test 命令\`]
72
+ - Lint:[\`填入 lint 命令\`]
73
+
74
+ ## 项目规范
75
+
76
+ - [添加项目特有的编码规则]
77
+ - [添加团队约定]
78
+
79
+ ## 关键约束
80
+
81
+ - 遵循上级 One Mipham Corporation CLAUDE.md 技术规范
82
+ - 提交信息遵循 Conventional Commits
83
+ - 禁止在代码、日志、配置或提交历史中硬编码凭据
84
+ `
85
+ }
86
+
87
+ export function renderReadmeMd(projectName: string): string {
88
+ return `# ${projectName}
89
+
90
+ [一句话描述项目]
91
+
92
+ ## 快速开始
93
+
94
+ \`\`\`bash
95
+ # 安装依赖 / 运行项目
96
+ \`\`\`
97
+
98
+ ## 技术栈
99
+
100
+ [列出技术栈]
101
+
102
+ ## 文档
103
+
104
+ - \`CLAUDE.md\` — AI 协作规范
105
+ - \`MIPHAM.md\` — AI 交互人格
106
+ - \`README.md\` — 项目说明(本文件)
107
+
108
+ ## 许可
109
+
110
+ [填入许可证]
111
+ `
112
+ }
113
+
114
+ /** 检测目录是否为「愣建文件夹」——无任何条目(忽略 .DS_Store)。目录不存在则视为非空。 */
115
+ export function isEmptyProject(dir: string): boolean {
116
+ try {
117
+ const entries = readdirSync(dir).filter((e) => e !== '.DS_Store')
118
+ return entries.length === 0
119
+ } catch {
120
+ return false
121
+ }
122
+ }
123
+
124
+ export function scaffoldProject(targetDir: string, opts?: { gitInit?: boolean }): ScaffoldResult {
125
+ mkdirSync(targetDir, { recursive: true })
126
+
127
+ const name = basename(targetDir) || 'my-project'
128
+ const files = [
129
+ { path: 'CLAUDE.md', content: renderClaudeMd(name) },
130
+ { path: 'MIPHAM.md', content: renderMiphamMd(name) },
131
+ { path: 'README.md', content: renderReadmeMd(name) },
132
+ ]
133
+
134
+ const created: string[] = []
135
+ const skipped: string[] = []
136
+
137
+ for (const f of files) {
138
+ const full = join(targetDir, f.path)
139
+ if (existsSync(full)) {
140
+ skipped.push(f.path)
141
+ } else {
142
+ writeFileSync(full, f.content, 'utf-8')
143
+ created.push(f.path)
144
+ }
145
+ }
146
+
147
+ let gitInitialized = false
148
+ if (opts?.gitInit && !existsSync(join(targetDir, '.git'))) {
149
+ try {
150
+ execSync('git init', { cwd: targetDir, stdio: 'ignore' })
151
+ gitInitialized = true
152
+ } catch {
153
+ // git 不可用或失败 — 不阻断脚手架
154
+ }
155
+ }
156
+
157
+ return { created, skipped, gitInitialized }
158
+ }
@@ -64,8 +64,6 @@ const DAEMON_PERMISSION_MODES: ReadonlySet<PermissionMode> = new Set<PermissionM
64
64
  'default',
65
65
  'acceptEdits',
66
66
  'plan',
67
- 'auto',
68
- 'dontAsk',
69
67
  'bypassPermissions',
70
68
  ])
71
69
 
@@ -757,8 +757,6 @@
757
757
  "manual": "manual mode",
758
758
  "accept_edits": "accept edits on",
759
759
  "plan_mode": "plan mode / read-only",
760
- "auto": "auto mode",
761
- "dont_ask": "don't ask",
762
760
  "bypass": "bypass"
763
761
  },
764
762
  "loading": {
@@ -804,8 +802,7 @@
804
802
  "assistant": { "role_label": "Mipham Code" },
805
803
  "user": { "role_label": "User" },
806
804
  "input": {
807
- "placeholder": "Type a message (Esc to cancel)...",
808
- "vim_help": "[NORMAL] h/j/k/l w/b 0/$ dd yy p u / (Esc to insert)"
805
+ "placeholder": "Type a message (Esc to clear)..."
809
806
  },
810
807
  "slash_hints": {
811
808
  "label": "Commands:",
@@ -942,7 +939,7 @@
942
939
  "mcp": { "name": "MCP", "description": "Manage MCP server connections" }
943
940
  },
944
941
  "errors": {
945
- "tool_not_allowed": "Tool \"{name}\" requires user approval (permission: ask). The tool was not executed.",
942
+ "tool_not_allowed": "Tool \"{name}\" requires user approval (permission: ask). The tool was not executed. Press Shift+Tab to switch permission mode, or run /permissions.",
946
943
  "tool_blocked": "Tool \"{name}\" blocked by hook",
947
944
  "user_input_blocked": "User input blocked by hook.",
948
945
  "dlp_blocked": "Request blocked by DLP policy.",
@@ -963,6 +960,7 @@
963
960
  "mcp_register_failed": "[mcp] Failed to register tool \"{tool}\" from server \"{server}\": {error}",
964
961
  "max_tool_turns": "Max tool-calling turns ({max}) reached. Some tool calls were not executed.",
965
962
  "max_tool_turns_warning": "You've reached the maximum of {max} tool-calling rounds. {pending} tool call(s) were not executed. Please summarize what you found so far and any next steps the user should take.",
963
+ "turn_timeout": "Task produced no substantial output for {minutes} minutes and was stopped. Break the task into smaller steps or clarify the next step, then retry.",
966
964
  "mcp_not_connected": "Error: MCP server \"{server}\" not connected",
967
965
  "mcp_tool_error": "MCP tool error: {error}",
968
966
  "mcp_no_connection": "No connection for \"{name}\""