diff --git a/examples/workflows/project-bootstrap-automation/.gitignore b/examples/workflows/project-bootstrap-automation/.gitignore
new file mode 100644
index 0000000..17aa483
--- /dev/null
+++ b/examples/workflows/project-bootstrap-automation/.gitignore
@@ -0,0 +1 @@
+outputs/
diff --git a/examples/workflows/project-bootstrap-automation/README.md b/examples/workflows/project-bootstrap-automation/README.md
new file mode 100644
index 0000000..df6f5dd
--- /dev/null
+++ b/examples/workflows/project-bootstrap-automation/README.md
@@ -0,0 +1,75 @@
+# GitLink 项目一键初始化与协作启动工作流
+
+面向 GitLink 竞赛子赛题三的端到端自动化工作流示例。
+
+本项目聚焦开源项目从 0 到可协作状态的启动过程,使用 `gitlink-cli` 串联仓库检查、分支规划、初始 Issue 创建和结果回写等能力,自动生成 README、LICENSE、CI 配置、协作文档、初始化报告和结构化清单。该流程覆盖“项目配置 -> 初始化文件生成 -> GitLink 命令编排 -> 任务落地 -> 报告归档”的完整闭环。
+
+## 交付物
+
+- `scripts/bootstrap_project.go`:主工作流入口
+- `scripts/run_demo.ps1`:一键复现脚本
+- `examples/sample_project.json`:示例项目配置
+- `examples/verification_comment_config.json`:真实回写验证配置
+- `examples/demo_outputs/`:固定示例输出
+- `docs/architecture.md`:架构与流程说明
+- `docs/quickstart.md`:最短复现路径
+- `docs/runbook.md`:运行手册
+- `docs/verification.md`:验证记录
+- `docs/submission-checklist.md`:赛题要求映射
+- `scripts/bootstrap_project_test.go`:Go 单元测试
+
+## 运行方式
+
+进入本目录后执行 dry-run:
+
+```powershell
+.\scripts\run_demo.ps1
+```
+
+执行后会生成:
+
+- `outputs/*_bootstrap_report.md`
+- `outputs/*_summary.md`
+- `outputs/*_manifest.json`
+- `outputs/*_files.json`
+- `outputs/command_log_*.json`
+
+如需执行真实 GitLink 写操作,在完成 GitLink 认证并核对目标仓库后使用:
+
+```powershell
+.\scripts\run_demo.ps1 -Apply
+```
+
+如需连同仓库创建一起执行:
+
+```powershell
+.\scripts\run_demo.ps1 -Apply -CreateRepo
+```
+
+如需把初始化摘要发布到指定 Issue:
+
+```powershell
+.\scripts\run_demo.ps1 -Apply -PublishIssueNumber 1
+```
+
+## 工作流串联的 gitlink-cli 调用
+
+默认配置会规划 7 个 `gitlink-cli` 调用:
+
+1. `repo +info`
+2. `branch +list`
+3. `branch +create`
+4. `branch +create`
+5. `issue +create`
+6. `issue +create`
+7. `issue +create`
+
+当指定 `-PublishIssueNumber` 时,会额外追加 `issue +comment`,用于把初始化摘要回写到 GitLink Issue。
+当指定 `-CreateRepo` 时,会在检查仓库前追加 `repo +create`。
+
+## 场景价值
+
+- 降低新开源项目启动成本,避免 README、License、CI、初始任务缺失。
+- 将项目初始化过程结构化,便于团队复用和审计。
+- 将 `gitlink-cli` 的仓库、分支、Issue 和评论能力串联为可复现方案。
+- 支持 dry-run 和 apply 两种模式,兼顾演示稳定性和真实落地。
diff --git a/examples/workflows/project-bootstrap-automation/docs/architecture.md b/examples/workflows/project-bootstrap-automation/docs/architecture.md
new file mode 100644
index 0000000..440f328
--- /dev/null
+++ b/examples/workflows/project-bootstrap-automation/docs/architecture.md
@@ -0,0 +1,32 @@
+# 架构说明
+
+本工作流采用“配置输入 -> 资产生成 -> CLI 编排 -> GitLink 落地 -> 结果归档”的五段式架构。
+
+```mermaid
+flowchart LR
+ A["项目配置
sample_project.json"] --> B["资产生成
README / LICENSE / CI / 协作文档"]
+ B --> C["CLI 编排
repo / branch / issue / comment"]
+ C --> D["GitLink 项目空间
仓库 / 分支 / Issue"]
+ D --> E["结果归档
报告 / 摘要 / manifest / 命令日志"]
+ C --> E
+```
+
+## 模块职责
+
+| 模块 | 职责 |
+| --- | --- |
+| 配置输入 | 描述项目名称、目标仓库、初始化分支和初始 Issue |
+| 资产生成 | 生成 README、LICENSE、CI 配置、贡献指南和路线图 |
+| CLI 编排 | 规划或执行 `gitlink-cli` 命令,串联仓库、分支、Issue 和评论能力 |
+| GitLink 落地 | 在真实 GitLink 仓库中创建分支、Issue,并可回写摘要 |
+| 结果归档 | 输出 Markdown 报告、摘要、JSON manifest 和命令日志 |
+
+## 端到端链路
+
+1. 读取 `examples/sample_project.json`。
+2. 生成初始化文件包。
+3. 规划 `repo +info` 和 `branch +list` 检查目标状态。
+4. 规划或执行 `branch +create` 创建协作分支。
+5. 规划或执行 `issue +create` 创建初始任务。
+6. 可选执行 `issue +comment` 发布初始化摘要。
+7. 生成报告与命令日志,支撑复现和审计。
diff --git a/examples/workflows/project-bootstrap-automation/docs/quickstart.md b/examples/workflows/project-bootstrap-automation/docs/quickstart.md
new file mode 100644
index 0000000..b194178
--- /dev/null
+++ b/examples/workflows/project-bootstrap-automation/docs/quickstart.md
@@ -0,0 +1,56 @@
+# 快速开始
+
+## 1. 进入目录
+
+```powershell
+cd examples\workflows\project-bootstrap-automation
+```
+
+## 2. 运行 dry-run
+
+```powershell
+.\scripts\run_demo.ps1
+```
+
+该命令不会写入 GitLink,只生成初始化材料和命令计划。
+
+## 3. 查看输出
+
+```powershell
+Get-ChildItem outputs
+```
+
+重点查看:
+
+- `*_bootstrap_report.md`
+- `*_summary.md`
+- `*_manifest.json`
+- `command_log_*.json`
+
+## 4. 执行单元测试
+
+```powershell
+go test ./scripts
+```
+
+## 5. 执行真实写入
+
+确认目标仓库和认证状态后执行:
+
+```powershell
+.\scripts\run_demo.ps1 -Apply
+```
+
+执行真实写入前,应先通过 `gitlink-cli auth login` 或当前环境已配置的认证方式完成 GitLink 登录。
+
+如需创建目标仓库:
+
+```powershell
+.\scripts\run_demo.ps1 -Apply -CreateRepo
+```
+
+如需把摘要发布到指定 Issue:
+
+```powershell
+.\scripts\run_demo.ps1 -Apply -PublishIssueNumber 1
+```
diff --git a/examples/workflows/project-bootstrap-automation/docs/runbook.md b/examples/workflows/project-bootstrap-automation/docs/runbook.md
new file mode 100644
index 0000000..73d9020
--- /dev/null
+++ b/examples/workflows/project-bootstrap-automation/docs/runbook.md
@@ -0,0 +1,43 @@
+# 运行手册
+
+## 模式说明
+
+| 模式 | 命令 | 说明 |
+| --- | --- | --- |
+| dry-run | `.\scripts\run_demo.ps1` | 只生成材料和命令计划,不写入 GitLink |
+| apply | `.\scripts\run_demo.ps1 -Apply` | 执行真实 `gitlink-cli` 命令 |
+| apply + create repo | `.\scripts\run_demo.ps1 -Apply -CreateRepo` | 先创建仓库,再执行初始化命令 |
+| apply + comment | `.\scripts\run_demo.ps1 -Apply -PublishIssueNumber 1` | 执行真实命令,并将摘要评论到指定 Issue |
+
+## 配置文件
+
+默认配置位于:
+
+```text
+examples/sample_project.json
+```
+
+主要字段:
+
+- `project`:项目名称、描述、语言、许可证
+- `repository`:目标 GitLink 仓库 owner/name
+- `branches`:需要创建的协作分支
+- `issues`:初始化 Issue 列表
+- `publish.issue_number`:可选的摘要发布 Issue 编号
+
+## 输出文件
+
+| 文件 | 说明 |
+| --- | --- |
+| `*_bootstrap_report.md` | 初始化报告 |
+| `*_summary.md` | 可发布到 Issue 的摘要 |
+| `*_manifest.json` | 结构化初始化清单 |
+| `*_files.json` | 生成文件内容包 |
+| `command_log_*.json` | gitlink-cli 命令计划或执行结果 |
+
+## 安全边界
+
+- 默认 dry-run,不进行远端写操作。
+- 只有显式传入 `-Apply` 才执行真实 GitLink 命令。
+- `-PublishIssueNumber` 只在明确指定 Issue 编号时追加评论命令。
+- 所有命令会写入 `command_log_*.json`,便于复盘和审计。
diff --git a/examples/workflows/project-bootstrap-automation/docs/submission-checklist.md b/examples/workflows/project-bootstrap-automation/docs/submission-checklist.md
new file mode 100644
index 0000000..be99696
--- /dev/null
+++ b/examples/workflows/project-bootstrap-automation/docs/submission-checklist.md
@@ -0,0 +1,26 @@
+# 提交核对清单
+
+## 官方交付要求映射
+
+| 要求 | 本项目对应内容 |
+| --- | --- |
+| 工作流串联不少于 3 个 CLI 命令或 Skill 调用 | `scripts/bootstrap_project.go` 规划或执行 `repo +info`、`branch +list`、`branch +create`、`issue +create`、`issue +comment` |
+| 提供可复现执行脚本或 Agent 对话记录 | `scripts/run_demo.ps1` |
+| 在至少一个真实 GitLink 项目上运行并展示效果 | 已在 `puygob236/gitlink-bootstrap-demo` 完成仓库读取、分支读取、Issue 创建和 Issue 摘要回写验证 |
+| 提供工作流说明文档 | `README.md`、`docs/quickstart.md`、`docs/runbook.md` |
+| 提供架构图 | `docs/architecture.md` |
+| 代码开源并托管到 GitLink | 放置于 `examples/workflows/project-bootstrap-automation/` |
+| 提供完整中文 README | `README.md` |
+
+## 验证状态
+
+- `go test ./examples/workflows/project-bootstrap-automation/scripts`:通过
+- `.\scripts\run_demo.ps1`:通过
+- dry-run 生成 7 个 gitlink-cli 调用计划,满足赛题要求
+- `.\scripts\run_demo.ps1 -Config examples\verification_comment_config.json -Apply -PublishIssueNumber 4`:通过,3 个真实 gitlink-cli 调用状态均为 `ok`
+
+## 交付内容
+
+- `README.md`、`docs/`、`scripts/`、`examples/` 均位于本目录。
+- `outputs/` 为运行时生成目录,评审可通过复现脚本重新生成。
+- `examples/demo_outputs/` 用于保存固定示例产物。
diff --git a/examples/workflows/project-bootstrap-automation/docs/verification.md b/examples/workflows/project-bootstrap-automation/docs/verification.md
new file mode 100644
index 0000000..465a702
--- /dev/null
+++ b/examples/workflows/project-bootstrap-automation/docs/verification.md
@@ -0,0 +1,84 @@
+# 验证记录
+
+## 本地验证
+
+执行目录:
+
+```text
+examples/workflows/project-bootstrap-automation
+```
+
+单元测试:
+
+```powershell
+go test ./scripts
+```
+
+结果:
+
+```text
+ok github.com/gitlink-org/gitlink-cli/examples/workflows/project-bootstrap-automation/scripts
+```
+
+dry-run 复现:
+
+```powershell
+.\scripts\run_demo.ps1
+```
+
+结果:
+
+```text
+已生成初始化报告: outputs\puygob236_gitlink-bootstrap-demo_20260524_072107_bootstrap_report.md
+已生成初始化摘要: outputs\puygob236_gitlink-bootstrap-demo_20260524_072107_summary.md
+已生成文件清单: outputs\puygob236_gitlink-bootstrap-demo_20260524_072107_manifest.json
+已生成命令日志: outputs\command_log_20260524_072107.json
+模式: dry-run
+计划/执行 gitlink-cli 调用: 7 个
+```
+
+## 真实仓库验证计划
+
+目标仓库:
+
+```text
+puygob236/gitlink-bootstrap-demo
+```
+
+验证步骤:
+
+1. 确认 GitLink 认证可用。
+2. 创建或确认目标仓库存在。
+3. 执行 `.\scripts\run_demo.ps1 -Apply`。
+4. 检查分支、Issue 和输出报告。
+5. 如需展示回写能力,执行 `.\scripts\run_demo.ps1 -Apply -PublishIssueNumber `。
+
+## 真实仓库验证结果
+
+目标仓库:
+
+```text
+https://gitlink.org.cn/puygob236/gitlink-bootstrap-demo
+```
+
+已完成验证:
+
+- `repo +info`:成功读取 `puygob236/gitlink-bootstrap-demo` 仓库信息。
+- `branch +list`:成功读取 `master`、`develop`、`release/v0.1` 分支。
+- `issue +create`:成功创建初始化 Issue,生成项目任务清单。
+- `issue +comment`:成功将初始化摘要回写到 Issue。
+
+回写验证命令:
+
+```powershell
+.\scripts\run_demo.ps1 -Config examples\verification_comment_config.json -Apply -PublishIssueNumber 4
+```
+
+回写验证结果:
+
+```text
+模式: apply
+计划/执行 gitlink-cli 调用: 3 个
+```
+
+命令日志中 3 条调用状态均为 `ok`,无 stderr。
diff --git a/examples/workflows/project-bootstrap-automation/examples/demo_outputs/README.md b/examples/workflows/project-bootstrap-automation/examples/demo_outputs/README.md
new file mode 100644
index 0000000..441df6c
--- /dev/null
+++ b/examples/workflows/project-bootstrap-automation/examples/demo_outputs/README.md
@@ -0,0 +1,17 @@
+# 示例输出
+
+本目录保存 `scripts/bootstrap_project.go` 在 dry-run 模式下生成的固定示例产物,便于快速查看工作流输出格式。
+
+生成命令:
+
+```powershell
+go run scripts\bootstrap_project.go --config examples\sample_project.json --output-dir examples\demo_outputs --now 2026-05-24T08:00:00Z
+```
+
+产物说明:
+
+- `*_bootstrap_report.md`:项目初始化报告
+- `*_summary.md`:可发布到 Issue 的初始化摘要
+- `*_manifest.json`:结构化初始化清单
+- `*_files.json`:生成文件内容包
+- `command_log_*.json`:gitlink-cli 命令计划
diff --git a/examples/workflows/project-bootstrap-automation/examples/demo_outputs/command_log_20260524_080000.json b/examples/workflows/project-bootstrap-automation/examples/demo_outputs/command_log_20260524_080000.json
new file mode 100644
index 0000000..7380c91
--- /dev/null
+++ b/examples/workflows/project-bootstrap-automation/examples/demo_outputs/command_log_20260524_080000.json
@@ -0,0 +1,144 @@
+{
+ "mode": "dry-run",
+ "commands": [
+ {
+ "command": [
+ "gitlink-cli",
+ "repo",
+ "+info",
+ "--owner",
+ "puygob236",
+ "--repo",
+ "gitlink-bootstrap-demo",
+ "--format",
+ "json"
+ ],
+ "status": "planned",
+ "returncode": null,
+ "stdout": "",
+ "stderr": ""
+ },
+ {
+ "command": [
+ "gitlink-cli",
+ "branch",
+ "+list",
+ "--owner",
+ "puygob236",
+ "--repo",
+ "gitlink-bootstrap-demo",
+ "--format",
+ "json"
+ ],
+ "status": "planned",
+ "returncode": null,
+ "stdout": "",
+ "stderr": ""
+ },
+ {
+ "command": [
+ "gitlink-cli",
+ "branch",
+ "+create",
+ "--owner",
+ "puygob236",
+ "--repo",
+ "gitlink-bootstrap-demo",
+ "--name",
+ "develop",
+ "--from",
+ "master",
+ "--format",
+ "json"
+ ],
+ "status": "planned",
+ "returncode": null,
+ "stdout": "",
+ "stderr": ""
+ },
+ {
+ "command": [
+ "gitlink-cli",
+ "branch",
+ "+create",
+ "--owner",
+ "puygob236",
+ "--repo",
+ "gitlink-bootstrap-demo",
+ "--name",
+ "release/v0.1",
+ "--from",
+ "master",
+ "--format",
+ "json"
+ ],
+ "status": "planned",
+ "returncode": null,
+ "stdout": "",
+ "stderr": ""
+ },
+ {
+ "command": [
+ "gitlink-cli",
+ "issue",
+ "+create",
+ "--owner",
+ "puygob236",
+ "--repo",
+ "gitlink-bootstrap-demo",
+ "--title",
+ "完善项目 README 与快速开始文档",
+ "--body",
+ "仓库: `puygob236/gitlink-bootstrap-demo`\n\n类型: documentation\n优先级: normal\n\n## 任务清单\n\n- [ ] 补充项目背景和目标用户\n- [ ] 补充安装与运行步骤\n- [ ] 补充最小示例\n\n## 验收标准\n\nREADME 能支撑新贡献者在 10 分钟内完成本地启动。\n",
+ "--format",
+ "json"
+ ],
+ "status": "planned",
+ "returncode": null,
+ "stdout": "",
+ "stderr": ""
+ },
+ {
+ "command": [
+ "gitlink-cli",
+ "issue",
+ "+create",
+ "--owner",
+ "puygob236",
+ "--repo",
+ "gitlink-bootstrap-demo",
+ "--title",
+ "建立基础 CI 检查",
+ "--body",
+ "仓库: `puygob236/gitlink-bootstrap-demo`\n\n类型: ci\n优先级: high\n\n## 任务清单\n\n- [ ] 添加测试命令\n- [ ] 添加 lint 或格式检查\n- [ ] 在 PR 中展示检查结果\n\n## 验收标准\n\n每次 push 和 PR 均能触发基础检查。\n",
+ "--format",
+ "json"
+ ],
+ "status": "planned",
+ "returncode": null,
+ "stdout": "",
+ "stderr": ""
+ },
+ {
+ "command": [
+ "gitlink-cli",
+ "issue",
+ "+create",
+ "--owner",
+ "puygob236",
+ "--repo",
+ "gitlink-bootstrap-demo",
+ "--title",
+ "规划 v0.1 版本里程碑",
+ "--body",
+ "仓库: `puygob236/gitlink-bootstrap-demo`\n\n类型: release\n优先级: normal\n\n## 任务清单\n\n- [ ] 整理 v0.1 范围\n- [ ] 确定验收标准\n- [ ] 准备 Release Notes 模板\n\n## 验收标准\n\n形成可执行的 v0.1 版本任务列表。\n",
+ "--format",
+ "json"
+ ],
+ "status": "planned",
+ "returncode": null,
+ "stdout": "",
+ "stderr": ""
+ }
+ ]
+}
\ No newline at end of file
diff --git a/examples/workflows/project-bootstrap-automation/examples/demo_outputs/puygob236_gitlink-bootstrap-demo_20260524_080000_bootstrap_report.md b/examples/workflows/project-bootstrap-automation/examples/demo_outputs/puygob236_gitlink-bootstrap-demo_20260524_080000_bootstrap_report.md
new file mode 100644
index 0000000..d84edd3
--- /dev/null
+++ b/examples/workflows/project-bootstrap-automation/examples/demo_outputs/puygob236_gitlink-bootstrap-demo_20260524_080000_bootstrap_report.md
@@ -0,0 +1,41 @@
+# GitLink 项目初始化工作流报告
+
+## 目标项目
+
+- 仓库: `puygob236/gitlink-bootstrap-demo`
+- 项目名称: Open Research Toolkit
+- 描述: A reproducible GitLink project initialized by an end-to-end automation workflow.
+- 生成时间: 2026-05-24T08:00:00Z
+
+## 初始化文件
+
+| 文件 | 字节数 |
+| --- | ---: |
+| `README.md` | 545 |
+| `LICENSE` | 179 |
+| `.github/workflows/ci.yml` | 232 |
+| `docs/CONTRIBUTING.md` | 83 |
+| `docs/ROADMAP.md` | 92 |
+
+## 分支计划
+
+| 分支 | 来源 | 保护 |
+| --- | --- | --- |
+| `develop` | `master` | false |
+| `release/v0.1` | `master` | false |
+
+## 初始 Issue 计划
+
+| 序号 | 标题 | 优先级 |
+| ---: | --- | --- |
+| 1 | 完善项目 README 与快速开始文档 | normal |
+| 2 | 建立基础 CI 检查 | high |
+| 3 | 规划 v0.1 版本里程碑 | normal |
+
+## 工作流闭环
+
+1. 读取项目配置。
+2. 生成 README、LICENSE、CI 和协作文档。
+3. 调用 gitlink-cli 检查仓库和分支状态。
+4. 调用 gitlink-cli 创建初始化 Issue。
+5. 输出报告、摘要和结构化 manifest,必要时回写到 GitLink Issue。
diff --git a/examples/workflows/project-bootstrap-automation/examples/demo_outputs/puygob236_gitlink-bootstrap-demo_20260524_080000_files.json b/examples/workflows/project-bootstrap-automation/examples/demo_outputs/puygob236_gitlink-bootstrap-demo_20260524_080000_files.json
new file mode 100644
index 0000000..7b65eb6
--- /dev/null
+++ b/examples/workflows/project-bootstrap-automation/examples/demo_outputs/puygob236_gitlink-bootstrap-demo_20260524_080000_files.json
@@ -0,0 +1,7 @@
+{
+ ".github/workflows/ci.yml": "name: Go CI\n\non:\n push:\n pull_request:\n\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-go@v5\n with:\n go-version: \"1.23\"\n - run: go test ./...\n",
+ "LICENSE": "# License\n\nThis project is initialized with the `MulanPSL-2.0` license.\n\nThe final repository should keep the complete license text that matches the selected open-source license.\n",
+ "README.md": "# Open Research Toolkit\n\nA reproducible GitLink project initialized by an end-to-end automation workflow.\n\n## 项目信息\n\n- GitLink 仓库: `puygob236/gitlink-bootstrap-demo`\n- 技术方向: Go\n- 初始化来源: GitLink 项目一键初始化工作流\n\n## 快速开始\n\n```bash\ngit clone https://gitlink.org.cn/puygob236/gitlink-bootstrap-demo.git\ncd gitlink-bootstrap-demo\n```\n\n## 协作约定\n\n- 使用 Issue 跟踪需求、缺陷和文档任务。\n- 使用 Pull Request 合并代码变更。\n- 重要里程碑通过 Release Notes 记录。\n",
+ "docs/CONTRIBUTING.md": "# 贡献指南\n\n请通过 Issue 讨论需求,通过 Pull Request 提交变更。\n",
+ "docs/ROADMAP.md": "# Roadmap\n\n- [ ] 完成项目初始化\n- [ ] 建立基础测试\n- [ ] 发布第一个版本\n"
+}
\ No newline at end of file
diff --git a/examples/workflows/project-bootstrap-automation/examples/demo_outputs/puygob236_gitlink-bootstrap-demo_20260524_080000_manifest.json b/examples/workflows/project-bootstrap-automation/examples/demo_outputs/puygob236_gitlink-bootstrap-demo_20260524_080000_manifest.json
new file mode 100644
index 0000000..02e80ff
--- /dev/null
+++ b/examples/workflows/project-bootstrap-automation/examples/demo_outputs/puygob236_gitlink-bootstrap-demo_20260524_080000_manifest.json
@@ -0,0 +1,81 @@
+{
+ "repository": "puygob236/gitlink-bootstrap-demo",
+ "project": {
+ "name": "Open Research Toolkit",
+ "description": "A reproducible GitLink project initialized by an end-to-end automation workflow.",
+ "language": "Go",
+ "license": "MulanPSL-2.0"
+ },
+ "files": [
+ {
+ "path": "README.md",
+ "bytes": 545
+ },
+ {
+ "path": "LICENSE",
+ "bytes": 179
+ },
+ {
+ "path": ".github/workflows/ci.yml",
+ "bytes": 232
+ },
+ {
+ "path": "docs/CONTRIBUTING.md",
+ "bytes": 83
+ },
+ {
+ "path": "docs/ROADMAP.md",
+ "bytes": 92
+ }
+ ],
+ "branches": [
+ {
+ "name": "develop",
+ "from": "master",
+ "create": true,
+ "protect": false
+ },
+ {
+ "name": "release/v0.1",
+ "from": "master",
+ "create": true,
+ "protect": false
+ }
+ ],
+ "issues": [
+ {
+ "title": "完善项目 README 与快速开始文档",
+ "type": "documentation",
+ "priority": "normal",
+ "tasks": [
+ "补充项目背景和目标用户",
+ "补充安装与运行步骤",
+ "补充最小示例"
+ ],
+ "acceptance": "README 能支撑新贡献者在 10 分钟内完成本地启动。"
+ },
+ {
+ "title": "建立基础 CI 检查",
+ "type": "ci",
+ "priority": "high",
+ "tasks": [
+ "添加测试命令",
+ "添加 lint 或格式检查",
+ "在 PR 中展示检查结果"
+ ],
+ "acceptance": "每次 push 和 PR 均能触发基础检查。"
+ },
+ {
+ "title": "规划 v0.1 版本里程碑",
+ "type": "release",
+ "priority": "normal",
+ "tasks": [
+ "整理 v0.1 范围",
+ "确定验收标准",
+ "准备 Release Notes 模板"
+ ],
+ "acceptance": "形成可执行的 v0.1 版本任务列表。"
+ }
+ ],
+ "generated_at": "2026-05-24T08:00:00Z"
+}
\ No newline at end of file
diff --git a/examples/workflows/project-bootstrap-automation/examples/demo_outputs/puygob236_gitlink-bootstrap-demo_20260524_080000_summary.md b/examples/workflows/project-bootstrap-automation/examples/demo_outputs/puygob236_gitlink-bootstrap-demo_20260524_080000_summary.md
new file mode 100644
index 0000000..edd7318
--- /dev/null
+++ b/examples/workflows/project-bootstrap-automation/examples/demo_outputs/puygob236_gitlink-bootstrap-demo_20260524_080000_summary.md
@@ -0,0 +1,8 @@
+# GitLink 项目初始化摘要
+
+- 目标仓库: `puygob236/gitlink-bootstrap-demo`
+- 项目名称: Open Research Toolkit
+- 生成时间: 2026-05-24T08:00:00Z
+- 初始化文件: 5 个
+- 初始 Issue: 3 个
+- 分支动作: 2 个
diff --git a/examples/workflows/project-bootstrap-automation/examples/sample_project.json b/examples/workflows/project-bootstrap-automation/examples/sample_project.json
new file mode 100644
index 0000000..93d90d3
--- /dev/null
+++ b/examples/workflows/project-bootstrap-automation/examples/sample_project.json
@@ -0,0 +1,64 @@
+{
+ "project": {
+ "name": "Open Research Toolkit",
+ "description": "A reproducible GitLink project initialized by an end-to-end automation workflow.",
+ "language": "Go",
+ "license": "MulanPSL-2.0"
+ },
+ "repository": {
+ "owner": "puygob236",
+ "name": "gitlink-bootstrap-demo"
+ },
+ "branches": [
+ {
+ "name": "develop",
+ "from": "master",
+ "create": true,
+ "protect": false
+ },
+ {
+ "name": "release/v0.1",
+ "from": "master",
+ "create": true,
+ "protect": false
+ }
+ ],
+ "issues": [
+ {
+ "title": "完善项目 README 与快速开始文档",
+ "type": "documentation",
+ "priority": "normal",
+ "tasks": [
+ "补充项目背景和目标用户",
+ "补充安装与运行步骤",
+ "补充最小示例"
+ ],
+ "acceptance": "README 能支撑新贡献者在 10 分钟内完成本地启动。"
+ },
+ {
+ "title": "建立基础 CI 检查",
+ "type": "ci",
+ "priority": "high",
+ "tasks": [
+ "添加测试命令",
+ "添加 lint 或格式检查",
+ "在 PR 中展示检查结果"
+ ],
+ "acceptance": "每次 push 和 PR 均能触发基础检查。"
+ },
+ {
+ "title": "规划 v0.1 版本里程碑",
+ "type": "release",
+ "priority": "normal",
+ "tasks": [
+ "整理 v0.1 范围",
+ "确定验收标准",
+ "准备 Release Notes 模板"
+ ],
+ "acceptance": "形成可执行的 v0.1 版本任务列表。"
+ }
+ ],
+ "publish": {
+ "issue_number": 0
+ }
+}
diff --git a/examples/workflows/project-bootstrap-automation/examples/verification_comment_config.json b/examples/workflows/project-bootstrap-automation/examples/verification_comment_config.json
new file mode 100644
index 0000000..042741e
--- /dev/null
+++ b/examples/workflows/project-bootstrap-automation/examples/verification_comment_config.json
@@ -0,0 +1,17 @@
+{
+ "project": {
+ "name": "Open Research Toolkit",
+ "description": "A reproducible GitLink project initialized by an end-to-end automation workflow.",
+ "language": "Go",
+ "license": "MulanPSL-2.0"
+ },
+ "repository": {
+ "owner": "puygob236",
+ "name": "gitlink-bootstrap-demo"
+ },
+ "branches": [],
+ "issues": [],
+ "publish": {
+ "issue_number": 4
+ }
+}
diff --git a/examples/workflows/project-bootstrap-automation/scripts/bootstrap_project.go b/examples/workflows/project-bootstrap-automation/scripts/bootstrap_project.go
new file mode 100644
index 0000000..cdf0144
--- /dev/null
+++ b/examples/workflows/project-bootstrap-automation/scripts/bootstrap_project.go
@@ -0,0 +1,478 @@
+package main
+
+import (
+ "bytes"
+ "encoding/json"
+ "errors"
+ "flag"
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strconv"
+ "strings"
+ "time"
+)
+
+type ProjectConfig struct {
+ Project ProjectInfo `json:"project"`
+ Repository RepositoryInfo `json:"repository"`
+ Branches []BranchPlan `json:"branches"`
+ Issues []IssuePlan `json:"issues"`
+ Publish PublishConfig `json:"publish"`
+}
+
+type ProjectInfo struct {
+ Name string `json:"name"`
+ Description string `json:"description"`
+ Language string `json:"language"`
+ License string `json:"license"`
+}
+
+type RepositoryInfo struct {
+ Owner string `json:"owner"`
+ Name string `json:"name"`
+}
+
+type BranchPlan struct {
+ Name string `json:"name"`
+ From string `json:"from"`
+ Create *bool `json:"create"`
+ Protect bool `json:"protect"`
+}
+
+type IssuePlan struct {
+ Title string `json:"title"`
+ Type string `json:"type"`
+ Priority string `json:"priority"`
+ Tasks []string `json:"tasks"`
+ Acceptance string `json:"acceptance"`
+}
+
+type PublishConfig struct {
+ IssueNumber int `json:"issue_number"`
+}
+
+type FileManifestItem struct {
+ Path string `json:"path"`
+ Bytes int `json:"bytes"`
+}
+
+type OutputManifest struct {
+ Repository string `json:"repository"`
+ Project ProjectInfo `json:"project"`
+ Files []FileManifestItem `json:"files"`
+ Branches []BranchPlan `json:"branches"`
+ Issues []IssuePlan `json:"issues"`
+ GeneratedAt string `json:"generated_at"`
+}
+
+type CommandResult struct {
+ Command []string `json:"command"`
+ Status string `json:"status"`
+ ReturnCode *int `json:"returncode"`
+ Stdout string `json:"stdout"`
+ Stderr string `json:"stderr"`
+}
+
+type CommandLog struct {
+ Mode string `json:"mode"`
+ Commands []CommandResult `json:"commands"`
+}
+
+type OutputPaths struct {
+ Report string
+ Summary string
+ Manifest string
+ Files string
+}
+
+type Options struct {
+ ConfigPath string
+ OutputDir string
+ CLIBin string
+ Apply bool
+ CreateRepo bool
+ PublishIssueNumber int
+ Now string
+}
+
+func parseFlags(args []string) Options {
+ var opts Options
+ fs := flag.NewFlagSet("bootstrap-project", flag.ExitOnError)
+ fs.StringVar(&opts.ConfigPath, "config", filepath.FromSlash("examples/sample_project.json"), "配置文件路径")
+ fs.StringVar(&opts.OutputDir, "output-dir", "outputs", "输出目录")
+ fs.StringVar(&opts.CLIBin, "cli-bin", firstNonEmpty(os.Getenv("GITLINK_CLI_BIN"), "gitlink-cli"), "gitlink-cli 可执行文件路径")
+ fs.BoolVar(&opts.Apply, "apply", false, "执行真实 GitLink 写操作")
+ fs.BoolVar(&opts.CreateRepo, "create-repo", false, "仓库不存在时创建仓库")
+ fs.IntVar(&opts.PublishIssueNumber, "publish-issue-number", 0, "把初始化摘要评论到指定 Issue")
+ fs.StringVar(&opts.Now, "now", "", "固定当前时间,ISO8601 格式")
+ _ = fs.Parse(args)
+ return opts
+}
+
+func loadConfig(path string) (ProjectConfig, error) {
+ var config ProjectConfig
+ data, err := os.ReadFile(path)
+ if err != nil {
+ return config, fmt.Errorf("配置文件不存在: %s", path)
+ }
+ data = bytes.TrimPrefix(data, []byte{0xef, 0xbb, 0xbf})
+ if err := json.Unmarshal(data, &config); err != nil {
+ return config, err
+ }
+ return config, nil
+}
+
+func parseNow(value string) (time.Time, error) {
+ if strings.TrimSpace(value) == "" {
+ return time.Now().UTC(), nil
+ }
+ text := strings.ReplaceAll(strings.TrimSpace(value), "Z", "+00:00")
+ dt, err := time.Parse(time.RFC3339, text)
+ if err != nil {
+ return time.Time{}, err
+ }
+ return dt.UTC(), nil
+}
+
+func isoTime(value time.Time) string {
+ return value.UTC().Format(time.RFC3339)
+}
+
+func safeName(value string) string {
+ replacer := strings.NewReplacer("/", "_", "\\", "_", " ", "_")
+ return replacer.Replace(value)
+}
+
+func renderReadme(config ProjectConfig) string {
+ language := firstNonEmpty(config.Project.Language, "未指定")
+ return fmt.Sprintf("# %s\n\n%s\n\n## 项目信息\n\n- GitLink 仓库: `%s/%s`\n- 技术方向: %s\n- 初始化来源: GitLink 项目一键初始化工作流\n\n## 快速开始\n\n```bash\ngit clone https://gitlink.org.cn/%s/%s.git\ncd %s\n```\n\n## 协作约定\n\n- 使用 Issue 跟踪需求、缺陷和文档任务。\n- 使用 Pull Request 合并代码变更。\n- 重要里程碑通过 Release Notes 记录。\n",
+ config.Project.Name,
+ config.Project.Description,
+ config.Repository.Owner,
+ config.Repository.Name,
+ language,
+ config.Repository.Owner,
+ config.Repository.Name,
+ config.Repository.Name,
+ )
+}
+
+func renderLicense(config ProjectConfig) string {
+ licenseName := firstNonEmpty(config.Project.License, "MulanPSL-2.0")
+ return fmt.Sprintf("# License\n\nThis project is initialized with the `%s` license.\n\nThe final repository should keep the complete license text that matches the selected open-source license.\n", licenseName)
+}
+
+func renderCI(config ProjectConfig) string {
+ if strings.Contains(strings.ToLower(config.Project.Language), "go") {
+ return `name: Go CI
+
+on:
+ push:
+ pull_request:
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-go@v5
+ with:
+ go-version: "1.23"
+ - run: go test ./...
+`
+ }
+ return `name: Basic CI
+
+on:
+ push:
+ pull_request:
+
+jobs:
+ check:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - run: echo "Add project-specific checks here."
+`
+}
+
+func plannedFiles(config ProjectConfig) map[string]string {
+ return map[string]string{
+ "README.md": renderReadme(config),
+ "LICENSE": renderLicense(config),
+ ".github/workflows/ci.yml": renderCI(config),
+ "docs/CONTRIBUTING.md": "# 贡献指南\n\n请通过 Issue 讨论需求,通过 Pull Request 提交变更。\n",
+ "docs/ROADMAP.md": "# Roadmap\n\n- [ ] 完成项目初始化\n- [ ] 建立基础测试\n- [ ] 发布第一个版本\n",
+ }
+}
+
+func issueBody(item IssuePlan, config ProjectConfig) string {
+ repo := fmt.Sprintf("%s/%s", config.Repository.Owner, config.Repository.Name)
+ tasks := "- [ ] 待补充"
+ if len(item.Tasks) > 0 {
+ lines := make([]string, 0, len(item.Tasks))
+ for _, task := range item.Tasks {
+ lines = append(lines, "- [ ] "+task)
+ }
+ tasks = strings.Join(lines, "\n")
+ }
+ return fmt.Sprintf("仓库: `%s`\n\n类型: %s\n优先级: %s\n\n## 任务清单\n\n%s\n\n## 验收标准\n\n%s\n",
+ repo,
+ firstNonEmpty(item.Type, "task"),
+ firstNonEmpty(item.Priority, "normal"),
+ tasks,
+ firstNonEmpty(item.Acceptance, "完成后在本 Issue 中说明验证结果。"),
+ )
+}
+
+func shouldCreateBranch(branch BranchPlan) bool {
+ return branch.Create == nil || *branch.Create
+}
+
+func branchFrom(branch BranchPlan) string {
+ return firstNonEmpty(branch.From, "master")
+}
+
+func buildCLIPlan(config ProjectConfig, summary string, createRepo bool) [][]string {
+ owner := config.Repository.Owner
+ repo := config.Repository.Name
+ commands := [][]string{}
+ if createRepo {
+ commands = append(commands, []string{"repo", "+create", "--name", repo, "--description", config.Project.Description, "--format", "json"})
+ }
+ commands = append(commands,
+ []string{"repo", "+info", "--owner", owner, "--repo", repo, "--format", "json"},
+ []string{"branch", "+list", "--owner", owner, "--repo", repo, "--format", "json"},
+ )
+ for _, branch := range config.Branches {
+ if shouldCreateBranch(branch) {
+ commands = append(commands, []string{"branch", "+create", "--owner", owner, "--repo", repo, "--name", branch.Name, "--from", branchFrom(branch), "--format", "json"})
+ }
+ if branch.Protect {
+ commands = append(commands, []string{"branch", "+protect", "--owner", owner, "--repo", repo, "--name", branch.Name, "--format", "json"})
+ }
+ }
+ for _, issue := range config.Issues {
+ commands = append(commands, []string{"issue", "+create", "--owner", owner, "--repo", repo, "--title", issue.Title, "--body", issueBody(issue, config), "--format", "json"})
+ }
+ if summary != "" && config.Publish.IssueNumber > 0 {
+ commands = append(commands, []string{"issue", "+comment", "--owner", owner, "--repo", repo, "--number", strconv.Itoa(config.Publish.IssueNumber), "--body", summary, "--format", "json"})
+ }
+ return commands
+}
+
+func runCommand(cliBin string, args []string, apply bool) CommandResult {
+ command := append([]string{cliBin}, args...)
+ if !apply {
+ return CommandResult{Command: command, Status: "planned"}
+ }
+ cmd := exec.Command(cliBin, args...)
+ if strings.HasSuffix(strings.ToLower(cliBin), ".cmd") || strings.HasSuffix(strings.ToLower(cliBin), ".bat") {
+ cmd = exec.Command("cmd", append([]string{"/c", cliBin}, args...)...)
+ }
+ var stdout, stderr bytes.Buffer
+ cmd.Stdout = &stdout
+ cmd.Stderr = &stderr
+ err := cmd.Run()
+ returnCode := 0
+ status := "ok"
+ if err != nil {
+ status = "failed"
+ var exitErr *exec.ExitError
+ if errors.As(err, &exitErr) {
+ returnCode = exitErr.ExitCode()
+ } else {
+ returnCode = 1
+ }
+ if isIdempotentSkip(stderr.String()) {
+ status = "skipped"
+ }
+ }
+ return CommandResult{
+ Command: command,
+ Status: status,
+ ReturnCode: &returnCode,
+ Stdout: strings.TrimSpace(stdout.String()),
+ Stderr: strings.TrimSpace(stderr.String()),
+ }
+}
+
+func isIdempotentSkip(stderr string) bool {
+ knownMessages := []string{
+ "新分支已存在",
+ "branch already exists",
+ "repository already exists",
+ "仓库已存在",
+ }
+ for _, message := range knownMessages {
+ if strings.Contains(stderr, message) {
+ return true
+ }
+ }
+ return false
+}
+
+func writeOutputs(config ProjectConfig, outputDir string, now time.Time) (OutputPaths, error) {
+ owner := config.Repository.Owner
+ repo := config.Repository.Name
+ prefix := fmt.Sprintf("%s_%s_%s", safeName(owner), safeName(repo), now.UTC().Format("20060102_150405"))
+ if err := os.MkdirAll(outputDir, 0o755); err != nil {
+ return OutputPaths{}, err
+ }
+ files := plannedFiles(config)
+ fileManifest := make([]FileManifestItem, 0, len(files))
+ for _, path := range []string{"README.md", "LICENSE", ".github/workflows/ci.yml", "docs/CONTRIBUTING.md", "docs/ROADMAP.md"} {
+ if content, ok := files[path]; ok {
+ fileManifest = append(fileManifest, FileManifestItem{Path: path, Bytes: len([]byte(content))})
+ }
+ }
+ summary := fmt.Sprintf("# GitLink 项目初始化摘要\n\n- 目标仓库: `%s/%s`\n- 项目名称: %s\n- 生成时间: %s\n- 初始化文件: %d 个\n- 初始 Issue: %d 个\n- 分支动作: %d 个\n",
+ owner,
+ repo,
+ config.Project.Name,
+ isoTime(now),
+ len(files),
+ len(config.Issues),
+ len(config.Branches),
+ )
+ report := renderReport(config, fileManifest, now)
+ manifest := OutputManifest{
+ Repository: fmt.Sprintf("%s/%s", owner, repo),
+ Project: config.Project,
+ Files: fileManifest,
+ Branches: config.Branches,
+ Issues: config.Issues,
+ GeneratedAt: isoTime(now),
+ }
+ manifestJSON, err := json.MarshalIndent(manifest, "", " ")
+ if err != nil {
+ return OutputPaths{}, err
+ }
+ filesJSON, err := json.MarshalIndent(files, "", " ")
+ if err != nil {
+ return OutputPaths{}, err
+ }
+ paths := OutputPaths{
+ Report: filepath.Join(outputDir, prefix+"_bootstrap_report.md"),
+ Summary: filepath.Join(outputDir, prefix+"_summary.md"),
+ Manifest: filepath.Join(outputDir, prefix+"_manifest.json"),
+ Files: filepath.Join(outputDir, prefix+"_files.json"),
+ }
+ writes := map[string][]byte{
+ paths.Report: []byte(report),
+ paths.Summary: []byte(summary),
+ paths.Manifest: manifestJSON,
+ paths.Files: filesJSON,
+ }
+ for path, data := range writes {
+ if err := os.WriteFile(path, data, 0o644); err != nil {
+ return OutputPaths{}, err
+ }
+ }
+ return paths, nil
+}
+
+func renderReport(config ProjectConfig, fileManifest []FileManifestItem, now time.Time) string {
+ fileRows := []string{}
+ for _, item := range fileManifest {
+ fileRows = append(fileRows, fmt.Sprintf("| `%s` | %d |", item.Path, item.Bytes))
+ }
+ branchRows := []string{}
+ for _, item := range config.Branches {
+ branchRows = append(branchRows, fmt.Sprintf("| `%s` | `%s` | %t |", item.Name, branchFrom(item), item.Protect))
+ }
+ if len(branchRows) == 0 {
+ branchRows = append(branchRows, "| 无 | 无 | false |")
+ }
+ issueRows := []string{}
+ for idx, item := range config.Issues {
+ issueRows = append(issueRows, fmt.Sprintf("| %d | %s | %s |", idx+1, item.Title, firstNonEmpty(item.Priority, "normal")))
+ }
+ if len(issueRows) == 0 {
+ issueRows = append(issueRows, "| 0 | 无 | normal |")
+ }
+ return fmt.Sprintf("# GitLink 项目初始化工作流报告\n\n## 目标项目\n\n- 仓库: `%s/%s`\n- 项目名称: %s\n- 描述: %s\n- 生成时间: %s\n\n## 初始化文件\n\n| 文件 | 字节数 |\n| --- | ---: |\n%s\n\n## 分支计划\n\n| 分支 | 来源 | 保护 |\n| --- | --- | --- |\n%s\n\n## 初始 Issue 计划\n\n| 序号 | 标题 | 优先级 |\n| ---: | --- | --- |\n%s\n\n## 工作流闭环\n\n1. 读取项目配置。\n2. 生成 README、LICENSE、CI 和协作文档。\n3. 调用 gitlink-cli 检查仓库和分支状态。\n4. 调用 gitlink-cli 创建初始化 Issue。\n5. 输出报告、摘要和结构化 manifest,必要时回写到 GitLink Issue。\n",
+ config.Repository.Owner,
+ config.Repository.Name,
+ config.Project.Name,
+ config.Project.Description,
+ isoTime(now),
+ strings.Join(fileRows, "\n"),
+ strings.Join(branchRows, "\n"),
+ strings.Join(issueRows, "\n"),
+ )
+}
+
+func firstNonEmpty(values ...string) string {
+ for _, value := range values {
+ if strings.TrimSpace(value) != "" {
+ return value
+ }
+ }
+ return ""
+}
+
+func main() {
+ opts := parseFlags(os.Args[1:])
+ config, err := loadConfig(opts.ConfigPath)
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+ now, err := parseNow(opts.Now)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "无法解析 --now 的值: %v\n", err)
+ os.Exit(1)
+ }
+ if opts.PublishIssueNumber > 0 {
+ config.Publish.IssueNumber = opts.PublishIssueNumber
+ }
+ outputPaths, err := writeOutputs(config, opts.OutputDir, now)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "写入输出失败: %v\n", err)
+ os.Exit(1)
+ }
+ summaryBytes, err := os.ReadFile(outputPaths.Summary)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "读取摘要失败: %v\n", err)
+ os.Exit(1)
+ }
+ plan := buildCLIPlan(config, string(summaryBytes), opts.CreateRepo)
+ results := make([]CommandResult, 0, len(plan))
+ for _, command := range plan {
+ results = append(results, runCommand(opts.CLIBin, command, opts.Apply))
+ }
+ mode := "dry-run"
+ if opts.Apply {
+ mode = "apply"
+ }
+ commandLog := CommandLog{Mode: mode, Commands: results}
+ commandLogJSON, err := json.MarshalIndent(commandLog, "", " ")
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "生成命令日志失败: %v\n", err)
+ os.Exit(1)
+ }
+ commandLogPath := filepath.Join(opts.OutputDir, fmt.Sprintf("command_log_%s.json", now.UTC().Format("20060102_150405")))
+ if err := os.WriteFile(commandLogPath, commandLogJSON, 0o644); err != nil {
+ fmt.Fprintf(os.Stderr, "写入命令日志失败: %v\n", err)
+ os.Exit(1)
+ }
+ fmt.Printf("已生成初始化报告: %s\n", outputPaths.Report)
+ fmt.Printf("已生成初始化摘要: %s\n", outputPaths.Summary)
+ fmt.Printf("已生成文件清单: %s\n", outputPaths.Manifest)
+ fmt.Printf("已生成命令日志: %s\n", commandLogPath)
+ fmt.Printf("模式: %s\n", mode)
+ fmt.Printf("计划/执行 gitlink-cli 调用: %d 个\n", len(results))
+ failed := 0
+ for _, result := range results {
+ if result.Status == "failed" {
+ failed++
+ }
+ }
+ if failed > 0 {
+ fmt.Printf("失败命令: %d 个\n", failed)
+ os.Exit(1)
+ }
+}
diff --git a/examples/workflows/project-bootstrap-automation/scripts/bootstrap_project_test.go b/examples/workflows/project-bootstrap-automation/scripts/bootstrap_project_test.go
new file mode 100644
index 0000000..406ae13
--- /dev/null
+++ b/examples/workflows/project-bootstrap-automation/scripts/bootstrap_project_test.go
@@ -0,0 +1,126 @@
+package main
+
+import (
+ "encoding/json"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+ "time"
+)
+
+func sampleConfig() ProjectConfig {
+ create := true
+ return ProjectConfig{
+ Project: ProjectInfo{
+ Name: "Demo Project",
+ Description: "Demo description",
+ Language: "Go",
+ License: "MulanPSL-2.0",
+ },
+ Repository: RepositoryInfo{Owner: "alice", Name: "demo"},
+ Branches: []BranchPlan{{Name: "develop", From: "master", Create: &create}},
+ Issues: []IssuePlan{
+ {
+ Title: "Write README",
+ Type: "documentation",
+ Priority: "normal",
+ Tasks: []string{"Add quickstart", "Add license"},
+ },
+ },
+ }
+}
+
+func TestPlannedFilesIncludeRequiredProjectArtifacts(t *testing.T) {
+ files := plannedFiles(sampleConfig())
+ for _, path := range []string{"README.md", "LICENSE", ".github/workflows/ci.yml", "docs/CONTRIBUTING.md"} {
+ if _, ok := files[path]; !ok {
+ t.Fatalf("expected planned file %s", path)
+ }
+ }
+}
+
+func TestBuildCLIPlanChainsMoreThanThreeGitlinkCommands(t *testing.T) {
+ plan := buildCLIPlan(sampleConfig(), "", false)
+ if len(plan) < 4 {
+ t.Fatalf("expected at least 4 commands, got %d", len(plan))
+ }
+ if strings.Join(plan[0][:2], " ") != "repo +info" {
+ t.Fatalf("unexpected first command: %#v", plan[0])
+ }
+ if !containsCommand(plan, "branch +list") {
+ t.Fatalf("branch +list command missing: %#v", plan)
+ }
+ if !containsCommand(plan, "issue +create") {
+ t.Fatalf("issue +create command missing: %#v", plan)
+ }
+}
+
+func TestBuildCLIPlanCanCreateRepositoryFirst(t *testing.T) {
+ plan := buildCLIPlan(sampleConfig(), "", true)
+ if strings.Join(plan[0][:2], " ") != "repo +create" {
+ t.Fatalf("unexpected first command: %#v", plan[0])
+ }
+ if strings.Join(plan[1][:2], " ") != "repo +info" {
+ t.Fatalf("unexpected second command: %#v", plan[1])
+ }
+}
+
+func TestIssueBodyContainsChecklistAndRepository(t *testing.T) {
+ config := sampleConfig()
+ body := issueBody(config.Issues[0], config)
+ if !strings.Contains(body, "`alice/demo`") {
+ t.Fatalf("repository missing from body: %s", body)
+ }
+ if !strings.Contains(body, "- [ ] Add quickstart") {
+ t.Fatalf("checklist missing from body: %s", body)
+ }
+}
+
+func TestWriteOutputsCreatesReportManifestAndSummary(t *testing.T) {
+ tmp := t.TempDir()
+ paths, err := writeOutputs(sampleConfig(), tmp, time.Date(2026, 5, 24, 0, 0, 0, 0, time.UTC))
+ if err != nil {
+ t.Fatalf("writeOutputs returned error: %v", err)
+ }
+ for _, path := range []string{paths.Report, paths.Summary, paths.Manifest, paths.Files} {
+ if _, err := os.Stat(path); err != nil {
+ t.Fatalf("expected output %s: %v", path, err)
+ }
+ }
+ data, err := os.ReadFile(paths.Manifest)
+ if err != nil {
+ t.Fatalf("read manifest: %v", err)
+ }
+ var manifest OutputManifest
+ if err := json.Unmarshal(data, &manifest); err != nil {
+ t.Fatalf("unmarshal manifest: %v", err)
+ }
+ if manifest.Repository != "alice/demo" {
+ t.Fatalf("unexpected repository: %s", manifest.Repository)
+ }
+ if len(manifest.Files) < 4 {
+ t.Fatalf("expected at least 4 files, got %d", len(manifest.Files))
+ }
+ if filepath.Base(paths.Report) == "" {
+ t.Fatal("report path should include filename")
+ }
+}
+
+func TestBranchAlreadyExistsIsIdempotentSkip(t *testing.T) {
+ if !isIdempotentSkip("[-1] 新分支已存在!") {
+ t.Fatal("expected existing branch error to be skipped")
+ }
+ if isIdempotentSkip("[401] 请登录后再操作") {
+ t.Fatal("auth error should not be skipped")
+ }
+}
+
+func containsCommand(plan [][]string, command string) bool {
+ for _, item := range plan {
+ if len(item) >= 2 && strings.Join(item[:2], " ") == command {
+ return true
+ }
+ }
+ return false
+}
diff --git a/examples/workflows/project-bootstrap-automation/scripts/run_demo.ps1 b/examples/workflows/project-bootstrap-automation/scripts/run_demo.ps1
new file mode 100644
index 0000000..57345e1
--- /dev/null
+++ b/examples/workflows/project-bootstrap-automation/scripts/run_demo.ps1
@@ -0,0 +1,38 @@
+param(
+ [string]$Config = "examples/sample_project.json",
+ [string]$OutputDir = "outputs",
+ [switch]$Apply,
+ [switch]$CreateRepo,
+ [int]$PublishIssueNumber = 0
+)
+
+$ErrorActionPreference = "Stop"
+
+$args = @(
+ "run",
+ "scripts\bootstrap_project.go",
+ "--config", $Config,
+ "--output-dir", $OutputDir
+)
+
+if ($Apply.IsPresent) {
+ $cliCandidates = npm.cmd exec --yes --package=@gitlink-ai/cli -- cmd /c where gitlink-cli 2>$null
+ $cliPath = $cliCandidates | Where-Object { $_ -match 'gitlink-cli\.cmd$' } | Select-Object -First 1
+ if (-not $cliPath) {
+ $cliPath = $cliCandidates | Select-Object -First 1
+ }
+ if (-not $cliPath) {
+ throw "未能通过 npm exec 找到 gitlink-cli"
+ }
+ $args += @("--cli-bin", $cliPath, "--apply")
+}
+
+if ($CreateRepo.IsPresent) {
+ $args += "--create-repo"
+}
+
+if ($PublishIssueNumber -gt 0) {
+ $args += @("--publish-issue-number", "$PublishIssueNumber")
+}
+
+go @args