refactor: split backend into standalone server project

Remove embedded frontend and workflow editor assets, add standalone API deployment configuration, and retain the current backend service updates.
This commit is contained in:
t
2026-08-20 21:46:55 +08:00
parent 954a3fe8d9
commit 3d47227fbd
612 changed files with 515 additions and 97334 deletions
-225
View File
@@ -1,225 +0,0 @@
---
name: release-version
description: Use when preparing or publishing a new semantic version release, updating bilingual changelogs, creating Git tags, or publishing GitHub/Gitee release pages for this repository.
---
# Release Version
## Overview
Create releases with a strict `vx.y.z` tag, produce bilingual changelog entries from actual Git history, publish both the `docs` submodule update and repository tag, and create the GitHub and Gitee Release page entries in one controlled workflow.
Run the workflow from the repository root. Read [references/changelog-style.md](references/changelog-style.md) before drafting the human-facing update notes.
## Workflow
1. Validate the requested version.
2. Inspect the repository and determine the comparison range.
3. Draft bilingual changelog entries from the actual diff.
4. Commit and push the `docs` submodule.
5. Commit the parent repository update if the submodule pointer changed.
6. Create and push the annotated tag.
7. Create GitHub and Gitee Release page entries for the tag.
8. Verify both remote tags and both Release pages.
Do not skip the repository inspection step. Release notes must come from the real diff between tags, not from guesswork.
## Validate The Version
- Accept only tags that match `^v\d+\.\d+\.\d+$`.
- Reject date-style tags such as `v20260414`.
- Confirm the target tag does not already exist locally or on any configured remote.
- Prefer the latest reachable semver tag as the previous release tag.
- If no earlier semver tag exists, fall back to the latest reachable tag of any format and state that fallback in the changelog drafting notes.
Use the helper script first:
```bash
python3 .codex/skills/release-version/scripts/collect_release_context.py \
--repo . \
--tag v1.2.3
```
If the caller already specifies the previous tag, pass it explicitly:
```bash
python3 .codex/skills/release-version/scripts/collect_release_context.py \
--repo . \
--tag v1.2.3 \
--previous-tag v1.2.2
```
If the repository-local helper is unavailable, fall back to `~/.codex/skills/release-version/scripts/collect_release_context.py`.
## Inspect The Repository
- Check `git status --short` in the parent repo.
- Check `git -C docs status --short` in the `docs` submodule.
- Read the JSON output of `collect_release_context.py`.
- Use the commit list, changed files, and insertions/deletions to decide what is user-visible.
- Prioritize behavior changes, new features, fixes, migrations, API changes, configuration changes, and documentation changes that matter to adopters.
- Ignore pure formatting churn unless it changes usage.
If the working tree contains unrelated changes that would be risky to include in the release, stop and ask the user before proceeding.
## Draft The Changelog
Update these files:
- `docs/zh/docs/changelog.md`
- `docs/en/docs/changelog.md`
Prepend a new entry using this exact structure:
```md
## ${tag} (${yyyy-MM-dd})
### 更新内容
${content}
### 发布地址
- Github: <https://github.com/huabeitech/agent-desk/releases/tag/${tag}>
- Gitee: <https://gitee.com/huabeitech/agent-desk/releases/tag/${tag}>
```
For the English file, keep the same links and heading level, but translate the section heading and content naturally:
```md
## ${tag} (${yyyy-MM-dd})
### Updates
${content}
### Release Links
- Github: <https://github.com/huabeitech/agent-desk/releases/tag/${tag}>
- Gitee: <https://gitee.com/huabeitech/agent-desk/releases/tag/${tag}>
```
Changelog writing rules:
- Write concise, user-facing summaries instead of raw commit subjects.
- Keep Chinese and English entries semantically aligned.
- Prefer 3-6 bullets unless the release is extremely small.
- Group related changes into a single bullet when that reads better.
- Mention compatibility-sensitive changes explicitly.
- If the comparison baseline is a non-semver fallback tag, note that in your private reasoning, not in the public changelog unless the user asks for it.
## Commit And Push The Docs Submodule
After editing the changelog files:
1. Run `git -C docs status --short`.
2. Review the diff with `git -C docs diff -- zh/docs/changelog.md en/docs/changelog.md`.
3. Commit inside the `docs` submodule with a focused message such as `docs: update changelog for v1.2.3`.
4. Push the `docs` submodule commit to its remote branch.
Branch rule:
- If `docs` is on a local branch, push that branch.
- If `docs` is detached, push `HEAD` to `origin/main` unless the repository clearly uses another default branch.
## Commit The Parent Repository
If the `docs` submodule pointer changed in the parent repository, commit it before tagging. Otherwise the release tag will not reference the new changelog revision.
Recommended flow:
```bash
git status --short
git add docs
git commit -m "chore: update docs submodule for v1.2.3"
```
Only include unrelated parent-repo changes if the user explicitly wants them in the release commit.
## Create And Push The Tag
Create an annotated tag after the repository state is ready:
```bash
git tag -a v1.2.3 -m "Release v1.2.3"
```
Push the commit branch first if needed, then push the tag to every configured remote that should publish releases:
```bash
git push github HEAD
git push origin HEAD
git push github v1.2.3
git push origin v1.2.3
```
Adjust the branch name if `HEAD` is not tracking the intended release branch.
## Create GitHub And Gitee Releases
Pushing tags is not enough. The release is incomplete until both Release pages exist:
- GitHub: `https://github.com/huabeitech/agent-desk/releases/tag/${tag}`
- Gitee: `https://gitee.com/huabeitech/agent-desk/releases/tag/${tag}`
Use the same concise release notes derived from the changelog. Prefer a bilingual body with Chinese first and English second.
Required credentials:
- GitHub: `GITHUB_TOKEN` or `GH_TOKEN` with access to `huabeitech/agent-desk` and permission to create releases. For a fine-grained PAT, use an organization-allowed lifetime and grant the repository at least `Contents: Read and write` plus `Metadata: Read`.
- Gitee: `GITEE_ACCESS_TOKEN` or `GITEE_TOKEN` with release write access to `huabeitech/agent-desk`.
Never print tokens in command output or final responses. If the user pastes a token into the conversation, use it only for the requested release operation and recommend rotation after use.
Build the release body from the new changelog entry, for example:
```bash
mkdir -p /tmp/agent-desk-release
awk 'BEGIN{p=0} /^## v1\.2\.3 /{p=1; next} /^## v[0-9]/{if(p) exit} p{print}' \
docs/zh/docs/changelog.md | sed '/^### 发布地址/,$d' > /tmp/agent-desk-release/v1.2.3-zh.md
awk 'BEGIN{p=0} /^## v1\.2\.3 /{p=1; next} /^## v[0-9]/{if(p) exit} p{print}' \
docs/en/docs/changelog.md | sed '/^### Release Links/,$d' > /tmp/agent-desk-release/v1.2.3-en.md
{
printf '## 更新内容\n\n'
sed '1,/^### 更新内容$/d' /tmp/agent-desk-release/v1.2.3-zh.md
printf '\n## Updates\n\n'
sed '1,/^### Updates$/d' /tmp/agent-desk-release/v1.2.3-en.md
} > /tmp/agent-desk-release/v1.2.3-release-body.md
```
Create the GitHub Release:
```bash
token="${GITHUB_TOKEN:-$GH_TOKEN}"
curl -sS -o /tmp/github_release_v1.2.3.json -w '%{http_code}' \
-X POST https://api.github.com/repos/huabeitech/agent-desk/releases \
-H "Authorization: Bearer ${token}" \
-H 'Accept: application/vnd.github+json' \
-H 'X-GitHub-Api-Version: 2022-11-28' \
-H 'Content-Type: application/json' \
-d @<(jq -n --rawfile body /tmp/agent-desk-release/v1.2.3-release-body.md \
'{tag_name:"v1.2.3", target_commitish:"main", name:"v1.2.3", body:$body, draft:false, prerelease:false}')
```
Create the Gitee Release:
```bash
token="${GITEE_ACCESS_TOKEN:-$GITEE_TOKEN}"
curl -sS -o /tmp/gitee_release_v1.2.3.json -w '%{http_code}' \
-X POST https://gitee.com/api/v5/repos/huabeitech/agent-desk/releases \
-H 'Content-Type: application/json' \
-d @<(jq -n --rawfile body /tmp/agent-desk-release/v1.2.3-release-body.md --arg token "${token}" \
'{access_token:$token, tag_name:"v1.2.3", target_commitish:"main", name:"v1.2.3", body:$body, prerelease:false}')
```
If creation returns `422`/already exists, fetch the existing release and verify it references the target tag before treating it as complete. If GitHub returns `Resource not accessible by personal access token`, inspect the API message and ask for a token that satisfies the organization policy and repository permissions.
## Final Verification
- Confirm `git rev-parse v1.2.3^{tag}` succeeds.
- Confirm `git ls-remote --tags github v1.2.3` and `git ls-remote --tags origin v1.2.3` show the new tag.
- Confirm `curl -sS -o /tmp/github_release_verify.json -w '%{http_code}' https://api.github.com/repos/huabeitech/agent-desk/releases/tags/v1.2.3` returns `200`.
- Confirm `curl -sS -o /tmp/gitee_release_verify.json -w '%{http_code}' https://gitee.com/api/v5/repos/huabeitech/agent-desk/releases/tags/v1.2.3` returns `200`.
- Confirm the `docs` submodule remote contains the changelog commit.
- Confirm both parent and `docs` working trees are clean.
- Summarize the previous tag used for comparison, the files updated, the commit hashes created, the remotes pushed, and the GitHub/Gitee Release URLs.
@@ -1,4 +0,0 @@
interface:
display_name: "Release Version"
short_description: "Create semver tags and changelog drafts"
default_prompt: "Use $release-version to create a new vx.y.z release, update changelogs, and push docs plus tags."
@@ -1,61 +0,0 @@
# Changelog Style
Use this guide when drafting `docs/zh/changelog.md` and `docs/en/changelog.md`.
## Goal
Turn Git history into short release notes that explain what changed for adopters.
## Keep
- New user-facing features.
- Bug fixes with clear impact.
- Breaking or compatibility-sensitive changes.
- API, configuration, deployment, model, workflow, or schema changes that affect usage.
- Important documentation updates when they unlock new workflows.
## Drop Or Compress
- Pure refactors with no visible effect.
- Formatting-only changes.
- Internal rename churn.
- Mechanical dependency updates unless they fix a real issue.
## Chinese Style
- Use concise bullets.
- Prefer product or workflow language over commit jargon.
- Start with the effect, then mention the area if needed.
- Keep terms consistent across bullets.
Example:
```md
- 优化会话列表查询与筛选逻辑,减少后台定位问题时的人工排查成本。
- 修复消息发送链路中的异常处理,避免部分失败场景下页面状态不同步。
```
## English Style
- Mirror the Chinese meaning instead of translating word by word.
- Use direct release-note phrasing.
- Prefer active wording and concrete impact.
Example:
```md
- Improved conversation list querying and filtering so operators can locate problem cases faster.
- Fixed error handling in the message send flow to prevent UI state from drifting after partial failures.
```
## Grouping Heuristics
- Merge multiple commits into one bullet when they deliver one outcome.
- Separate bullets when the audience or impact differs.
- Keep both language versions aligned in bullet count when possible.
## Before Finalizing
- Re-check that every bullet is supported by the diff.
- Remove statements that depend on assumptions you cannot verify from code, tests, or commits.
- Keep the notes short enough to scan in under a minute.
@@ -1,221 +0,0 @@
#!/usr/bin/env python3
"""Collect release context between a target tag and its comparison baseline."""
from __future__ import annotations
import argparse
import json
import re
import subprocess
import sys
from pathlib import Path
SEMVER_TAG_RE = re.compile(r"^v(\d+)\.(\d+)\.(\d+)$")
def run_git(
repo: Path,
args: list[str],
allow_failure: bool = False,
timeout_seconds: int = 15,
) -> str:
try:
result = subprocess.run(
["git", *args],
cwd=repo,
capture_output=True,
text=True,
check=False,
timeout=timeout_seconds,
)
except subprocess.TimeoutExpired:
if allow_failure:
return ""
raise RuntimeError(f"git command timed out: git {' '.join(args)}")
if result.returncode != 0 and not allow_failure:
raise RuntimeError(result.stderr.strip() or "git command failed")
return result.stdout.strip()
def parse_semver(tag: str) -> tuple[int, int, int] | None:
match = SEMVER_TAG_RE.match(tag)
if not match:
return None
return tuple(int(part) for part in match.groups())
def list_reachable_tags(repo: Path) -> list[str]:
output = run_git(repo, ["tag", "--merged", "HEAD"])
return [line.strip() for line in output.splitlines() if line.strip()]
def choose_previous_tag(repo: Path, tags: list[str], target_tag: str | None) -> tuple[str | None, str | None]:
semver_tags: list[tuple[tuple[int, int, int], str]] = []
other_tags: list[str] = []
target_semver = parse_semver(target_tag) if target_tag else None
for tag in tags:
if target_tag and tag == target_tag:
continue
parsed = parse_semver(tag)
if parsed is None:
other_tags.append(tag)
continue
if target_semver is not None and parsed >= target_semver:
continue
semver_tags.append((parsed, tag))
if semver_tags:
semver_tags.sort(key=lambda item: item[0], reverse=True)
return semver_tags[0][1], "semver"
if not other_tags:
return None, None
candidates: list[tuple[int, str]] = []
for tag in other_tags:
ts = run_git(repo, ["log", "-1", "--format=%ct", tag], allow_failure=True)
try:
candidates.append((int(ts), tag))
except ValueError:
continue
if not candidates:
return other_tags[-1], "fallback"
candidates.sort(reverse=True)
return candidates[0][1], "fallback"
def ensure_tag_absent(repo: Path, tag: str) -> None:
local = run_git(repo, ["tag", "--list", tag])
if local:
raise ValueError(f"target tag already exists locally: {tag}")
remotes_output = run_git(repo, ["remote"])
for remote in [line.strip() for line in remotes_output.splitlines() if line.strip()]:
remote_hit = run_git(
repo,
["ls-remote", "--tags", remote, tag],
allow_failure=True,
timeout_seconds=8,
)
if remote_hit:
raise ValueError(f"target tag already exists on remote {remote}: {tag}")
def ensure_tag_exists(repo: Path, tag: str) -> None:
hit = run_git(repo, ["rev-parse", "--verify", "--quiet", tag], allow_failure=True)
if not hit:
raise ValueError(f"previous tag does not exist locally: {tag}")
def get_commit_list(repo: Path, rev_range: str) -> list[dict[str, str]]:
output = run_git(repo, ["log", "--reverse", "--date=short", "--pretty=format:%H%x09%ad%x09%s", rev_range])
commits: list[dict[str, str]] = []
for line in output.splitlines():
commit_hash, date_str, subject = line.split("\t", 2)
commits.append(
{
"hash": commit_hash,
"short_hash": commit_hash[:7],
"date": date_str,
"subject": subject,
}
)
return commits
def get_changed_files(repo: Path, rev_range: str) -> list[str]:
output = run_git(repo, ["diff", "--name-only", rev_range])
return [line.strip() for line in output.splitlines() if line.strip()]
def get_numstat(repo: Path, rev_range: str) -> dict[str, int]:
output = run_git(repo, ["diff", "--numstat", rev_range])
changed_files = 0
insertions = 0
deletions = 0
for line in output.splitlines():
parts = line.split("\t")
if len(parts) < 3:
continue
added, removed = parts[0], parts[1]
changed_files += 1
if added.isdigit():
insertions += int(added)
if removed.isdigit():
deletions += int(removed)
return {
"changed_files": changed_files,
"insertions": insertions,
"deletions": deletions,
}
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--repo", default=".", help="Repository root. Defaults to current directory.")
parser.add_argument("--tag", help="Target release tag to validate, for example v1.2.3.")
parser.add_argument("--previous-tag", help="Explicit comparison baseline.")
return parser
def main() -> int:
args = build_parser().parse_args()
repo_path = Path(args.repo).expanduser().resolve()
if not (repo_path / ".git").exists():
print(json.dumps({"error": f"not a git repository: {repo_path}"}))
return 1
if args.tag and parse_semver(args.tag) is None:
print(json.dumps({"error": f"invalid tag format: {args.tag}", "expected": "vx.y.z"}))
return 1
try:
if args.tag:
ensure_tag_absent(repo_path, args.tag)
if args.previous_tag:
ensure_tag_exists(repo_path, args.previous_tag)
except ValueError as exc:
print(json.dumps({"error": str(exc)}))
return 1
tags = list_reachable_tags(repo_path)
previous_tag = args.previous_tag
previous_tag_source = "explicit" if previous_tag else None
if previous_tag is None:
previous_tag, previous_tag_source = choose_previous_tag(repo_path, tags, args.tag)
rev_range = "HEAD"
if previous_tag:
rev_range = f"{previous_tag}..HEAD"
try:
commits = get_commit_list(repo_path, rev_range)
changed_files = get_changed_files(repo_path, rev_range)
stats = get_numstat(repo_path, rev_range)
head_commit = run_git(repo_path, ["rev-parse", "HEAD"])
except RuntimeError as exc:
print(json.dumps({"error": str(exc)}))
return 1
payload: dict[str, object] = {
"repo": str(repo_path),
"target_tag": args.tag,
"previous_tag": previous_tag,
"previous_tag_source": previous_tag_source,
"rev_range": rev_range,
"head_commit": head_commit,
"commit_count": len(commits),
"commits": commits,
"changed_files": changed_files,
}
payload.update(stats)
print(json.dumps(payload, indent=2, ensure_ascii=False))
return 0
if __name__ == "__main__":
sys.exit(main())