@andyqiu/codeforge 0.3.10 → 0.3.12

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.
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * adr-index-sync.mjs — 自动同步 docs/adr/README.md 索引表(泛化版,由 codeforge adr-init 下发)
4
+ *
5
+ * 与 CodeForge 自仓库 scripts/adr-index-sync.mjs 的区别:
6
+ * - LEGACY_ID_MAP 默认为空(不展示「曾用编号」列)
7
+ * - README marker 改为 HTML 注释锚点:`<!-- adr-index:start --> / <!-- adr-index:end -->`
8
+ * - ROOT 支持 ADR_ROOT 环境变量参数化
9
+ *
10
+ * 用法:
11
+ * node scripts/adr-index-sync.mjs # 默认:写回 README.md
12
+ * node scripts/adr-index-sync.mjs --check # 仅检查,不写;不同步退出码 1
13
+ *
14
+ * 环境变量:
15
+ * ADR_ROOT 项目根目录绝对/相对路径;默认 path.resolve(__dirname, "..")
16
+ * ADR_DIR ADR 目录;默认 <ROOT>/docs/adr
17
+ *
18
+ * 工作原理:
19
+ * 1. 扫 docs/adr/*.md(纯 slug 格式)取 frontmatter
20
+ * 2. 按 date 升序排序(同 date 按 id 二级排序)生成索引行
21
+ * 3. 在 README.md 找 marker `<!-- adr-index:start --> ... <!-- adr-index:end -->` 之间整段替换
22
+ * 4. --check 模式不写文件,diff 不为空则 exit 1
23
+ */
24
+
25
+ import { existsSync, promises as fs } from "node:fs"
26
+ import * as path from "node:path"
27
+ import { fileURLToPath } from "node:url"
28
+
29
+ const DEFAULT_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..")
30
+ const ROOT = process.env.ADR_ROOT ? path.resolve(process.env.ADR_ROOT) : DEFAULT_ROOT
31
+ const ADR_DIR = process.env.ADR_DIR ? path.resolve(process.env.ADR_DIR) : path.join(ROOT, "docs", "adr")
32
+ const README = path.join(ADR_DIR, "README.md")
33
+ const CHECK_ONLY = process.argv.includes("--check")
34
+
35
+ const SLUG_RE = /^[a-z][a-z0-9-]*\.md$/
36
+ const isTemplate = (f) => f === "template.md"
37
+ const isReadme = (f) => f === "README.md"
38
+ const isAdrFile = (f) => !isTemplate(f) && !isReadme(f) && SLUG_RE.test(f)
39
+
40
+ // 模板行:固定 prepend 在首行;template.md 单独列出避免被 isAdrFile 排除
41
+ const TEMPLATE_LINE = "| template | — | Template(模板) | — | — |"
42
+
43
+ // 下发版默认为空映射;如目标项目从旧 NNNN- 编号体系迁移,可在此显式补充
44
+ const LEGACY_ID_MAP = new Map()
45
+
46
+ async function parseADR(filepath) {
47
+ const raw = await fs.readFile(filepath, "utf8")
48
+ const m = raw.match(/^---\n([\s\S]*?)\n---/)
49
+ if (!m) return null
50
+ const fm = {}
51
+ let curArr = null
52
+ for (const line of m[1].split("\n")) {
53
+ const kv = line.match(/^([a-z][\w-]*):\s*(.*)$/)
54
+ if (kv) {
55
+ const v = kv[2].trim()
56
+ if (/^\[.*\]$/.test(v)) {
57
+ fm[kv[1]] = v.slice(1, -1).split(",")
58
+ .map(s => s.trim().replace(/^["']|["']$/g, ""))
59
+ .filter(Boolean)
60
+ curArr = null
61
+ } else if (v === "" || v === "[]") {
62
+ fm[kv[1]] = []
63
+ curArr = fm[kv[1]]
64
+ } else {
65
+ fm[kv[1]] = v.replace(/^["']|["']$/g, "")
66
+ curArr = null
67
+ }
68
+ } else if (curArr && line.startsWith(" - ")) {
69
+ curArr.push(line.slice(4).trim().replace(/^["']|["']$/g, ""))
70
+ }
71
+ }
72
+ return fm
73
+ }
74
+
75
+ async function buildIndex() {
76
+ const files = (await fs.readdir(ADR_DIR)).filter(isAdrFile)
77
+ const rows = []
78
+ for (const f of files) {
79
+ const fm = await parseADR(path.join(ADR_DIR, f))
80
+ if (!fm) continue
81
+ const id = f.replace(/\.md$/, "")
82
+ const date = String(fm.date || "").trim() || "—"
83
+ const title = String(fm.title || "").replace(/^["']|["']$/g, "")
84
+ const status = fm.status || "?"
85
+ const sb = Array.isArray(fm["superseded-by"]) ? fm["superseded-by"].filter(Boolean) : []
86
+ const sup = Array.isArray(fm.supersedes) ? fm.supersedes.filter(Boolean) : []
87
+ let rel = "—"
88
+ if (sb.length) rel = `by ${sb.map((x) => String(x).trim()).join(", ")}`
89
+ else if (sup.length) rel = `Supersedes ${sup.map((x) => String(x).trim()).join(", ")}`
90
+ const statusCell = status === "Superseded" ? `**${status}**` : status
91
+ const titleCell = `[${title}](./${f})`
92
+ // LEGACY_ID_MAP 留作扩展:如目标项目有旧编号要展示,可在源码 const 处补充并改表头
93
+ rows.push({
94
+ id,
95
+ date,
96
+ title,
97
+ status,
98
+ sb,
99
+ sup,
100
+ file: f,
101
+ line: `| ${id} | ${date} | ${titleCell} | ${statusCell} | ${rel} |`,
102
+ })
103
+ }
104
+ rows.sort((a, b) => a.date.localeCompare(b.date) || a.id.localeCompare(b.id))
105
+ return [TEMPLATE_LINE, ...rows.map((r) => r.line)]
106
+ }
107
+
108
+ async function main() {
109
+ if (!existsSync(README)) {
110
+ console.error(`✗ 未找到 ${README}`)
111
+ process.exit(1)
112
+ }
113
+ const old = await fs.readFile(README, "utf8")
114
+ const rows = await buildIndex()
115
+ const tableHeader = "| ID | Date | 标题 | 状态 | 替换关系 |\n|---|---|---|---|---|"
116
+ const tableBlock = `${tableHeader}\n${rows.join("\n")}`
117
+
118
+ // marker:HTML 注释锚点
119
+ const markerRe = /(<!--\s*adr-index:start\s*-->)[\s\S]*?(<!--\s*adr-index:end\s*-->)/
120
+ const m = old.match(markerRe)
121
+ if (!m) {
122
+ console.error(`✗ README 找不到 marker "<!-- adr-index:start --> ... <!-- adr-index:end -->"`)
123
+ console.error(` 请确认 ${path.relative(ROOT, README)} 含一对 HTML 注释锚点。`)
124
+ process.exit(1)
125
+ }
126
+
127
+ const newContent = old.replace(markerRe, `$1\n${tableBlock}\n$2`)
128
+
129
+ if (CHECK_ONLY) {
130
+ if (newContent === old) {
131
+ console.log(`✓ README 索引与 ADR 文件同步(${rows.length} 行)`)
132
+ process.exit(0)
133
+ } else {
134
+ console.error(`✗ README 索引与 ADR 文件不同步`)
135
+ console.error(` 跑 \`node scripts/adr-index-sync.mjs\` 自动修复`)
136
+ process.exit(1)
137
+ }
138
+ }
139
+
140
+ if (newContent === old) {
141
+ console.log(`✓ README 索引已是最新(${rows.length} 行),未改动`)
142
+ } else {
143
+ await fs.writeFile(README, newContent, "utf8")
144
+ console.log(`✓ README 索引已更新:${rows.length} 行`)
145
+ }
146
+ }
147
+
148
+ main().catch((err) => {
149
+ console.error(err)
150
+ process.exit(1)
151
+ })
package/bin/codeforge.mjs CHANGED
@@ -14,6 +14,8 @@
14
14
  * rollback [--target=<path>] [--dry-run]
15
15
  * 恢复到上一次 backup(auto_install 失败时手动救场)
16
16
  * runtime where [<path>] 打印当前项目对应的全局运行时目录
17
+ * adr-init [--force] [--dry-run] [--write-prepare] [--no-pre-push]
18
+ * 把 ADR 体系(hooks + scripts + 模板)下发到当前 git 项目
17
19
  * help [<cmd>] 打印帮助
18
20
  *
19
21
  * 默认 --project(项目级,最安全)。
@@ -299,6 +301,79 @@ function cmdRuntime(args) {
299
301
  return r.status ?? 1
300
302
  }
301
303
 
304
+ // ────────────────────────────────────────────────────────────────────
305
+ // 子命令:adr-init —— 把 ADR 体系下发到当前 git 项目 (ADR:adr-distribution-init)
306
+ // ────────────────────────────────────────────────────────────────────
307
+ //
308
+ // 实现走 dist/adr-init.js 这个**确定路径**(由 scripts/build-with-version.mjs 第二
309
+ // bun build entry 产出)。既适用 npm 包安装(pkgRoot/dist/adr-init.js),也适用本
310
+ // 仓库 dev 期(repoRoot/dist/adr-init.js)。没有 fallback —— 缺 dist 文件就明确报错
311
+ // 提示跑 npm run build,比 ts-node / 源码动态 import 简单可靠。
312
+ async function cmdAdrInit(args) {
313
+ const cwd = process.cwd()
314
+ const opts = {
315
+ cwd,
316
+ force: !!args.flags.force,
317
+ dryRun: !!args.flags["dry-run"] || !!args.flags.dryRun,
318
+ writePrepare: !!args.flags["write-prepare"] || !!args.flags.writePrepare,
319
+ installPrePush: args.flags["no-pre-push"] ? false : true,
320
+ }
321
+
322
+ const adrInitPath = path.join(REPO_ROOT, "dist", "adr-init.js")
323
+ if (!existsSync(adrInitPath)) {
324
+ err(`找不到 ${adrInitPath}`)
325
+ err(`如果是从源码运行:请先跑 npm run build`)
326
+ err(`如果是 npm 安装:可能是发布缺文件,请重装或反馈给 @andyqiu/codeforge`)
327
+ return 1
328
+ }
329
+
330
+ log(`CodeForge adr-init —— 下发 ADR 体系到 ${cwd}`)
331
+ log(` force : ${opts.force}`)
332
+ log(` dry-run : ${opts.dryRun}`)
333
+ log(` write-prepare : ${opts.writePrepare}`)
334
+ log(` install pre-push: ${opts.installPrePush}`)
335
+ hr()
336
+
337
+ // ESM dynamic import 必须用 file:// URL,Win 路径才能正确解析
338
+ let mod
339
+ try {
340
+ mod = await import(url.pathToFileURL(adrInitPath).href)
341
+ } catch (e) {
342
+ err(`加载 ${adrInitPath} 失败:${e?.message ?? e}`)
343
+ return 1
344
+ }
345
+ if (typeof mod.runAdrInit !== "function") {
346
+ err(`${adrInitPath} 缺少 runAdrInit 导出(bundle 可能损坏,请重建 dist/)`)
347
+ return 1
348
+ }
349
+
350
+ let result
351
+ try {
352
+ result = await mod.runAdrInit(opts)
353
+ } catch (e) {
354
+ err(`adr-init 执行抛错:${e?.stack ?? e}`)
355
+ return 1
356
+ }
357
+
358
+ if (result.wrote?.length) {
359
+ ok(`已写入 ${result.wrote.length} 个文件:`)
360
+ for (const f of result.wrote) console.log(` + ${f}`)
361
+ }
362
+ if (result.skipped?.length) {
363
+ warn(`已跳过 ${result.skipped.length} 个已存在文件(用 --force 覆盖):`)
364
+ for (const f of result.skipped) console.log(` · ${f}`)
365
+ }
366
+ if (result.backedUp?.length) {
367
+ ok(`已备份 ${result.backedUp.length} 个原文件:`)
368
+ for (const f of result.backedUp) console.log(` ↳ ${f}`)
369
+ }
370
+ for (const s of result.suggestions ?? []) log(s)
371
+ for (const w of result.warnings ?? []) warn(w)
372
+ if (opts.dryRun) log("dry-run 模式:以上未实际写盘")
373
+ hr()
374
+ return result.ok === false ? 1 : 0
375
+ }
376
+
302
377
  // ────────────────────────────────────────────────────────────────────
303
378
 
304
379
  // 子命令:help
@@ -313,6 +388,8 @@ function cmdHelp() {
313
388
  codeforge version
314
389
  codeforge rollback [--target=<path>] [--dry-run] # 恢复最近 backup(auto_install 失败救场)
315
390
  codeforge runtime where [<path>] # 打印当前项目的全局运行时目录
391
+ codeforge adr-init [--force] [--dry-run] [--write-prepare] [--no-pre-push]
392
+ # 把 ADR 校验体系(hooks + scripts + 模板)下发到当前 git 项目
316
393
 
317
394
  一行命令(远程安装):
318
395
  npx @andyqiu/codeforge install
@@ -326,6 +403,12 @@ function cmdHelp() {
326
403
  --enable-legacy-tools 启用旧版 file-based tools(默认禁用,避免 zod 跨实例 bug)
327
404
  --target rollback 子命令:指定 index.js 路径(默认自动探测)
328
405
 
406
+ 参数(adr-init 专用):
407
+ --force 覆盖已存在文件(覆盖前自动 .bak.<ts> 备份)
408
+ --dry-run 只打印将要执行的写入计划,不实际写盘
409
+ --write-prepare 合并 git config core.hooksPath 到 package.json scripts.prepare(写前 backup package.json;默认只建议不写)
410
+ --no-pre-push 跳过 .githooks/pre-push 生成(只装 pre-commit)
411
+
329
412
  CodeForge 是 opencode 1.14+ 的单 bundle plugin(持续跟主线,src/index.ts → dist/index.js),
330
413
  通过往 opencode.json 写一行 plugin entry 来装载。
331
414
  不支持 Cursor / Claude Code / Codex —— 它们的扩展机制不同,强行映射只会变成
@@ -336,9 +419,12 @@ CodeForge 是 opencode 1.14+ 的单 bundle plugin(持续跟主线,src/index.
336
419
  }
337
420
 
338
421
  // ────────────────────────────────────────────────────────────────────
339
- // main
422
+ // main —— async (ADR:adr-distribution-init: adr-init 需 dynamic import)
340
423
  // ────────────────────────────────────────────────────────────────────
341
- function main() {
424
+ //
425
+ // 改成 async 后所有同步 case `return <number>` 自动被包成 Promise<number>,外层 .then
426
+ // 拿到数字调 process.exit 即可;现有 case 的 exit code 行为不变。
427
+ async function main() {
342
428
  const argv = process.argv.slice(2)
343
429
  if (argv.length === 0 || ["-h", "--help"].includes(argv[0])) {
344
430
  return cmdHelp()
@@ -366,6 +452,8 @@ function main() {
366
452
  return cmdRollback(args)
367
453
  case "runtime":
368
454
  return cmdRuntime(args)
455
+ case "adr-init":
456
+ return cmdAdrInit(args)
369
457
 
370
458
  case "help":
371
459
  return cmdHelp()
@@ -376,4 +464,9 @@ function main() {
376
464
  }
377
465
  }
378
466
 
379
- process.exit(main())
467
+ main()
468
+ .then((code) => process.exit(typeof code === "number" ? code : 0))
469
+ .catch((e) => {
470
+ err(`未捕获错误:${e?.stack ?? e}`)
471
+ process.exit(1)
472
+ })
package/codeforge.json CHANGED
@@ -11,6 +11,7 @@
11
11
  "_doc": "规划者:复杂推理 + 长程规划,需要最强模型 + 大 thinking budget。",
12
12
  "model": "anthropic/claude-opus-4-7",
13
13
  "category": "deep",
14
+ "tier": "deep",
14
15
  "thinking": { "type": "enabled", "budget_tokens": 8000 },
15
16
  "fallback_models": [
16
17
  "openai/gpt-5.5",
@@ -19,10 +20,34 @@
19
20
  ]
20
21
  },
21
22
  "coder": {
22
- "_doc": "编码者:长上下文 agentic 编码,Opus 4.7 是最佳选择。thinking 2000 是经验起步档(>4096 边际收益骤降;agentic 多轮 tool call 不需要长思考链)。",
23
+ "_doc": "编码者:默认 balanced 档(Sonnet),复杂任务由难度分级自动升到 coder-deep(Opus)。thinking 4000 适合常规 agentic 编码。",
24
+ "model": "anthropic/claude-sonnet-4-6",
25
+ "category": "balanced",
26
+ "tier": "balanced",
27
+ "thinking": { "type": "enabled", "budget_tokens": 4000 },
28
+ "fallback_models": [
29
+ "openai/gpt-5.5",
30
+ "anthropic/claude-opus-4-7",
31
+ "google/gemini-3-pro"
32
+ ]
33
+ },
34
+ "coder-quick": {
35
+ "_doc": "编码者(快速档):Sonnet + 最小 thinking,适合 typo fix / 单行改动 / 文档更新。",
36
+ "model": "anthropic/claude-sonnet-4-6",
37
+ "category": "balanced",
38
+ "tier": "quick",
39
+ "thinking": { "type": "enabled", "budget_tokens": 2000 },
40
+ "fallback_models": [
41
+ "openai/gpt-5.5",
42
+ "anthropic/claude-sonnet-4-6"
43
+ ]
44
+ },
45
+ "coder-deep": {
46
+ "_doc": "编码者(深度档):Opus + 大 thinking,适合跨 3+ 文件重构 / 安全改动 / 数据迁移。",
23
47
  "model": "anthropic/claude-opus-4-7",
24
48
  "category": "deep",
25
- "thinking": { "type": "enabled", "budget_tokens": 2000 },
49
+ "tier": "deep",
50
+ "thinking": { "type": "enabled", "budget_tokens": 8000 },
26
51
  "fallback_models": [
27
52
  "openai/gpt-5.5",
28
53
  "anthropic/claude-sonnet-4-6",
@@ -33,6 +58,7 @@
33
58
  "_doc": "审查者:跨视角的独立评审更适合换个模型家族(避免同模型偏见)。GPT-5.5 主,Anthropic 备用。thinking 2000:审查 prompt 一般不大,长思考链浪费。",
34
59
  "model": "openai/gpt-5.5",
35
60
  "category": "ultrabrain",
61
+ "tier": "deep",
36
62
  "thinking": { "type": "enabled", "budget_tokens": 2000 },
37
63
  "fallback_models": [
38
64
  "anthropic/claude-opus-4-7",
@@ -0,0 +1,67 @@
1
+ ---
2
+ description: 一键把 ADR 体系(adr-check + git hooks + 模板)下发到当前 git 项目
3
+ ---
4
+
5
+ <!-- ADR:adr-distribution-init -->
6
+ <!--
7
+ codeforge 元数据(opencode 不读,由 plugins / workflow-engine 解析):
8
+ name: adr-init
9
+ version: 1.0.0
10
+ trigger_workflow: null
11
+ allowed_tools: adr-init
12
+ 说明:单 tool 命令,把 CodeForge 的 ADR 体系一键下发到当前 git 项目
13
+ -->
14
+
15
+
16
+ # /adr-init — 一键下发 ADR 体系
17
+
18
+ 把 CodeForge 维护的 ADR 体系(决策先行 + 三向引用 + 触发式补全 + README 索引同步)下发到**当前 git 项目**:
19
+
20
+ - `scripts/adr-check.mjs`、`scripts/adr-index-sync.mjs`(泛化版,支持 `ADR_ROOT` / `ADR_DIR` 环境变量)
21
+ - `.githooks/pre-commit`(staged 模式校验)、`.githooks/pre-push`(last-commit 模式校验)
22
+ - `docs/adr/README.md`(索引骨架,含 HTML 注释 marker)、`docs/adr/template.md`
23
+ - `.github/pull_request_template.md`(ADR checklist)
24
+
25
+ 零 npm 依赖:用 `git config core.hooksPath .githooks`(自动启用,无需 husky)。
26
+
27
+ ## 输入
28
+
29
+ 可选参数:$ARGUMENTS
30
+
31
+ 支持的 flag(在用户对话里说,由 LLM 解析后传给 `adr-init` tool):
32
+
33
+ - `--force`:覆盖已存在文件(覆盖前自动 `.bak.<时间戳>` 备份)
34
+ - `--dry-run`:只打印将要执行的写入计划,不实际写盘
35
+ - `--write-prepare`:(npm 项目)自动合并 `git config core.hooksPath .githooks` 到 `package.json scripts.prepare`;写前自动 backup package.json
36
+ - `--no-pre-push`:只装 pre-commit,不装 pre-push
37
+
38
+ ## 用法
39
+
40
+ ```
41
+ /adr-init
42
+ /adr-init --dry-run
43
+ /adr-init --force
44
+ /adr-init --write-prepare
45
+ ```
46
+
47
+ 也可在 shell 跑等价命令:
48
+
49
+ ```
50
+ npx @andyqiu/codeforge adr-init [--force] [--dry-run] [--write-prepare] [--no-pre-push]
51
+ ```
52
+
53
+ ## 行为
54
+
55
+ 1. 检查当前目录是否 git 仓库(否则报错并停止)
56
+ 2. 解析 `assets/adr-init/` 资产根(兼容 npm 全局 / npx / 本仓库 dev 三种安装形态)
57
+ 3. 按清单复制文件:
58
+ - 默认 `exists → skip`;`--force` 覆盖前自动 `.bak.<ts>` 备份
59
+ - hook 文件自动 `chmod 0o755`(Windows 上吞错)
60
+ 4. 运行 `git config core.hooksPath .githooks` 启用 hooks
61
+ 5. 报告写入 / 跳过 / 备份 / 建议 / 警告
62
+
63
+ ## 失败回退
64
+
65
+ - 当前目录非 git 仓库 → 返回 `reason: "not_git_repo"`,建议用户先 `git init`
66
+ - 资产路径找不到(npm 包 `files` 未含 `assets/`)→ 返回 `reason: "assets_not_found"`,提示重装
67
+ - `git config` 失败 → 仍返回 ok=true,但 warnings 含具体错误,请用户手动执行
@@ -0,0 +1,87 @@
1
+ ---
2
+ description: 临时把当前任务的 agent 模型升到 deep 档(Opus + 大 thinking);改动 stage 到 pending-changes 等待用户审批
3
+ agent: planner
4
+ ---
5
+
6
+ <!--
7
+ codeforge 元数据(opencode 不读,由 plugins / workflow-engine 解析):
8
+ name: deep
9
+ version: 1.0.0
10
+ trigger_workflow: null
11
+ allowed_tools: model_chain, ast_edit, pending_changes
12
+ 说明:方案 A 用户显式 override 入口;与 /quick 对称;与 /model-switch 共享 ast_edit + pending_changes 落地路径
13
+ ADR-0060(model-tier-three-layer-escalation,Phase 2d)
14
+ -->
15
+
16
+
17
+ # /deep — 临时升档到 deep(Opus + 大 thinking)
18
+
19
+ 把指定 agent 的模型临时升到 `deep` 档(默认 `anthropic/claude-opus-4-7` + 大 thinking budget)。
20
+ 改动会 stage 到 pending-changes,**由用户审批后 apply 才会真正生效**。
21
+
22
+ ## 输入
23
+
24
+ 参数:$ARGUMENTS
25
+
26
+ ## 用法
27
+
28
+ ```
29
+ /deep # 默认升 coder 到 deep 档
30
+ /deep coder # 同上,显式指定
31
+ /deep planner # 升 planner 到 deep 档
32
+ /deep reviewer # 升 reviewer 到 deep 档
33
+ ```
34
+
35
+ ## 行为
36
+
37
+ 1. **解析参数**:
38
+ - `$1` = agent 名,缺省默认 `coder`
39
+ - agent 名必须在 `codeforge.json` 的 `models.agents.*` 里存在;不存在则列出可选 agent 提示用户
40
+
41
+ 2. **查当前档位**:调用 `model_chain({ agent })` 读 `codeforge.json` 拿当前 agent 的模型链
42
+
43
+ 3. **判断是否需要切换**(去噪,避免误操作):
44
+ - 若当前 model 已经命中 `models.categories.deep`(或 agent 已是 deep 档变体)→ 直接告知用户「<agent> 当前已是 deep 档(model=xxx),无需切换」,**不 stage**
45
+ - 若当前已是 `ultra` 档(更高档)→ 提示「<agent> 当前是 ultra 档(比 deep 更高),如确需降到 deep 请显式确认」,**不 stage**
46
+ - 否则进入切换流程
47
+
48
+ 4. **执行切换**(复用 `/model-switch` 的等价路径,不直接调它,按 ast_edit + pending_changes 标准模式自行 stage):
49
+ 1. 从 `codeforge.json` 的 `models.categories.deep`(或 `models.tiers.deep`,若已落地 Phase 2a)读出目标 model id 和 thinking 配置
50
+ 2. 用 `ast_edit` 修改 `codeforge.json`:把 `models.agents.<agent>.model` 改为 deep 档 model;旧主模型自动 prepend 到该 agent 的 fallback 链头部(避免重启后被切回去)
51
+ 3. 改动用 `pending_changes.stage` 暂存;**不调 apply**
52
+ 4. 输出 pending-changes ID 给用户审批
53
+
54
+ 5. **审批后提示**:用户 apply 之后,主动提示跑 `npm run sync:models` 把新配置同步到 `agents/<agent>.md` 的 frontmatter
55
+
56
+ 6. **本次会话即时生效说明**:明确告诉用户「`/deep` 改的是 `codeforge.json`,本次会话已加载的 agent 不会立即换 model;下次新会话或重启 opencode 后生效」——避免用户误以为本 turn 就变了
57
+
58
+ ## 何时用
59
+
60
+ - 当前任务跨多文件 / 涉及 refactor / 安全 / 数据迁移 / 架构决策,预判默认档(balanced)搞不定
61
+ - reviewer 已经 REQUEST_CHANGES 或 BLOCK,需要更强模型重新出方案 / 重写代码
62
+ - 想用 deep 档跑一遍对比 balanced 档的产出差异(benchmark 场景)
63
+
64
+ ## 与 /quick / /model-switch 的关系
65
+
66
+ - `/quick` ←→ `/deep`:完全对称,方向相反
67
+ - `/model-switch agent <model-id>`:底层通用切换;`/deep` 是它的「按档位语义糖」,自动算出 deep 档 model 不用用户记 id
68
+ - 三者最终都走相同的 `ast_edit + pending_changes` 路径,没有"另一套"写文件机制
69
+
70
+ ## 安全约束
71
+
72
+ - ❌ 禁止直接 apply pending-changes(必须用户审批)
73
+ - ❌ 禁止改 `codeforge.json` 之外的文件
74
+ - ❌ 禁止在没有 `models.categories.deep`(或 `models.tiers.deep`)配置时硬编码 model id —— 必须从配置读
75
+ - ✅ stage 完一定要输出 pending id 给用户
76
+ - ✅ apply 后一定要提示 `npm run sync:models`
77
+
78
+ ## 失败回退
79
+
80
+ - `model_chain` 报 `config_not_found`:提示用户先在项目根创建 `codeforge.json`
81
+ - `models.categories.deep` / `models.tiers.deep` 缺失:提示用户先在 `codeforge.json` 添加 deep 档定义,参考 `schemas/codeforge.schema.json`
82
+ - agent 不在配置:列出所有可选 agent 名让用户选
83
+
84
+ ## 元数据
85
+
86
+ - `agent`: planner(命令由 planner 角色执行配置类决策操作)
87
+ - `trigger_workflow`: null
@@ -0,0 +1,92 @@
1
+ ---
2
+ description: 临时把当前任务的 agent 模型降到 quick 档(Sonnet/Haiku 档,省 token);改动 stage 到 pending-changes 等待用户审批
3
+ agent: planner
4
+ ---
5
+
6
+ <!--
7
+ codeforge 元数据(opencode 不读,由 plugins / workflow-engine 解析):
8
+ name: quick
9
+ version: 1.0.0
10
+ trigger_workflow: null
11
+ allowed_tools: model_chain, ast_edit, pending_changes
12
+ 说明:方案 A 用户显式 override 入口;与 /deep 对称;与 /model-switch 共享 ast_edit + pending_changes 落地路径
13
+ ADR-0060(model-tier-three-layer-escalation,Phase 2d)
14
+ -->
15
+
16
+
17
+ # /quick — 临时降档到 quick(Sonnet/Haiku 档,省 token)
18
+
19
+ 把指定 agent 的模型临时降到 `quick` 档(默认 `anthropic/claude-sonnet-4-5` 或更轻量的 Haiku,按 `codeforge.json` 配置)。
20
+ 改动会 stage 到 pending-changes,**由用户审批后 apply 才会真正生效**。
21
+
22
+ ## 输入
23
+
24
+ 参数:$ARGUMENTS
25
+
26
+ ## 用法
27
+
28
+ ```
29
+ /quick # 默认降 coder 到 quick 档
30
+ /quick coder # 同上,显式指定
31
+ /quick planner # 降 planner 到 quick 档(不推荐——planner 出方案质量很影响后续)
32
+ /quick reviewer # 降 reviewer 到 quick 档(不推荐——会降低评审质量)
33
+ ```
34
+
35
+ ## 行为
36
+
37
+ 1. **解析参数**:
38
+ - `$1` = agent 名,缺省默认 `coder`
39
+ - agent 名必须在 `codeforge.json` 的 `models.agents.*` 里存在;不存在则列出可选 agent 提示用户
40
+
41
+ 2. **查当前档位**:调用 `model_chain({ agent })` 读 `codeforge.json` 拿当前 agent 的模型链
42
+
43
+ 3. **判断是否需要切换**(去噪,避免误操作):
44
+ - 若当前 model 已经命中 `models.categories.quick`(或 agent 已是 quick 档变体)→ 直接告知用户「<agent> 当前已是 quick 档(model=xxx),无需切换」,**不 stage**
45
+ - 否则进入切换流程
46
+
47
+ 4. **质量风险提示(不可省)**:在 stage 之前主动给用户输出一段警告:
48
+ - 「⚠️ 降到 quick 档可能降低产出质量。建议仅在以下场景使用:①改 typo / 格式化等简单任务;②临时省 token 跑探索性 prompt;③明知任务简单想加速」
49
+ - 若用户降的是 `planner` 或 `reviewer`,额外提示「⚠️ <agent> 是质量关键节点,降档可能导致方案/评审失误」
50
+ - 仍按用户意图执行 stage(提示是软约束,不阻断)
51
+
52
+ 5. **执行切换**(复用 `/model-switch` 的等价路径,按 ast_edit + pending_changes 标准模式自行 stage):
53
+ 1. 从 `codeforge.json` 的 `models.categories.quick`(或 `models.tiers.quick`,若已落地 Phase 2a)读出目标 model id 和 thinking 配置
54
+ 2. 用 `ast_edit` 修改 `codeforge.json`:把 `models.agents.<agent>.model` 改为 quick 档 model;旧主模型自动 prepend 到该 agent 的 fallback 链头部(避免重启后被切回去,方便升回去)
55
+ 3. 改动用 `pending_changes.stage` 暂存;**不调 apply**
56
+ 4. 输出 pending-changes ID 给用户审批
57
+
58
+ 6. **审批后提示**:用户 apply 之后,主动提示跑 `npm run sync:models` 把新配置同步到 `agents/<agent>.md` 的 frontmatter
59
+
60
+ 7. **本次会话即时生效说明**:明确告诉用户「`/quick` 改的是 `codeforge.json`,本次会话已加载的 agent 不会立即换 model;下次新会话或重启 opencode 后生效」
61
+
62
+ ## 何时用
63
+
64
+ - 改 typo / 文档措辞 / 格式化等简单任务,不需要 opus 级推理
65
+ - 跑探索性 prompt 想省 token(先 quick 探路,方向对了再 `/deep` 升档重做)
66
+ - benchmark 场景:跑同任务对比 quick / balanced / deep 的产出差异
67
+
68
+ ## 与 /deep / /model-switch 的关系
69
+
70
+ - `/deep` ←→ `/quick`:完全对称,方向相反
71
+ - `/model-switch agent <model-id>`:底层通用切换;`/quick` 是它的「按档位语义糖」,自动算出 quick 档 model 不用用户记 id
72
+ - 三者最终都走相同的 `ast_edit + pending_changes` 路径,没有"另一套"写文件机制
73
+
74
+ ## 安全约束
75
+
76
+ - ❌ 禁止直接 apply pending-changes(必须用户审批)
77
+ - ❌ 禁止改 `codeforge.json` 之外的文件
78
+ - ❌ 禁止在没有 `models.categories.quick`(或 `models.tiers.quick`)配置时硬编码 model id —— 必须从配置读
79
+ - ✅ 降档前必须输出质量风险提示
80
+ - ✅ stage 完一定要输出 pending id 给用户
81
+ - ✅ apply 后一定要提示 `npm run sync:models`
82
+
83
+ ## 失败回退
84
+
85
+ - `model_chain` 报 `config_not_found`:提示用户先在项目根创建 `codeforge.json`
86
+ - `models.categories.quick` / `models.tiers.quick` 缺失:提示用户先在 `codeforge.json` 添加 quick 档定义,参考 `schemas/codeforge.schema.json`
87
+ - agent 不在配置:列出所有可选 agent 名让用户选
88
+
89
+ ## 元数据
90
+
91
+ - `agent`: planner(命令由 planner 角色执行配置类决策操作)
92
+ - `trigger_workflow`: null