@andyqiu/codeforge 0.4.0 → 0.5.1

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.
@@ -66,12 +66,13 @@ fallback_models:
66
66
  | 场景 | 该做什么 | MUST NOT |
67
67
  |---|---|---|
68
68
  | 用户问简单问题 / 寻求解释 / 对比方案讨论(≤ 800 字能答完) | **自己直接答**,不派任何 agent | ❌ 派 planner 或 coder |
69
- | **小改动 short-circuit**:用户指明确切文件位置 + 改动 ≤ 1 文件 + 估算 < 5 行 + 用户已给出修改思路 | 跳过 planner,**直接派 coder**,prompt 自包含改动需求(无 plan_id 路径) | ❌ 派 planner 再让它派 coder |
69
+ | **小改动 short-circuit**:用户指明确切文件位置 + 改动 ≤ 1 文件 + 估算 < 5 行 + 用户已给出修改思路 | 跳过 planner,**直接派 coder**,prompt 自包含改动需求(无 plan_id 路径);coder 完成后同样走「coder 回报正常完成」行的自动 reviewer 流程 | ❌ 派 planner 再让它派 coder |
70
70
  | 复杂多步任务(含设计 / 涉及多文件 / 不确定改哪 / 需要查历史经验) | **派 planner 出方案** → 等 planner 回 boomerang(含 `plan_id: plan-xxx`)→ **自动派 reviewer 审方案**(`review_target=plan_only` + plan_id):APPROVE → 派 coder 执行(带 plan_id + sessionId);REQUEST_CHANGES (`plan_review_loop_count` < 3) → 自动派 planner 改方案,loop +1;REQUEST_CHANGES (loop = 3) → 转告用户三选一;BLOCK → 转告用户 + 建议派 planner 重设计 | ❌ 派完 planner 直接派 coder;❌ 绕过方案 BLOCK |
71
71
  | **决策 review(Q3-a 范围)** | 先派 reviewer 审决策合理性(`review_target=decision_only`):APPROVE → 按决策派;REQUEST_CHANGES → reviewer 给替代方案,loop 1 次后转告用户;BLOCK → 转告用户 | ❌ 审用户业务选择本身;❌ 把所有 question 都套 review |
72
72
  | 用户要"独立交付物" | 派 coder 子 session 写并直接落到 session worktree;prompt 明示"final response 不要粘回长内容" | ❌ 自己在父对话吐长文档 |
73
73
  | 用户要查项目结构 / 历史经验 | 自己调 `smart_search` / `repo_map` / `read` / `plan_read` | ❌ 为此派 subagent |
74
- | **subagent 回报正常完成** | 拿到 boomerang 摘要后决定下一棒:**询问用户是否 `/merge`** / 派 reviewer / 派下个 phase / 收尾 | ❌ 重新审查 subagent 的代码;❌ 默认 subagent 会自派下一棒 |
74
+ | **coder 回报正常完成** | 自动派 reviewer 审代码改动(`review_target` 按文件类型自动检测);后续按下方「reviewer 报三档」独立行处理 | ❌ 跳过 reviewer 直接问用户是否 merge;❌ 重新审查代码 |
75
+ | **其他 subagent 回报正常完成**(planner / reviewer 等) | 按上下文决定下一棒:派 reviewer / 派下个 phase / 收尾 | ❌ 默认 subagent 会自派下一棒 |
75
76
  | **subagent 报错 / 中断 / 摘要为空** | **立刻停下**,把错误首行原文 + 子 session id 转告用户,问「重试 / 改方案 / 跳过」三选一 | ❌ 盲目"再派一次试试" |
76
77
  | **subagent 长时间无回报** | 提醒用户「子 session 仍在跑,按 `Ctrl+→` 可切过去看进度」 | ❌ 主动 Esc 取消;❌ 重派 |
77
78
  | **reviewer 报 REQUEST_CHANGES(代码 review,`code_review_loop_count` < 3)** | **自动派 coder 修**(带具体到行的 reviewer 意见 + 原 plan_id + sessionId),loop +1 | ❌ 同时派多个 coder;❌ 不带 reviewer 意见 |
