feat: launch official product site and one-click installers

This commit is contained in:
2026-03-06 22:28:20 +08:00
parent ceb3557dde
commit b6f46a07a0
4 changed files with 472 additions and 182 deletions

View File

@@ -1,55 +1,59 @@
# DevOps Skills # DevOps Skills
面向 **Gitea Issue 驱动交付** 的技能仓库,内置 `gitea-issue-devops-agent`,支持: Issue-Driven DevOps 平台技能仓库,核心产品是 `gitea-issue-devops-agent`
- 根据 issue 指定分支执行修复与提测 它把交付流程固化为:
- 分支级预览环境槽位分配与回收
- 按变更范围智能部署(避免无意义重启服务端)
- 自动 / 半自动 / 全人工 三种协作模式
- 图片类 issue 证据抓取与审阅
## 文档网页 `Issue -> Branch -> Preview Slot -> Test Loop -> Human-Confirmed Merge`
- 站点文件:`site/index.html` ## 公网产品页
- 仓库内查看:`https://fun-md.com/Fun_MD/devops-skills/src/branch/main/site/index.html`
- 原始页面`https://fun-md.com/Fun_MD/devops-skills/raw/branch/main/site/index.html` - 产品官网`https://fun-md.com/Fun_MD/devops-skills/raw/branch/main/site/index.html`
- 仓库入口:`https://fun-md.com/Fun_MD/devops-skills`
## 一键安装
Linux:
```bash
curl -fsSL https://fun-md.com/Fun_MD/devops-skills/raw/branch/main/install/install.sh | bash
```
macOS:
```bash
curl -fsSL https://fun-md.com/Fun_MD/devops-skills/raw/branch/main/install/install.sh | bash
```
Windows (PowerShell):
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -Command "iwr -useb https://fun-md.com/Fun_MD/devops-skills/raw/branch/main/install/install.ps1 | iex"
```
安装目标目录:
- `~/.codex/skills/gitea-issue-devops-agent`
## 技能路径 ## 技能路径
- `skills/gitea-issue-devops-agent/SKILL.md` - `skills/gitea-issue-devops-agent/SKILL.md`
## 安装 ## 核心能力
```bash - 三种执行模式:`automatic` / `semi-automatic` / `manual`
git clone https://fun-md.com/Fun_MD/devops-skills.git - issue 图片证据抓取(含 attachments/assets 三路兜底)
cd devops-skills - 按变更范围部署(`skip` / `client_only` / `server_only` / `full_stack` / `infra_only`
mkdir -p ~/.codex/skills - 预览槽位池分配与自动回收TTL + 关闭释放)
cp -r skills/gitea-issue-devops-agent ~/.codex/skills/ - 最终代码合并必须人工确认
```
Windows PowerShell:
```powershell
git clone https://fun-md.com/Fun_MD/devops-skills.git
cd devops-skills
New-Item -ItemType Directory -Force $HOME\.codex\skills | Out-Null
Copy-Item .\skills\gitea-issue-devops-agent $HOME\.codex\skills\gitea-issue-devops-agent -Recurse -Force
```
## 核心脚本 ## 核心脚本
- `skills/gitea-issue-devops-agent/scripts/issue_audit.py` - `skills/gitea-issue-devops-agent/scripts/issue_audit.py`
- issue 拉取、质量评分、去重、附件/图片抓取
- `skills/gitea-issue-devops-agent/scripts/change_scope.py` - `skills/gitea-issue-devops-agent/scripts/change_scope.py`
- 识别 `skip/client_only/server_only/full_stack/infra_only`
- `skills/gitea-issue-devops-agent/scripts/preview_slot_allocator.py` - `skills/gitea-issue-devops-agent/scripts/preview_slot_allocator.py`
- 分支预览槽位分配、复用、释放、TTL 回收
## 工作流模板 ## .gitea/workflows 模板
仓库提供 `.gitea/workflows` 示例,可直接接入:
- `.gitea/workflows/issue-branch-preview.yml` - `.gitea/workflows/issue-branch-preview.yml`
- `.gitea/workflows/preview-slot-reclaim.yml` - `.gitea/workflows/preview-slot-reclaim.yml`
用于实现“分配槽位 + 按变更范围部署 + 自动回收”。

38
install/install.ps1 Normal file
View File

@@ -0,0 +1,38 @@
param(
[string]$RepoUrl = "https://fun-md.com/Fun_MD/devops-skills.git",
[string]$CodexHome = "$HOME\.codex"
)
$ErrorActionPreference = "Stop"
$skillName = "gitea-issue-devops-agent"
$targetDir = Join-Path $CodexHome "skills\$skillName"
$tmpRoot = Join-Path $env:TEMP ("devops-skills-" + [Guid]::NewGuid().ToString("N"))
try {
if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
throw "[install] git is required but not found."
}
Write-Host "[install] downloading $skillName from $RepoUrl"
git clone --depth 1 $RepoUrl $tmpRoot | Out-Null
$sourceDir = Join-Path $tmpRoot "skills\$skillName"
if (-not (Test-Path $sourceDir)) {
throw "[install] skill directory not found in repository."
}
New-Item -ItemType Directory -Force (Join-Path $CodexHome "skills") | Out-Null
if (Test-Path $targetDir) {
Remove-Item -Recurse -Force $targetDir
}
Copy-Item -Path $sourceDir -Destination $targetDir -Recurse -Force
Write-Host "[install] done"
Write-Host "[install] installed path: $targetDir"
}
finally {
if (Test-Path $tmpRoot) {
Remove-Item -Recurse -Force $tmpRoot
}
}

33
install/install.sh Normal file
View File

@@ -0,0 +1,33 @@
#!/usr/bin/env bash
set -euo pipefail
REPO_URL="${REPO_URL:-https://fun-md.com/Fun_MD/devops-skills.git}"
SKILL_NAME="gitea-issue-devops-agent"
CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
TARGET_DIR="${CODEX_HOME}/skills/${SKILL_NAME}"
TMP_DIR="$(mktemp -d)"
cleanup() {
rm -rf "$TMP_DIR"
}
trap cleanup EXIT
if ! command -v git >/dev/null 2>&1; then
echo "[install] git is required but not found."
exit 1
fi
echo "[install] downloading ${SKILL_NAME} from ${REPO_URL}"
git clone --depth 1 "$REPO_URL" "$TMP_DIR/repo" >/dev/null 2>&1
if [ ! -d "$TMP_DIR/repo/skills/${SKILL_NAME}" ]; then
echo "[install] skill directory not found in repository."
exit 1
fi
mkdir -p "${CODEX_HOME}/skills"
rm -rf "$TARGET_DIR"
cp -R "$TMP_DIR/repo/skills/${SKILL_NAME}" "$TARGET_DIR"
echo "[install] done"
echo "[install] installed path: ${TARGET_DIR}"

View File

@@ -3,202 +3,417 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Gitea Issue DevOps Agent</title> <title>Issue-Driven DevOps Agent | Branch-Scoped Delivery Platform</title>
<style> <style>
:root { :root {
--bg: #f4f7fb; --bg-0: #070b18;
--panel: #ffffff; --bg-1: #101936;
--ink: #0f172a; --bg-2: #13274f;
--muted: #475569; --line: #30406b;
--brand: #0ea5e9; --text: #ecf3ff;
--brand-2: #14b8a6; --text-soft: #b9c7e6;
--line: #dbe5ef; --brand: #36d1ff;
--ok: #16a34a; --brand-2: #56f6c8;
--warn: #ffd86b;
--panel: rgba(16, 25, 54, 0.76);
--panel-strong: rgba(10, 17, 38, 0.82);
--ok: #56f6c8;
--danger: #ff7f8f;
--shadow: 0 20px 60px rgba(3, 8, 26, 0.45);
} }
* { box-sizing: border-box; } * { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body { body {
margin: 0; color: var(--text);
font-family: "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; font-family: "Space Grotesk", "PingFang SC", "Microsoft YaHei", sans-serif;
color: var(--ink);
background: background:
radial-gradient(circle at 90% 0%, #d9f6ff 0%, transparent 40%), radial-gradient(circle at 0% 0%, rgba(54, 209, 255, 0.22), transparent 30%),
radial-gradient(circle at 10% 10%, #e3fff4 0%, transparent 30%), radial-gradient(circle at 100% 10%, rgba(86, 246, 200, 0.18), transparent 28%),
var(--bg); linear-gradient(140deg, var(--bg-0) 0%, var(--bg-1) 42%, var(--bg-2) 100%);
line-height: 1.6; line-height: 1.58;
} }
.wrap { .page {
max-width: 1050px; max-width: 1140px;
margin: 0 auto; margin: 0 auto;
padding: 32px 20px 64px; padding: 24px 20px 80px;
}
.nav {
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
margin-bottom: 20px;
}
.logo {
display: inline-flex;
align-items: center;
gap: 10px;
font-weight: 700;
font-size: 15px;
letter-spacing: 0.2px;
}
.logo-dot {
width: 12px;
height: 12px;
border-radius: 50%;
background: linear-gradient(120deg, var(--brand), var(--brand-2));
box-shadow: 0 0 0 5px rgba(86, 246, 200, 0.18);
}
.nav-links {
display: flex;
gap: 14px;
flex-wrap: wrap;
}
.nav a {
color: var(--text-soft);
text-decoration: none;
border: 1px solid transparent;
padding: 8px 12px;
border-radius: 999px;
}
.nav a:hover {
color: var(--text);
border-color: var(--line);
background: rgba(255, 255, 255, 0.05);
} }
.hero { .hero {
background: linear-gradient(125deg, #0f172a 0%, #0b3f63 45%, #0d7f86 100%); border: 1px solid var(--line);
color: #f8fafc; background:
border-radius: 20px; linear-gradient(145deg, rgba(86, 246, 200, 0.12), transparent 35%),
padding: 28px 28px 20px; linear-gradient(325deg, rgba(54, 209, 255, 0.18), transparent 40%),
box-shadow: 0 22px 55px rgba(2, 20, 38, 0.35); var(--panel-strong);
border-radius: 24px;
padding: 34px 28px 28px;
box-shadow: var(--shadow);
position: relative;
overflow: hidden;
} }
.hero h1 { margin: 0 0 10px; font-size: clamp(26px, 4vw, 38px); } .hero::after {
.hero p { margin: 0; max-width: 880px; color: #dceafd; } content: "";
.chips { margin-top: 14px; display: flex; flex-wrap: wrap; gap: 10px; } position: absolute;
.chip { right: -60px;
background: rgba(255,255,255,0.14); top: -65px;
border: 1px solid rgba(255,255,255,0.24); width: 180px;
border-radius: 999px; height: 180px;
padding: 6px 12px; background: radial-gradient(circle, rgba(86, 246, 200, 0.35), transparent 70%);
font-size: 13px; pointer-events: none;
}
.hero h1 {
margin: 0 0 10px;
font-size: clamp(30px, 4.4vw, 52px);
line-height: 1.05;
letter-spacing: -0.6px;
max-width: 880px;
}
.hero p {
margin: 0;
font-size: clamp(15px, 2.2vw, 19px);
max-width: 880px;
color: var(--text-soft);
}
.hero-cta {
margin-top: 18px;
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.btn {
border: 1px solid var(--line);
border-radius: 12px;
padding: 10px 14px;
color: var(--text);
text-decoration: none;
font-size: 14px;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn.primary {
border-color: transparent;
background: linear-gradient(120deg, #2db6ff, #49f2ca);
color: #06253a;
font-weight: 700;
}
.btn:hover { filter: brightness(1.06); }
.stats {
margin-top: 16px;
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 10px;
}
.stat {
border: 1px solid var(--line);
border-radius: 14px;
padding: 12px;
background: rgba(18, 30, 60, 0.58);
}
.stat .v {
font-size: 24px;
font-weight: 700;
letter-spacing: -0.3px;
}
.stat .k {
color: var(--text-soft);
font-size: 12px;
margin-top: 2px;
} }
section { section {
background: var(--panel);
border: 1px solid var(--line);
border-radius: 16px;
margin-top: 16px; margin-top: 16px;
border: 1px solid var(--line);
border-radius: 18px;
padding: 20px; padding: 20px;
background: var(--panel);
box-shadow: var(--shadow);
} }
h2 { margin: 0 0 12px; font-size: 22px; } h2 {
h3 { margin: 18px 0 8px; font-size: 18px; } margin: 0 0 12px;
p, li { color: var(--muted); } font-size: 26px;
ul { margin: 8px 0 0 20px; padding: 0; } line-height: 1.12;
code, pre { letter-spacing: -0.25px;
font-family: "Cascadia Code", Consolas, Menlo, monospace;
font-size: 13px;
} }
pre { h3 {
margin: 10px 0 0; margin: 0 0 8px;
background: #0f172a; font-size: 18px;
color: #d9ecff; line-height: 1.2;
border-radius: 12px;
padding: 12px;
overflow: auto;
border: 1px solid #263142;
} }
.cards { p { margin: 0; color: var(--text-soft); }
.value-grid, .install-grid, .tool-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
gap: 12px; gap: 12px;
} }
.value-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.install-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.tool-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.card { .card {
border: 1px solid #385082;
background: rgba(20, 34, 68, 0.82);
border-radius: 14px;
padding: 14px;
}
.card p { margin-top: 4px; }
.flow {
display: grid;
gap: 10px;
grid-template-columns: repeat(5, minmax(0, 1fr));
}
.step {
border: 1px dashed #4f6796;
border-radius: 12px;
padding: 10px;
background: rgba(20, 34, 68, 0.55);
}
.step strong {
display: inline-block;
margin-bottom: 6px;
color: #dff5ff;
}
.step p { font-size: 13px; }
.install-card code, pre, .tool-card code {
font-family: "Cascadia Code", Consolas, Menlo, monospace;
font-size: 12.8px;
}
pre {
margin: 8px 0 0;
border: 1px solid #415983;
border-radius: 10px;
padding: 10px;
overflow: auto;
background: #081126;
color: #dcf3ff;
white-space: pre-wrap;
word-break: break-all;
}
.copy {
margin-top: 8px;
border: 1px solid #4e6899;
background: rgba(255, 255, 255, 0.03);
color: var(--text);
border-radius: 8px;
padding: 6px 10px;
cursor: pointer;
font-size: 12px;
}
.copy.ok { border-color: #4fcd99; color: var(--ok); }
.list {
margin: 10px 0 0;
padding: 0;
list-style: none;
display: grid;
gap: 8px;
}
.list li {
border: 1px solid #3b4f78;
border-radius: 10px;
padding: 9px 10px;
color: #d2e0f9;
background: rgba(15, 25, 49, 0.7);
}
.tagline {
margin-top: 10px;
color: var(--warn);
font-weight: 600;
}
.footer {
margin-top: 16px;
border: 1px solid var(--line); border: 1px solid var(--line);
border-radius: 14px; border-radius: 14px;
padding: 14px; padding: 14px;
background: linear-gradient(180deg, #fbfeff 0%, #f7fbff 100%); background: rgba(6, 11, 25, 0.66);
display: flex;
justify-content: space-between;
gap: 10px;
flex-wrap: wrap;
} }
.badge { .footer a {
display: inline-block; color: var(--brand);
padding: 4px 8px; text-decoration: none;
border-radius: 999px; font-weight: 600;
font-size: 12px; }
color: #065f46; .footer a:hover { text-decoration: underline; }
background: #d1fae5; @media (max-width: 1040px) {
border: 1px solid #a7f3d0; .stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.value-grid, .install-grid, .tool-grid { grid-template-columns: 1fr; }
.flow { grid-template-columns: 1fr; }
} }
a { color: #0369a1; text-decoration: none; }
a:hover { text-decoration: underline; }
.ok { color: var(--ok); font-weight: 600; }
</style> </style>
</head> </head>
<body> <body>
<main class="wrap"> <main class="page">
<nav class="nav">
<div class="logo"><span class="logo-dot"></span> Issue-Driven DevOps Agent</div>
<div class="nav-links">
<a href="#install">安装</a>
<a href="#tools">工具</a>
<a href="#workflow">工作流</a>
</div>
</nav>
<header class="hero"> <header class="hero">
<h1>Gitea Issue DevOps Agent</h1> <h1> Issue 变成可追踪、可复用、可规模化的交付流水线</h1>
<p> <p>
一个把 <strong>Issue → Branch → Preview Env → 测试闭环</strong> 固化到技能与脚本中的交付方案。 我们不是“会修 bug 的脚本”,而是一个面向真实研发组织的 DevOps 交付平台:
核心目标是提升交付速度,同时避免“每个分支都全量起服务”的资源浪费 <strong>Issue → Branch → Preview Slot → 提测闭环</strong>,并且始终保留工程师对最终合并的控制权
</p> </p>
<div class="chips"> <div class="hero-cta">
<span class="chip">自动 / 半自动 / 全人工</span> <a class="btn primary" href="https://fun-md.com/Fun_MD/devops-skills" target="_blank" rel="noopener noreferrer">访问仓库</a>
<span class="chip">Issue 图片证据抓取</span> <a class="btn" href="#install">一键安装</a>
<span class="chip">变更范围智能部署</span> <a class="btn" href="../skills/gitea-issue-devops-agent/SKILL.md">查看 Skill 规范</a>
<span class="chip">槽位池自动回收</span> </div>
<div class="stats">
<article class="stat"><div class="v">3</div><div class="k">执行模式(自动/半自动/人工)</div></article>
<article class="stat"><div class="v">5</div><div class="k">部署范围策略skip→full_stack</div></article>
<article class="stat"><div class="v">1:1</div><div class="k">Issue/Branch/Preview Slot 绑定</div></article>
<article class="stat"><div class="v">24h+</div><div class="k">可配置 TTL 自动回收</div></article>
</div> </div>
</header> </header>
<section> <section id="value">
<h2>核心价值</h2> <h2>核心价值</h2>
<div class="cards"> <div class="value-grid">
<article class="card"> <article class="card">
<h3>1. 分支隔离提测</h3> <h3>分支隔离,主干稳定</h3>
<p>每个 issue 绑定分支预览槽位,主干环境保持稳定,避免相互覆盖</p> <p>每个 issue 分配独立分支预览槽位,不再发生“提测相互覆盖”,主干环境用于稳定回归</p>
</article> </article>
<article class="card"> <article class="card">
<h3>2. 资源按需分配</h3> <h3>资源智能节流</h3>
<p>根据变更范围判断 <code>client_only/server_only/full_stack</code>,不变更服务端就不重启服务端。</p> <p>根据代码变更范围自动判定部署策略。仅前端改动时不重启服务端,直接复用共享后端。</p>
</article> </article>
<article class="card"> <article class="card">
<h3>3. 可审计闭环</h3> <h3>证据驱动闭环</h3>
<p>每次提测都可回溯到 commit、测试结果、环境 URL、验证步骤,且合并始终由工程师人工确认</p> <p>自动沉淀 commit、测试结果、环境链接、验证步骤。Issue 可关闭、可回溯、可审计</p>
</article>
</div>
<p class="tagline">这是一套用于长期演进的研发基础设施,不是临时脚本集合。</p>
</section>
<section id="workflow">
<h2>工作流拓扑</h2>
<div class="flow">
<div class="step"><strong>1. 引导连接</strong><p>输入 repo_url、api_key、mode完成连通性校验。</p></div>
<div class="step"><strong>2. 质量审计</strong><p>拉取 issue 与图片附件,做质量评分与去重分组。</p></div>
<div class="step"><strong>3. 分支执行</strong><p>严格在 issue 指定分支改动,保留变更可追踪性。</p></div>
<div class="step"><strong>4. 按范围部署</strong><p>change_scope 自动判断 skip/client/server/full_stack。</p></div>
<div class="step"><strong>5. 自动回收</strong><p>槽位 TTL + 关闭释放,减少预览环境资源占用。</p></div>
</div>
<ul class="list">
<li>工作流模板:<code>.gitea/workflows/issue-branch-preview.yml</code></li>
<li>回收模板:<code>.gitea/workflows/preview-slot-reclaim.yml</code></li>
<li>合并策略:所有模式下都要求工程师人工确认最终合并</li>
</ul>
</section>
<section id="install">
<h2>一键安装Windows / macOS / Linux</h2>
<p>以下命令会把技能安装到本机 <code>~/.codex/skills/gitea-issue-devops-agent</code></p>
<div class="install-grid">
<article class="card install-card">
<h3>Linux</h3>
<pre id="cmd-linux">curl -fsSL https://fun-md.com/Fun_MD/devops-skills/raw/branch/main/install/install.sh | bash</pre>
<button class="copy" data-target="cmd-linux">复制命令</button>
</article>
<article class="card install-card">
<h3>macOS</h3>
<pre id="cmd-macos">curl -fsSL https://fun-md.com/Fun_MD/devops-skills/raw/branch/main/install/install.sh | bash</pre>
<button class="copy" data-target="cmd-macos">复制命令</button>
</article>
<article class="card install-card">
<h3>Windows (PowerShell)</h3>
<pre id="cmd-win">powershell -NoProfile -ExecutionPolicy Bypass -Command "iwr -useb https://fun-md.com/Fun_MD/devops-skills/raw/branch/main/install/install.ps1 | iex"</pre>
<button class="copy" data-target="cmd-win">复制命令</button>
</article> </article>
</div> </div>
</section> </section>
<section> <section id="tools">
<h2>安装指南</h2> <h2>核心工具</h2>
<h3>1) 获取技能仓库</h3> <div class="tool-grid">
<pre><code>git clone https://fun-md.com/Fun_MD/devops-skills.git <article class="card tool-card">
cd devops-skills</code></pre> <h3>issue_audit.py</h3>
<h3>2) 安装到 Codex skills</h3> <p>抓取 issue、评论和图片附件完成质量评分、去重与回归候选识别。</p>
<pre><code># Linux / macOS <pre>python skills/gitea-issue-devops-agent/scripts/issue_audit.py --base-url https://fun-md.com --repo FunMD/document-collab --token &lt;TOKEN&gt; --state all --download-attachments --output-dir .tmp/issue-audit</pre>
mkdir -p ~/.codex/skills </article>
cp -r skills/gitea-issue-devops-agent ~/.codex/skills/ <article class="card tool-card">
<h3>change_scope.py</h3>
# Windows PowerShell <p>输出部署范围建议,决定是否需要服务端重启。</p>
New-Item -ItemType Directory -Force $HOME\.codex\skills | Out-Null <pre>python skills/gitea-issue-devops-agent/scripts/change_scope.py --repo-path . --base-ref origin/main --head-ref HEAD</pre>
Copy-Item .\skills\gitea-issue-devops-agent $HOME\.codex\skills\gitea-issue-devops-agent -Recurse -Force</code></pre> </article>
<h3>3) 首次引导参数</h3> <article class="card tool-card">
<ul> <h3>preview_slot_allocator.py</h3>
<li><code>repo_url</code>(仓库地址)</li> <p>管理 preview 槽位分配、复用、释放、TTL 回收。</p>
<li><code>api_key</code>(具备 issue 读写权限)</li> <pre>python skills/gitea-issue-devops-agent/scripts/preview_slot_allocator.py --state-file .tmp/preview-slots.json --slots preview-a,preview-b --repo FunMD/document-collab --issue 48 --branch dev --ttl-hours 24 --url-template https://{slot}.qa.example.com --evict-oldest</pre>
<li><code>mode</code><code>automatic</code> / <code>semi-automatic</code> / <code>manual</code></li> </article>
<li>可选:<code>reviewers</code><code>test_entry</code><code>deploy_env</code><code>health_endpoint</code><code>min_quality_score</code></li> </div>
</ul>
</section> </section>
<section> <footer class="footer">
<h2>工具使用说明</h2> <span>Skill Path: <code>skills/gitea-issue-devops-agent/SKILL.md</code></span>
<h3>issue_audit.py拉取 issue + 图片证据)</h3> <span><a href="https://fun-md.com/Fun_MD/devops-skills/raw/branch/main/site/index.html" target="_blank" rel="noopener noreferrer">公网页面链接</a></span>
<pre><code>python skills/gitea-issue-devops-agent/scripts/issue_audit.py \ </footer>
--base-url https://fun-md.com \
--repo FunMD/document-collab \
--token &lt;GITEA_TOKEN&gt; \
--state all \
--download-attachments \
--output-dir .tmp/issue-audit</code></pre>
<h3>change_scope.py按改动范围决策部署</h3>
<pre><code>python skills/gitea-issue-devops-agent/scripts/change_scope.py \
--repo-path . \
--base-ref origin/main \
--head-ref HEAD</code></pre>
<h3>preview_slot_allocator.py分配 / 复用 / 释放槽位)</h3>
<pre><code>python skills/gitea-issue-devops-agent/scripts/preview_slot_allocator.py \
--state-file .tmp/preview-slots.json \
--slots preview-a,preview-b \
--repo FunMD/document-collab \
--issue 48 \
--branch dev \
--ttl-hours 24 \
--url-template https://{slot}.qa.example.com \
--evict-oldest</code></pre>
</section>
<section>
<h2>.gitea/workflows 接入</h2>
<p>
本仓库已包含示例工作流:<code>.gitea/workflows/issue-branch-preview.yml</code>
<code>.gitea/workflows/preview-slot-reclaim.yml</code>,用于完成以下自动化链路:
</p>
<ul>
<li>push 到 issue 分支后:自动分配槽位 + 变更范围识别 + 选择性部署</li>
<li>issue 关闭 / 定时任务:自动释放或回收过期槽位</li>
</ul>
<p class="ok">建议先在测试仓库验证工作流变量后再推广到生产仓库。</p>
</section>
<section>
<span class="badge">Skill Path</span>
<p><a href="../skills/gitea-issue-devops-agent/SKILL.md">skills/gitea-issue-devops-agent/SKILL.md</a></p>
</section>
</main> </main>
<script>
const copyButtons = document.querySelectorAll(".copy");
copyButtons.forEach((btn) => {
btn.addEventListener("click", async () => {
const targetId = btn.getAttribute("data-target");
const el = document.getElementById(targetId);
if (!el) return;
try {
await navigator.clipboard.writeText(el.textContent.trim());
btn.classList.add("ok");
btn.textContent = "已复制";
setTimeout(() => {
btn.classList.remove("ok");
btn.textContent = "复制命令";
}, 1400);
} catch (err) {
btn.textContent = "复制失败";
setTimeout(() => {
btn.textContent = "复制命令";
}, 1400);
}
});
});
</script>
</body> </body>
</html> </html>