2019 lines
54 KiB
Bash
Executable File
2019 lines
54 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
set -euo pipefail
|
||
|
||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||
# shellcheck source=scripts/github/_gh_env.sh
|
||
source "$SCRIPT_DIR/_gh_env.sh"
|
||
# shellcheck source=scripts/github/_gh_retry.sh
|
||
source "$SCRIPT_DIR/_gh_retry.sh"
|
||
|
||
gh_env_prepare "$REPO_ROOT"
|
||
|
||
TARGET_REPO=""
|
||
DRY_RUN=0
|
||
SKIP_LABELS=0
|
||
SKIP_MILESTONES=0
|
||
SKIP_ISSUES=0
|
||
SKIP_PREFLIGHT=0
|
||
PROJECT_OWNER=""
|
||
PROJECT_NUMBER=""
|
||
PROJECT_URL=""
|
||
PROJECT_ID=""
|
||
PROJECT_STATUS_FIELD="Status"
|
||
PROJECT_PRIORITY_FIELD="Priority"
|
||
PROJECT_WEEK_FIELD="Week"
|
||
PROJECT_TYPE_FIELD="Work Type"
|
||
PROJECT_STATUS_VALUE=""
|
||
|
||
declare -A ITEM_URLS
|
||
declare -A ITEM_NUMBERS
|
||
|
||
declare -A MILESTONE_TITLES=(
|
||
[week1]="Platform Week 1 - Truth Sources and Contracts"
|
||
[week2]="Platform Week 2 - Routing and Knowledge Integration"
|
||
[week3]="Platform Week 3 - Runtime Closure and Observability"
|
||
)
|
||
|
||
declare -A MILESTONE_DUE_ON=(
|
||
[week1]="2026-03-13T23:59:59Z"
|
||
[week2]="2026-03-20T23:59:59Z"
|
||
[week3]="2026-03-27T23:59:59Z"
|
||
)
|
||
|
||
MILESTONE_CACHE="[]"
|
||
ISSUE_CACHE="[]"
|
||
PROJECT_LIST_CACHE="[]"
|
||
PROJECT_FIELD_CACHE="[]"
|
||
PROJECT_ITEM_CACHE="[]"
|
||
|
||
usage() {
|
||
cat <<'EOF'
|
||
Usage:
|
||
bash scripts/github/seed-platform-project.sh [options]
|
||
|
||
Options:
|
||
--repo OWNER/REPO Target GitHub repository (default: parse from git remote)
|
||
--dry-run Print gh actions without executing them
|
||
--skip-labels Skip label creation/update
|
||
--skip-milestones Skip milestone creation/update
|
||
--skip-issues Skip epic and issue creation/update
|
||
--skip-preflight Skip GitHub DNS/token/project preflight checks
|
||
--project-owner X GitHub Project owner login
|
||
--project-number N GitHub Project number
|
||
--project-url URL GitHub Project URL, for example https://github.com/orgs/OWNER/projects/3/views/1
|
||
--project-type-field NAME
|
||
Project field name for item type (default: Work Type)
|
||
--project-priority-field NAME
|
||
Project field name for Priority (default: Priority)
|
||
--project-week-field NAME
|
||
Project field name for Week (default: Week)
|
||
--project-status-field NAME
|
||
Project field name for Status (default: Status)
|
||
--project-status-value VALUE
|
||
Optional project status value to set on every created item
|
||
-h, --help Show help
|
||
|
||
Examples:
|
||
bash scripts/github/seed-platform-project.sh
|
||
bash scripts/github/seed-platform-project.sh --repo lingjingwanxiang/ljwx-deploy
|
||
bash scripts/github/seed-platform-project.sh --dry-run
|
||
bash scripts/github/seed-platform-project.sh --repo lingjingwanxiang/ljwx-deploy --project-owner lingjingwanxiang --project-number 1
|
||
bash scripts/github/seed-platform-project.sh --repo lingjingwanxiang/ljwx-deploy --project-url https://github.com/orgs/lingjingwanxiang/projects/3/views/1
|
||
bash scripts/github/seed-platform-project.sh --repo lingjingwanxiang/ljwx-deploy --project-url https://github.com/orgs/lingjingwanxiang/projects/3/views/1 --project-type-field "Work Type"
|
||
EOF
|
||
}
|
||
|
||
log_info() {
|
||
echo "[信息] $1"
|
||
}
|
||
|
||
log_warn() {
|
||
echo "[警告] $1"
|
||
}
|
||
|
||
fail() {
|
||
echo "[错误] $1" >&2
|
||
exit 1
|
||
}
|
||
|
||
require_command() {
|
||
local command_name="$1"
|
||
if ! command -v "$command_name" >/dev/null 2>&1; then
|
||
fail "缺少命令: ${command_name}"
|
||
fi
|
||
}
|
||
|
||
parse_args() {
|
||
while [[ $# -gt 0 ]]; do
|
||
case "$1" in
|
||
--repo)
|
||
TARGET_REPO="${2:-}"
|
||
shift 2
|
||
;;
|
||
--dry-run)
|
||
DRY_RUN=1
|
||
shift
|
||
;;
|
||
--skip-labels)
|
||
SKIP_LABELS=1
|
||
shift
|
||
;;
|
||
--skip-milestones)
|
||
SKIP_MILESTONES=1
|
||
shift
|
||
;;
|
||
--skip-issues)
|
||
SKIP_ISSUES=1
|
||
shift
|
||
;;
|
||
--skip-preflight)
|
||
SKIP_PREFLIGHT=1
|
||
shift
|
||
;;
|
||
--project-owner)
|
||
PROJECT_OWNER="${2:-}"
|
||
shift 2
|
||
;;
|
||
--project-number)
|
||
PROJECT_NUMBER="${2:-}"
|
||
shift 2
|
||
;;
|
||
--project-url)
|
||
PROJECT_URL="${2:-}"
|
||
shift 2
|
||
;;
|
||
--project-type-field)
|
||
PROJECT_TYPE_FIELD="${2:-}"
|
||
shift 2
|
||
;;
|
||
--project-priority-field)
|
||
PROJECT_PRIORITY_FIELD="${2:-}"
|
||
shift 2
|
||
;;
|
||
--project-week-field)
|
||
PROJECT_WEEK_FIELD="${2:-}"
|
||
shift 2
|
||
;;
|
||
--project-status-field)
|
||
PROJECT_STATUS_FIELD="${2:-}"
|
||
shift 2
|
||
;;
|
||
--project-status-value)
|
||
PROJECT_STATUS_VALUE="${2:-}"
|
||
shift 2
|
||
;;
|
||
-h|--help)
|
||
usage
|
||
exit 0
|
||
;;
|
||
*)
|
||
fail "未知参数: $1"
|
||
;;
|
||
esac
|
||
done
|
||
}
|
||
|
||
project_enabled() {
|
||
[[ -n "$PROJECT_OWNER" && -n "$PROJECT_NUMBER" ]]
|
||
}
|
||
|
||
resolve_project_from_url() {
|
||
local url="$1"
|
||
local owner=""
|
||
local number=""
|
||
|
||
if [[ "$url" =~ ^https://github\.com/orgs/([^/]+)/projects/([0-9]+)(/.*)?$ ]]; then
|
||
owner="${BASH_REMATCH[1]}"
|
||
number="${BASH_REMATCH[2]}"
|
||
elif [[ "$url" =~ ^https://github\.com/users/([^/]+)/projects/([0-9]+)(/.*)?$ ]]; then
|
||
owner="${BASH_REMATCH[1]}"
|
||
number="${BASH_REMATCH[2]}"
|
||
else
|
||
fail "无法解析 GitHub Project URL: ${url}"
|
||
fi
|
||
|
||
if [[ -n "$PROJECT_OWNER" && "$PROJECT_OWNER" != "$owner" ]]; then
|
||
fail "--project-owner 与 --project-url 中的 owner 不一致"
|
||
fi
|
||
|
||
if [[ -n "$PROJECT_NUMBER" && "$PROJECT_NUMBER" != "$number" ]]; then
|
||
fail "--project-number 与 --project-url 中的 project number 不一致"
|
||
fi
|
||
|
||
PROJECT_OWNER="$owner"
|
||
PROJECT_NUMBER="$number"
|
||
}
|
||
|
||
resolve_repo_from_remote() {
|
||
local remote_url
|
||
local owner=""
|
||
local repo=""
|
||
remote_url="$(git config --get remote.origin.url || true)"
|
||
|
||
if [[ -z "$remote_url" ]]; then
|
||
fail "无法从 git remote 解析 GitHub 仓库,请显式传入 --repo OWNER/REPO"
|
||
fi
|
||
|
||
if [[ "$remote_url" =~ ^https://github\.com/([^/]+)/([^/]+)$ ]]; then
|
||
owner="${BASH_REMATCH[1]}"
|
||
repo="${BASH_REMATCH[2]%.git}"
|
||
echo "${owner}/${repo}"
|
||
return
|
||
fi
|
||
|
||
if [[ "$remote_url" =~ ^git@github\.com:([^/]+)/([^/]+)$ ]]; then
|
||
owner="${BASH_REMATCH[1]}"
|
||
repo="${BASH_REMATCH[2]%.git}"
|
||
echo "${owner}/${repo}"
|
||
return
|
||
fi
|
||
|
||
fail "remote.origin.url 不是可识别的 GitHub 地址: ${remote_url}"
|
||
}
|
||
|
||
resolve_target_repo() {
|
||
if [[ -n "$TARGET_REPO" ]]; then
|
||
return
|
||
fi
|
||
|
||
TARGET_REPO="$(resolve_repo_from_remote)"
|
||
}
|
||
|
||
ensure_auth() {
|
||
if [[ "$DRY_RUN" == "1" ]]; then
|
||
return
|
||
fi
|
||
|
||
if [[ -n "${GH_TOKEN:-${GITHUB_TOKEN:-}}" ]]; then
|
||
return
|
||
fi
|
||
|
||
if ! gh auth status >/dev/null 2>&1; then
|
||
fail "未设置 GH_TOKEN/GITHUB_TOKEN,且 gh 未登录"
|
||
fi
|
||
}
|
||
|
||
print_command() {
|
||
local rendered=""
|
||
local arg
|
||
for arg in "$@"; do
|
||
rendered+=" $(printf '%q' "$arg")"
|
||
done
|
||
echo "[dry-run]${rendered}"
|
||
}
|
||
|
||
run_or_print() {
|
||
if [[ "$DRY_RUN" == "1" ]]; then
|
||
print_command "$@"
|
||
return 0
|
||
fi
|
||
|
||
if [[ "${1:-}" == "gh" ]]; then
|
||
gh_retry_command "执行 GitHub CLI 命令" "$@"
|
||
return 0
|
||
fi
|
||
|
||
"$@"
|
||
}
|
||
|
||
run_quiet() {
|
||
if [[ "$DRY_RUN" == "1" ]]; then
|
||
print_command "$@"
|
||
return 0
|
||
fi
|
||
|
||
if [[ "${1:-}" == "gh" ]]; then
|
||
gh_retry_command "执行 GitHub CLI 命令" "$@" >/dev/null
|
||
return 0
|
||
fi
|
||
|
||
"$@" >/dev/null
|
||
}
|
||
|
||
validate_project_args() {
|
||
if [[ -n "$PROJECT_OWNER" && -z "$PROJECT_NUMBER" ]]; then
|
||
fail "使用 GitHub Project 时必须同时提供 --project-number"
|
||
fi
|
||
|
||
if [[ -z "$PROJECT_OWNER" && -n "$PROJECT_NUMBER" ]]; then
|
||
fail "使用 GitHub Project 时必须同时提供 --project-owner"
|
||
fi
|
||
}
|
||
|
||
ensure_project_access() {
|
||
if ! project_enabled || [[ "$DRY_RUN" == "1" ]]; then
|
||
return
|
||
fi
|
||
|
||
if ! gh_retry_command "验证 GitHub Project 访问" gh project list --owner "$PROJECT_OWNER" --limit 1 >/dev/null 2>&1; then
|
||
fail "无法访问 GitHub Project,请确认 GH_TOKEN/gh 登录具备 project scope"
|
||
fi
|
||
}
|
||
|
||
run_preflight() {
|
||
local -a args=()
|
||
|
||
if [[ "$DRY_RUN" == "1" || "$SKIP_PREFLIGHT" == "1" ]]; then
|
||
return
|
||
fi
|
||
|
||
args+=(--repo "$TARGET_REPO")
|
||
if project_enabled; then
|
||
args+=(--project-owner "$PROJECT_OWNER" --project-number "$PROJECT_NUMBER")
|
||
fi
|
||
|
||
log_info "执行 GitHub preflight"
|
||
bash "$SCRIPT_DIR/preflight.sh" "${args[@]}"
|
||
}
|
||
|
||
refresh_project_list_cache() {
|
||
if ! project_enabled || [[ "$DRY_RUN" == "1" ]]; then
|
||
return
|
||
fi
|
||
|
||
PROJECT_LIST_CACHE="$(gh_retry_command "读取 GitHub Project 列表" gh project list --owner "$PROJECT_OWNER" --format json)"
|
||
}
|
||
|
||
refresh_project_field_cache() {
|
||
if ! project_enabled || [[ "$DRY_RUN" == "1" ]]; then
|
||
return
|
||
fi
|
||
|
||
PROJECT_FIELD_CACHE="$(gh_retry_command "读取 GitHub Project 字段" gh project field-list "$PROJECT_NUMBER" --owner "$PROJECT_OWNER" --format json)"
|
||
}
|
||
|
||
refresh_project_item_cache() {
|
||
if ! project_enabled || [[ "$DRY_RUN" == "1" ]]; then
|
||
return
|
||
fi
|
||
|
||
PROJECT_ITEM_CACHE="$(gh_retry_command "读取 GitHub Project 条目" gh project item-list "$PROJECT_NUMBER" --owner "$PROJECT_OWNER" --limit 500 --format json)"
|
||
}
|
||
|
||
refresh_milestone_cache() {
|
||
if [[ "$DRY_RUN" == "1" ]]; then
|
||
return
|
||
fi
|
||
|
||
MILESTONE_CACHE="$(gh_api_retry "读取 milestone 列表" "repos/${TARGET_REPO}/milestones?state=all&per_page=100")"
|
||
}
|
||
|
||
refresh_issue_cache() {
|
||
if [[ "$DRY_RUN" == "1" ]]; then
|
||
return
|
||
fi
|
||
|
||
ISSUE_CACHE="$(gh_retry_command "读取 issue 列表" gh issue list -R "$TARGET_REPO" --state all --limit 500 --json number,title,url)"
|
||
}
|
||
|
||
lookup_milestone_number() {
|
||
local title="$1"
|
||
|
||
python3 -c '
|
||
import json
|
||
import sys
|
||
|
||
title = sys.argv[1]
|
||
raw = sys.stdin.read().strip() or "[]"
|
||
items = json.loads(raw)
|
||
for item in items:
|
||
if item.get("title") == title:
|
||
print(item.get("number", ""))
|
||
break
|
||
' "$title" <<<"$MILESTONE_CACHE"
|
||
}
|
||
|
||
lookup_issue_record() {
|
||
local title="$1"
|
||
|
||
python3 -c '
|
||
import json
|
||
import sys
|
||
|
||
title = sys.argv[1]
|
||
raw = sys.stdin.read().strip() or "[]"
|
||
items = json.loads(raw)
|
||
for item in items:
|
||
if item.get("title") == title:
|
||
number = item.get("number", "")
|
||
url = item.get("url", "")
|
||
print(f"{number}\t{url}")
|
||
break
|
||
' "$title" <<<"$ISSUE_CACHE"
|
||
}
|
||
|
||
lookup_project_id() {
|
||
local project_number="$1"
|
||
|
||
python3 -c '
|
||
import json
|
||
import sys
|
||
|
||
target = str(sys.argv[1])
|
||
raw = sys.stdin.read().strip() or "[]"
|
||
payload = json.loads(raw)
|
||
|
||
if isinstance(payload, dict):
|
||
items = payload.get("projects", [])
|
||
elif isinstance(payload, list):
|
||
items = payload
|
||
else:
|
||
items = []
|
||
|
||
for item in items:
|
||
if str(item.get("number", "")) == target:
|
||
print(item.get("id", ""))
|
||
break
|
||
' "$project_number" <<<"$PROJECT_LIST_CACHE"
|
||
}
|
||
|
||
lookup_project_field_record() {
|
||
local field_name="$1"
|
||
|
||
python3 -c '
|
||
import json
|
||
import sys
|
||
|
||
target = sys.argv[1]
|
||
raw = sys.stdin.read().strip() or "[]"
|
||
payload = json.loads(raw)
|
||
|
||
if isinstance(payload, dict):
|
||
fields = payload.get("fields", [])
|
||
elif isinstance(payload, list):
|
||
fields = payload
|
||
else:
|
||
fields = []
|
||
|
||
for field in fields:
|
||
if field.get("name") == target:
|
||
field_id = field.get("id", "")
|
||
field_type = field.get("dataType") or field.get("type") or field.get("__typename", "")
|
||
print(f"{field_id}\t{field_type}")
|
||
break
|
||
' "$field_name" <<<"$PROJECT_FIELD_CACHE"
|
||
}
|
||
|
||
lookup_project_option_id() {
|
||
local field_name="$1"
|
||
local option_name="$2"
|
||
|
||
python3 -c '
|
||
import json
|
||
import sys
|
||
|
||
field_name = sys.argv[1]
|
||
option_name = sys.argv[2]
|
||
raw = sys.stdin.read().strip() or "[]"
|
||
payload = json.loads(raw)
|
||
|
||
if isinstance(payload, dict):
|
||
fields = payload.get("fields", [])
|
||
elif isinstance(payload, list):
|
||
fields = payload
|
||
else:
|
||
fields = []
|
||
|
||
for field in fields:
|
||
if field.get("name") != field_name:
|
||
continue
|
||
for option in field.get("options", []) or []:
|
||
if option.get("name") == option_name:
|
||
print(option.get("id", ""))
|
||
raise SystemExit(0)
|
||
break
|
||
' "$field_name" "$option_name" <<<"$PROJECT_FIELD_CACHE"
|
||
}
|
||
|
||
lookup_project_item_id() {
|
||
local issue_url="$1"
|
||
|
||
python3 -c '
|
||
import json
|
||
import sys
|
||
|
||
target_url = sys.argv[1]
|
||
target_number = target_url.rstrip("/").split("/")[-1]
|
||
raw = sys.stdin.read().strip() or "[]"
|
||
payload = json.loads(raw)
|
||
|
||
if isinstance(payload, dict):
|
||
items = payload.get("items", [])
|
||
elif isinstance(payload, list):
|
||
items = payload
|
||
else:
|
||
items = []
|
||
|
||
for item in items:
|
||
content = item.get("content") or {}
|
||
content_url = content.get("url") or item.get("url") or ""
|
||
content_number = str(content.get("number") or "")
|
||
if content_url == target_url or content_number == target_number:
|
||
print(item.get("id", ""))
|
||
break
|
||
' "$issue_url" <<<"$PROJECT_ITEM_CACHE"
|
||
}
|
||
|
||
milestone_description() {
|
||
local milestone_id="$1"
|
||
|
||
case "$milestone_id" in
|
||
week1)
|
||
cat <<'EOF'
|
||
Delivery checkpoint for locking platform truth sources and contract baselines.
|
||
|
||
Exit criteria:
|
||
- release/platform-version.yaml carries composite platform version fields used for review.
|
||
- platform/assembly/env-matrix.yaml captures concrete environment deltas for active profiles.
|
||
- capability registry and router contracts have explicit Week 1 execution issues with rollback paths.
|
||
EOF
|
||
;;
|
||
week2)
|
||
cat <<'EOF'
|
||
Delivery checkpoint for moving routing and knowledge work onto explicit Git-managed configuration.
|
||
|
||
Exit criteria:
|
||
- ljwx-chat routing integration is driven by platform configuration instead of hardcoded model mapping.
|
||
- platform knowledge schema and publish/invalidate MVP expectations are explicit in Git.
|
||
- Week 2 execution issues carry evidence and rollback plans before implementation starts.
|
||
EOF
|
||
;;
|
||
week3)
|
||
cat <<'EOF'
|
||
Delivery checkpoint for operational closure and observability follow-up.
|
||
|
||
Exit criteria:
|
||
- bootstrap and rollback minimum loop is validated against Git-managed scripts and docs.
|
||
- runtime evidence and observability work has a bounded epic after truth sources are locked.
|
||
- closure review can point to Git, ArgoCD, and smoke/evidence outputs without oral context.
|
||
EOF
|
||
;;
|
||
*)
|
||
fail "未知 milestone: ${milestone_id}"
|
||
;;
|
||
esac
|
||
}
|
||
|
||
ensure_milestone() {
|
||
local milestone_id="$1"
|
||
local title="${MILESTONE_TITLES[$milestone_id]}"
|
||
local due_on="${MILESTONE_DUE_ON[$milestone_id]}"
|
||
local description
|
||
local number
|
||
|
||
description="$(milestone_description "$milestone_id")"
|
||
number="$(lookup_milestone_number "$title")"
|
||
|
||
if [[ -n "$number" ]]; then
|
||
log_info "更新 milestone: ${title}"
|
||
run_quiet gh api -X PATCH "repos/${TARGET_REPO}/milestones/${number}" \
|
||
-f title="$title" \
|
||
-f state="open" \
|
||
-f due_on="$due_on" \
|
||
-f description="$description"
|
||
else
|
||
log_info "创建 milestone: ${title}"
|
||
run_quiet gh api -X POST "repos/${TARGET_REPO}/milestones" \
|
||
-f title="$title" \
|
||
-f state="open" \
|
||
-f due_on="$due_on" \
|
||
-f description="$description"
|
||
fi
|
||
|
||
if [[ "$DRY_RUN" != "1" ]]; then
|
||
refresh_milestone_cache
|
||
fi
|
||
}
|
||
|
||
ensure_label() {
|
||
local name="$1"
|
||
local color="$2"
|
||
local description="$3"
|
||
|
||
log_info "同步 label: ${name}"
|
||
run_quiet gh label create "$name" \
|
||
--repo "$TARGET_REPO" \
|
||
--color "$color" \
|
||
--description "$description" \
|
||
--force
|
||
}
|
||
|
||
create_labels() {
|
||
ensure_label "platform" "1D76DB" "Platform delivery and governance work"
|
||
|
||
ensure_label "type:truth-source" "0E8A16" "Source-of-truth and version boundary work"
|
||
ensure_label "type:contract" "0052CC" "Schema and interface contract work"
|
||
ensure_label "type:routing" "FBCA04" "Routing strategy and model dispatch work"
|
||
ensure_label "type:knowledge" "5319E7" "Knowledge schema, taxonomy, and publication work"
|
||
ensure_label "type:capability" "C2E0C6" "Capability registry and service ownership work"
|
||
ensure_label "type:workflow" "F9D0C4" "Workflow and publish/invalidate execution path work"
|
||
ensure_label "type:ops" "D93F0B" "Bootstrap, rollback, and runbook work"
|
||
ensure_label "type:observability" "006B75" "Runtime evidence and observability work"
|
||
|
||
ensure_label "priority:P0" "B60205" "Production blocker or same-wave must-do item"
|
||
ensure_label "priority:P1" "D93F0B" "Current milestone coordination item"
|
||
ensure_label "priority:P2" "FBCA04" "Planned follow-up after current milestone"
|
||
ensure_label "priority:P3" "C5DEF5" "Backlog or optimization item"
|
||
|
||
ensure_label "week:1" "0E8A16" "Current wave Week 1 work"
|
||
ensure_label "week:2" "1D76DB" "Current wave Week 2 work"
|
||
ensure_label "week:3" "5319E7" "Current wave Week 3 work"
|
||
|
||
ensure_label "status:blocked" "B60205" "Blocked by unresolved dependency"
|
||
ensure_label "status:needs-decision" "FBCA04" "Needs decision before execution"
|
||
ensure_label "status:ready" "0E8A16" "Ready for execution"
|
||
}
|
||
|
||
ensure_project_id() {
|
||
if ! project_enabled || [[ "$DRY_RUN" == "1" ]]; then
|
||
return
|
||
fi
|
||
|
||
PROJECT_ID="$(lookup_project_id "$PROJECT_NUMBER")"
|
||
if [[ -z "$PROJECT_ID" ]]; then
|
||
fail "未找到 GitHub Project #${PROJECT_NUMBER}(owner: ${PROJECT_OWNER})"
|
||
fi
|
||
}
|
||
|
||
ensure_project_field() {
|
||
local field_name="$1"
|
||
local field_kind="$2"
|
||
local option_csv="${3:-}"
|
||
local record=""
|
||
local field_id=""
|
||
local output=""
|
||
|
||
if ! project_enabled; then
|
||
return
|
||
fi
|
||
|
||
if [[ "$DRY_RUN" == "1" ]]; then
|
||
if [[ "$field_kind" == "SINGLE_SELECT" ]]; then
|
||
print_command gh project field-create "$PROJECT_NUMBER" --owner "$PROJECT_OWNER" --name "$field_name" --data-type "$field_kind" --single-select-options "$option_csv"
|
||
else
|
||
print_command gh project field-create "$PROJECT_NUMBER" --owner "$PROJECT_OWNER" --name "$field_name" --data-type "$field_kind"
|
||
fi
|
||
return
|
||
fi
|
||
|
||
record="$(lookup_project_field_record "$field_name")"
|
||
if [[ -n "$record" ]]; then
|
||
IFS=$'\t' read -r field_id _ <<<"$record"
|
||
fi
|
||
|
||
if [[ -n "$field_id" ]]; then
|
||
return
|
||
fi
|
||
|
||
log_info "创建 project field: ${field_name}"
|
||
if [[ "$field_kind" == "SINGLE_SELECT" ]]; then
|
||
if ! output="$(gh_retry_command "创建 Project 字段 ${field_name}" gh project field-create "$PROJECT_NUMBER" \
|
||
--owner "$PROJECT_OWNER" \
|
||
--name "$field_name" \
|
||
--data-type "$field_kind" \
|
||
--single-select-options "$option_csv" 2>&1 >/dev/null)"; then
|
||
if [[ "$output" == *"reserved value"* ]]; then
|
||
log_warn "Project 字段名被 GitHub 保留,已跳过自动创建: ${field_name}。请改用 --project-type-field 或其他自定义字段名。"
|
||
return
|
||
fi
|
||
if [[ "$output" == *"already been taken"* ]]; then
|
||
log_warn "Project 字段名已存在但 field-list 未返回,已跳过自动创建: ${field_name}"
|
||
return
|
||
fi
|
||
fail "创建 project field 失败 (${field_name}): ${output}"
|
||
fi
|
||
else
|
||
if ! output="$(gh_retry_command "创建 Project 字段 ${field_name}" gh project field-create "$PROJECT_NUMBER" \
|
||
--owner "$PROJECT_OWNER" \
|
||
--name "$field_name" \
|
||
--data-type "$field_kind" 2>&1 >/dev/null)"; then
|
||
if [[ "$output" == *"reserved value"* ]]; then
|
||
log_warn "Project 字段名被 GitHub 保留,已跳过自动创建: ${field_name}"
|
||
return
|
||
fi
|
||
if [[ "$output" == *"already been taken"* ]]; then
|
||
log_warn "Project 字段名已存在但 field-list 未返回,已跳过自动创建: ${field_name}"
|
||
return
|
||
fi
|
||
fail "创建 project field 失败 (${field_name}): ${output}"
|
||
fi
|
||
fi
|
||
|
||
refresh_project_field_cache
|
||
}
|
||
|
||
ensure_project_fields() {
|
||
if ! project_enabled; then
|
||
return
|
||
fi
|
||
|
||
if [[ "$DRY_RUN" != "1" ]]; then
|
||
refresh_project_list_cache
|
||
ensure_project_id
|
||
refresh_project_field_cache
|
||
fi
|
||
|
||
ensure_project_field "$PROJECT_TYPE_FIELD" "SINGLE_SELECT" "Epic,Issue,Follow-up"
|
||
ensure_project_field "$PROJECT_PRIORITY_FIELD" "SINGLE_SELECT" "P0,P1,P2,P3"
|
||
ensure_project_field "$PROJECT_WEEK_FIELD" "SINGLE_SELECT" "2026-W10,2026-W11,2026-W12"
|
||
|
||
if [[ -n "$PROJECT_STATUS_VALUE" ]]; then
|
||
ensure_project_field "$PROJECT_STATUS_FIELD" "SINGLE_SELECT" "Ready,In Progress,Blocked,Done"
|
||
fi
|
||
|
||
if [[ "$DRY_RUN" != "1" ]]; then
|
||
refresh_project_field_cache
|
||
fi
|
||
}
|
||
|
||
sync_project_field_value() {
|
||
local item_id="$1"
|
||
local field_name="$2"
|
||
local field_value="$3"
|
||
local record=""
|
||
local field_id=""
|
||
local field_type=""
|
||
local option_id=""
|
||
|
||
if ! project_enabled || [[ -z "$field_value" ]]; then
|
||
return
|
||
fi
|
||
|
||
if [[ "$DRY_RUN" == "1" ]]; then
|
||
log_info "Project 字段待同步: ${field_name}=${field_value}"
|
||
return
|
||
fi
|
||
|
||
record="$(lookup_project_field_record "$field_name")"
|
||
if [[ -z "$record" ]]; then
|
||
log_warn "Project 字段不存在,已跳过: ${field_name}"
|
||
return
|
||
fi
|
||
|
||
IFS=$'\t' read -r field_id field_type <<<"$record"
|
||
field_type="${field_type^^}"
|
||
|
||
if [[ -z "$field_id" ]]; then
|
||
log_warn "Project 字段 ID 缺失,已跳过: ${field_name}"
|
||
return
|
||
fi
|
||
|
||
if [[ "$field_type" == *"SINGLE_SELECT"* || "$field_type" == *"SINGLESELECT"* ]]; then
|
||
option_id="$(lookup_project_option_id "$field_name" "$field_value")"
|
||
if [[ -z "$option_id" ]]; then
|
||
log_warn "Project 字段 ${field_name} 未找到选项 ${field_value},已跳过"
|
||
return
|
||
fi
|
||
|
||
run_quiet gh project item-edit \
|
||
--id "$item_id" \
|
||
--project-id "$PROJECT_ID" \
|
||
--field-id "$field_id" \
|
||
--single-select-option-id "$option_id"
|
||
return
|
||
fi
|
||
|
||
run_quiet gh project item-edit \
|
||
--id "$item_id" \
|
||
--project-id "$PROJECT_ID" \
|
||
--field-id "$field_id" \
|
||
--text "$field_value"
|
||
}
|
||
|
||
sync_issue_to_project() {
|
||
local issue_id="$1"
|
||
local issue_url="$2"
|
||
local item_id=""
|
||
|
||
if ! project_enabled || [[ -z "$issue_url" ]]; then
|
||
return
|
||
fi
|
||
|
||
if [[ "$DRY_RUN" == "1" ]]; then
|
||
print_command gh project item-add "$PROJECT_NUMBER" --owner "$PROJECT_OWNER" --url "$issue_url"
|
||
log_info "Project 字段待同步: ${PROJECT_TYPE_FIELD}=$(issue_project_type_value "$issue_id"), ${PROJECT_PRIORITY_FIELD}=$(issue_project_priority_value "$issue_id"), ${PROJECT_WEEK_FIELD}=$(issue_week_text "$issue_id")"
|
||
if [[ -n "$PROJECT_STATUS_VALUE" ]]; then
|
||
log_info "Project 字段待同步: ${PROJECT_STATUS_FIELD}=${PROJECT_STATUS_VALUE}"
|
||
fi
|
||
return
|
||
fi
|
||
|
||
if ! gh_retry_command "添加 Issue 到 GitHub Project" gh project item-add "$PROJECT_NUMBER" --owner "$PROJECT_OWNER" --url "$issue_url" >/dev/null 2>&1; then
|
||
log_warn "Project item-add 返回非零,尝试继续按现有 item 处理: ${issue_url}"
|
||
fi
|
||
|
||
refresh_project_item_cache
|
||
item_id="$(lookup_project_item_id "$issue_url")"
|
||
if [[ -z "$item_id" ]]; then
|
||
fail "Project 中未找到 issue item: ${issue_url}"
|
||
fi
|
||
|
||
sync_project_field_value "$item_id" "$PROJECT_TYPE_FIELD" "$(issue_project_type_value "$issue_id")"
|
||
sync_project_field_value "$item_id" "$PROJECT_PRIORITY_FIELD" "$(issue_project_priority_value "$issue_id")"
|
||
sync_project_field_value "$item_id" "$PROJECT_WEEK_FIELD" "$(issue_week_text "$issue_id")"
|
||
|
||
if [[ -n "$PROJECT_STATUS_VALUE" ]]; then
|
||
sync_project_field_value "$item_id" "$PROJECT_STATUS_FIELD" "$PROJECT_STATUS_VALUE"
|
||
fi
|
||
}
|
||
|
||
issue_title() {
|
||
local issue_id="$1"
|
||
|
||
case "$issue_id" in
|
||
epic_truth_sources)
|
||
echo "[Platform][Epic] Week 1 truth sources and env contracts"
|
||
;;
|
||
epic_contracts)
|
||
echo "[Platform][Epic] Week 1 capability registry and router contracts"
|
||
;;
|
||
epic_chat_routing)
|
||
echo "[Platform][Epic] Week 2 chat routing configuration integration"
|
||
;;
|
||
epic_knowledge)
|
||
echo "[Platform][Epic] Week 2 knowledge schema and publish baseline"
|
||
;;
|
||
epic_ops)
|
||
echo "[Platform][Epic] Week 3 bootstrap and rollback minimum closure"
|
||
;;
|
||
epic_observability)
|
||
echo "[Platform][Epic] Week 3 runtime evidence and observability baseline"
|
||
;;
|
||
issue_platform_version)
|
||
echo "[Platform][P0] Complete composite version fields in release/platform-version.yaml"
|
||
;;
|
||
issue_env_matrix)
|
||
echo "[Platform][P0] Complete environment delta fields in platform/assembly/env-matrix.yaml"
|
||
;;
|
||
issue_capability_alignment)
|
||
echo "[Platform][P0] Align capabilities.yaml with service-capability-map.yaml"
|
||
;;
|
||
issue_router_contracts)
|
||
echo "[Platform][P0] Document and validate router request/decision/tool-result contracts"
|
||
;;
|
||
issue_chat_routing)
|
||
echo "[Platform][P0] Externalize ljwx-chat routing config and remove hardcoded model mapping"
|
||
;;
|
||
issue_knowledge_schema)
|
||
echo "[Platform][P0] Establish minimal knowledge schema and directory conventions"
|
||
;;
|
||
issue_knowledge_publish)
|
||
echo "[Platform][P0] Define knowledge publish and invalidate MVP"
|
||
;;
|
||
issue_bootstrap_rollback)
|
||
echo "[Platform][P0] Validate bootstrap and rollback minimum closed loop"
|
||
;;
|
||
*)
|
||
fail "未知 issue title: ${issue_id}"
|
||
;;
|
||
esac
|
||
}
|
||
|
||
issue_milestone_id() {
|
||
local issue_id="$1"
|
||
|
||
case "$issue_id" in
|
||
epic_truth_sources|epic_contracts|issue_platform_version|issue_env_matrix|issue_capability_alignment|issue_router_contracts)
|
||
echo "week1"
|
||
;;
|
||
epic_chat_routing|epic_knowledge|issue_chat_routing|issue_knowledge_schema|issue_knowledge_publish)
|
||
echo "week2"
|
||
;;
|
||
epic_ops|epic_observability|issue_bootstrap_rollback)
|
||
echo "week3"
|
||
;;
|
||
*)
|
||
fail "未知 issue milestone: ${issue_id}"
|
||
;;
|
||
esac
|
||
}
|
||
|
||
issue_week_text() {
|
||
local issue_id="$1"
|
||
|
||
case "$issue_id" in
|
||
epic_truth_sources|epic_contracts|issue_platform_version|issue_env_matrix|issue_capability_alignment|issue_router_contracts)
|
||
echo "2026-W10"
|
||
;;
|
||
epic_chat_routing|epic_knowledge|issue_chat_routing|issue_knowledge_schema|issue_knowledge_publish)
|
||
echo "2026-W11"
|
||
;;
|
||
epic_ops|epic_observability|issue_bootstrap_rollback)
|
||
echo "2026-W12"
|
||
;;
|
||
*)
|
||
fail "未知 issue week: ${issue_id}"
|
||
;;
|
||
esac
|
||
}
|
||
|
||
issue_project_type_value() {
|
||
local issue_id="$1"
|
||
|
||
case "$issue_id" in
|
||
epic_*)
|
||
echo "Epic"
|
||
;;
|
||
issue_*)
|
||
echo "Issue"
|
||
;;
|
||
*)
|
||
echo ""
|
||
;;
|
||
esac
|
||
}
|
||
|
||
issue_project_priority_value() {
|
||
local issue_id="$1"
|
||
|
||
case "$issue_id" in
|
||
epic_truth_sources|epic_contracts|epic_chat_routing|epic_knowledge|epic_ops|epic_observability)
|
||
echo "P1"
|
||
;;
|
||
issue_platform_version|issue_env_matrix|issue_capability_alignment|issue_router_contracts|issue_chat_routing|issue_knowledge_schema|issue_knowledge_publish|issue_bootstrap_rollback)
|
||
echo "P0"
|
||
;;
|
||
*)
|
||
echo ""
|
||
;;
|
||
esac
|
||
}
|
||
|
||
labels_for_issue() {
|
||
local issue_id="$1"
|
||
|
||
case "$issue_id" in
|
||
epic_truth_sources)
|
||
echo "platform type:truth-source priority:P1 week:1 status:ready"
|
||
;;
|
||
epic_contracts)
|
||
echo "platform type:contract type:capability priority:P1 week:1 status:ready"
|
||
;;
|
||
epic_chat_routing)
|
||
echo "platform type:routing priority:P1 week:2 status:ready"
|
||
;;
|
||
epic_knowledge)
|
||
echo "platform type:knowledge type:workflow priority:P1 week:2 status:ready"
|
||
;;
|
||
epic_ops)
|
||
echo "platform type:ops priority:P1 week:3 status:ready"
|
||
;;
|
||
epic_observability)
|
||
echo "platform type:observability priority:P1 week:3 status:ready"
|
||
;;
|
||
issue_platform_version)
|
||
echo "platform type:truth-source priority:P0 week:1 status:ready"
|
||
;;
|
||
issue_env_matrix)
|
||
echo "platform type:truth-source priority:P0 week:1 status:ready"
|
||
;;
|
||
issue_capability_alignment)
|
||
echo "platform type:capability priority:P0 week:1 status:ready"
|
||
;;
|
||
issue_router_contracts)
|
||
echo "platform type:contract priority:P0 week:1 status:ready"
|
||
;;
|
||
issue_chat_routing)
|
||
echo "platform type:routing priority:P0 week:2 status:ready"
|
||
;;
|
||
issue_knowledge_schema)
|
||
echo "platform type:knowledge priority:P0 week:2 status:ready"
|
||
;;
|
||
issue_knowledge_publish)
|
||
echo "platform type:knowledge type:workflow priority:P0 week:2 status:ready"
|
||
;;
|
||
issue_bootstrap_rollback)
|
||
echo "platform type:ops priority:P0 week:3 status:ready"
|
||
;;
|
||
*)
|
||
fail "未知 issue labels: ${issue_id}"
|
||
;;
|
||
esac
|
||
}
|
||
|
||
parent_epic_id() {
|
||
local issue_id="$1"
|
||
|
||
case "$issue_id" in
|
||
issue_platform_version|issue_env_matrix)
|
||
echo "epic_truth_sources"
|
||
;;
|
||
issue_capability_alignment|issue_router_contracts)
|
||
echo "epic_contracts"
|
||
;;
|
||
issue_chat_routing)
|
||
echo "epic_chat_routing"
|
||
;;
|
||
issue_knowledge_schema|issue_knowledge_publish)
|
||
echo "epic_knowledge"
|
||
;;
|
||
issue_bootstrap_rollback)
|
||
echo "epic_ops"
|
||
;;
|
||
*)
|
||
echo ""
|
||
;;
|
||
esac
|
||
}
|
||
|
||
repo_list_for_issue() {
|
||
local issue_id="$1"
|
||
|
||
case "$issue_id" in
|
||
issue_chat_routing|epic_chat_routing)
|
||
cat <<'EOF'
|
||
- ljwx-deploy
|
||
- ljwx-chat
|
||
EOF
|
||
;;
|
||
*)
|
||
cat <<'EOF'
|
||
- ljwx-deploy
|
||
EOF
|
||
;;
|
||
esac
|
||
}
|
||
|
||
child_issue_line() {
|
||
local child_id="$1"
|
||
local title
|
||
title="$(issue_title "$child_id")"
|
||
|
||
if [[ -n "${ITEM_URLS[$child_id]:-}" ]]; then
|
||
echo "- ${ITEM_URLS[$child_id]} (${title})"
|
||
else
|
||
echo "- ${title}"
|
||
fi
|
||
}
|
||
|
||
render_issue_body() {
|
||
local issue_id="$1"
|
||
local week_text
|
||
local milestone_id
|
||
local milestone_title
|
||
local parent_id
|
||
local parent_url="None"
|
||
local project_plan_path="docs/platform/github-project-plan.md"
|
||
|
||
week_text="$(issue_week_text "$issue_id")"
|
||
milestone_id="$(issue_milestone_id "$issue_id")"
|
||
milestone_title="${MILESTONE_TITLES[$milestone_id]}"
|
||
parent_id="$(parent_epic_id "$issue_id")"
|
||
|
||
if [[ -n "$parent_id" && -n "${ITEM_URLS[$parent_id]:-}" ]]; then
|
||
parent_url="${ITEM_URLS[$parent_id]}"
|
||
fi
|
||
|
||
case "$issue_id" in
|
||
epic_truth_sources)
|
||
cat <<EOF
|
||
## Summary
|
||
Lock the Week 1 truth-source files so later routing and knowledge changes are anchored in Git-managed platform version and environment definitions instead of ad-hoc notes.
|
||
|
||
## Type
|
||
- [x] Epic
|
||
- [ ] Issue
|
||
- [ ] Follow-up
|
||
|
||
## Priority
|
||
- [ ] P0
|
||
- [x] P1
|
||
- [ ] P2
|
||
- [ ] P3
|
||
|
||
## Week
|
||
${week_text}
|
||
|
||
## Repos touched
|
||
$(repo_list_for_issue "$issue_id")
|
||
|
||
## Files / Paths expected to change
|
||
- release/platform-version.yaml
|
||
- platform/assembly/env-matrix.yaml
|
||
- docs/platform/deployment-environments.md
|
||
|
||
## Dependencies
|
||
- None
|
||
|
||
## Acceptance Criteria
|
||
- [ ] Child execution issues are created and linked under this epic.
|
||
- [ ] Platform version and environment matrix changes stay in Git-managed truth-source files.
|
||
- [ ] Reviewers can trace Week 1 environment behavior without oral context.
|
||
|
||
## Evidence Required
|
||
- [ ] Issue links for child execution items
|
||
- [ ] PR link
|
||
- [ ] Manifest or config diff
|
||
- [ ] Runtime verification output
|
||
- [ ] Docs updated if contract or behavior changed
|
||
|
||
## Rollback Plan
|
||
- Revert target: child PRs merged under this epic
|
||
- Re-apply or resync step: resync affected overlays after reverting child changes
|
||
- Verification after rollback: confirm version and environment references match the previous known-good state
|
||
|
||
## Links
|
||
- Milestone: ${milestone_title}
|
||
- Planning source: ${project_plan_path}
|
||
- Child issues:
|
||
$(child_issue_line issue_platform_version)
|
||
$(child_issue_line issue_env_matrix)
|
||
EOF
|
||
;;
|
||
epic_contracts)
|
||
cat <<EOF
|
||
## Summary
|
||
Align the capability registry, service capability map, and router contract documents so ownership and execution boundaries remain explicit before routing integration work starts.
|
||
|
||
## Type
|
||
- [x] Epic
|
||
- [ ] Issue
|
||
- [ ] Follow-up
|
||
|
||
## Priority
|
||
- [ ] P0
|
||
- [x] P1
|
||
- [ ] P2
|
||
- [ ] P3
|
||
|
||
## Week
|
||
${week_text}
|
||
|
||
## Repos touched
|
||
$(repo_list_for_issue "$issue_id")
|
||
|
||
## Files / Paths expected to change
|
||
- platform/assembly/capabilities.yaml
|
||
- platform/contracts/service-capability-map.yaml
|
||
- platform/contracts/router-request.schema.json
|
||
- platform/contracts/router-decision.schema.json
|
||
- platform/contracts/tool-result.schema.json
|
||
- docs/platform/routing-strategy.md
|
||
|
||
## Dependencies
|
||
- None
|
||
|
||
## Acceptance Criteria
|
||
- [ ] Capability ownership and router contracts have bounded child execution issues.
|
||
- [ ] Every runtime-facing capability change remains explainable from Git.
|
||
- [ ] Reviewers can see the request, decision, and tool-result boundary without reading source code first.
|
||
|
||
## Evidence Required
|
||
- [ ] Issue links for child execution items
|
||
- [ ] PR link
|
||
- [ ] Manifest or config diff
|
||
- [ ] Runtime verification output
|
||
- [ ] Docs updated if contract or behavior changed
|
||
|
||
## Rollback Plan
|
||
- Revert target: child PRs merged under this epic
|
||
- Re-apply or resync step: restore prior contract and capability files, then rerun validation
|
||
- Verification after rollback: schema references and capability ownership match the previous release baseline
|
||
|
||
## Links
|
||
- Milestone: ${milestone_title}
|
||
- Planning source: ${project_plan_path}
|
||
- Child issues:
|
||
$(child_issue_line issue_capability_alignment)
|
||
$(child_issue_line issue_router_contracts)
|
||
EOF
|
||
;;
|
||
epic_chat_routing)
|
||
cat <<EOF
|
||
## Summary
|
||
Move chat-side routing decisions onto platform-managed configuration so routing behavior is no longer hidden in ljwx-chat hardcoded model mapping.
|
||
|
||
## Type
|
||
- [x] Epic
|
||
- [ ] Issue
|
||
- [ ] Follow-up
|
||
|
||
## Priority
|
||
- [ ] P0
|
||
- [x] P1
|
||
- [ ] P2
|
||
- [ ] P3
|
||
|
||
## Week
|
||
${week_text}
|
||
|
||
## Repos touched
|
||
$(repo_list_for_issue "$issue_id")
|
||
|
||
## Files / Paths expected to change
|
||
- platform/routing/routes.dev.yaml
|
||
- platform/routing/routes.prod.yaml
|
||
- release/platform-version.yaml
|
||
- ljwx-chat/src/config/modelRouting.ts
|
||
- ljwx-chat/src/app/(backend)/webapi/chat/[provider]/route.ts
|
||
- ljwx-chat/src/server/modules/ModelRuntime/index.ts
|
||
|
||
## Dependencies
|
||
- ${ITEM_URLS[epic_contracts]:-Week 1 contract epic}
|
||
|
||
## Acceptance Criteria
|
||
- [ ] A child execution issue exists for the chat routing integration point.
|
||
- [ ] Platform routing files become the explicit entrypoint for chat-side dispatch behavior.
|
||
- [ ] Hardcoded model mapping is either removed or downgraded to a compatibility shim with explicit ownership.
|
||
|
||
## Evidence Required
|
||
- [ ] Issue links for child execution items
|
||
- [ ] PR link
|
||
- [ ] Manifest or config diff
|
||
- [ ] Runtime verification output
|
||
- [ ] Docs updated if contract or behavior changed
|
||
|
||
## Rollback Plan
|
||
- Revert target: child PRs merged under this epic across ljwx-deploy and ljwx-chat
|
||
- Re-apply or resync step: restore previous routing config and chat runtime behavior
|
||
- Verification after rollback: chat requests follow the previous model mapping path and smoke checks still pass
|
||
|
||
## Links
|
||
- Milestone: ${milestone_title}
|
||
- Planning source: ${project_plan_path}
|
||
- Child issues:
|
||
$(child_issue_line issue_chat_routing)
|
||
EOF
|
||
;;
|
||
epic_knowledge)
|
||
cat <<EOF
|
||
## Summary
|
||
Define the minimum knowledge schema, directory conventions, and publish/invalidate flow in Git before retrieval behavior expands.
|
||
|
||
## Type
|
||
- [x] Epic
|
||
- [ ] Issue
|
||
- [ ] Follow-up
|
||
|
||
## Priority
|
||
- [ ] P0
|
||
- [x] P1
|
||
- [ ] P2
|
||
- [ ] P3
|
||
|
||
## Week
|
||
${week_text}
|
||
|
||
## Repos touched
|
||
$(repo_list_for_issue "$issue_id")
|
||
|
||
## Files / Paths expected to change
|
||
- platform/contracts/knowledge-document.schema.yaml
|
||
- platform/contracts/knowledge-chunk.schema.yaml
|
||
- platform/knowledge/source-registry.yaml
|
||
- platform/knowledge/publish-targets.yaml
|
||
- platform/knowledge/acl-policies.yaml
|
||
- docs/platform/knowledge-pipeline.md
|
||
|
||
## Dependencies
|
||
- ${ITEM_URLS[epic_truth_sources]:-Week 1 truth-source epic}
|
||
|
||
## Acceptance Criteria
|
||
- [ ] Child execution issues exist for knowledge schema and publish/invalidate MVP work.
|
||
- [ ] Knowledge publication rules stay in Git-managed config instead of narrative docs only.
|
||
- [ ] Reviewers can tell how content enters and leaves retrieval targets without oral context.
|
||
|
||
## Evidence Required
|
||
- [ ] Issue links for child execution items
|
||
- [ ] PR link
|
||
- [ ] Manifest or config diff
|
||
- [ ] Runtime verification output
|
||
- [ ] Docs updated if contract or behavior changed
|
||
|
||
## Rollback Plan
|
||
- Revert target: child PRs merged under this epic
|
||
- Re-apply or resync step: restore previous schema and publish target definitions
|
||
- Verification after rollback: retrieval targets and ACL expectations match the prior baseline
|
||
|
||
## Links
|
||
- Milestone: ${milestone_title}
|
||
- Planning source: ${project_plan_path}
|
||
- Child issues:
|
||
$(child_issue_line issue_knowledge_schema)
|
||
$(child_issue_line issue_knowledge_publish)
|
||
EOF
|
||
;;
|
||
epic_ops)
|
||
cat <<EOF
|
||
## Summary
|
||
Close the minimum bootstrap and rollback loop so platform recovery is runnable from Git-managed scripts and documents rather than operator memory.
|
||
|
||
## Type
|
||
- [x] Epic
|
||
- [ ] Issue
|
||
- [ ] Follow-up
|
||
|
||
## Priority
|
||
- [ ] P0
|
||
- [x] P1
|
||
- [ ] P2
|
||
- [ ] P3
|
||
|
||
## Week
|
||
${week_text}
|
||
|
||
## Repos touched
|
||
$(repo_list_for_issue "$issue_id")
|
||
|
||
## Files / Paths expected to change
|
||
- scripts/bootstrap/cluster-init.sh
|
||
- scripts/ops/run_rollback_drill.sh
|
||
- docs/platform/bootstrap-and-migration.md
|
||
- docs/rollback-drill.md
|
||
- runbooks/
|
||
|
||
## Dependencies
|
||
- ${ITEM_URLS[epic_truth_sources]:-Week 1 truth-source epic}
|
||
|
||
## Acceptance Criteria
|
||
- [ ] A child execution issue exists for the bootstrap/rollback minimum loop.
|
||
- [ ] Bootstrap and rollback docs point to runnable scripts instead of narrative-only steps.
|
||
- [ ] An on-call engineer can identify the minimum revert path from Git.
|
||
|
||
## Evidence Required
|
||
- [ ] Issue links for child execution items
|
||
- [ ] PR link
|
||
- [ ] Manifest or config diff
|
||
- [ ] Runtime verification output
|
||
- [ ] Docs updated if contract or behavior changed
|
||
|
||
## Rollback Plan
|
||
- Revert target: child PRs merged under this epic
|
||
- Re-apply or resync step: restore prior scripts and runbooks
|
||
- Verification after rollback: rollback drill returns to the previous known-good checklist
|
||
|
||
## Links
|
||
- Milestone: ${milestone_title}
|
||
- Planning source: ${project_plan_path}
|
||
- Child issues:
|
||
$(child_issue_line issue_bootstrap_rollback)
|
||
EOF
|
||
;;
|
||
epic_observability)
|
||
cat <<EOF
|
||
## Summary
|
||
Bound the runtime evidence and observability follow-up work so trace, fallback, and evidence closure do not begin on top of unstable truth sources.
|
||
|
||
## Type
|
||
- [x] Epic
|
||
- [ ] Issue
|
||
- [ ] Follow-up
|
||
|
||
## Priority
|
||
- [ ] P0
|
||
- [x] P1
|
||
- [ ] P2
|
||
- [ ] P3
|
||
|
||
## Week
|
||
${week_text}
|
||
|
||
## Repos touched
|
||
$(repo_list_for_issue "$issue_id")
|
||
|
||
## Files / Paths expected to change
|
||
- scripts/evidence/
|
||
- evidence/
|
||
- apps/ljwx-platform-observability/
|
||
- docs/platform/engineering-execution-guide.md
|
||
|
||
## Dependencies
|
||
- ${ITEM_URLS[epic_truth_sources]:-Week 1 truth-source epic}
|
||
- ${ITEM_URLS[epic_chat_routing]:-Week 2 routing epic}
|
||
- ${ITEM_URLS[epic_knowledge]:-Week 2 knowledge epic}
|
||
|
||
## Acceptance Criteria
|
||
- [ ] Follow-up execution issues are scoped only after truth-source and routing baselines stabilize.
|
||
- [ ] Evidence, trace, and observability paths point to Git-managed artifacts and dashboards.
|
||
- [ ] Closure reviews can name the exact evidence entrypoints to use.
|
||
|
||
## Evidence Required
|
||
- [ ] Follow-up issue links
|
||
- [ ] PR link
|
||
- [ ] Manifest or config diff
|
||
- [ ] Runtime verification output
|
||
- [ ] Docs updated if contract or behavior changed
|
||
|
||
## Rollback Plan
|
||
- Revert target: any follow-up PRs linked from this epic
|
||
- Re-apply or resync step: restore previous evidence pipeline or dashboard configuration
|
||
- Verification after rollback: nightly evidence and observability pointers match the last known-good state
|
||
|
||
## Links
|
||
- Milestone: ${milestone_title}
|
||
- Planning source: ${project_plan_path}
|
||
- Follow-up issue seed: create after Week 1 and Week 2 baselines land
|
||
EOF
|
||
;;
|
||
issue_platform_version)
|
||
cat <<EOF
|
||
## Summary
|
||
Expand release/platform-version.yaml so release review can trace integrated platform behavior across routing, capability, knowledge, and workflow baselines from one composite version file.
|
||
|
||
## Type
|
||
- [ ] Epic
|
||
- [x] Issue
|
||
- [ ] Follow-up
|
||
|
||
## Priority
|
||
- [x] P0
|
||
- [ ] P1
|
||
- [ ] P2
|
||
- [ ] P3
|
||
|
||
## Week
|
||
${week_text}
|
||
|
||
## Repos touched
|
||
$(repo_list_for_issue "$issue_id")
|
||
|
||
## Files / Paths expected to change
|
||
- release/platform-version.yaml
|
||
- platform/README.md
|
||
- docs/platform/platform-repo-design.md
|
||
|
||
## Dependencies
|
||
- Parent epic: ${parent_url}
|
||
|
||
## Acceptance Criteria
|
||
- [ ] The components section lists concrete delivery_ref and config_ref values for active platform services.
|
||
- [ ] config_versions covers the routing, capability, knowledge, workflow, and environment references used in release review.
|
||
- [ ] The file explains when the composite platform version must change.
|
||
|
||
## Evidence Required
|
||
- [ ] PR link
|
||
- [ ] Manifest or config diff
|
||
- [ ] Runtime verification output
|
||
- [ ] Docs updated if contract or behavior changed
|
||
|
||
## Rollback Plan
|
||
- Revert target: release/platform-version.yaml and related docs PR
|
||
- Re-apply or resync step: restore previous composite version file and rerun release verification
|
||
- Verification after rollback: release review points back to the prior known-good version references
|
||
|
||
## Links
|
||
- Milestone: ${milestone_title}
|
||
- Parent epic: ${parent_url}
|
||
- Planning source: ${project_plan_path}
|
||
EOF
|
||
;;
|
||
issue_env_matrix)
|
||
cat <<EOF
|
||
## Summary
|
||
Complete the environment delta fields in platform/assembly/env-matrix.yaml so dev and prod differences stop living in oral context.
|
||
|
||
## Type
|
||
- [ ] Epic
|
||
- [x] Issue
|
||
- [ ] Follow-up
|
||
|
||
## Priority
|
||
- [x] P0
|
||
- [ ] P1
|
||
- [ ] P2
|
||
- [ ] P3
|
||
|
||
## Week
|
||
${week_text}
|
||
|
||
## Repos touched
|
||
$(repo_list_for_issue "$issue_id")
|
||
|
||
## Files / Paths expected to change
|
||
- platform/assembly/env-matrix.yaml
|
||
- release/platform-version.yaml
|
||
- docs/platform/deployment-environments.md
|
||
|
||
## Dependencies
|
||
- Parent epic: ${parent_url}
|
||
|
||
## Acceptance Criteria
|
||
- [ ] Active profiles carry concrete service map, smoke target, sync, public HTTPS, release policy, and approval details.
|
||
- [ ] The dev and prod environment sections explain which profile is default and which gate is manual or automated.
|
||
- [ ] The composite platform version file points to the correct environment profile reference.
|
||
|
||
## Evidence Required
|
||
- [ ] PR link
|
||
- [ ] Manifest or config diff
|
||
- [ ] Runtime verification output
|
||
- [ ] Docs updated if contract or behavior changed
|
||
|
||
## Rollback Plan
|
||
- Revert target: platform/assembly/env-matrix.yaml and related docs PR
|
||
- Re-apply or resync step: restore the previous matrix and rerun smoke target checks
|
||
- Verification after rollback: deployment review reflects the previous environment delta definitions
|
||
|
||
## Links
|
||
- Milestone: ${milestone_title}
|
||
- Parent epic: ${parent_url}
|
||
- Planning source: ${project_plan_path}
|
||
EOF
|
||
;;
|
||
issue_capability_alignment)
|
||
cat <<EOF
|
||
## Summary
|
||
Align platform/assembly/capabilities.yaml with platform/contracts/service-capability-map.yaml so runtime capability ownership is explicit and auditable.
|
||
|
||
## Type
|
||
- [ ] Epic
|
||
- [x] Issue
|
||
- [ ] Follow-up
|
||
|
||
## Priority
|
||
- [x] P0
|
||
- [ ] P1
|
||
- [ ] P2
|
||
- [ ] P3
|
||
|
||
## Week
|
||
${week_text}
|
||
|
||
## Repos touched
|
||
$(repo_list_for_issue "$issue_id")
|
||
|
||
## Files / Paths expected to change
|
||
- platform/assembly/capabilities.yaml
|
||
- platform/contracts/service-capability-map.yaml
|
||
- docs/platform/component-responsibilities.md
|
||
|
||
## Dependencies
|
||
- Parent epic: ${parent_url}
|
||
|
||
## Acceptance Criteria
|
||
- [ ] Every runtime-facing capability in the service map exists in the capability registry or is explicitly marked non-runtime.
|
||
- [ ] Every capability owner_service maps back to a service entry with matching responsibility.
|
||
- [ ] Timeout, auth, and audit fields cover the critical dispatch capabilities used by routing.
|
||
|
||
## Evidence Required
|
||
- [ ] PR link
|
||
- [ ] Manifest or config diff
|
||
- [ ] Runtime verification output
|
||
- [ ] Docs updated if contract or behavior changed
|
||
|
||
## Rollback Plan
|
||
- Revert target: capability registry and service capability map PR
|
||
- Re-apply or resync step: restore the previous capability files and rerun contract review
|
||
- Verification after rollback: ownership and dispatch expectations match the prior baseline
|
||
|
||
## Links
|
||
- Milestone: ${milestone_title}
|
||
- Parent epic: ${parent_url}
|
||
- Planning source: ${project_plan_path}
|
||
EOF
|
||
;;
|
||
issue_router_contracts)
|
||
cat <<EOF
|
||
## Summary
|
||
Document and validate the router request, router decision, and tool-result contracts so routing evidence can be reviewed before runtime integration starts.
|
||
|
||
## Type
|
||
- [ ] Epic
|
||
- [x] Issue
|
||
- [ ] Follow-up
|
||
|
||
## Priority
|
||
- [x] P0
|
||
- [ ] P1
|
||
- [ ] P2
|
||
- [ ] P3
|
||
|
||
## Week
|
||
${week_text}
|
||
|
||
## Repos touched
|
||
$(repo_list_for_issue "$issue_id")
|
||
|
||
## Files / Paths expected to change
|
||
- platform/contracts/router-request.schema.json
|
||
- platform/contracts/router-decision.schema.json
|
||
- platform/contracts/tool-result.schema.json
|
||
- docs/platform/routing-strategy.md
|
||
- scripts/ci/check-smart-cs-contract.sh
|
||
|
||
## Dependencies
|
||
- Parent epic: ${parent_url}
|
||
|
||
## Acceptance Criteria
|
||
- [ ] The three schemas cover the fields described in docs/platform/routing-strategy.md.
|
||
- [ ] A validation entrypoint exists or is documented for schema review in CI.
|
||
- [ ] The request -> decision -> tool-result evidence path is described for reviewers and operators.
|
||
|
||
## Evidence Required
|
||
- [ ] PR link
|
||
- [ ] Manifest or config diff
|
||
- [ ] Runtime verification output
|
||
- [ ] Docs updated if contract or behavior changed
|
||
|
||
## Rollback Plan
|
||
- Revert target: router schema and validation-doc PR
|
||
- Re-apply or resync step: restore prior schemas and CI notes
|
||
- Verification after rollback: contract review falls back to the previous documented schema set
|
||
|
||
## Links
|
||
- Milestone: ${milestone_title}
|
||
- Parent epic: ${parent_url}
|
||
- Planning source: ${project_plan_path}
|
||
EOF
|
||
;;
|
||
issue_chat_routing)
|
||
cat <<EOF
|
||
## Summary
|
||
Define the ljwx-chat routing configuration entrypoint and remove hardcoded model mapping dependencies so chat behavior can be explained from platform config.
|
||
|
||
## Type
|
||
- [ ] Epic
|
||
- [x] Issue
|
||
- [ ] Follow-up
|
||
|
||
## Priority
|
||
- [x] P0
|
||
- [ ] P1
|
||
- [ ] P2
|
||
- [ ] P3
|
||
|
||
## Week
|
||
${week_text}
|
||
|
||
## Repos touched
|
||
$(repo_list_for_issue "$issue_id")
|
||
|
||
## Files / Paths expected to change
|
||
- platform/routing/routes.dev.yaml
|
||
- platform/routing/routes.prod.yaml
|
||
- release/platform-version.yaml
|
||
- ljwx-chat/src/config/modelRouting.ts
|
||
- ljwx-chat/src/app/(backend)/webapi/chat/[provider]/route.ts
|
||
- ljwx-chat/src/server/modules/ModelRuntime/index.ts
|
||
- ljwx-chat/docs/MODEL_MAPPING_TEST_GUIDE.md
|
||
|
||
## Dependencies
|
||
- Parent epic: ${parent_url}
|
||
- Contract baseline: ${ITEM_URLS[epic_contracts]:-Week 1 contract epic}
|
||
|
||
## Acceptance Criteria
|
||
- [ ] ljwx-chat has one explicit platform-driven routing configuration entrypoint.
|
||
- [ ] Hardcoded model mapping is removed or reduced to a compatibility bridge with bounded ownership.
|
||
- [ ] The changed path is documented so reviewers can trace request routing from platform config to chat runtime.
|
||
|
||
## Evidence Required
|
||
- [ ] PR link
|
||
- [ ] Manifest or config diff
|
||
- [ ] Runtime verification output
|
||
- [ ] Docs updated if contract or behavior changed
|
||
|
||
## Rollback Plan
|
||
- Revert target: ljwx-deploy and ljwx-chat PRs linked to this issue
|
||
- Re-apply or resync step: restore previous model mapping behavior and rerun chat smoke tests
|
||
- Verification after rollback: chat requests follow the prior OpenClaw or model routing path without new platform config
|
||
|
||
## Links
|
||
- Milestone: ${milestone_title}
|
||
- Parent epic: ${parent_url}
|
||
- Planning source: ${project_plan_path}
|
||
EOF
|
||
;;
|
||
issue_knowledge_schema)
|
||
cat <<EOF
|
||
## Summary
|
||
Establish the minimum knowledge schema and directory conventions needed to keep retrieval inputs versioned in platform config rather than scattered docs.
|
||
|
||
## Type
|
||
- [ ] Epic
|
||
- [x] Issue
|
||
- [ ] Follow-up
|
||
|
||
## Priority
|
||
- [x] P0
|
||
- [ ] P1
|
||
- [ ] P2
|
||
- [ ] P3
|
||
|
||
## Week
|
||
${week_text}
|
||
|
||
## Repos touched
|
||
$(repo_list_for_issue "$issue_id")
|
||
|
||
## Files / Paths expected to change
|
||
- platform/contracts/knowledge-document.schema.yaml
|
||
- platform/contracts/knowledge-chunk.schema.yaml
|
||
- platform/knowledge/source-registry.yaml
|
||
- platform/knowledge/taxonomy.yaml
|
||
- docs/platform/knowledge-pipeline.md
|
||
|
||
## Dependencies
|
||
- Parent epic: ${parent_url}
|
||
|
||
## Acceptance Criteria
|
||
- [ ] The minimum document and chunk schemas define the fields needed for publication and invalidation decisions.
|
||
- [ ] The source registry and taxonomy reflect the intended directory and classification boundary.
|
||
- [ ] Reviewers can point to one Git-managed location for schema and directory conventions.
|
||
|
||
## Evidence Required
|
||
- [ ] PR link
|
||
- [ ] Manifest or config diff
|
||
- [ ] Runtime verification output
|
||
- [ ] Docs updated if contract or behavior changed
|
||
|
||
## Rollback Plan
|
||
- Revert target: knowledge schema and directory convention PR
|
||
- Re-apply or resync step: restore previous schema files and taxonomy references
|
||
- Verification after rollback: knowledge ingestion expectations match the prior baseline
|
||
|
||
## Links
|
||
- Milestone: ${milestone_title}
|
||
- Parent epic: ${parent_url}
|
||
- Planning source: ${project_plan_path}
|
||
EOF
|
||
;;
|
||
issue_knowledge_publish)
|
||
cat <<EOF
|
||
## Summary
|
||
Define the knowledge publish and invalidate MVP so content lifecycle rules are explicit before runtime retrieval and dataset syncs expand.
|
||
|
||
## Type
|
||
- [ ] Epic
|
||
- [x] Issue
|
||
- [ ] Follow-up
|
||
|
||
## Priority
|
||
- [x] P0
|
||
- [ ] P1
|
||
- [ ] P2
|
||
- [ ] P3
|
||
|
||
## Week
|
||
${week_text}
|
||
|
||
## Repos touched
|
||
$(repo_list_for_issue "$issue_id")
|
||
|
||
## Files / Paths expected to change
|
||
- platform/knowledge/publish-targets.yaml
|
||
- platform/knowledge/acl-policies.yaml
|
||
- platform/knowledge/source-registry.yaml
|
||
- docs/platform/knowledge-pipeline.md
|
||
- docs/platform/engineering-execution-guide.md
|
||
|
||
## Dependencies
|
||
- Parent epic: ${parent_url}
|
||
- Schema baseline: ${ITEM_URLS[issue_knowledge_schema]:-Knowledge schema issue}
|
||
|
||
## Acceptance Criteria
|
||
- [ ] Publish targets define the minimum retrieval destinations and scope boundaries.
|
||
- [ ] Invalidate triggers are explicit for source deletion, ACL drift, review-state change, expiry, and scope change.
|
||
- [ ] The MVP path is reviewable from Git without assuming external system behavior.
|
||
|
||
## Evidence Required
|
||
- [ ] PR link
|
||
- [ ] Manifest or config diff
|
||
- [ ] Runtime verification output
|
||
- [ ] Docs updated if contract or behavior changed
|
||
|
||
## Rollback Plan
|
||
- Revert target: publish/invalidate MVP PR
|
||
- Re-apply or resync step: restore prior publish target and ACL definitions
|
||
- Verification after rollback: knowledge lifecycle behavior falls back to the previous documented state
|
||
|
||
## Links
|
||
- Milestone: ${milestone_title}
|
||
- Parent epic: ${parent_url}
|
||
- Planning source: ${project_plan_path}
|
||
EOF
|
||
;;
|
||
issue_bootstrap_rollback)
|
||
cat <<EOF
|
||
## Summary
|
||
Validate the minimum bootstrap and rollback closed loop so platform recovery can be rehearsed from Git-managed scripts, docs, and runbooks.
|
||
|
||
## Type
|
||
- [ ] Epic
|
||
- [x] Issue
|
||
- [ ] Follow-up
|
||
|
||
## Priority
|
||
- [x] P0
|
||
- [ ] P1
|
||
- [ ] P2
|
||
- [ ] P3
|
||
|
||
## Week
|
||
${week_text}
|
||
|
||
## Repos touched
|
||
$(repo_list_for_issue "$issue_id")
|
||
|
||
## Files / Paths expected to change
|
||
- scripts/bootstrap/cluster-init.sh
|
||
- scripts/ops/run_rollback_drill.sh
|
||
- docs/platform/bootstrap-and-migration.md
|
||
- docs/rollback-drill.md
|
||
- runbooks/
|
||
|
||
## Dependencies
|
||
- Parent epic: ${parent_url}
|
||
|
||
## Acceptance Criteria
|
||
- [ ] Bootstrap docs point to a runnable minimum path from host prep to smoke verification.
|
||
- [ ] Rollback drill docs point to a runnable minimum revert path with verification after rollback.
|
||
- [ ] Reviewers can identify the exact scripts and documents that form the minimum recovery loop.
|
||
|
||
## Evidence Required
|
||
- [ ] PR link
|
||
- [ ] Manifest or config diff
|
||
- [ ] Runtime verification output
|
||
- [ ] Docs updated if contract or behavior changed
|
||
|
||
## Rollback Plan
|
||
- Revert target: bootstrap/rollback docs or script PR
|
||
- Re-apply or resync step: restore prior scripts and drill docs
|
||
- Verification after rollback: the previous recovery checklist and dry-run behavior are restored
|
||
|
||
## Links
|
||
- Milestone: ${milestone_title}
|
||
- Parent epic: ${parent_url}
|
||
- Planning source: ${project_plan_path}
|
||
EOF
|
||
;;
|
||
*)
|
||
fail "未知 issue body: ${issue_id}"
|
||
;;
|
||
esac
|
||
}
|
||
|
||
sync_issue() {
|
||
local issue_id="$1"
|
||
local title
|
||
local milestone_id
|
||
local milestone_title
|
||
local body_file
|
||
local -a edit_label_args=()
|
||
local -a create_label_args=()
|
||
local labels_text
|
||
local label
|
||
local record
|
||
local issue_number=""
|
||
local issue_url=""
|
||
|
||
title="$(issue_title "$issue_id")"
|
||
milestone_id="$(issue_milestone_id "$issue_id")"
|
||
milestone_title="${MILESTONE_TITLES[$milestone_id]}"
|
||
body_file="$(mktemp)"
|
||
render_issue_body "$issue_id" >"$body_file"
|
||
|
||
labels_text="$(labels_for_issue "$issue_id")"
|
||
for label in $labels_text; do
|
||
edit_label_args+=(--add-label "$label")
|
||
create_label_args+=(--label "$label")
|
||
done
|
||
|
||
if [[ "$DRY_RUN" != "1" ]]; then
|
||
record="$(lookup_issue_record "$title")"
|
||
if [[ -n "$record" ]]; then
|
||
IFS=$'\t' read -r issue_number issue_url <<<"$record"
|
||
fi
|
||
fi
|
||
|
||
if [[ -n "$issue_number" ]]; then
|
||
log_info "更新 issue: ${title}"
|
||
run_quiet gh issue edit "$issue_number" \
|
||
--repo "$TARGET_REPO" \
|
||
--title "$title" \
|
||
--body-file "$body_file" \
|
||
--milestone "$milestone_title" \
|
||
"${edit_label_args[@]}"
|
||
else
|
||
log_info "创建 issue: ${title}"
|
||
if [[ "$DRY_RUN" == "1" ]]; then
|
||
run_or_print gh issue create \
|
||
--repo "$TARGET_REPO" \
|
||
--title "$title" \
|
||
--body-file "$body_file" \
|
||
--milestone "$milestone_title" \
|
||
"${create_label_args[@]}"
|
||
issue_url="https://github.com/${TARGET_REPO}/issues/TBD-${issue_id}"
|
||
issue_number="TBD"
|
||
else
|
||
issue_url="$(gh_retry_command "创建 GitHub Issue" gh issue create \
|
||
--repo "$TARGET_REPO" \
|
||
--title "$title" \
|
||
--body-file "$body_file" \
|
||
--milestone "$milestone_title" \
|
||
"${create_label_args[@]}")"
|
||
issue_number="${issue_url##*/}"
|
||
fi
|
||
fi
|
||
|
||
ITEM_URLS["$issue_id"]="$issue_url"
|
||
ITEM_NUMBERS["$issue_id"]="$issue_number"
|
||
|
||
sync_issue_to_project "$issue_id" "$issue_url"
|
||
|
||
rm -f "$body_file"
|
||
|
||
if [[ "$DRY_RUN" != "1" ]]; then
|
||
refresh_issue_cache
|
||
fi
|
||
}
|
||
|
||
sync_epics() {
|
||
sync_issue "epic_truth_sources"
|
||
sync_issue "epic_contracts"
|
||
sync_issue "epic_chat_routing"
|
||
sync_issue "epic_knowledge"
|
||
sync_issue "epic_ops"
|
||
sync_issue "epic_observability"
|
||
}
|
||
|
||
sync_child_issues() {
|
||
sync_issue "issue_platform_version"
|
||
sync_issue "issue_env_matrix"
|
||
sync_issue "issue_capability_alignment"
|
||
sync_issue "issue_router_contracts"
|
||
sync_issue "issue_chat_routing"
|
||
sync_issue "issue_knowledge_schema"
|
||
sync_issue "issue_knowledge_publish"
|
||
sync_issue "issue_bootstrap_rollback"
|
||
}
|
||
|
||
print_summary() {
|
||
cat <<EOF
|
||
[完成] 目标仓库: ${TARGET_REPO}
|
||
$(if project_enabled; then printf 'GitHub Project: %s/%s\n' "$PROJECT_OWNER" "$PROJECT_NUMBER"; fi)
|
||
|
||
Milestones:
|
||
- ${MILESTONE_TITLES[week1]}
|
||
- ${MILESTONE_TITLES[week2]}
|
||
- ${MILESTONE_TITLES[week3]}
|
||
|
||
Epics:
|
||
- ${ITEM_URLS[epic_truth_sources]:-未执行}
|
||
- ${ITEM_URLS[epic_contracts]:-未执行}
|
||
- ${ITEM_URLS[epic_chat_routing]:-未执行}
|
||
- ${ITEM_URLS[epic_knowledge]:-未执行}
|
||
- ${ITEM_URLS[epic_ops]:-未执行}
|
||
- ${ITEM_URLS[epic_observability]:-未执行}
|
||
|
||
P0 Issues:
|
||
- ${ITEM_URLS[issue_platform_version]:-未执行}
|
||
- ${ITEM_URLS[issue_env_matrix]:-未执行}
|
||
- ${ITEM_URLS[issue_capability_alignment]:-未执行}
|
||
- ${ITEM_URLS[issue_router_contracts]:-未执行}
|
||
- ${ITEM_URLS[issue_chat_routing]:-未执行}
|
||
- ${ITEM_URLS[issue_knowledge_schema]:-未执行}
|
||
- ${ITEM_URLS[issue_knowledge_publish]:-未执行}
|
||
- ${ITEM_URLS[issue_bootstrap_rollback]:-未执行}
|
||
EOF
|
||
}
|
||
|
||
main() {
|
||
parse_args "$@"
|
||
if [[ -n "$PROJECT_URL" ]]; then
|
||
resolve_project_from_url "$PROJECT_URL"
|
||
fi
|
||
validate_project_args
|
||
require_command "gh"
|
||
require_command "python3"
|
||
resolve_target_repo
|
||
ensure_auth
|
||
run_preflight
|
||
ensure_project_access
|
||
|
||
if [[ "$SKIP_LABELS" != "1" ]]; then
|
||
create_labels
|
||
else
|
||
log_warn "已跳过 labels"
|
||
fi
|
||
|
||
if [[ "$SKIP_MILESTONES" != "1" ]]; then
|
||
refresh_milestone_cache
|
||
ensure_milestone "week1"
|
||
ensure_milestone "week2"
|
||
ensure_milestone "week3"
|
||
else
|
||
log_warn "已跳过 milestones"
|
||
fi
|
||
|
||
if project_enabled; then
|
||
ensure_project_fields
|
||
fi
|
||
|
||
if [[ "$SKIP_ISSUES" != "1" ]]; then
|
||
if [[ "$DRY_RUN" != "1" ]]; then
|
||
refresh_issue_cache
|
||
fi
|
||
sync_epics
|
||
sync_child_issues
|
||
if [[ "$DRY_RUN" != "1" ]]; then
|
||
sync_epics
|
||
fi
|
||
else
|
||
log_warn "已跳过 epics/issues"
|
||
fi
|
||
|
||
print_summary
|
||
}
|
||
|
||
main "$@"
|