package/bin/codeforge.mjs CHANGED
@@ -70,7 +70,10 @@ function getVersion() {
70
70
  // 上下文;process.argv[1] 保留用户实际调用路径,不会被解析。
71
71
  function isFromNpm() {
72
72
  const entry = process.argv[1] ?? ""
73
- return entry.includes(`${path.sep}node_modules${path.sep}`)
73
+ if (entry.includes(`${path.sep}node_modules${path.sep}`)) return true
74
+ // fallback:npm 包里没有 build-with-version.mjs(只有 dist/),检测它不存在即为 npm 场景
75
+ const buildScript = path.join(REPO_ROOT, "scripts", "build-with-version.mjs")
76
+ return !existsSync(buildScript)
74
77
  }
75
78
 
76
79
  // ────────────────────────────────────────────────────────────────────
@@ -0,0 +1,62 @@
1
+ ---
2
+ description: 查看当前 session worktree 的改动(文件列表+统计,或完整 diff)
3
+ ---
4
+
5
+ <!--
6
+ codeforge 元数据(opencode 不读,由 plugins / workflow-engine 解析):
7
+ name: diff
8
+ version: 1.0.0
9
+ requires_human_approval: false
10
+ 说明:底层调用 session_merge 工具的 action=diff
11
+ 默认显示改动文件列表 + 统计(stat=true)
12
+ --full 显示完整 diff 内容(stat=false)
13
+ 没有 worktree 时友好提示
14
+ -->
15
+
16
+
17
+ # /diff — 查看当前 session worktree 改动
18
+
19
+ **ADR: worktree-session-isolation Phase 3**
20
+
21
+ 快速查看当前 session worktree 中的代码改动,无需触发 merge 或 review 流程。
22
+
23
+ ## 输入
24
+
25
+ 用户参数:$ARGUMENTS
26
+
27
+ ## 用法
28
+
29
+ ```
30
+ /diff # 显示改动文件列表 + 统计(git diff --stat 风格)
31
+ /diff --full # 显示完整 diff 内容
32
+ ```
33
+
34
+ ## 行为说明
35
+
36
+ | 模式 | 触发条件 | 输出内容 |
37
+ |------|----------|----------|
38
+ | **stat 模式**(默认) | `/diff` | 改动文件列表 + 增删行数统计 |
39
+ | **full 模式** | `/diff --full` | 完整 diff 内容(含每行改动) |
40
+ | **无 worktree** | session 无写操作 | 友好提示:当前 session 没有写操作,无 worktree 改动 |
41
+
42
+ ## 底层调用
43
+
44
+ ```
45
+ /diff → session_merge(action="diff", stat=true)
46
+ /diff --full → session_merge(action="diff", stat=false)
47
+ ```
48
+
49
+ ## 何时使用
50
+
51
+ - 在 `/merge` 之前确认改动范围
52
+ - 向用户或 reviewer 展示本次 session 做了什么
53
+ - 调试时快速确认某文件是否已被修改
54
+
55
+ ## 与其他命令的关系
56
+
57
+ | 命令 | 用途 |
58
+ |------|------|
59
+ | `/diff` | 本命令:只读查看 worktree 改动,不触发任何合并 |
60
+ | `/merge` | 触发 review-fix-review 审批闭环后 squash merge 入主仓 |
61
+ | `/discard-session` | 放弃当前 session 所有改动(不可恢复) |
62
+ | `session_merge action=status` | 查看 worktree 绑定状态(非 diff 内容) |
package/dist/index.js CHANGED
@@ -13763,6 +13763,7 @@ var description26 = [
13763
13763
  "- action=status:查询当前 session worktree 状态(任意 agent 可调)",
13764
13764
  "- action=merge:codeforge orchestrator 在用户触发 /merge 后调(**subagent 禁止**)",
13765
13765
  "- action=discard:用户决定放弃当前 session 改动时调",
13766
+ "- action=diff:查看当前 session worktree 相对主仓(baseSha)的改动;stat=true 只看文件列表",
13766
13767
  "**注意**:",
13767
13768
  "- merge 走 review-fix-review 闭环(默认 3 轮);force=true 跳过 review 直接 squash",
13768
13769
  "- subagent (coder/reviewer/planner) 调 action=merge 会被 Phase 2 guard plugin 拒绝",
@@ -13784,6 +13785,11 @@ var ArgsSchema26 = z27.discriminatedUnion("action", [
13784
13785
  z27.object({
13785
13786
  action: z27.literal("discard"),
13786
13787
  session_id: z27.string().optional().describe("默认当前 session")
13788
+ }),
13789
+ z27.object({
13790
+ action: z27.literal("diff"),
13791
+ session_id: z27.string().optional().describe("默认当前 session"),
13792
+ stat: z27.boolean().optional().describe("true=只显示文件列表+统计,false=完整 diff(默认 false)")
13787
13793
  })
13788
13794
  ]);
13789
13795
  var _ctx = {};
@@ -13833,6 +13839,63 @@ async function execute26(input) {
13833
13839
  data: { discarded: true, sessionId }
13834
13840
  };
13835
13841
  }
13842
+ if (args.action === "diff") {
13843
+ const entry = await getSessionWorktree(sessionId, mainRoot);
13844
+ if (!entry) {
13845
+ return { ok: false, action: "diff", error: `session ${sessionId} 没有绑定 worktree` };
13846
+ }
13847
+ const { execFile: execFile4 } = await import("node:child_process");
13848
+ const { promisify: promisify2 } = await import("node:util");
13849
+ const execFileAsync = promisify2(execFile4);
13850
+ const diffArgs = args.stat ? ["diff", "--stat", entry.baseSha] : ["diff", entry.baseSha];
13851
+ let diffOutput = "";
13852
+ let changedFiles = [];
13853
+ try {
13854
+ const { stdout } = await execFileAsync("git", diffArgs, {
13855
+ cwd: entry.worktreePath,
13856
+ maxBuffer: 5242880
13857
+ });
13858
+ diffOutput = stdout;
13859
+ const { stdout: nameOnly } = await execFileAsync("git", ["diff", "--name-only", entry.baseSha], { cwd: entry.worktreePath, maxBuffer: 1048576 });
13860
+ changedFiles = nameOnly.trim().split(`
13861
+ `).filter(Boolean);
13862
+ } catch (e) {
13863
+ const msg = e instanceof Error ? e.message : String(e);
13864
+ if (!msg.includes("unknown revision") && !msg.includes("bad object")) {
13865
+ return { ok: false, action: "diff", error: `git diff 失败: ${msg}` };
13866
+ }
13867
+ try {
13868
+ const fallbackArgs = args.stat ? ["diff", "--stat", "HEAD"] : ["diff", "HEAD"];
13869
+ const { stdout } = await execFileAsync("git", fallbackArgs, {
13870
+ cwd: entry.worktreePath,
13871
+ maxBuffer: 5242880
13872
+ });
13873
+ diffOutput = stdout;
13874
+ const { stdout: nameOnly } = await execFileAsync("git", ["diff", "--name-only", "HEAD"], { cwd: entry.worktreePath, maxBuffer: 1048576 });
13875
+ changedFiles = nameOnly.trim().split(`
13876
+ `).filter(Boolean);
13877
+ } catch (e2) {
13878
+ return {
13879
+ ok: false,
13880
+ action: "diff",
13881
+ error: `git diff 失败: ${e2 instanceof Error ? e2.message : String(e2)}`
13882
+ };
13883
+ }
13884
+ }
13885
+ return {
13886
+ ok: true,
13887
+ action: "diff",
13888
+ data: {
13889
+ sessionId,
13890
+ worktreePath: entry.worktreePath,
13891
+ branch: entry.branch,
13892
+ baseSha: entry.baseSha,
13893
+ changedFiles,
13894
+ fileCount: changedFiles.length,
13895
+ diff: diffOutput
13896
+ }
13897
+ };
13898
+ }
13836
13899
  if (!_ctx.spawner) {
13837
13900
  return {
13838
13901
  ok: false,
@@ -13840,11 +13903,12 @@ async function execute26(input) {
13840
13903
  error: "session_merge: SubagentSpawner 未注入(Phase 1 runtime 暂未接 wire-up;" + "Phase 3 commands/merge.md 落地后会通过 __setContext 注入)"
13841
13904
  };
13842
13905
  }
13906
+ const mergeArgs = args;
13843
13907
  const result = await runMergeLoop({
13844
13908
  sessionId,
13845
13909
  mainRoot,
13846
- ...args.plan_id ? { planId: args.plan_id } : {},
13847
- ...args.force ? { force: true } : {},
13910
+ ...mergeArgs.plan_id ? { planId: mergeArgs.plan_id } : {},
13911
+ ...mergeArgs.force ? { force: true } : {},
13848
13912
  spawner: _ctx.spawner
13849
13913
  });
13850
13914
  return { ok: true, action: "merge", data: result };
@@ -20596,7 +20660,7 @@ import * as zlib from "node:zlib";
20596
20660
  // lib/version-injected.ts
20597
20661
  function getInjectedVersion() {
20598
20662
  try {
20599
- const v = "0.4.0";
20663
+ const v = "0.5.1";
20600
20664
  if (typeof v === "string" && /^\d+\.\d+\.\d+/.test(v)) {
20601
20665
  return v;
20602
20666
  }