@andyqiu/codeforge 0.5.28 → 0.6.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/install.sh DELETED
@@ -1,726 +0,0 @@
1
- #!/usr/bin/env bash
2
- # ============================================================================
3
- # CodeForge install.sh — 零侵入安装到 opencode (v0.1+ 单 bundle 架构)
4
- #
5
- # CodeForge 改为 OMO 风格的单 plugin bundle 架构:
6
- # 1. 17 个能力被 bun build 编译成 dist/index.js 一个 ESM bundle
7
- # 2. 在 opencode.json 里只占 1 行 plugin entry
8
- # 3. 永久避免 opencode 1.14+ 早期版本的 zod 跨实例 bug(issue #12336/#21155)
9
- #
10
- # 行为:
11
- # 1. 检测 opencode CLI / KH MCP
12
- # 2. 构建 dist/index.js(除非 --skip-build)
13
- # 3. 把 dist/index.js 复制到 ~/.config/opencode/codeforge/index.js
14
- # 4. 在 ~/.config/opencode/opencode.json 的 "plugin" 数组追加 file:// URL
15
- # 5. 把 agents/commands 用 file-by-file copy 注入(带白名单)
16
- # 6. 把 workflows/context-templates/review-profiles 整目录拷贝
17
- # 7. 智能合并 AGENTS.md:
18
- # - 项目模式 + 已有 AGENTS.md → 替换 <!-- knowledge-hub:start -->...<!-- knowledge-hub:end --> 块(带 .bak 备份)
19
- # - 项目模式 + 无 AGENTS.md → 生成含 marker 块的短版骨架
20
- # - 全局模式 → 跳过(context-templates 已经放进 ~/.config/opencode/context-templates/ 给所有项目复用)
21
- # 8. --global 时生成 ~/.config/codeforge/kh.json 模板(不含 token,硬约束 #2)
22
- # 9. 输出验证清单
23
- #
24
- # 用法:
25
- # ./install.sh # 项目级(默认)
26
- # ./install.sh --global # 全局
27
- # ./install.sh --uninstall # 卸载
28
- # ./install.sh --dry-run # 仅打印操作,不执行
29
- # ./install.sh --skip-build # 跳过 npm run build
30
- # ============================================================================
31
-
32
- set -euo pipefail
33
-
34
- # ────────────── 颜色 ──────────────
35
- if [[ -t 1 ]]; then
36
- C_RESET='\033[0m'; C_BOLD='\033[1m'
37
- C_RED='\033[31m'; C_GREEN='\033[32m'; C_YELLOW='\033[33m'; C_BLUE='\033[34m'; C_CYAN='\033[36m'
38
- else
39
- C_RESET=''; C_BOLD=''; C_RED=''; C_GREEN=''; C_YELLOW=''; C_BLUE=''; C_CYAN=''
40
- fi
41
-
42
- log() { printf '%b\n' "${C_CYAN}[codeforge]${C_RESET} $*"; }
43
- ok() { printf '%b\n' "${C_GREEN}✓${C_RESET} $*"; }
44
- warn() { printf '%b\n' "${C_YELLOW}⚠${C_RESET} $*"; }
45
- err() { printf '%b\n' "${C_RED}✗${C_RESET} $*" >&2; }
46
- hr() { printf '%b\n' "${C_BOLD}────────────────────────────────────────────────${C_RESET}"; }
47
-
48
- # ────────────── ADR-0050: WSL 路径错位拒绝(先于参数解析,用原始 $* 检测 --uninstall) ──────────────
49
- # 防止在 WSL 中 $HOME 落在 /mnt/c|d|e/Users/ 导致 codeforge 配置写到
50
- # Windows 文件系统(NTFS 性能差、权限模型不同、git/symlink 行为异常)。
51
- #
52
- # 触发条件:
53
- # - 系统真存在 wslpath 命令(WSL 环境标志),或
54
- # - 测试旁路 CODEFORGE_FORCE_WSL_CHECK=1(CI/单测中强制走分支)
55
- #
56
- # 用户逃生口:
57
- # - CODEFORGE_ALLOW_WSL_WINDOWS_HOME=1 → 强制继续(不推荐)
58
- # - --uninstall 参数 → guard 整段跳过(用户可能就是要从错位位置清理掉,
59
- # ADR-0050 / reviewer Phase 3 #3:卸载场景必须能跑通)
60
- #
61
- # 这里用 `$*` 原始参数串做模式匹配,**不依赖**下方 while 循环解析出的 ACTION
62
- # 变量,因为本段必须在参数解析之前执行(错位路径下连参数解析的 set -u 都可能
63
- # 因为环境异常而炸,guard 必须最早跑)。
64
- if [[ " $* " != *" --uninstall "* ]]; then
65
- if command -v wslpath >/dev/null 2>&1 || [[ "${CODEFORGE_FORCE_WSL_CHECK:-}" == "1" ]]; then
66
- case "$HOME" in
67
- /mnt/c/*|/mnt/d/*|/mnt/e/*)
68
- err "在 WSL 中检测到 \$HOME=$HOME 落在 Windows 挂载路径下"
69
- err "这会导致 codeforge 配置写到 Windows 文件系统,引发权限 / 性能 / 路径转换问题"
70
- err "请改用 WSL 原生 home(通常是 /home/<user>/),或在 /etc/wsl.conf 中显式设置 default=<linux-user>"
71
- err "如确实需要继续,设置环境变量 CODEFORGE_ALLOW_WSL_WINDOWS_HOME=1 跳过检查(不推荐)"
72
- err "如果你是想卸载之前装错位置的 codeforge,加 --uninstall 参数会跳过本检查"
73
- if [[ "${CODEFORGE_ALLOW_WSL_WINDOWS_HOME:-}" != "1" ]]; then
74
- exit 1
75
- fi
76
- warn "已通过 CODEFORGE_ALLOW_WSL_WINDOWS_HOME=1 跳过 WSL 路径检查(请确保你清楚后果)"
77
- ;;
78
- esac
79
- fi
80
- fi
81
-
82
- # ────────────── 参数 ──────────────
83
- MODE="project"
84
- ACTION="install"
85
- DRY_RUN=0
86
- SKIP_BUILD=0
87
-
88
- while [[ $# -gt 0 ]]; do
89
- case "$1" in
90
- --global) MODE="global" ;;
91
- --project) MODE="project" ;;
92
- --uninstall) ACTION="uninstall" ;;
93
- --dry-run) DRY_RUN=1 ;;
94
- --skip-build) SKIP_BUILD=1 ;;
95
- -h|--help)
96
- sed -n '3,30p' "$0"
97
- exit 0
98
- ;;
99
- *) err "未知参数:$1"; exit 2 ;;
100
- esac
101
- shift
102
- done
103
-
104
-
105
- # ────────────── 路径解析 ──────────────
106
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
107
- SOURCE_ROOT="$SCRIPT_DIR"
108
-
109
- if [[ "$MODE" == "global" ]]; then
110
- TARGET_ROOT="${XDG_CONFIG_HOME:-$HOME/.config}/opencode"
111
- else
112
- TARGET_ROOT="$(pwd)/.opencode"
113
- fi
114
-
115
- # CodeForge 自身的全局配置目录(与 opencode 配置目录同级)
116
- CODEFORGE_CFG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/codeforge"
117
-
118
- # v0.1 之前装的目录(卸载时一并清掉)
119
- LEGACY_DIRS=(agent command tool tools plugin plugins lib)
120
- # v0.1+ 才有的目录(review-profiles 由 ADR:reviewer-multi-profile 引入)
121
- # agent-templates 由 ADR:agent-templates-move-to-root 移到根目录(supersedes agent-templates-distribution)
122
- MANAGED_DIRS=(codeforge agents commands workflows context-templates review-profiles agent-templates)
123
-
124
- BUNDLE_SRC_REL="dist/index.js"
125
- BUNDLE_DST_REL="codeforge/index.js"
126
-
127
- # 文件级 copy 白名单(.md,排除 README/_*/.bak/.*)
128
- MD_COPY_DIRS=("agents:agents" "commands:commands")
129
- # 整目录拷贝(review-profiles 由 ADR:reviewer-multi-profile 引入,避开 maxdepth 1 限制)
130
- # ADR:agent-templates-move-to-root — agent-templates 随 install 分发到全局目录
131
- COPY_DIRS=("workflows:workflows" "context-templates:context-templates" "review-profiles:review-profiles" "agent-templates:agent-templates")
132
-
133
- # KH 行为规范模板(B3 智能合并的输入)
134
- KH_TEMPLATE_REL="context-templates/kh-instructions.md"
135
-
136
- # ────────────── 工具函数 ──────────────
137
- run() {
138
- if [[ $DRY_RUN -eq 1 ]]; then
139
- printf " ${C_BLUE}[dry-run]${C_RESET} %s\n" "$*"
140
- else
141
- eval "$@"
142
- fi
143
- }
144
-
145
- ensure_dir() {
146
- if [[ ! -d "$1" ]]; then
147
- run "mkdir -p '$1'"
148
- fi
149
- }
150
-
151
- # 计算 plugin 入口的 file:// URL
152
- plugin_uri() {
153
- local abs="$TARGET_ROOT/$BUNDLE_DST_REL"
154
- echo "file://$abs"
155
- }
156
-
157
- opencode_cfg_path() {
158
- echo "$TARGET_ROOT/opencode.json"
159
- }
160
-
161
- # 配置默认 agent → codeforge(ADR-0056 D5 / D2 实施细节)
162
- #
163
- # 在 ~/.config/opencode/opencode.json 顶层幂等合并 "default_agent": "codeforge"。
164
- # 行为规则:
165
- # - 字段不存在 → 写入 codeforge
166
- # - 字段已是 codeforge → unchanged
167
- # - 字段为其他值(用户自配) → 跳过不动 + 打 warn(绝不覆盖用户配置)
168
- # 仅在 MODE=user/global 调用(项目级配置由用户自己管,不动)。
169
- # 失败不阻塞 install 主流程(warn + return 0)。
170
- # 用 node 处理 JSON(避免 sed/awk 处理 JSON 转义边界)。
171
- configure_default_agent() {
172
- local opencode_json="${XDG_CONFIG_HOME:-$HOME/.config}/opencode/opencode.json"
173
- local target_agent="codeforge"
174
-
175
- if [[ ! -f "$opencode_json" ]]; then
176
- warn "opencode.json 不存在,跳过 default_agent 配置(首次安装 opencode 后请手动重跑 codeforge install)"
177
- return 0
178
- fi
179
-
180
- if ! command -v node >/dev/null 2>&1; then
181
- warn "未找到 node,跳过 default_agent 配置(请安装 Node.js >= 20 后重跑 install)"
182
- return 0
183
- fi
184
-
185
- if [[ $DRY_RUN -eq 1 ]]; then
186
- printf " ${C_BLUE}[dry-run]${C_RESET} configure default_agent=%s in %s\n" "$target_agent" "$opencode_json"
187
- return 0
188
- fi
189
-
190
- local result
191
- result=$(CFG="$opencode_json" TARGET="$target_agent" node -e '
192
- const fs = require("node:fs");
193
- const path = process.env.CFG;
194
- const target = process.env.TARGET;
195
- let cfg;
196
- try { cfg = JSON.parse(fs.readFileSync(path, "utf8")); }
197
- catch (e) { console.log("ERROR: " + e.message); process.exit(0); }
198
- const current = cfg.default_agent;
199
- if (current === undefined) {
200
- cfg.default_agent = target;
201
- fs.writeFileSync(path, JSON.stringify(cfg, null, 2) + "\n", "utf8");
202
- console.log("SET default_agent=" + target);
203
- } else if (current === target) {
204
- console.log("UNCHANGED (already " + target + ")");
205
- } else {
206
- console.log("SKIPPED: user has default_agent=\"" + current + "\", not touching");
207
- }
208
- ' 2>&1) || {
209
- warn "default_agent 配置失败(不阻塞 install 主流程)"
210
- return 0
211
- }
212
- ok "default_agent: $result"
213
- }
214
-
215
- # 卸载时还原 default_agent(仅当当前值是 codeforge 时移除字段)。
216
- # 用户改成其他值的不动。
217
- restore_default_agent() {
218
- local opencode_json="${XDG_CONFIG_HOME:-$HOME/.config}/opencode/opencode.json"
219
- if [[ ! -f "$opencode_json" ]]; then
220
- return 0
221
- fi
222
- if ! command -v node >/dev/null 2>&1; then
223
- warn "未找到 node,跳过 default_agent 卸载还原"
224
- return 0
225
- fi
226
- if [[ $DRY_RUN -eq 1 ]]; then
227
- printf " ${C_BLUE}[dry-run]${C_RESET} restore default_agent in %s (remove if codeforge)\n" "$opencode_json"
228
- return 0
229
- fi
230
-
231
- local result
232
- result=$(CFG="$opencode_json" node -e '
233
- const fs = require("node:fs");
234
- const path = process.env.CFG;
235
- let cfg;
236
- try { cfg = JSON.parse(fs.readFileSync(path, "utf8")); }
237
- catch { process.exit(0); }
238
- if (cfg.default_agent === "codeforge") {
239
- delete cfg.default_agent;
240
- fs.writeFileSync(path, JSON.stringify(cfg, null, 2) + "\n", "utf8");
241
- console.log("REMOVED default_agent (was codeforge)");
242
- } else if (cfg.default_agent !== undefined) {
243
- console.log("SKIPPED: default_agent=\"" + cfg.default_agent + "\" not set by codeforge");
244
- } else {
245
- console.log("NOOP (no default_agent)");
246
- }
247
- ' 2>&1) || return 0
248
- ok "default_agent: $result"
249
- }
250
-
251
- # 写 ~/.config/codeforge/kh.json 模板(仅 --global 调用)
252
- #
253
- # 硬约束 #2:API key 绝不能落盘,模板里**不写** token / apiKey 字段。
254
- # 用户必须通过环境变量 KNOWLEDGE_API_KEY 提供。
255
- # 已存在则跳过,避免覆盖用户自定义。
256
- write_kh_template() {
257
- ensure_dir "$CODEFORGE_CFG_DIR"
258
- local kh_file="$CODEFORGE_CFG_DIR/kh.json"
259
- if [[ -f "$kh_file" ]]; then
260
- ok "已存在 ${kh_file},跳过覆盖"
261
- return 0
262
- fi
263
- if [[ $DRY_RUN -eq 1 ]]; then
264
- printf " ${C_BLUE}[dry-run]${C_RESET} write %s (no token)\n" "$kh_file"
265
- return 0
266
- fi
267
- cat > "$kh_file" <<'EOF'
268
- {
269
- "url": "http://10.5.60.26:8900/mcp",
270
- "timeoutMs": 5000,
271
- "maxRetries": 1
272
- }
273
- EOF
274
- ok "已生成 ${kh_file}(API key 请通过环境变量 KNOWLEDGE_API_KEY 提供,禁止写入文件)"
275
- }
276
-
277
- # 智能合并项目根 AGENTS.md:
278
- # - 已有 AGENTS.md → 替换 <!-- knowledge-hub:start -->...<!-- knowledge-hub:end --> 块(带 .bak 备份)
279
- # - 不存在 AGENTS.md → 生成含 marker 块的短版骨架
280
- #
281
- # 实现委托给 scripts/merge-agents-md.mjs CLI(C.4 单一入口),
282
- # 算法源自 lib/agents-merge.ts,sh/ps1 共用同一份 Node 实现,
283
- # 避免 install 脚本各自维护算法副本造成漂移。
284
- merge_project_agents_md() {
285
- local agents_target="$1"
286
- local template_path="$2"
287
- local cli_path="$SOURCE_ROOT/scripts/merge-agents-md.mjs"
288
- if [[ ! -f "$template_path" ]]; then
289
- warn "模板不存在,跳过 AGENTS.md 合并: $template_path"
290
- return 0
291
- fi
292
- if [[ ! -f "$cli_path" ]]; then
293
- warn "merge CLI 不存在,跳过 AGENTS.md 智能合并: $cli_path"
294
- return 0
295
- fi
296
- if ! command -v node >/dev/null 2>&1; then
297
- warn "未找到 node,跳过 AGENTS.md 智能合并(请安装 Node.js >= 20)"
298
- return 0
299
- fi
300
- local cli_args=(--target "$agents_target" --template "$template_path")
301
- if [[ $DRY_RUN -eq 1 ]]; then
302
- cli_args+=(--dry-run)
303
- fi
304
- node "$cli_path" "${cli_args[@]}" || {
305
- warn "AGENTS.md 合并失败(不阻塞 install 主流程)"
306
- return 0
307
- }
308
- }
309
-
310
- # 用 node 重写 opencode.json 的 plugin 数组(追加自己 + 去掉所有 codeforge legacy entry)
311
- write_plugin_entry() {
312
- local cfg; cfg="$(opencode_cfg_path)"
313
- local uri; uri="$(plugin_uri)"
314
-
315
- if ! command -v node >/dev/null 2>&1; then
316
- err "需要 node 才能改写 opencode.json,请安装 Node.js >= 20"
317
- return 1
318
- fi
319
-
320
- ensure_dir "$TARGET_ROOT"
321
-
322
- if [[ $DRY_RUN -eq 1 ]]; then
323
- printf " ${C_BLUE}[dry-run]${C_RESET} write %s plugin entry: %s\n" "$cfg" "$uri"
324
- return 0
325
- fi
326
-
327
- CFG="$cfg" URI="$uri" node -e '
328
- const fs = require("node:fs");
329
- const path = process.env.CFG;
330
- const uri = process.env.URI;
331
- let cfg = {};
332
- if (fs.existsSync(path)) {
333
- try { cfg = JSON.parse(fs.readFileSync(path, "utf8")); }
334
- catch { fs.copyFileSync(path, path + ".bak." + Date.now()); cfg = {}; }
335
- }
336
- if (!cfg.$schema) cfg.$schema = "https://opencode.ai/config.json";
337
- if (!Array.isArray(cfg.plugin)) cfg.plugin = [];
338
- const cleaned = [];
339
- for (const e of cfg.plugin) {
340
- const s = String(e);
341
- if (/\/codeforge\/index\.js$/.test(s)) continue;
342
- if (/\/plugins\/[^/]+\.ts$/.test(s) && /opencode/.test(s)) continue;
343
- if (/\/\.opencode\/plugins\//.test(s)) continue;
344
- cleaned.push(e);
345
- }
346
- cleaned.push(uri);
347
- cfg.plugin = cleaned;
348
- fs.writeFileSync(path, JSON.stringify(cfg, null, 2) + "\n", "utf8");
349
- '
350
- ok "opencode.json 已写入 plugin entry: $uri"
351
- }
352
-
353
- remove_plugin_entry() {
354
- local cfg; cfg="$(opencode_cfg_path)"
355
- if [[ ! -f "$cfg" ]]; then return 0; fi
356
- if ! command -v node >/dev/null 2>&1; then
357
- warn "未找到 node,跳过 opencode.json plugin entry 清理"
358
- return 0
359
- fi
360
-
361
- if [[ $DRY_RUN -eq 1 ]]; then
362
- printf " ${C_BLUE}[dry-run]${C_RESET} rewrite %s without codeforge plugin entry\n" "$cfg"
363
- return 0
364
- fi
365
-
366
- CFG="$cfg" node -e '
367
- const fs = require("node:fs");
368
- const path = process.env.CFG;
369
- let cfg;
370
- try { cfg = JSON.parse(fs.readFileSync(path, "utf8")); }
371
- catch { return; }
372
- if (!Array.isArray(cfg.plugin)) return;
373
- cfg.plugin = cfg.plugin.filter(e => {
374
- const s = String(e);
375
- if (/\/codeforge\/index\.js$/.test(s)) return false;
376
- if (/\/plugins\/[^/]+\.ts$/.test(s)) return false;
377
- if (/\/\.opencode\/plugins\//.test(s)) return false;
378
- return true;
379
- });
380
- fs.writeFileSync(path, JSON.stringify(cfg, null, 2) + "\n", "utf8");
381
- '
382
- ok "opencode.json 已移除 codeforge plugin entry"
383
- }
384
-
385
- # 卸载
386
- uninstall() {
387
- log "卸载 CodeForge from: $TARGET_ROOT"
388
- remove_plugin_entry
389
- # 还原 default_agent(仅 user/global mode;project mode 不动用户级配置)
390
- if [[ "$MODE" != "project" ]]; then
391
- restore_default_agent
392
- fi
393
- for name in "${LEGACY_DIRS[@]}" "${MANAGED_DIRS[@]}"; do
394
- local p="$TARGET_ROOT/$name"
395
- if [[ -e "$p" || -L "$p" ]]; then
396
- run "rm -rf '$p'"
397
- ok "已删除 $p"
398
- fi
399
- done
400
- hr
401
- # 细粒度删除 skills:只删 CodeForge 自己的 skill,不删用户自装的(如 frontend-design)
402
- OWNED_SKILLS=(ambiguity-gate devils-advocate ears-zh example-mapping success-criteria weighted-dimensions)
403
- for skill_name in "${OWNED_SKILLS[@]}"; do
404
- local p="$TARGET_ROOT/skills/$skill_name"
405
- if [[ -e "$p" || -L "$p" ]]; then
406
- run "rm -rf '$p'"
407
- ok "已删除 skill: $p"
408
- fi
409
- done
410
- ok "卸载完成(opencode 自身和你的 AGENTS.md / ~/.config/codeforge/kh.json 不会被动)"
411
- }
412
-
413
- # 检测
414
- detect_opencode() {
415
- if command -v opencode >/dev/null 2>&1; then
416
- local v; v="$(opencode --version 2>/dev/null || echo "unknown")"
417
- ok "检测到 opencode: $v"
418
- else
419
- warn "未检测到 opencode CLI"
420
- warn " 安装方式:https://github.com/sst/opencode#installation"
421
- fi
422
- }
423
-
424
- detect_kh_mcp() {
425
- local cfg="${XDG_CONFIG_HOME:-$HOME/.config}/opencode/opencode.json"
426
- if [[ -f "$cfg" ]] && grep -q "knowledge-hub\|code-forge-knowledge-hub" "$cfg" 2>/dev/null; then
427
- ok "检测到 Knowledge Hub MCP 已注册"
428
- else
429
- warn "未在 $cfg 中找到 Knowledge Hub MCP"
430
- warn " 配置示例见 docs/PRD.md §6.2"
431
- fi
432
- }
433
-
434
- # ────────────── 主流程 ──────────────
435
- hr
436
- log "${C_BOLD}CodeForge installer${C_RESET} (mode=$MODE, action=$ACTION, dry-run=$DRY_RUN)"
437
- hr
438
- log "Source : $SOURCE_ROOT"
439
- log "Target : $TARGET_ROOT"
440
- hr
441
-
442
- if [[ "$ACTION" == "uninstall" ]]; then
443
- uninstall
444
- exit 0
445
- fi
446
-
447
- # Step 1/8: 环境检测
448
- log "Step 1/8: 环境检测"
449
- detect_opencode
450
- detect_kh_mcp
451
-
452
- # Step 2/8: build dist bundle
453
- log "Step 2/8: 构建 dist/index.js 单 bundle"
454
- BUNDLE_SRC="$SOURCE_ROOT/$BUNDLE_SRC_REL"
455
- if [[ $SKIP_BUILD -eq 1 ]]; then
456
- warn "已跳过 build(--skip-build),使用现有 dist/index.js"
457
- else
458
- if [[ $DRY_RUN -eq 1 ]]; then
459
- printf " ${C_BLUE}[dry-run]${C_RESET} npm run build\n"
460
- else
461
- (cd "$SOURCE_ROOT" && npm run build) || { err "npm run build 失败"; exit 1; }
462
- fi
463
- fi
464
- if [[ ! -f "$BUNDLE_SRC" && $DRY_RUN -eq 0 ]]; then
465
- if [[ $SKIP_BUILD -eq 1 ]]; then
466
- err "找不到 ${BUNDLE_SRC}(npm 包可能损坏)"
467
- err " 请尝试重装:npx @andyqiu/codeforge install [--global]"
468
- else
469
- err "找不到 ${BUNDLE_SRC},请先成功执行 npm run build"
470
- fi
471
- exit 1
472
- fi
473
- if [[ -f "$BUNDLE_SRC" ]]; then
474
- size=$(wc -c < "$BUNDLE_SRC" | tr -d ' ')
475
- ok "bundle 已就绪: $BUNDLE_SRC (${size} bytes)"
476
- fi
477
-
478
- # Step 3/8: 准备目录 + 清理 legacy
479
- log "Step 3/8: 准备目标目录 + 清理 legacy 注入物"
480
- ensure_dir "$TARGET_ROOT"
481
- for legacy in "${LEGACY_DIRS[@]}"; do
482
- p="$TARGET_ROOT/$legacy"
483
- if [[ -e "$p" || -L "$p" ]]; then
484
- run "rm -rf '$p'"
485
- warn "已清理 legacy 目录: $p"
486
- fi
487
- done
488
-
489
- # Step 4/8: 装 bundle
490
- log "Step 4/8: 装入 dist/index.js bundle"
491
- BUNDLE_DST="$TARGET_ROOT/$BUNDLE_DST_REL"
492
- ensure_dir "$(dirname "$BUNDLE_DST")"
493
- run "cp -f '$BUNDLE_SRC' '$BUNDLE_DST'"
494
- ok "bundle → $BUNDLE_DST"
495
- write_plugin_entry
496
-
497
- # 写 VERSION marker 文件(用户 cat 一行查版本,不依赖 grep bundle)
498
- CF_VERSION="$(node -e "console.log(require('$SOURCE_ROOT/package.json').version)" 2>/dev/null || echo "unknown")"
499
- VERSION_FILE="$TARGET_ROOT/codeforge/VERSION"
500
- if [[ $DRY_RUN -eq 0 ]]; then
501
- printf "%s\n" "$CF_VERSION" > "$VERSION_FILE"
502
- fi
503
- ok "VERSION → ${VERSION_FILE} (${CF_VERSION})"
504
-
505
- # Step 5/8: 装 agents / commands / workflows / context-templates / review-profiles
506
- log "Step 5/8: 装 agents / commands / workflows / context-templates / review-profiles"
507
- for entry in "${MD_COPY_DIRS[@]}"; do
508
- src_name="${entry%%:*}"; dst_name="${entry##*:}"
509
- src_path="$SOURCE_ROOT/$src_name"
510
- dst_path="$TARGET_ROOT/$dst_name"
511
- if [[ ! -d "$src_path" ]]; then
512
- warn "源目录不存在,跳过: $src_path"; continue
513
- fi
514
- if [[ -e "$dst_path" || -L "$dst_path" ]]; then
515
- run "rm -rf '$dst_path'"
516
- fi
517
- ensure_dir "$dst_path"
518
- count=0
519
- while IFS= read -r f; do
520
- base="$(basename "$f")"
521
- if [[ "$base" == "README.md" || "$base" == _* || "$base" == *.bak || "$base" == .* ]]; then continue; fi
522
- run "cp -f '$f' '$dst_path/'"
523
- count=$((count+1))
524
- done < <(find "$src_path" -maxdepth 1 -type f -name '*.md')
525
- ok "$src_name/ → $dst_path ($count 个 .md)"
526
- done
527
- for entry in "${COPY_DIRS[@]}"; do
528
- src_name="${entry%%:*}"; dst_name="${entry##*:}"
529
- src_path="$SOURCE_ROOT/$src_name"
530
- dst_path="$TARGET_ROOT/$dst_name"
531
- if [[ ! -d "$src_path" ]]; then
532
- warn "源目录不存在,跳过: $src_path"; continue
533
- fi
534
- ensure_dir "$dst_path"
535
- run "cp -R '$src_path/.' '$dst_path/'"
536
- ok "$src_name/ → $dst_path (整目录拷贝)"
537
- done
538
-
539
-
540
- # Step 5b/8: 装 skills/(opencode skill 目录:~/.config/opencode/skills/ 或 .opencode/skills/)
541
- # skills/ 在项目顶级,git 追踪,npm 包含。安装时整目录 rsync 到 target。
542
- # 细粒度 uninstall:只删 CodeForge 自己的 skill 子目录,不碰用户自装的其他 skill。
543
- log "Step 5b/8: 装 skills/"
544
- SKILLS_SRC="$SOURCE_ROOT/skills"
545
- SKILLS_DST="$TARGET_ROOT/skills"
546
- if [[ -d "$SKILLS_SRC" ]]; then
547
- ensure_dir "$SKILLS_DST"
548
- skill_count=0
549
- while IFS= read -r skill_dir; do
550
- skill_name="$(basename "$skill_dir")"
551
- dst_skill="$SKILLS_DST/$skill_name"
552
- if [[ -e "$dst_skill" || -L "$dst_skill" ]]; then
553
- run "rm -rf '$dst_skill'"
554
- fi
555
- run "cp -R '$skill_dir' '$dst_skill'"
556
- skill_count=$((skill_count + 1))
557
- done < <(find "$SKILLS_SRC" -mindepth 1 -maxdepth 1 -type d)
558
- ok "skills/ → $SKILLS_DST ($skill_count 个 skill)"
559
- else
560
- warn "skills/ 目录不存在,跳过(发布包未含 skills)"
561
- fi
562
-
563
- # Step 5c/8: 装 assets/(adr-init 模板资产:scripts/ + githooks/ + docs/)
564
- # assets/ 在项目顶级,git 追踪,npm 包含。安装时整目录拷贝到 TARGET_ROOT/assets/。
565
- # resolveAssetsRoot(tools/adr-init.ts)从 dist/index.js 所在目录向上找 package.json+assets/adr-init,
566
- # 命中 ~/.config/opencode(层 1),因此 assets 必须装到 TARGET_ROOT/assets/adr-init/。
567
- log "Step 5c/8: 装 assets/"
568
- ASSETS_SRC="$SOURCE_ROOT/assets"
569
- ASSETS_DST="$TARGET_ROOT/assets"
570
- if [[ -d "$ASSETS_SRC" ]]; then
571
- ensure_dir "$ASSETS_DST"
572
- run "cp -R '$ASSETS_SRC/.' '$ASSETS_DST/'"
573
- ok "assets/ → $ASSETS_DST"
574
- else
575
- warn "assets/ 目录不存在,跳过(发布包未含 assets)"
576
- fi
577
-
578
- # Step 6/8: AGENTS.md 智能合并(仅项目模式)
579
- log "Step 6/8: AGENTS.md 智能合并"
580
- if [[ "$MODE" == "project" ]]; then
581
- agents_target="$(pwd)/AGENTS.md"
582
- template_path="$SOURCE_ROOT/$KH_TEMPLATE_REL"
583
- merge_project_agents_md "$agents_target" "$template_path"
584
- else
585
- ok "全局模式:跳过项目 AGENTS.md 合并(context-templates 已装到 $TARGET_ROOT/context-templates)"
586
- fi
587
-
588
- # Step 7/8: KH 全局配置模板(仅 --global)
589
- log "Step 7/8: KH 全局配置模板"
590
- if [[ "$MODE" == "global" ]]; then
591
- write_kh_template
592
- else
593
- ok "项目级安装,跳过 KH 全局模板(要装请加 --global)"
594
- fi
595
-
596
- # Step 8/8: 配置默认 agent → codeforge(ADR-0056 D5)
597
- # 仅 user/global mode 配置用户级 ~/.config/opencode/opencode.json
598
- # project mode 不动用户级配置(项目应由用户自决是否覆盖)
599
- # Windows 用户暂需手动配置(install.ps1 尚未实现该步骤,留待 ADR-0059)
600
- log "Step 8/8: 配置默认 agent → codeforge"
601
- if [[ "$MODE" == "global" ]]; then
602
- configure_default_agent
603
- else
604
- ok "项目级安装,跳过用户级 default_agent 配置(要装请加 --global)"
605
- fi
606
-
607
- # 如果是 CodeForge 开发仓库本身,重新生成 dev shim(install 会清掉 .opencode/plugins/)
608
- # ADR-0055:本仓库内 opencode 加载本项目 dist/ 而非全局 stable,依赖 dev shim 让位
609
- if [ -f ".codeforge/.dev-marker" ] && [ -f "scripts/dev-sync.mjs" ]; then
610
- node scripts/dev-sync.mjs --no-build 2>/dev/null || true
611
- echo " ↳ dev shim regenerated (.opencode/plugins/codeforge-dev.js)"
612
- fi
613
-
614
-
615
- # Step 9/9: 安装后自检(ADR:worktree-guard-fail-loud Fix D)
616
- # 在「验证清单」前主动跑 4 项关键依赖检查;warn 不阻塞 install,但用户能立刻看到红绿
617
- verify_install() {
618
- local has_warn=0
619
- log "Step 9/9: 安装后自检"
620
-
621
- # 1. git 版本(worktree 隔离硬依赖;需要 >= 2.5)
622
- if command -v git >/dev/null 2>&1; then
623
- local gv major minor
624
- gv="$(git --version 2>/dev/null | awk '{print $3}')"
625
- major="$(printf '%s' "$gv" | cut -d. -f1)"
626
- minor="$(printf '%s' "$gv" | cut -d. -f2)"
627
- # 数值校验失败时按 0 处理,避免 "[: : integer expression expected"
628
- case "$major" in *[!0-9]*|"") major=0 ;; esac
629
- case "$minor" in *[!0-9]*|"") minor=0 ;; esac
630
- if [ "$major" -gt 2 ] || { [ "$major" -eq 2 ] && [ "$minor" -ge 5 ]; }; then
631
- ok "git $gv (>= 2.5 ✓ worktree 支持)"
632
- else
633
- warn "git $gv 版本太旧(worktree 需要 >= 2.5);worktree 隔离将失效(写操作会被 DENY)"
634
- has_warn=1
635
- fi
636
- else
637
- warn "git 未安装;worktree 隔离将失效(写操作会被 DENY)"
638
- has_warn=1
639
- fi
640
-
641
- # 1b. git 仓库探测(reviewer 追加:Fix D 补 git rev-parse --git-dir 检查)
642
- # 全局模式下当前目录通常**不是** CodeForge 自己的项目,因此只 info 提示;
643
- # 项目模式下若当前目录不是 git 仓库,必然影响 worktree 隔离 → warn。
644
- if command -v git >/dev/null 2>&1; then
645
- if git rev-parse --git-dir >/dev/null 2>&1; then
646
- ok "当前目录是 git 仓库(git rev-parse --git-dir ✓)"
647
- else
648
- if [ "$MODE" = "project" ]; then
649
- warn "当前目录不是 git 仓库(git rev-parse --git-dir 失败);worktree 隔离将失效"
650
- warn " 解决:\`git init && git add -A && git commit -m 'initial'\`"
651
- has_warn=1
652
- else
653
- log " ↳ 当前目录非 git 仓库(全局安装无碍;进入实际项目使用时该项目需是 git 仓库)"
654
- fi
655
- fi
656
- fi
657
-
658
- # 2. opencode 版本
659
- if command -v opencode >/dev/null 2>&1; then
660
- local ov
661
- ov="$(opencode --version 2>/dev/null || echo unknown)"
662
- ok "opencode $ov"
663
- else
664
- warn "opencode 未在 PATH 中;CodeForge 无入口可用(请先安装 opencode)"
665
- has_warn=1
666
- fi
667
-
668
- # 3. plugin 注册检查
669
- local cfg
670
- cfg="$(opencode_cfg_path)"
671
- if [ -f "$cfg" ] && grep -q '"codeforge"' "$cfg" 2>/dev/null; then
672
- ok "plugin 已注册:$cfg"
673
- else
674
- if [ "$MODE" = "project" ]; then
675
- log " ↳ 项目级配置 $cfg 未包含 codeforge entry(如需用 codeforge agent,请编辑该文件)"
676
- else
677
- warn "plugin 未在 $cfg 中注册"
678
- has_warn=1
679
- fi
680
- fi
681
-
682
- # 4. dev-marker 误用检测(仅 global 模式有意义)
683
- if [ "$MODE" = "global" ]; then
684
- if [ -f "$HOME/.codeforge/.dev-marker" ] || [ -f "$HOME/.dev-marker" ]; then
685
- warn "检测到 \$HOME 附近有 .codeforge/.dev-marker —— 可能导致所有项目让位到本地 plugin"
686
- warn " 建议删除:rm \$HOME/.codeforge/.dev-marker"
687
- has_warn=1
688
- fi
689
- fi
690
-
691
- if [ "$has_warn" -eq 0 ]; then
692
- ok "${C_BOLD}自检通过${C_RESET}"
693
- else
694
- warn "${C_BOLD}自检发现 $has_warn 项问题,详见上方${C_RESET}"
695
- fi
696
- }
697
- verify_install
698
-
699
-
700
- # 验证清单
701
- hr
702
- ok "${C_BOLD}CodeForge v${CF_VERSION} 安装完成${C_RESET}"
703
- hr
704
- cat <<EOF
705
- ${C_BOLD}验证清单:${C_RESET}
706
- 1. 列出注入的扩展点:
707
- $ ls -la "$TARGET_ROOT"
708
- 2. 看 opencode.json 里 plugin entry:
709
- $ cat "$(opencode_cfg_path)"
710
- 3. 让 opencode 校验配置:
711
- $ opencode debug config
712
- -> 期望看到 1 个 codeforge plugin entry(不是 17 个)
713
- 4. 跑一次最小 dogfood,看 plugin import + activation 日志:
714
- $ opencode run "hello"
715
- $ tail -n 50 \$HOME/.cache/codeforge/plugins.log
716
- 5. KH 配置(仅 --global 安装时生成):
717
- $ cat "$CODEFORGE_CFG_DIR/kh.json"
718
- $ export KNOWLEDGE_API_KEY=<your-token> # ← API key 必须走环境变量
719
- 6. AGENTS.md 智能合并(仅项目级):
720
- - 已有 AGENTS.md → KH 块被替换成模板内容,原文件备份成 *.bak.<timestamp>
721
- - 模板源:${SOURCE_ROOT}/${KH_TEMPLATE_REL}(修改后请重新 install)
722
-
723
- ${C_BOLD}卸载:${C_RESET}
724
- $ ./install.sh --uninstall ${MODE/project/}
725
- EOF
726
- hr