From 33ad8e62a0ff0980fbe08b29ffe194f6ff4b685f Mon Sep 17 00:00:00 2001
From: Jiachen Li <3115250840@qq.com>
Date: Mon, 25 May 2026 22:39:40 +0800
Subject: [PATCH 1/9] feat(workflows): add project bootstrap automation example
---
.../project-bootstrap-automation/.gitignore | 1 +
.../project-bootstrap-automation/README.md | 75 +++
.../docs/architecture.md | 32 ++
.../docs/quickstart.md | 56 ++
.../docs/runbook.md | 43 ++
.../docs/submission-checklist.md | 26 +
.../docs/verification.md | 84 +++
.../examples/demo_outputs/README.md | 17 +
.../command_log_20260524_080000.json | 144 ++++++
...p-demo_20260524_080000_bootstrap_report.md | 41 ++
...-bootstrap-demo_20260524_080000_files.json | 7 +
...otstrap-demo_20260524_080000_manifest.json | 81 +++
...-bootstrap-demo_20260524_080000_summary.md | 8 +
.../examples/sample_project.json | 64 +++
.../examples/verification_comment_config.json | 17 +
.../scripts/bootstrap_project.go | 478 ++++++++++++++++++
.../scripts/bootstrap_project_test.go | 126 +++++
.../scripts/run_demo.ps1 | 38 ++
18 files changed, 1338 insertions(+)
create mode 100644 examples/workflows/project-bootstrap-automation/.gitignore
create mode 100644 examples/workflows/project-bootstrap-automation/README.md
create mode 100644 examples/workflows/project-bootstrap-automation/docs/architecture.md
create mode 100644 examples/workflows/project-bootstrap-automation/docs/quickstart.md
create mode 100644 examples/workflows/project-bootstrap-automation/docs/runbook.md
create mode 100644 examples/workflows/project-bootstrap-automation/docs/submission-checklist.md
create mode 100644 examples/workflows/project-bootstrap-automation/docs/verification.md
create mode 100644 examples/workflows/project-bootstrap-automation/examples/demo_outputs/README.md
create mode 100644 examples/workflows/project-bootstrap-automation/examples/demo_outputs/command_log_20260524_080000.json
create mode 100644 examples/workflows/project-bootstrap-automation/examples/demo_outputs/puygob236_gitlink-bootstrap-demo_20260524_080000_bootstrap_report.md
create mode 100644 examples/workflows/project-bootstrap-automation/examples/demo_outputs/puygob236_gitlink-bootstrap-demo_20260524_080000_files.json
create mode 100644 examples/workflows/project-bootstrap-automation/examples/demo_outputs/puygob236_gitlink-bootstrap-demo_20260524_080000_manifest.json
create mode 100644 examples/workflows/project-bootstrap-automation/examples/demo_outputs/puygob236_gitlink-bootstrap-demo_20260524_080000_summary.md
create mode 100644 examples/workflows/project-bootstrap-automation/examples/sample_project.json
create mode 100644 examples/workflows/project-bootstrap-automation/examples/verification_comment_config.json
create mode 100644 examples/workflows/project-bootstrap-automation/scripts/bootstrap_project.go
create mode 100644 examples/workflows/project-bootstrap-automation/scripts/bootstrap_project_test.go
create mode 100644 examples/workflows/project-bootstrap-automation/scripts/run_demo.ps1
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
From b319c44b02d3ae6778209989f1e226cb3fcd0a14 Mon Sep 17 00:00:00 2001
From: Jiachen Li <3115250840@qq.com>
Date: Mon, 25 May 2026 22:53:04 +0800
Subject: [PATCH 2/9] docs(workflows): align project bootstrap validation
command
---
.../project-bootstrap-automation/docs/submission-checklist.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/examples/workflows/project-bootstrap-automation/docs/submission-checklist.md b/examples/workflows/project-bootstrap-automation/docs/submission-checklist.md
index be99696..e68df41 100644
--- a/examples/workflows/project-bootstrap-automation/docs/submission-checklist.md
+++ b/examples/workflows/project-bootstrap-automation/docs/submission-checklist.md
@@ -14,7 +14,7 @@
## 验证状态
-- `go test ./examples/workflows/project-bootstrap-automation/scripts`:通过
+- `go test ./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`
From a2fd36b4a67757b75e27dd3fc7dc00fde4e63fff Mon Sep 17 00:00:00 2001
From: Jiachen Li <3115250840@qq.com>
Date: Thu, 4 Jun 2026 14:44:22 +0800
Subject: [PATCH 3/9] docs(workflows): add project bootstrap submission
materials
---
.../project-bootstrap-automation/README.md | 13 ++
.../docs/architecture.md | 4 +-
.../docs/assets/bootstrap-architecture.svg | 120 ++++++++++++++++
.../docs/demo-video-script.md | 135 ++++++++++++++++++
.../docs/submission-checklist.md | 6 +-
.../docs/workflow-spec.md | 76 ++++++++++
6 files changed, 351 insertions(+), 3 deletions(-)
create mode 100644 examples/workflows/project-bootstrap-automation/docs/assets/bootstrap-architecture.svg
create mode 100644 examples/workflows/project-bootstrap-automation/docs/demo-video-script.md
create mode 100644 examples/workflows/project-bootstrap-automation/docs/workflow-spec.md
diff --git a/examples/workflows/project-bootstrap-automation/README.md b/examples/workflows/project-bootstrap-automation/README.md
index df6f5dd..2e309ed 100644
--- a/examples/workflows/project-bootstrap-automation/README.md
+++ b/examples/workflows/project-bootstrap-automation/README.md
@@ -11,10 +11,13 @@
- `examples/sample_project.json`:示例项目配置
- `examples/verification_comment_config.json`:真实回写验证配置
- `examples/demo_outputs/`:固定示例输出
+- `docs/workflow-spec.md`:工作流说明文档
- `docs/architecture.md`:架构与流程说明
+- `docs/assets/bootstrap-architecture.svg`:架构图
- `docs/quickstart.md`:最短复现路径
- `docs/runbook.md`:运行手册
- `docs/verification.md`:验证记录
+- `docs/demo-video-script.md`:演示视频录制脚本
- `docs/submission-checklist.md`:赛题要求映射
- `scripts/bootstrap_project_test.go`:Go 单元测试
@@ -67,6 +70,16 @@
当指定 `-PublishIssueNumber` 时,会额外追加 `issue +comment`,用于把初始化摘要回写到 GitLink Issue。
当指定 `-CreateRepo` 时,会在检查仓库前追加 `repo +create`。
+## 文档索引
+
+- 工作流说明:`docs/workflow-spec.md`
+- 架构说明与架构图:`docs/architecture.md`
+- 复现指南:`docs/quickstart.md`
+- 运行手册:`docs/runbook.md`
+- 验证记录:`docs/verification.md`
+- 演示视频脚本:`docs/demo-video-script.md`
+- 提交核对清单:`docs/submission-checklist.md`
+
## 场景价值
- 降低新开源项目启动成本,避免 README、License、CI、初始任务缺失。
diff --git a/examples/workflows/project-bootstrap-automation/docs/architecture.md b/examples/workflows/project-bootstrap-automation/docs/architecture.md
index 440f328..851735d 100644
--- a/examples/workflows/project-bootstrap-automation/docs/architecture.md
+++ b/examples/workflows/project-bootstrap-automation/docs/architecture.md
@@ -1,6 +1,8 @@
# 架构说明
-本工作流采用“配置输入 -> 资产生成 -> CLI 编排 -> GitLink 落地 -> 结果归档”的五段式架构。
+本工作流采用“配置输入 -> 资产生成 -> CLI 编排 -> GitLink 落地 -> 结果归档”的五段式架构。正式架构图见 `docs/assets/bootstrap-architecture.svg`。
+
+
```mermaid
flowchart LR
diff --git a/examples/workflows/project-bootstrap-automation/docs/assets/bootstrap-architecture.svg b/examples/workflows/project-bootstrap-automation/docs/assets/bootstrap-architecture.svg
new file mode 100644
index 0000000..df0dbd9
--- /dev/null
+++ b/examples/workflows/project-bootstrap-automation/docs/assets/bootstrap-architecture.svg
@@ -0,0 +1,120 @@
+
diff --git a/examples/workflows/project-bootstrap-automation/docs/demo-video-script.md b/examples/workflows/project-bootstrap-automation/docs/demo-video-script.md
new file mode 100644
index 0000000..a6967ae
--- /dev/null
+++ b/examples/workflows/project-bootstrap-automation/docs/demo-video-script.md
@@ -0,0 +1,135 @@
+# 演示视频脚本
+
+本文档用于录制参赛演示视频。建议视频时长控制在 3 到 5 分钟,录屏范围包括终端、项目目录和 GitLink 页面。
+
+## 录制前准备
+
+1. 打开终端,进入仓库根目录。
+2. 切换到 `project-bootstrap-automation-fork` 分支。
+3. 确认当前目录无未提交运行产物。
+4. 浏览器打开验证仓库页面:
+ `https://gitlink.org.cn/puygob236/gitlink-bootstrap-demo`
+5. 如需展示真实回写,提前完成 `gitlink-cli auth login`。
+
+## 镜头一:项目定位
+
+展示目录:
+
+```powershell
+cd examples\workflows\project-bootstrap-automation
+Get-ChildItem
+```
+
+讲解要点:
+
+- 本项目是 GitLink 子赛题三端到端自动化工作流。
+- 场景是项目一键初始化与协作启动。
+- 主实现为 Go,入口是 `scripts/bootstrap_project.go`。
+
+## 镜头二:架构和交付物
+
+展示文档:
+
+```powershell
+Get-Content docs\workflow-spec.md -TotalCount 40
+Get-Content docs\architecture.md -TotalCount 35
+```
+
+讲解要点:
+
+- 工作流分为配置输入、资产生成、CLI 编排、GitLink 落地、结果归档。
+- 默认串联 `repo +info`、`branch +list`、`branch +create`、`issue +create`。
+- 可选追加 `issue +comment` 完成结果回写。
+
+## 镜头三:单元测试
+
+执行命令:
+
+```powershell
+go test -count=1 ./scripts
+```
+
+讲解要点:
+
+- 测试覆盖文件生成、CLI 计划、Issue 正文、输出 manifest 和幂等跳过判断。
+- 测试通过后再进行 dry-run 演示。
+
+## 镜头四:dry-run 复现
+
+执行命令:
+
+```powershell
+.\scripts\run_demo.ps1
+```
+
+讲解要点:
+
+- dry-run 不写入 GitLink,只生成材料和命令计划。
+- 输出中应显示 7 个 `gitlink-cli` 调用计划。
+- 该模式适合评审复现和本地检查。
+
+展示输出:
+
+```powershell
+Get-ChildItem outputs
+Get-Content outputs\command_log_*.json -TotalCount 80
+```
+
+## 镜头五:查看生成报告
+
+执行命令:
+
+```powershell
+Get-Content outputs\*_bootstrap_report.md -TotalCount 80
+Get-Content outputs\*_summary.md
+```
+
+讲解要点:
+
+- 初始化报告包含目标项目、生成文件、分支计划和 Issue 计划。
+- 摘要可用于回写到 GitLink Issue。
+
+## 镜头六:真实仓库验证
+
+展示 GitLink 页面:
+
+```text
+https://gitlink.org.cn/puygob236/gitlink-bootstrap-demo
+```
+
+讲解要点:
+
+- 该仓库用于真实运行验证。
+- 已验证仓库读取、分支读取、Issue 创建和 Issue 摘要回写。
+- 真实写入命令记录在 `docs/verification.md`。
+
+可展示命令:
+
+```powershell
+Get-Content docs\verification.md
+```
+
+## 镜头七:赛题要求映射
+
+展示命令:
+
+```powershell
+Get-Content docs\submission-checklist.md
+```
+
+讲解要点:
+
+- 工作流串联超过 3 个 CLI 调用。
+- 提供可复现脚本。
+- 已在真实 GitLink 项目上验证。
+- 提供说明文档和架构图。
+
+## 录制后清理
+
+演示结束后删除运行时输出目录:
+
+```powershell
+Remove-Item outputs -Recurse -Force
+```
+
+`outputs/` 是可复现运行产物,不作为固定源码提交;固定示例保存在 `examples/demo_outputs/`。
diff --git a/examples/workflows/project-bootstrap-automation/docs/submission-checklist.md b/examples/workflows/project-bootstrap-automation/docs/submission-checklist.md
index e68df41..b0d5233 100644
--- a/examples/workflows/project-bootstrap-automation/docs/submission-checklist.md
+++ b/examples/workflows/project-bootstrap-automation/docs/submission-checklist.md
@@ -7,8 +7,9 @@
| 工作流串联不少于 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` |
+| 提供工作流说明文档 | `README.md`、`docs/workflow-spec.md`、`docs/quickstart.md`、`docs/runbook.md` |
+| 提供架构图 | `docs/architecture.md`、`docs/assets/bootstrap-architecture.svg` |
+| 提供演示材料 | `docs/demo-video-script.md` |
| 代码开源并托管到 GitLink | 放置于 `examples/workflows/project-bootstrap-automation/` |
| 提供完整中文 README | `README.md` |
@@ -22,5 +23,6 @@
## 交付内容
- `README.md`、`docs/`、`scripts/`、`examples/` 均位于本目录。
+- `docs/demo-video-script.md` 可用于录制演示视频。
- `outputs/` 为运行时生成目录,评审可通过复现脚本重新生成。
- `examples/demo_outputs/` 用于保存固定示例产物。
diff --git a/examples/workflows/project-bootstrap-automation/docs/workflow-spec.md b/examples/workflows/project-bootstrap-automation/docs/workflow-spec.md
new file mode 100644
index 0000000..d688a78
--- /dev/null
+++ b/examples/workflows/project-bootstrap-automation/docs/workflow-spec.md
@@ -0,0 +1,76 @@
+# 工作流说明
+
+## 场景定位
+
+本工作流面向 GitLink 子赛题三“构建端到端自动化工作流”,选择“项目一键初始化”作为应用场景。目标是在新开源项目创建初期,将项目配置、初始化文件、协作分支、初始 Issue 和执行报告统一串联,形成可复现、可审计的启动流程。
+
+该场景覆盖开源项目常见的启动缺口:
+
+- README、License、CI 配置和协作文档不完整。
+- 初始任务缺少统一模板,Issue 粒度和验收标准不一致。
+- 分支、Issue、报告产物分散,难以复盘初始化过程。
+- 真实写入和演示复现之间缺少安全边界。
+
+## 端到端流程
+
+工作流由 `scripts/bootstrap_project.go` 实现,默认读取 `examples/sample_project.json`,并按以下顺序执行:
+
+1. 解析项目配置,读取项目名称、仓库 owner/name、许可证、初始化分支和初始 Issue。
+2. 生成初始化文件包,包括 README、LICENSE、CI 配置、贡献指南和路线图。
+3. 规划或执行 `repo +info`,检查目标 GitLink 仓库状态。
+4. 规划或执行 `branch +list`,读取分支状态。
+5. 规划或执行 `branch +create`,创建协作分支。
+6. 规划或执行 `issue +create`,创建初始化任务。
+7. 可选执行 `issue +comment`,将初始化摘要回写到指定 Issue。
+8. 生成 Markdown 报告、摘要、manifest、文件包和命令日志。
+
+## 串联的 GitLink CLI 能力
+
+默认 dry-run 配置会生成 7 个 `gitlink-cli` 调用计划:
+
+| 顺序 | CLI 能力 | 用途 |
+| ---: | --- | --- |
+| 1 | `repo +info` | 检查目标仓库信息 |
+| 2 | `branch +list` | 读取当前分支列表 |
+| 3 | `branch +create` | 创建 `develop` 协作分支 |
+| 4 | `branch +create` | 创建 `release/v0.1` 发布分支 |
+| 5 | `issue +create` | 创建 README 与快速开始任务 |
+| 6 | `issue +create` | 创建 CI 检查任务 |
+| 7 | `issue +create` | 创建 v0.1 里程碑任务 |
+
+当传入 `-PublishIssueNumber` 时,会追加 `issue +comment`,用于把初始化摘要发布到指定 GitLink Issue。
+
+## 运行模式
+
+| 模式 | 命令 | 行为 |
+| --- | --- | --- |
+| 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` | 执行真实命令并回写摘要 |
+
+## 输出产物
+
+运行后会生成以下文件:
+
+| 文件 | 说明 |
+| --- | --- |
+| `*_bootstrap_report.md` | 初始化报告,展示目标项目、生成文件、分支计划和 Issue 计划 |
+| `*_summary.md` | 可发布到 Issue 的初始化摘要 |
+| `*_manifest.json` | 结构化初始化清单 |
+| `*_files.json` | 生成文件内容包 |
+| `command_log_*.json` | gitlink-cli 命令计划或执行结果 |
+
+固定示例输出保存在 `examples/demo_outputs/`,用于评审快速查看产物格式。`outputs/` 是运行时目录,可通过脚本重新生成。
+
+## 工程边界
+
+- 主实现使用 Go,便于与 `gitlink-cli` 主仓库技术栈保持一致。
+- 默认 dry-run,避免演示阶段误写远端仓库。
+- 真实写入必须显式传入 `-Apply`。
+- 命令日志记录每个 CLI 调用的状态,便于复盘和排查。
+- 测试覆盖文件生成、CLI 编排、Issue 内容生成、幂等跳过判断和输出 manifest。
+
+## 赛题价值
+
+该工作流不是单个命令封装,而是面向真实开源项目启动流程的组合式方案。它把 `gitlink-cli` 的仓库、分支、Issue 和评论能力整合为一个可复现闭环,符合子赛题三对“串联多个 CLI 命令或 Skill 调用”“真实项目运行展示”“工作流说明文档和架构图”的要求。
From 5711aa36544e297a9d828c3ae979ea4cfee094f3 Mon Sep 17 00:00:00 2001
From: puygob236 <3115250840@qq.com>
Date: Thu, 4 Jun 2026 14:45:46 +0800
Subject: [PATCH 4/9] Update architecture.md
---
.../workflows/project-bootstrap-automation/docs/architecture.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/examples/workflows/project-bootstrap-automation/docs/architecture.md b/examples/workflows/project-bootstrap-automation/docs/architecture.md
index 851735d..164be7a 100644
--- a/examples/workflows/project-bootstrap-automation/docs/architecture.md
+++ b/examples/workflows/project-bootstrap-automation/docs/architecture.md
@@ -1,4 +1,4 @@
-# 架构说明
+# 架构说明
本工作流采用“配置输入 -> 资产生成 -> CLI 编排 -> GitLink 落地 -> 结果归档”的五段式架构。正式架构图见 `docs/assets/bootstrap-architecture.svg`。
From 82ac20ae74c62e07099f09b8eac4c4470ca08974 Mon Sep 17 00:00:00 2001
From: Jiachen Li <3115250840@qq.com>
Date: Thu, 4 Jun 2026 14:57:42 +0800
Subject: [PATCH 5/9] docs(workflows): fix project bootstrap architecture
layout
---
.../docs/assets/bootstrap-architecture.svg | 57 ++++++++++---------
1 file changed, 29 insertions(+), 28 deletions(-)
diff --git a/examples/workflows/project-bootstrap-automation/docs/assets/bootstrap-architecture.svg b/examples/workflows/project-bootstrap-automation/docs/assets/bootstrap-architecture.svg
index df0dbd9..ce999d8 100644
--- a/examples/workflows/project-bootstrap-automation/docs/assets/bootstrap-architecture.svg
+++ b/examples/workflows/project-bootstrap-automation/docs/assets/bootstrap-architecture.svg
@@ -38,22 +38,22 @@
GitLink Project Bootstrap Automation
Configuration-driven project initialization, CLI orchestration, GitLink execution, and reproducible evidence
-
-
-
- Input
+
+
+
+ Input
Project metadata
Repository target
Branches
Initial issues
-
+
sample_project.json
-
-
-
- Assets
+
+
+
+ Assets
README
LICENSE
Go CI
@@ -61,7 +61,7 @@
ROADMAP
-
+
CLI Orchestration
@@ -75,19 +75,20 @@
issue +create
-
-
-
- GitLink
+
+
+
+ GitLink
Repository state
- Collaboration branches
- Bootstrap issues
- Issue comment
-
- Apply mode only writes remotely
+ Collaboration
+ branches
+ Bootstrap issues
+ Issue comment
+
+ Apply mode writes remotely
-
+
Evidence
@@ -98,13 +99,13 @@
command log
-
-
-
-
+
+
+
+
-
- Dry-run records planned commands; apply mode executes and records results
+
+ Dry-run records planned commands; apply mode executes and records results
@@ -114,7 +115,7 @@
7 default CLI calls
Reproducible dry-run
-
- Validated on real GitLink repository
+
+ Validated on real GitLink repo
From 63539872671ade3f461ab6a65c7cd99c00073944 Mon Sep 17 00:00:00 2001
From: Jiachen Li <3115250840@qq.com>
Date: Thu, 4 Jun 2026 15:18:50 +0800
Subject: [PATCH 6/9] docs(workflows): use rendered project bootstrap
architecture figure
---
.../project-bootstrap-automation/README.md | 2 +-
.../docs/architecture.md | 4 ++--
.../docs/assets/bootstrap-architecture.png | Bin 0 -> 1142106 bytes
.../docs/submission-checklist.md | 2 +-
4 files changed, 4 insertions(+), 4 deletions(-)
create mode 100644 examples/workflows/project-bootstrap-automation/docs/assets/bootstrap-architecture.png
diff --git a/examples/workflows/project-bootstrap-automation/README.md b/examples/workflows/project-bootstrap-automation/README.md
index 2e309ed..be12237 100644
--- a/examples/workflows/project-bootstrap-automation/README.md
+++ b/examples/workflows/project-bootstrap-automation/README.md
@@ -13,7 +13,7 @@
- `examples/demo_outputs/`:固定示例输出
- `docs/workflow-spec.md`:工作流说明文档
- `docs/architecture.md`:架构与流程说明
-- `docs/assets/bootstrap-architecture.svg`:架构图
+- `docs/assets/bootstrap-architecture.png`:架构图
- `docs/quickstart.md`:最短复现路径
- `docs/runbook.md`:运行手册
- `docs/verification.md`:验证记录
diff --git a/examples/workflows/project-bootstrap-automation/docs/architecture.md b/examples/workflows/project-bootstrap-automation/docs/architecture.md
index 164be7a..ac4a575 100644
--- a/examples/workflows/project-bootstrap-automation/docs/architecture.md
+++ b/examples/workflows/project-bootstrap-automation/docs/architecture.md
@@ -1,8 +1,8 @@
# 架构说明
-本工作流采用“配置输入 -> 资产生成 -> CLI 编排 -> GitLink 落地 -> 结果归档”的五段式架构。正式架构图见 `docs/assets/bootstrap-architecture.svg`。
+本工作流采用“配置输入 -> 资产生成 -> CLI 编排 -> GitLink 落地 -> 结果归档”的五段式架构。正式架构图见 `docs/assets/bootstrap-architecture.png`。
-
+
```mermaid
flowchart LR
diff --git a/examples/workflows/project-bootstrap-automation/docs/assets/bootstrap-architecture.png b/examples/workflows/project-bootstrap-automation/docs/assets/bootstrap-architecture.png
new file mode 100644
index 0000000000000000000000000000000000000000..8791309079db1b16b9e2713d4cba5a2e9fa0e068
GIT binary patch
literal 1142106
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