@andyqiu/codeforge 0.6.12 → 0.7.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/agents/codeforge.md +3 -3
- package/bin/codeforge.mjs +17 -127
- package/codeforge.config.yaml +0 -1
- package/codeforge.json +1 -1
- package/commands/parallel.md +1 -1
- package/dist/index.js +1716 -2198
- package/install.mjs +2 -15
- package/package.json +1 -12
- package/workflows/feature-dev.yaml +0 -3
package/agents/codeforge.md
CHANGED
|
@@ -48,7 +48,7 @@ fallback_models:
|
|
|
48
48
|
- **收到 discover-spec-suggest plugin 注入的 candidate-specs 提示后,必须明文跟用户确认**是否走该 spec 路径 —— 用户确认前**不允许**静默把 `spec=<slug>` 塞进派 planner/coder 的 prompt
|
|
49
49
|
- **方案 review 门控**(ADR:full-chain-auto-review-gating):派 planner 拿到 `plan_id` 后,**默认自动派 reviewer 审方案**(`review_target=plan_only`,prompt 含 plan_id),按 reviewer Decision 走「Review 门控行为说明」章节流程
|
|
50
50
|
- **决策 review 门控(Q3-a 范围)**:向用户询问并收到选择后,若用户选择**直接驱动「派哪个 agent 做什么任务」**的决策,在派下一棒之前先派 reviewer 审决策合理性(`review_target=decision_only`)
|
|
51
|
-
- **fallback 补写审批记录**(ADR:apply-hard-gate + ADR:decision-token-vs-approval-verdict-layering):解析 reviewer boomerang 摘要时,若看到 `## Decision` 节首行经 parseDecision 解析为 `APPROVE`(含 `APPROVE_WITH_NOTES` 字面量归一情况)但对应审批在 `<runtimeDir>/approvals/` 下没有记录 → 立即调 `review_approval({ verdict: "APPROVE", pendingIds: [...], notes: "<摘要>", source: "codeforge-fallback" })` 补写,并通过 `tui.showToast`
|
|
51
|
+
- **fallback 补写审批记录**(ADR:apply-hard-gate + ADR:decision-token-vs-approval-verdict-layering + ADR:review-approval-auto-covered-sha):解析 reviewer boomerang 摘要时,若看到 `## Decision` 节首行经 parseDecision 解析为 `APPROVE`(含 `APPROVE_WITH_NOTES` 字面量归一情况)但对应审批在 `<runtimeDir>/approvals/` 下没有记录 → 立即调 `review_approval({ verdict: "APPROVE", pendingIds: [...], notes: "<摘要>", source: "codeforge-fallback" })` 补写,并通过 `tui.showToast` 提醒用户。(`coveredSha` / `reviewTarget` 由 `review_approval` 工具对 `session:<sid>` id 自动补全,无需手传)
|
|
52
52
|
- **session_merge 前置条件**(ADR:session-merge-approval-hard-gate):调用 `session_merge action=merge` 前**必须**确认当前 session 已通过 reviewer APPROVE(即 approval-store 已有 `session:<sid>` 或 `plan:<plan_id>` 的 APPROVE / APPROVE_WITH_NOTES 记录);若 `runMergeLoop` 报 `block_pause` 提示 approval-store 缺记录,**必须**先按 fallback 约束补写 `review_approval` 再让用户重试 `/merge`,若用户拒绝补写,转告"重派 reviewer / `/merge --force` / 改方案"三选一。
|
|
53
53
|
|
|
54
54
|
**MUST NOT**
|
|
@@ -80,7 +80,7 @@ fallback_models:
|
|
|
80
80
|
| **reviewer 报 REQUEST_CHANGES(loop = 3)** | 转告用户「reviewer 3 次仍 REQUEST_CHANGES」,问「接受 `/merge` / 手动改 / `/discard-session`」三选一 | ❌ 继续派 coder |
|
|
81
81
|
| **reviewer 报 BLOCK** | 转告用户 + 建议派 planner 重设计(带原 plan_id + BLOCK 理由),等用户拍板 | ❌ 派 coder 强行绕过 BLOCK |
|
|
82
82
|
| **review-fix-review 全部通过(APPROVE)** | codeforge orchestrator 自动调 `session_merge action=merge` 完成合入(ADR:codeforge-merge-permission);用户也可通过 `/merge` 命令触发 | ❌ force=true 不告知用户;❌ 派其他 sub-agent 调 session_merge action=merge(会被 guard 拦截) |
|
|
83
|
-
| **runMergeLoop 报 `block_pause`(reviewer 摘要 APPROVE 但 approval-store 无记录,ADR:session-merge-approval-hard-gate)** | 立即按 fallback 补写约束调 `review_approval({verdict:"APPROVE", pendingIds:["session:<sid>"], source:"codeforge-fallback"})`
|
|
83
|
+
| **runMergeLoop 报 `block_pause`(reviewer 摘要 APPROVE 但 approval-store 无记录,ADR:session-merge-approval-hard-gate)** | 立即按 fallback 补写约束调 `review_approval({verdict:"APPROVE", pendingIds:["session:<sid>"], source:"codeforge-fallback"})` 补写(`coveredSha`/`reviewTarget` 工具自动补全,无需手传,ADR:review-approval-auto-covered-sha);补写后请用户重试 `/merge`;若用户拒绝补写,转告"重派 reviewer 确认调 `review_approval` / `/merge --force` 跳过 review / 改方案"三选一 | ❌ 静默忽略 block_pause;❌ 不告知用户原因直接走 force |
|
|
84
84
|
| **coder 回报「PRE 阻断、拒绝启动」** | 转告用户阻断点 + 解除路径,等用户拍板,**不自动派下一棒** | ❌ 自动重派 coder 并强塞 `pre_ack=` |
|
|
85
85
|
| 用户中途插入新需求(原 task 未结束) | 询问用户「先取消 / 等当前完 / 并行」三选一 | ❌ 默默丢弃;❌ 同时派多个不告知 |
|
|
86
86
|
| **可并行任务** | 自动判断依赖,无强依赖时自动并行调度 | ❌ 串行派 N 个独立 task |
|
|
@@ -148,7 +148,7 @@ codeforge 在 session 内维护两个 loop 计数器:
|
|
|
148
148
|
|
|
149
149
|
- `smart_search` / `repo_map` / `read` / `plan_read`:只读上下文准备;**互不依赖时必须并发 emit**
|
|
150
150
|
- `task`:派 subagent(按「难度分级」选变体)
|
|
151
|
-
- `review_approval`:仅用于 **fallback 补写审批记录** —— reviewer 漏调时,codeforge 补写并传 `source: "codeforge-fallback"`
|
|
151
|
+
- `review_approval`:仅用于 **fallback 补写审批记录** —— reviewer 漏调时,codeforge 补写并传 `source: "codeforge-fallback"`(`coveredSha`/`reviewTarget` 工具对 `session:<sid>` id 自动补全,无需手传;ADR:review-approval-auto-covered-sha)
|
|
152
152
|
- `session_merge`:
|
|
153
153
|
- `action=diff stat=true session_id=<id>`:**核查某 session worktree 相对 baseSha 的改动文件列表**(用于校验 coder 写入;优先信任 boomerang 的 diff --stat,仅在 boomerang 缺证据时调)。`stat=false` 返回完整 diff
|
|
154
154
|
- `action=status session_id=<id>`:查 session worktree 绑定状态(worktree_branch 提取失败时的兜底核查)
|
package/bin/codeforge.mjs
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* codeforge — opencode 零侵入扩展包的安装入口
|
|
4
|
+
* ADR:cli-deadcode-cleanup
|
|
4
5
|
*
|
|
5
6
|
* 一行命令安装:
|
|
6
7
|
* npm install -g @andyqiu/codeforge
|
|
7
8
|
*
|
|
8
9
|
* 子命令:
|
|
9
|
-
* install [--dry-run]
|
|
10
|
+
* install [--dry-run]
|
|
10
11
|
* 把 CodeForge 单 bundle 注入到 opencode(写 opencode.json plugin entry)
|
|
11
12
|
* uninstall 卸载(不动 opencode 自身)
|
|
12
13
|
* list 探测 opencode 是否在机器上
|
|
13
14
|
* version 打印版本
|
|
14
|
-
* rollback [--target=<path>] [--dry-run]
|
|
15
|
-
* 恢复到上一次 backup(auto_install 失败时手动救场)
|
|
16
15
|
* runtime where [<path>] 打印当前项目对应的全局运行时目录
|
|
17
16
|
* adr-init [--force] [--dry-run] [--write-prepare] [--no-pre-push]
|
|
18
17
|
* 把 ADR 体系(hooks + scripts + 模板)下发到当前 git 项目
|
|
@@ -25,8 +24,7 @@
|
|
|
25
24
|
*/
|
|
26
25
|
|
|
27
26
|
import { spawnSync } from "node:child_process"
|
|
28
|
-
import {
|
|
29
|
-
import { homedir } from "node:os"
|
|
27
|
+
import { existsSync, readFileSync } from "node:fs"
|
|
30
28
|
import * as path from "node:path"
|
|
31
29
|
import * as url from "node:url"
|
|
32
30
|
|
|
@@ -94,15 +92,12 @@ function parseArgs(argv) {
|
|
|
94
92
|
|
|
95
93
|
// ────────────────────────────────────────────────────────────────────
|
|
96
94
|
// opencode installer:薄壳,统一调 install.mjs(Node ESM 跨平台)
|
|
97
|
-
// ADR:unify-install-to-node-mjs
|
|
98
|
-
//
|
|
99
|
-
// 用于 upgrade 命令:旧版 CLI 调新包时,新包里只有 install.mjs
|
|
95
|
+
// ADR:unify-install-to-node-mjs / ADR:cli-deadcode-cleanup
|
|
96
|
+
// install.sh/.ps1 已合并进 install.mjs,installer 永远走 node。
|
|
100
97
|
// ────────────────────────────────────────────────────────────────────
|
|
101
98
|
function resolveInstallerScript() {
|
|
102
99
|
const mjsScript = path.join(REPO_ROOT, "install.mjs")
|
|
103
|
-
if (existsSync(mjsScript)) return { script: mjsScript
|
|
104
|
-
const shScript = path.join(REPO_ROOT, "install.sh")
|
|
105
|
-
if (existsSync(shScript)) return { script: shScript, useNode: false }
|
|
100
|
+
if (existsSync(mjsScript)) return { script: mjsScript }
|
|
106
101
|
return null
|
|
107
102
|
}
|
|
108
103
|
|
|
@@ -112,24 +107,14 @@ function installOpencode({ scope, dryRun, extraArgs, quiet = false, verbose = fa
|
|
|
112
107
|
err(`installer 脚本不存在:${path.join(REPO_ROOT, "install.mjs")}`)
|
|
113
108
|
return 2
|
|
114
109
|
}
|
|
115
|
-
const { script
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (extraArgs) args.push(...extraArgs)
|
|
124
|
-
} else {
|
|
125
|
-
// fallback:旧版包里只有 install.sh(v0.5 过渡期)
|
|
126
|
-
cmd = "bash"
|
|
127
|
-
args = [script]
|
|
128
|
-
if (scope === "global") args.push("--global")
|
|
129
|
-
if (dryRun) args.push("--dry-run")
|
|
130
|
-
if (extraArgs) args.push(...extraArgs)
|
|
131
|
-
}
|
|
132
|
-
if (!quiet) log(`opencode: ${useNode ? "node" : "bash"} ${args.join(" ")}`)
|
|
110
|
+
const { script } = resolved
|
|
111
|
+
const cmd = process.execPath
|
|
112
|
+
const args = [script]
|
|
113
|
+
if (scope === "global") args.push("--global")
|
|
114
|
+
if (dryRun) args.push("--dry-run")
|
|
115
|
+
if (verbose) args.push("--verbose")
|
|
116
|
+
if (extraArgs) args.push(...extraArgs)
|
|
117
|
+
if (!quiet) log(`opencode: node ${args.join(" ")}`)
|
|
133
118
|
const r = spawnSync(cmd, args, { stdio: quiet ? "pipe" : "inherit", cwd: REPO_ROOT })
|
|
134
119
|
if (quiet && r.status !== 0) {
|
|
135
120
|
if (r.stderr) process.stderr.write(r.stderr)
|
|
@@ -144,15 +129,11 @@ function uninstallOpencode({ scope }) {
|
|
|
144
129
|
err(`installer 脚本不存在:${path.join(REPO_ROOT, "install.mjs")}`)
|
|
145
130
|
return 2
|
|
146
131
|
}
|
|
147
|
-
const { script
|
|
148
|
-
|
|
149
|
-
err("Windows 下旧版 install.sh 不支持,请重新安装:npm install -g @andyqiu/codeforge")
|
|
150
|
-
return 2
|
|
151
|
-
}
|
|
152
|
-
const cmd = useNode ? process.execPath : "bash"
|
|
132
|
+
const { script } = resolved
|
|
133
|
+
const cmd = process.execPath
|
|
153
134
|
const args = [script, "--uninstall"]
|
|
154
135
|
if (scope === "global") args.push("--global")
|
|
155
|
-
log(`opencode uninstall:
|
|
136
|
+
log(`opencode uninstall: node ${args.join(" ")}`)
|
|
156
137
|
const r = spawnSync(cmd, args, { stdio: "inherit", cwd: REPO_ROOT })
|
|
157
138
|
return r.status ?? 1
|
|
158
139
|
}
|
|
@@ -175,7 +156,6 @@ function cmdInstall(args) {
|
|
|
175
156
|
const autoSkipBuild = fromNpm
|
|
176
157
|
const verbose = !!args.flags.verbose
|
|
177
158
|
const extraArgs = []
|
|
178
|
-
if (args.flags["enable-legacy-tools"]) extraArgs.push("--enable-legacy-tools")
|
|
179
159
|
if (args.flags["skip-build"] || autoSkipBuild) extraArgs.push("--skip-build")
|
|
180
160
|
|
|
181
161
|
// npm 场景:dist/index.js 必须已经被 prepack 打好;找不到就直接报错并提示
|
|
@@ -222,80 +202,6 @@ function cmdList() {
|
|
|
222
202
|
return 0
|
|
223
203
|
}
|
|
224
204
|
|
|
225
|
-
// ────────────────────────────────────────────────────────────────────
|
|
226
|
-
// 子命令:rollback(ADR-0047)—— 把最近的 .bak.<ver> 文件恢复回 index.js
|
|
227
|
-
// ────────────────────────────────────────────────────────────────────
|
|
228
|
-
function defaultBundlePath() {
|
|
229
|
-
const candidates = [path.join(homedir(), ".config", "opencode", "codeforge", "index.js")]
|
|
230
|
-
if (process.platform === "win32") {
|
|
231
|
-
if (process.env.APPDATA) candidates.push(path.join(process.env.APPDATA, "opencode", "codeforge", "index.js"))
|
|
232
|
-
if (process.env.LOCALAPPDATA) candidates.push(path.join(process.env.LOCALAPPDATA, "opencode", "codeforge", "index.js"))
|
|
233
|
-
}
|
|
234
|
-
for (const c of candidates) {
|
|
235
|
-
if (existsSync(c)) return c
|
|
236
|
-
}
|
|
237
|
-
return candidates[0]
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
function cmdRollback(args) {
|
|
241
|
-
const target = typeof args.flags.target === "string" ? args.flags.target : defaultBundlePath()
|
|
242
|
-
const dryRun = !!args.flags["dry-run"] || !!args.flags.dryRun
|
|
243
|
-
|
|
244
|
-
log(`CodeForge rollback —— 恢复到上一次 backup`)
|
|
245
|
-
log(` target : ${target}`)
|
|
246
|
-
log(` dry-run : ${dryRun}`)
|
|
247
|
-
hr()
|
|
248
|
-
|
|
249
|
-
const dir = path.dirname(target)
|
|
250
|
-
const base = path.basename(target)
|
|
251
|
-
if (!existsSync(dir)) {
|
|
252
|
-
err(`目标目录不存在:${dir}`)
|
|
253
|
-
err(`提示:先跑 codeforge install 安装一次,自动更新失败时再 rollback。`)
|
|
254
|
-
return 1
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
let entries
|
|
258
|
-
try {
|
|
259
|
-
entries = readdirSync(dir)
|
|
260
|
-
} catch (e) {
|
|
261
|
-
err(`读取目录失败:${e.message}`)
|
|
262
|
-
return 1
|
|
263
|
-
}
|
|
264
|
-
const prefix = `${base}.bak.`
|
|
265
|
-
const backups = entries
|
|
266
|
-
.filter((f) => f.startsWith(prefix))
|
|
267
|
-
.map((f) => {
|
|
268
|
-
const full = path.join(dir, f)
|
|
269
|
-
let mtimeMs = 0
|
|
270
|
-
try { mtimeMs = statSync(full).mtimeMs } catch {}
|
|
271
|
-
return { full, name: f, mtimeMs, ver: f.substring(prefix.length) }
|
|
272
|
-
})
|
|
273
|
-
.sort((a, b) => b.mtimeMs - a.mtimeMs)
|
|
274
|
-
|
|
275
|
-
if (backups.length === 0) {
|
|
276
|
-
err(`找不到任何 backup(pattern: ${prefix}*)`)
|
|
277
|
-
err(`提示:自动更新尚未执行过 / backup 已被清理。可跑 npm install -g @andyqiu/codeforge 重装。`)
|
|
278
|
-
return 1
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
const latest = backups[0]
|
|
282
|
-
log(`找到 ${backups.length} 个 backup,最新:${latest.name}`)
|
|
283
|
-
if (dryRun) {
|
|
284
|
-
ok(`[dry-run] 会执行:cp ${latest.full} → ${target}`)
|
|
285
|
-
return 0
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
try {
|
|
289
|
-
copyFileSync(latest.full, target)
|
|
290
|
-
} catch (e) {
|
|
291
|
-
err(`恢复失败:${e.message}`)
|
|
292
|
-
return 1
|
|
293
|
-
}
|
|
294
|
-
ok(`已恢复 ${latest.name} → ${base}(版本:${latest.ver})`)
|
|
295
|
-
ok(`下次启动 opencode 即可生效`)
|
|
296
|
-
return 0
|
|
297
|
-
}
|
|
298
|
-
|
|
299
205
|
// ────────────────────────────────────────────────────────────────────
|
|
300
206
|
// 子命令:upgrade —— 升级到 npm latest 并重新 install --global
|
|
301
207
|
// ────────────────────────────────────────────────────────────────────
|
|
@@ -366,17 +272,6 @@ async function cmdUpgrade(args) {
|
|
|
366
272
|
return 1
|
|
367
273
|
}
|
|
368
274
|
|
|
369
|
-
// npm install 成功后刷新 opencode plugin 缓存,确保重启后加载新版本
|
|
370
|
-
try {
|
|
371
|
-
const cacheDir = path.join(homedir(), ".cache", "opencode", "packages", "@andyqiu", "teamkit@latest")
|
|
372
|
-
if (existsSync(cacheDir)) {
|
|
373
|
-
rmSync(cacheDir, { recursive: true, force: true })
|
|
374
|
-
log("已清除 opencode plugin 缓存,重启 opencode 后生效")
|
|
375
|
-
}
|
|
376
|
-
} catch {
|
|
377
|
-
warn("清除 opencode 缓存失败(非致命,可手动重启 opencode)")
|
|
378
|
-
}
|
|
379
|
-
|
|
380
275
|
const code = installOpencode({ scope: "global", dryRun: false, extraArgs: ["--skip-build"], quiet: true })
|
|
381
276
|
if (code !== 0) {
|
|
382
277
|
err(`install --global 失败 (exit=${code})`)
|
|
@@ -512,7 +407,6 @@ function cmdHelp() {
|
|
|
512
407
|
codeforge uninstall
|
|
513
408
|
codeforge list
|
|
514
409
|
codeforge version
|
|
515
|
-
codeforge rollback [--target=<path>] [--dry-run] # 恢复最近 backup(auto_install 失败救场)
|
|
516
410
|
codeforge upgrade|update [--dry-run] # 升级到 npm latest 并重新 install --global
|
|
517
411
|
codeforge doctor [--project] # 诊断 CodeForge 安装健康状态(manifest/disk/source 三方比对)
|
|
518
412
|
codeforge runtime where [<path>] # 打印当前项目的全局运行时目录
|
|
@@ -525,8 +419,6 @@ function cmdHelp() {
|
|
|
525
419
|
参数:
|
|
526
420
|
--dry-run 只打印操作,不真改
|
|
527
421
|
--skip-build 跳过 npm run build(已有 dist/index.js 时增量装)
|
|
528
|
-
--enable-legacy-tools 启用旧版 file-based tools(默认禁用,避免 zod 跨实例 bug)
|
|
529
|
-
--target rollback 子命令:指定 index.js 路径(默认自动探测)
|
|
530
422
|
|
|
531
423
|
参数(adr-init 专用):
|
|
532
424
|
--force 覆盖已存在文件(覆盖前自动 .bak.<ts> 备份)
|
|
@@ -573,8 +465,6 @@ async function main() {
|
|
|
573
465
|
case "-v":
|
|
574
466
|
console.log(getVersion())
|
|
575
467
|
return 0
|
|
576
|
-
case "rollback":
|
|
577
|
-
return cmdRollback(args)
|
|
578
468
|
case "upgrade":
|
|
579
469
|
case "update":
|
|
580
470
|
return cmdUpgrade(args)
|
package/codeforge.config.yaml
CHANGED
package/codeforge.json
CHANGED
|
@@ -152,7 +152,7 @@
|
|
|
152
152
|
},
|
|
153
153
|
|
|
154
154
|
"update": {
|
|
155
|
-
"_doc": "自动更新检查(ADR-0047)。opencode 启动时由 plugins/update-checker.ts 后台拉 npm registry latest,auto_install=true 时自动替换 ~/.config/opencode/codeforge/index.js(下次启动生效),失败/离线静默回退到 GitHub Releases
|
|
155
|
+
"_doc": "自动更新检查(ADR-0047)。opencode 启动时由 plugins/update-checker.ts 后台拉 npm registry latest,auto_install=true 时自动替换 ~/.config/opencode/codeforge/index.js(下次启动生效),失败/离线静默回退到 GitHub Releases。手动降级:npm install -g @andyqiu/codeforge@<旧版本>。",
|
|
156
156
|
"auto_check_enabled": true,
|
|
157
157
|
"interval_hours": 6,
|
|
158
158
|
"package": "@andyqiu/codeforge",
|