Files
ljwx-deploy/scripts/github/seed-platform-project.sh
T

2019 lines
54 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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 "$@"