@andyqiu/codeforge 0.3.11 → 0.3.13
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/README.md +45 -3
- package/agents/codeforge.md +5 -4
- package/agents/coder-deep.md +3 -0
- package/agents/coder-quick.md +3 -0
- package/agents/coder.md +3 -0
- package/agents/discover-challenger.md +161 -0
- package/agents/discover.md +357 -0
- package/agents/planner.md +12 -8
- package/agents/reviewer.md +3 -0
- package/assets/adr-init/.github/pull_request_template.md +22 -0
- package/assets/adr-init/docs/adr/README.md +105 -0
- package/assets/adr-init/docs/adr/template.md +83 -0
- package/assets/adr-init/githooks/pre-commit.sh +46 -0
- package/assets/adr-init/githooks/pre-push.sh +23 -0
- package/assets/adr-init/scripts/adr-check.mjs +428 -0
- package/assets/adr-init/scripts/adr-index-sync.mjs +151 -0
- package/bin/codeforge.mjs +96 -3
- package/codeforge.json +24 -0
- package/commands/adr-init.md +67 -0
- package/dist/adr-init.js +207 -0
- package/dist/index.js +1691 -580
- package/install.ps1 +555 -516
- package/install.sh +33 -0
- package/package.json +22 -5
- package/scripts/sync-agent-models.mjs +1 -1
- package/skills/ambiguity-gate/SKILL.md +99 -0
- package/skills/devils-advocate/SKILL.md +74 -0
- package/skills/ears-zh/SKILL.md +126 -0
- package/skills/example-mapping/SKILL.md +96 -0
- package/skills/success-criteria/SKILL.md +84 -0
- package/skills/weighted-dimensions/SKILL.md +109 -0
- package/workflows/bugfix.yaml +3 -3
- package/workflows/discover-flow.yaml +150 -0
- package/workflows/feature-dev.yaml +3 -3
- package/workflows/parallel-explore.yaml +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
@@ -65,6 +65,30 @@
|
|
|
65
65
|
"anthropic/claude-sonnet-4-6",
|
|
66
66
|
"google/gemini-3-pro"
|
|
67
67
|
]
|
|
68
|
+
},
|
|
69
|
+
"discover": {
|
|
70
|
+
"_doc": "需求澄清者(采访人设):长程对话 + 强 thinking,复用 planner 同档(Opus deep)。",
|
|
71
|
+
"model": "anthropic/claude-opus-4-7",
|
|
72
|
+
"category": "deep",
|
|
73
|
+
"tier": "deep",
|
|
74
|
+
"thinking": { "type": "enabled", "budget_tokens": 6000 },
|
|
75
|
+
"fallback_models": [
|
|
76
|
+
"openai/gpt-5.5",
|
|
77
|
+
"anthropic/claude-sonnet-4-6",
|
|
78
|
+
"google/gemini-3-pro"
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
"discover-challenger": {
|
|
82
|
+
"_doc": "强对抗子 agent:跨家族避免同源偏见(参考 reviewer 选型),结论由离散触发组合锁死。",
|
|
83
|
+
"model": "openai/gpt-5.5",
|
|
84
|
+
"category": "ultrabrain",
|
|
85
|
+
"tier": "deep",
|
|
86
|
+
"thinking": { "type": "enabled", "budget_tokens": 3000 },
|
|
87
|
+
"fallback_models": [
|
|
88
|
+
"anthropic/claude-opus-4-7",
|
|
89
|
+
"anthropic/claude-sonnet-4-6",
|
|
90
|
+
"google/gemini-3-pro"
|
|
91
|
+
]
|
|
68
92
|
}
|
|
69
93
|
},
|
|
70
94
|
|
|
@@ -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 含具体错误,请用户手动执行
|
package/dist/adr-init.js
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// lib/adr-init.ts
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { existsSync, promises as fsp } from "node:fs";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import * as url from "node:url";
|
|
6
|
+
function resolveAssetsRoot() {
|
|
7
|
+
const here = path.dirname(url.fileURLToPath(import.meta.url));
|
|
8
|
+
let dir = here;
|
|
9
|
+
for (let i = 0;i < 6; i++) {
|
|
10
|
+
if (existsSync(path.join(dir, "package.json")) && existsSync(path.join(dir, "assets", "adr-init"))) {
|
|
11
|
+
return path.join(dir, "assets", "adr-init");
|
|
12
|
+
}
|
|
13
|
+
const parent = path.dirname(dir);
|
|
14
|
+
if (parent === dir)
|
|
15
|
+
break;
|
|
16
|
+
dir = parent;
|
|
17
|
+
}
|
|
18
|
+
throw new Error(`assets/adr-init/ 未找到(从 ${here} 上溯 6 层);` + `如果是 npm 安装请检查 package.json "files" 字段是否包含 "assets/"`);
|
|
19
|
+
}
|
|
20
|
+
function isGitRepo(cwd) {
|
|
21
|
+
try {
|
|
22
|
+
const r = spawnSync("git", ["rev-parse", "--show-toplevel"], {
|
|
23
|
+
cwd,
|
|
24
|
+
stdio: "pipe",
|
|
25
|
+
encoding: "utf8"
|
|
26
|
+
});
|
|
27
|
+
return r.status === 0;
|
|
28
|
+
} catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function runGitConfigHooksPath(cwd) {
|
|
33
|
+
try {
|
|
34
|
+
const r = spawnSync("git", ["config", "core.hooksPath", ".githooks"], {
|
|
35
|
+
cwd,
|
|
36
|
+
stdio: "pipe",
|
|
37
|
+
encoding: "utf8"
|
|
38
|
+
});
|
|
39
|
+
if (r.status === 0)
|
|
40
|
+
return { ok: true };
|
|
41
|
+
return { ok: false, error: (r.stderr ?? "").trim() || `exit=${r.status}` };
|
|
42
|
+
} catch (e) {
|
|
43
|
+
return { ok: false, error: e instanceof Error ? e.message : String(e) };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function runAdrInit(opts = {}) {
|
|
47
|
+
const cwd = path.resolve(opts.cwd ?? process.cwd());
|
|
48
|
+
const force = !!opts.force;
|
|
49
|
+
const dryRun = !!opts.dryRun;
|
|
50
|
+
const writePrepare = !!opts.writePrepare;
|
|
51
|
+
const installPrePush = opts.installPrePush !== false;
|
|
52
|
+
const result = {
|
|
53
|
+
ok: true,
|
|
54
|
+
wrote: [],
|
|
55
|
+
skipped: [],
|
|
56
|
+
backedUp: [],
|
|
57
|
+
suggestions: [],
|
|
58
|
+
warnings: [],
|
|
59
|
+
dryRun
|
|
60
|
+
};
|
|
61
|
+
if (!isGitRepo(cwd)) {
|
|
62
|
+
result.ok = false;
|
|
63
|
+
result.reason = "not_git_repo";
|
|
64
|
+
result.warnings.push(`${cwd} 不是 git 仓库;adr-init 需要 git 才能下发 hooks`);
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
let assetsRoot;
|
|
68
|
+
try {
|
|
69
|
+
assetsRoot = resolveAssetsRoot();
|
|
70
|
+
} catch (e) {
|
|
71
|
+
result.ok = false;
|
|
72
|
+
result.reason = "assets_not_found";
|
|
73
|
+
result.warnings.push(e instanceof Error ? e.message : String(e));
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
77
|
+
const plan = [
|
|
78
|
+
{ src: "scripts/adr-check.mjs", dst: "scripts/adr-check.mjs" },
|
|
79
|
+
{ src: "scripts/adr-index-sync.mjs", dst: "scripts/adr-index-sync.mjs" },
|
|
80
|
+
{ src: "docs/adr/README.md", dst: "docs/adr/README.md" },
|
|
81
|
+
{ src: "docs/adr/template.md", dst: "docs/adr/template.md" },
|
|
82
|
+
{
|
|
83
|
+
src: ".github/pull_request_template.md",
|
|
84
|
+
dst: ".github/pull_request_template.md"
|
|
85
|
+
},
|
|
86
|
+
{ src: "githooks/pre-commit.sh", dst: ".githooks/pre-commit", chmod: 493 }
|
|
87
|
+
];
|
|
88
|
+
if (installPrePush) {
|
|
89
|
+
plan.push({
|
|
90
|
+
src: "githooks/pre-push.sh",
|
|
91
|
+
dst: ".githooks/pre-push",
|
|
92
|
+
chmod: 493
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
for (const item of plan) {
|
|
96
|
+
const srcAbs = path.join(assetsRoot, item.src);
|
|
97
|
+
const dstAbs = path.join(cwd, item.dst);
|
|
98
|
+
if (!existsSync(srcAbs)) {
|
|
99
|
+
result.warnings.push(`资产缺失:${item.src}(跳过 ${item.dst})`);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (existsSync(dstAbs)) {
|
|
103
|
+
if (!force) {
|
|
104
|
+
result.skipped.push(item.dst);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const bakRel = `${item.dst}.bak.${ts}`;
|
|
108
|
+
if (!dryRun) {
|
|
109
|
+
try {
|
|
110
|
+
await fsp.copyFile(dstAbs, path.join(cwd, bakRel));
|
|
111
|
+
} catch (e) {
|
|
112
|
+
result.ok = false;
|
|
113
|
+
result.reason = "io_error";
|
|
114
|
+
result.warnings.push(`备份 ${item.dst} 失败:${e instanceof Error ? e.message : String(e)}`);
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
result.backedUp.push(bakRel);
|
|
119
|
+
}
|
|
120
|
+
if (!dryRun) {
|
|
121
|
+
try {
|
|
122
|
+
await fsp.mkdir(path.dirname(dstAbs), { recursive: true });
|
|
123
|
+
await fsp.copyFile(srcAbs, dstAbs);
|
|
124
|
+
if (item.chmod !== undefined) {
|
|
125
|
+
try {
|
|
126
|
+
await fsp.chmod(dstAbs, item.chmod);
|
|
127
|
+
} catch (e) {
|
|
128
|
+
if (process.platform !== "win32") {
|
|
129
|
+
result.warnings.push(`chmod ${item.dst} 失败:${e instanceof Error ? e.message : String(e)}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
} catch (e) {
|
|
134
|
+
result.ok = false;
|
|
135
|
+
result.reason = "io_error";
|
|
136
|
+
result.warnings.push(`写入 ${item.dst} 失败:${e instanceof Error ? e.message : String(e)}`);
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
result.wrote.push(item.dst);
|
|
141
|
+
}
|
|
142
|
+
if (!dryRun) {
|
|
143
|
+
const r = runGitConfigHooksPath(cwd);
|
|
144
|
+
if (r.ok) {
|
|
145
|
+
result.suggestions.push("已运行:git config core.hooksPath .githooks(hooks 已启用)");
|
|
146
|
+
} else {
|
|
147
|
+
result.warnings.push(`自动启用 hooks 失败:${r.error ?? "未知错误"};请手动执行 git config core.hooksPath .githooks`);
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
result.suggestions.push("[dry-run] 将运行:git config core.hooksPath .githooks");
|
|
151
|
+
}
|
|
152
|
+
const pkgPath = path.join(cwd, "package.json");
|
|
153
|
+
const isNpm = existsSync(pkgPath);
|
|
154
|
+
if (isNpm) {
|
|
155
|
+
if (writePrepare) {
|
|
156
|
+
try {
|
|
157
|
+
const raw = await fsp.readFile(pkgPath, "utf8");
|
|
158
|
+
const pkg = JSON.parse(raw);
|
|
159
|
+
const existing = (pkg.scripts && typeof pkg.scripts.prepare === "string" ? pkg.scripts.prepare : "") ?? "";
|
|
160
|
+
const target = "git config core.hooksPath .githooks";
|
|
161
|
+
if (existing.includes(target)) {
|
|
162
|
+
result.suggestions.push(`package.json scripts.prepare 已含目标命令,无需改动`);
|
|
163
|
+
} else {
|
|
164
|
+
const bakRel = `package.json.bak.${ts}`;
|
|
165
|
+
if (!dryRun) {
|
|
166
|
+
try {
|
|
167
|
+
await fsp.copyFile(pkgPath, path.join(cwd, bakRel));
|
|
168
|
+
} catch (e) {
|
|
169
|
+
result.warnings.push(`备份 package.json 失败:${e instanceof Error ? e.message : String(e)}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
result.backedUp.push(bakRel);
|
|
173
|
+
if (existing.includes("husky")) {
|
|
174
|
+
result.warnings.push(`package.json scripts.prepare 已含 husky,仍合并写入;如有问题可回滚 ${bakRel}`);
|
|
175
|
+
}
|
|
176
|
+
const merged = existing ? `${existing} && ${target}` : target;
|
|
177
|
+
pkg.scripts = { ...pkg.scripts ?? {}, prepare: merged };
|
|
178
|
+
if (!dryRun) {
|
|
179
|
+
try {
|
|
180
|
+
await fsp.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + `
|
|
181
|
+
`, "utf8");
|
|
182
|
+
} catch (e) {
|
|
183
|
+
result.ok = false;
|
|
184
|
+
result.reason = "io_error";
|
|
185
|
+
result.warnings.push(`写入 package.json 失败:${e instanceof Error ? e.message : String(e)}`);
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
result.wrote.push("package.json");
|
|
190
|
+
}
|
|
191
|
+
} catch (e) {
|
|
192
|
+
result.warnings.push(`读取 package.json 失败:${e instanceof Error ? e.message : String(e)}`);
|
|
193
|
+
}
|
|
194
|
+
} else {
|
|
195
|
+
result.suggestions.push(`(可选) 在 package.json scripts.prepare 追加:"git config core.hooksPath .githooks",` + `或下次跑 codeforge adr-init --write-prepare 自动合并(写前自动 backup package.json)`);
|
|
196
|
+
}
|
|
197
|
+
} else {
|
|
198
|
+
result.suggestions.push("非 npm 项目:请在 README / AGENTS.md 提醒首次 clone 后跑 git config core.hooksPath .githooks");
|
|
199
|
+
}
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
var adr_init_default = { runAdrInit, resolveAssetsRoot };
|
|
203
|
+
export {
|
|
204
|
+
runAdrInit,
|
|
205
|
+
resolveAssetsRoot,
|
|
206
|
+
adr_init_default as default
|
|
207
|
+
};
|