feat: 融合 devflow-claude P0 批机制 (REQ-20260416-0017)
P0-1: SessionStart Hook — hooks/session-context.sh 从分支名解析 REQ-ID,调 MCP API 查询需求详情注入 system-reminder P0-2: PreToolUse Hook — hooks/pre-tool-confirm.sh 拦截生产推送、force push、docker prod 容器操作、git reset --hard 等 P0-3: Release Draft 闸门设计文档 — docs/design/release-draft-gate.md 完整架构 + 渐进式落地路径(拆 7 个子任务延后) 附最小可用脚本 hooks/release-draft.sh 创建 Gitea draft release P0-4: Memory 隔离规则 — 写入 req-prd / req-design / req-workflow 禁止 auto-memory 污染模板产出物(章节结构、字段定义、文档结构) P0-5: CLAUDE.md 架构检查 + 架构片段库 dev-coding skill 执行前检查架构关键词 新增 templates/claude-md-snippets/ 含 Go+Gin / React+AntD / Vue+Element / MCP+TS / generic 五套骨架 P0-6: /commit 分支保护自动化 — 新 skill dev-commit-plugin 保护分支自动建功能分支 + Conventional Commits + REQ-XXX 自动关联 安装: bash hooks/install.sh 后续: P0-3 完整实现拆 7 个子任务(P0-3.1 ~ P0-3.7) 建议先部署 hooks 跑 1-2 周观察,再推进 Release 机制落地
This commit is contained in:
100
hooks/README.md
Normal file
100
hooks/README.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# Claude Code Hooks
|
||||
|
||||
本目录包含 ai-proj-helper 体系的 Claude Code 钩子脚本。
|
||||
|
||||
**REQ-20260416-0017 P0 批 — 源自 devflow-claude 借鉴**
|
||||
|
||||
## 脚本清单
|
||||
|
||||
| 脚本 | 事件 | 作用 |
|
||||
|------|------|------|
|
||||
| `session-context.sh` | SessionStart | 从分支名解析 REQ-ID,注入需求上下文到会话 |
|
||||
| `pre-tool-confirm.sh` | PreToolUse (Bash) | 拦截生产发布、force push、docker 生产容器、reset --hard 等危险操作 |
|
||||
|
||||
## 安装(用户级,一次即可)
|
||||
|
||||
### 方式 1:编辑 `~/.claude/settings.json`
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"hooks": {
|
||||
"SessionStart": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "/Users/donglinlai/coding/qiudl/ai-proj-helper/hooks/session-context.sh",
|
||||
"timeout": 10
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "/Users/donglinlai/coding/qiudl/ai-proj-helper/hooks/pre-tool-confirm.sh",
|
||||
"timeout": 30
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 方式 2:一键安装脚本
|
||||
|
||||
```bash
|
||||
bash /Users/donglinlai/coding/qiudl/ai-proj-helper/hooks/install.sh
|
||||
```
|
||||
|
||||
## 验证
|
||||
|
||||
### SessionStart hook
|
||||
|
||||
1. 切到一个带 REQ-ID 的分支:`git checkout feat/REQ-20260416-0017-xxx`
|
||||
2. 打开新的 Claude Code 会话
|
||||
3. 会话开头应看到 `# 需求上下文(SessionStart Hook)` 块
|
||||
|
||||
### PreToolUse hook
|
||||
|
||||
让 AI 尝试执行这些命令中的任一条,应弹出原生确认对话框:
|
||||
|
||||
- `git push origin main`
|
||||
- `git push --force`
|
||||
- `tea pr merge --base main`
|
||||
- `docker rm ai_postgres_prod`
|
||||
- `git reset --hard`
|
||||
|
||||
## 依赖
|
||||
|
||||
- `bash` (macOS / Linux 默认)
|
||||
- `jq` (PreToolUse hook 需要,`brew install jq`)
|
||||
- `python3` (SessionStart hook 解析 JSON)
|
||||
- `curl` (SessionStart hook 调 MCP API)
|
||||
|
||||
## 自定义
|
||||
|
||||
### 修改 MCP API 地址
|
||||
|
||||
在项目根目录创建 `.ai-proj-env`:
|
||||
|
||||
```bash
|
||||
export AI_PROJ_API_BASE="https://api.ai-proj.example.com"
|
||||
export AI_PROJ_MCP_KEY="your-mcp-api-key"
|
||||
```
|
||||
|
||||
或设置全局环境变量。
|
||||
|
||||
### 拦截更多命令
|
||||
|
||||
编辑 `pre-tool-confirm.sh`,在 `REASON` 赋值的 elif 链中增加规则。
|
||||
|
||||
## 设计原则
|
||||
|
||||
1. **快速失败退出**:不处理的命令立即 `exit 0`,不影响性能
|
||||
2. **非侵入性**:网络/依赖缺失时静默退出,不阻塞正常工作流
|
||||
3. **可复现**:hook 脚本跟随仓库分发,方便团队一致部署
|
||||
81
hooks/install.sh
Executable file
81
hooks/install.sh
Executable file
@@ -0,0 +1,81 @@
|
||||
#!/bin/bash
|
||||
# install.sh
|
||||
# 一键把 ai-proj-helper hooks 注册到 ~/.claude/settings.json
|
||||
#
|
||||
# 用法: bash hooks/install.sh
|
||||
|
||||
set -e
|
||||
|
||||
HOOKS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SETTINGS_FILE="$HOME/.claude/settings.json"
|
||||
|
||||
if ! command -v python3 >/dev/null 2>&1; then
|
||||
echo "❌ 需要 python3(用于合并 JSON)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v jq >/dev/null 2>&1; then
|
||||
echo "⚠️ 未安装 jq,PreToolUse hook 将无法正常工作"
|
||||
echo " 请执行: brew install jq"
|
||||
fi
|
||||
|
||||
mkdir -p "$HOME/.claude"
|
||||
|
||||
python3 << EOF
|
||||
import json
|
||||
import os
|
||||
|
||||
settings_file = "$SETTINGS_FILE"
|
||||
hooks_dir = "$HOOKS_DIR"
|
||||
|
||||
# 读已有配置
|
||||
if os.path.exists(settings_file):
|
||||
with open(settings_file) as f:
|
||||
data = json.load(f)
|
||||
else:
|
||||
data = {}
|
||||
|
||||
# 合并 hooks
|
||||
hooks = data.setdefault("hooks", {})
|
||||
|
||||
# SessionStart
|
||||
session_start = hooks.setdefault("SessionStart", [])
|
||||
session_cmd = f"{hooks_dir}/session-context.sh"
|
||||
already_registered = any(
|
||||
any(h.get("command") == session_cmd for h in entry.get("hooks", []))
|
||||
for entry in session_start
|
||||
)
|
||||
if not already_registered:
|
||||
session_start.append({
|
||||
"hooks": [{"type": "command", "command": session_cmd, "timeout": 10}]
|
||||
})
|
||||
print("✅ 注册 SessionStart hook")
|
||||
else:
|
||||
print("⏭️ SessionStart hook 已存在")
|
||||
|
||||
# PreToolUse (Bash)
|
||||
pre_tool = hooks.setdefault("PreToolUse", [])
|
||||
pre_cmd = f"{hooks_dir}/pre-tool-confirm.sh"
|
||||
already_registered = any(
|
||||
entry.get("matcher") == "Bash" and any(h.get("command") == pre_cmd for h in entry.get("hooks", []))
|
||||
for entry in pre_tool
|
||||
)
|
||||
if not already_registered:
|
||||
pre_tool.append({
|
||||
"matcher": "Bash",
|
||||
"hooks": [{"type": "command", "command": pre_cmd, "timeout": 30}]
|
||||
})
|
||||
print("✅ 注册 PreToolUse (Bash) hook")
|
||||
else:
|
||||
print("⏭️ PreToolUse hook 已存在")
|
||||
|
||||
# 写回
|
||||
with open(settings_file, "w") as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
|
||||
print(f"\n📝 已更新: {settings_file}")
|
||||
print("\n下一步:")
|
||||
print(" 1. 重启 Claude Code 会话")
|
||||
print(" 2. 切到含 REQ-ID 的分支测试 SessionStart hook")
|
||||
print(" 3. 让 AI 尝试 'git push origin main' 测试 PreToolUse hook")
|
||||
EOF
|
||||
94
hooks/pre-tool-confirm.sh
Executable file
94
hooks/pre-tool-confirm.sh
Executable file
@@ -0,0 +1,94 @@
|
||||
#!/bin/bash
|
||||
# pre-tool-confirm.sh
|
||||
# PreToolUse Hook: 拦截危险操作,强制原生确认对话框
|
||||
#
|
||||
# 输入格式(stdin JSON):
|
||||
# { "tool_name": "Bash", "tool_input": { "command": "..." } }
|
||||
#
|
||||
# 输出格式(stdout JSON):
|
||||
# { "hookSpecificOutput": { "hookEventName": "PreToolUse",
|
||||
# "permissionDecision": "ask",
|
||||
# "permissionDecisionReason": "..." } }
|
||||
#
|
||||
# 安装方式:在 ~/.claude/settings.json 配置
|
||||
# hooks.PreToolUse:
|
||||
# - matcher: "Bash"
|
||||
# hooks:
|
||||
# - type: command
|
||||
# command: "<path>/hooks/pre-tool-confirm.sh"
|
||||
# timeout: 30
|
||||
#
|
||||
# 参考:devflow-claude confirm-before-commit.sh + ai-proj memory 规则
|
||||
# REQ-20260416-0017 P0-2
|
||||
|
||||
set -e
|
||||
|
||||
INPUT=$(cat)
|
||||
|
||||
if ! command -v jq >/dev/null 2>&1; then
|
||||
# 没有 jq 就不处理
|
||||
exit 0
|
||||
fi
|
||||
|
||||
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
|
||||
|
||||
if [ -z "$COMMAND" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
REASON=""
|
||||
|
||||
# ============ 1. 生产分支推送 ============
|
||||
if echo "$COMMAND" | grep -qE '\bgit\s+push\b.*\b(origin\s+)?(main|master)\b'; then
|
||||
REASON="⚠️ 即将推送到 main/master 生产分支。确认已过 PR 评审?"
|
||||
|
||||
# ============ 2. 强制推送 ============
|
||||
elif echo "$COMMAND" | grep -qE '\bgit\s+push\b.*(--force|--force-with-lease|-f\b)'; then
|
||||
REASON="⛔ 危险:force push 会覆盖远程历史,可能丢失他人提交。确认继续?"
|
||||
|
||||
# ============ 3. tea pr merge --base main ============
|
||||
elif echo "$COMMAND" | grep -qE '\btea\s+pr\s+merge\b.*--base\s+main\b'; then
|
||||
REASON="⚠️ 即将合并 PR 到 main 分支,合并后将触发生产部署。确认 PR 已测试?"
|
||||
|
||||
# ============ 4. docker rm/stop 生产容器 ============
|
||||
elif echo "$COMMAND" | grep -qE '\bdocker\s+(rm|stop|kill)\b.*\b(ai_postgres_prod|ai_backend_prod|ai_frontend_prod|ai_redis_prod)\b'; then
|
||||
REASON="⛔ 危险:即将停止/删除生产容器!2026-04-05 曾因此宕机 15 分钟。确认是灾难恢复?"
|
||||
|
||||
# ============ 5. docker rm/stop 其他 prod 相关 ============
|
||||
elif echo "$COMMAND" | grep -qE '\bdocker\s+(rm|stop|kill)\b.*_prod\b'; then
|
||||
REASON="⚠️ 即将停止/删除含 _prod 的容器。确认是生产环境?"
|
||||
|
||||
# ============ 6. git reset --hard / clean -fd ============
|
||||
elif echo "$COMMAND" | grep -qE '\bgit\s+reset\s+--hard\b'; then
|
||||
REASON="⚠️ git reset --hard 会丢弃所有未提交改动。确认继续?"
|
||||
|
||||
elif echo "$COMMAND" | grep -qE '\bgit\s+clean\s+.*-f'; then
|
||||
REASON="⚠️ git clean -f 会删除未跟踪文件。确认继续?"
|
||||
|
||||
# ============ 7. rm -rf / 系统路径 ============
|
||||
elif echo "$COMMAND" | grep -qE '\brm\s+.*-[rf]+.*\s+(/|/\*|~|\$HOME)'; then
|
||||
REASON="⛔ 危险:rm -rf 指向系统根或家目录。确认继续?"
|
||||
|
||||
# ============ 8. ssh 生产服务器 + 破坏性命令 ============
|
||||
elif echo "$COMMAND" | grep -qE 'ssh\s+\S*prod\S*.*\b(rm|drop|truncate|delete)\b'; then
|
||||
REASON="⛔ 危险:在生产服务器执行破坏性命令。确认继续?"
|
||||
|
||||
# ============ 9. psql/mysql 生产数据库 + DROP/TRUNCATE/DELETE ============
|
||||
elif echo "$COMMAND" | grep -qiE '(psql|mysql).*prod.*\b(DROP|TRUNCATE|DELETE FROM)\b'; then
|
||||
REASON="⛔ 危险:生产数据库 DROP/TRUNCATE/DELETE。确认已备份?"
|
||||
|
||||
# ============ 10. MCP advance_delivery_stage force=true ============
|
||||
# 这种会走 MCP 而不是 Bash,本 hook 不好拦,留给另一个 matcher 处理
|
||||
fi
|
||||
|
||||
if [ -n "$REASON" ]; then
|
||||
jq -n --arg reason "$REASON" '{
|
||||
hookSpecificOutput: {
|
||||
hookEventName: "PreToolUse",
|
||||
permissionDecision: "ask",
|
||||
permissionDecisionReason: $reason
|
||||
}
|
||||
}'
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
161
hooks/release-draft.sh
Executable file
161
hooks/release-draft.sh
Executable file
@@ -0,0 +1,161 @@
|
||||
#!/bin/bash
|
||||
# release-draft.sh
|
||||
# 最小可用版:从本地 git 仓库创建 Gitea draft release
|
||||
#
|
||||
# 用法:
|
||||
# export GITEA_TOKEN=$(bw get password "Gitea - qiudl Token")
|
||||
# bash release-draft.sh v1.2.0 [--from v1.1.9]
|
||||
#
|
||||
# 输出: 创建的 draft release URL(待人工 publish)
|
||||
# REQ-20260416-0017 P0-3 最小可用脚本
|
||||
|
||||
set -e
|
||||
|
||||
VERSION="$1"
|
||||
if [ -z "$VERSION" ]; then
|
||||
echo "❌ 用法: $0 <version> [--from <previous_tag>]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
FROM_TAG=""
|
||||
if [ "$2" = "--from" ]; then
|
||||
FROM_TAG="$3"
|
||||
fi
|
||||
|
||||
if ! git rev-parse --git-dir >/dev/null 2>&1; then
|
||||
echo "❌ 不在 git 仓库内"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
REPO_SLUG=$(git remote get-url origin 2>/dev/null | \
|
||||
sed -E 's|.*[:/]([^/]+/[^/]+)\.git$|\1|' | \
|
||||
sed -E 's|.*[:/]([^/]+/[^/]+)$|\1|')
|
||||
|
||||
if [ -z "$REPO_SLUG" ]; then
|
||||
echo "❌ 无法从 git remote 推断 OWNER/REPO"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$GITEA_TOKEN" ]; then
|
||||
echo "❌ 需要 GITEA_TOKEN 环境变量"
|
||||
echo " export GITEA_TOKEN=\$(bw get password 'Gitea - qiudl Token')"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
GITEA_URL="${GITEA_URL:-https://gitea.pipexerp.com}"
|
||||
|
||||
# 推断 from
|
||||
if [ -z "$FROM_TAG" ]; then
|
||||
FROM_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
|
||||
fi
|
||||
|
||||
TO_REF="HEAD"
|
||||
|
||||
# 生成 changelog 内容
|
||||
echo "📋 生成 changelog..."
|
||||
|
||||
CHANGELOG=""
|
||||
if [ -n "$FROM_TAG" ]; then
|
||||
COMMITS=$(git log --pretty=format:'- %s (%h)' "${FROM_TAG}..${TO_REF}" 2>/dev/null || echo "")
|
||||
else
|
||||
COMMITS=$(git log --pretty=format:'- %s (%h)' "${TO_REF}" 2>/dev/null || echo "")
|
||||
fi
|
||||
|
||||
if [ -z "$COMMITS" ]; then
|
||||
echo "⚠️ 无 commit,放弃"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 按类型分组
|
||||
FEATS=$(echo "$COMMITS" | grep -iE 'feat(\(|:)|新功能' || true)
|
||||
FIXES=$(echo "$COMMITS" | grep -iE 'fix(\(|:)|修复' || true)
|
||||
CHORES=$(echo "$COMMITS" | grep -iE 'chore(\(|:)' || true)
|
||||
OTHERS=$(echo "$COMMITS" | grep -vE 'feat(\(|:)|fix(\(|:)|chore(\(|:)|新功能|修复' || true)
|
||||
|
||||
CHANGELOG="## 发布内容
|
||||
|
||||
**版本**: \`${VERSION}\`
|
||||
**区间**: \`${FROM_TAG:-init}..${TO_REF}\`
|
||||
|
||||
"
|
||||
|
||||
if [ -n "$FEATS" ]; then
|
||||
CHANGELOG="${CHANGELOG}### 新功能
|
||||
|
||||
${FEATS}
|
||||
|
||||
"
|
||||
fi
|
||||
|
||||
if [ -n "$FIXES" ]; then
|
||||
CHANGELOG="${CHANGELOG}### Bug 修复
|
||||
|
||||
${FIXES}
|
||||
|
||||
"
|
||||
fi
|
||||
|
||||
if [ -n "$CHORES" ]; then
|
||||
CHANGELOG="${CHANGELOG}### 杂项
|
||||
|
||||
${CHORES}
|
||||
|
||||
"
|
||||
fi
|
||||
|
||||
if [ -n "$OTHERS" ]; then
|
||||
CHANGELOG="${CHANGELOG}### 其他
|
||||
|
||||
${OTHERS}
|
||||
|
||||
"
|
||||
fi
|
||||
|
||||
CHANGELOG="${CHANGELOG}
|
||||
|
||||
---
|
||||
|
||||
⚠️ **这是 draft release**,审查无误后点击 'Publish release' 按钮才会触发生产部署。
|
||||
|
||||
📋 审查要点:
|
||||
- [ ] 所有改动已过 PR 评审
|
||||
- [ ] SQL migration 已验证(如有)
|
||||
- [ ] 回滚方案已确认(如有)
|
||||
- [ ] 生产环境准备就绪"
|
||||
|
||||
# 创建 draft release
|
||||
echo "🚀 创建 Gitea draft release..."
|
||||
|
||||
BODY_JSON=$(python3 -c "
|
||||
import json
|
||||
print(json.dumps({
|
||||
'tag_name': '$VERSION',
|
||||
'target_commitish': 'main',
|
||||
'name': '$VERSION',
|
||||
'body': '''$CHANGELOG''',
|
||||
'draft': True,
|
||||
'prerelease': False,
|
||||
}))
|
||||
")
|
||||
|
||||
RESP=$(curl -s -X POST \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$BODY_JSON" \
|
||||
"${GITEA_URL}/api/v1/repos/${REPO_SLUG}/releases")
|
||||
|
||||
HTML_URL=$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('html_url',''))" 2>/dev/null)
|
||||
|
||||
if [ -n "$HTML_URL" ]; then
|
||||
echo "✅ Draft release 已创建"
|
||||
echo "🔗 $HTML_URL"
|
||||
echo ""
|
||||
echo "⏭️ 下一步:"
|
||||
echo " 1. 打开链接审查产物清单"
|
||||
echo " 2. 确认无误后点 'Publish release' 按钮"
|
||||
echo " 3. CI/CD 将自动触发生产部署"
|
||||
else
|
||||
echo "❌ 创建失败"
|
||||
echo "$RESP" | head -20
|
||||
exit 1
|
||||
fi
|
||||
130
hooks/session-context.sh
Executable file
130
hooks/session-context.sh
Executable file
@@ -0,0 +1,130 @@
|
||||
#!/bin/bash
|
||||
# session-context.sh
|
||||
# SessionStart Hook: 会话启动时自动注入需求上下文
|
||||
#
|
||||
# 从当前 Git 分支名解析 REQ-ID,调用 ai-proj MCP API 查询需求详情,
|
||||
# 把标题 / 状态 / delivery_stage / reviewer / 进行中需求数注入 system-reminder。
|
||||
#
|
||||
# 安装方式:
|
||||
# 在 ~/.claude/settings.json 的 hooks.SessionStart 配置:
|
||||
# {
|
||||
# "command": "/Users/donglinlai/coding/qiudl/ai-proj-helper/hooks/session-context.sh",
|
||||
# "timeout": 10
|
||||
# }
|
||||
#
|
||||
# 参考:devflow-claude 同名脚本 + ai-proj MCP 适配
|
||||
# REQ-20260416-0017 P0-1
|
||||
|
||||
set -e
|
||||
|
||||
# 仅在 git 仓库内执行
|
||||
if ! git rev-parse --git-dir >/dev/null 2>&1; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
|
||||
if [ -z "$REPO_ROOT" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
# ============ 1. 当前分支 → REQ ID ============
|
||||
BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo "")
|
||||
REQ_ID=""
|
||||
|
||||
if [ -n "$BRANCH" ]; then
|
||||
# 匹配 feat/REQ-20260416-0017-xxx / fix/REQ-20260416-0017 / feature/req-20260416-0017
|
||||
REQ_ID=$(echo "$BRANCH" | grep -oiE 'REQ-[0-9]{8}-[0-9]{4}' | head -1 | tr '[:lower:]' '[:upper:]')
|
||||
fi
|
||||
|
||||
# ============ 2. 无 REQ 时仅输出分支信息(静默退出条件) ============
|
||||
if [ -z "$REQ_ID" ]; then
|
||||
# 只要不在 main/develop 上就提示一下
|
||||
case "$BRANCH" in
|
||||
main|master|develop|"") exit 0 ;;
|
||||
esac
|
||||
echo "# 会话上下文"
|
||||
echo ""
|
||||
echo "- 当前分支: \`${BRANCH}\`(未检测到 REQ ID)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ============ 3. 查询 MCP API ============
|
||||
# MCP API 通过 localhost:8080 直连(ai-proj 本地后端)或 ai-proj-prod
|
||||
# 这里优先读项目根的 .ai-proj-env 决定环境
|
||||
API_BASE="${AI_PROJ_API_BASE:-}"
|
||||
API_TOKEN="${AI_PROJ_MCP_KEY:-}"
|
||||
|
||||
if [ -f "$REPO_ROOT/.ai-proj-env" ]; then
|
||||
# shellcheck disable=SC1091
|
||||
source "$REPO_ROOT/.ai-proj-env"
|
||||
fi
|
||||
|
||||
if [ -z "$API_BASE" ]; then
|
||||
# 默认走本地 dev
|
||||
API_BASE="http://localhost:8080"
|
||||
fi
|
||||
|
||||
# 查询需求
|
||||
RESP=""
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
if [ -n "$API_TOKEN" ]; then
|
||||
RESP=$(curl -s --max-time 3 -H "X-MCP-API-Key: $API_TOKEN" \
|
||||
"${API_BASE}/api/v1/mcp/requirements/by-display-id/${REQ_ID}" 2>/dev/null || echo "")
|
||||
else
|
||||
RESP=$(curl -s --max-time 3 \
|
||||
"${API_BASE}/api/v1/mcp/requirements/by-display-id/${REQ_ID}" 2>/dev/null || echo "")
|
||||
fi
|
||||
fi
|
||||
|
||||
# ============ 4. 解析并输出 ============
|
||||
echo "# 需求上下文(SessionStart Hook)"
|
||||
echo ""
|
||||
echo "- 分支: \`${BRANCH}\`"
|
||||
echo "- 需求: **${REQ_ID}**"
|
||||
|
||||
if [ -n "$RESP" ] && command -v python3 >/dev/null 2>&1; then
|
||||
# 尝试用 python 解析
|
||||
PARSED=$(python3 -c "
|
||||
import sys, json
|
||||
try:
|
||||
d = json.loads('''$RESP''')
|
||||
data = d.get('data', {})
|
||||
if not data:
|
||||
sys.exit(0)
|
||||
title = data.get('title', '?')
|
||||
status = data.get('status', '?')
|
||||
stage = data.get('delivery_stage', '?')
|
||||
priority = data.get('priority', '?')
|
||||
project = data.get('project_name', '?')
|
||||
print(f'title={title}')
|
||||
print(f'status={status}')
|
||||
print(f'stage={stage}')
|
||||
print(f'priority={priority}')
|
||||
print(f'project={project}')
|
||||
except Exception:
|
||||
pass
|
||||
" 2>/dev/null)
|
||||
|
||||
if [ -n "$PARSED" ]; then
|
||||
TITLE=$(echo "$PARSED" | grep '^title=' | sed 's/^title=//')
|
||||
STATUS=$(echo "$PARSED" | grep '^status=' | sed 's/^status=//')
|
||||
STAGE=$(echo "$PARSED" | grep '^stage=' | sed 's/^stage=//')
|
||||
PRIORITY=$(echo "$PARSED" | grep '^priority=' | sed 's/^priority=//')
|
||||
PROJECT=$(echo "$PARSED" | grep '^project=' | sed 's/^project=//')
|
||||
|
||||
[ -n "$TITLE" ] && echo "- 标题: ${TITLE}"
|
||||
[ -n "$PROJECT" ] && echo "- 项目: ${PROJECT}"
|
||||
[ -n "$STATUS" ] && echo "- 状态: ${STATUS}"
|
||||
[ -n "$STAGE" ] && echo "- 交付阶段: ${STAGE}"
|
||||
[ -n "$PRIORITY" ] && echo "- 优先级: ${PRIORITY}"
|
||||
else
|
||||
echo "- 📡 MCP API 响应为空或未授权(API_BASE=${API_BASE})"
|
||||
fi
|
||||
else
|
||||
echo "- ⚠️ 无法连接 MCP API(${API_BASE}),仅显示分支信息"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "💡 相关命令:\`/req get ${REQ_ID}\` 查看详情 · \`/commit\` 智能提交"
|
||||
Reference in New Issue
Block a user