specline 1.0.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -12
- package/cli.mjs +407 -11
- package/package.json +5 -1
- package/templates/.cursor/agents/specline-backend-dev.md +1 -1
- package/templates/.cursor/agents/specline-code-reviewer.md +1 -1
- package/templates/.cursor/agents/specline-config-dev.md +52 -0
- package/templates/.cursor/agents/specline-config-reviewer.md +79 -0
- package/templates/.cursor/agents/specline-frontend-dev.md +1 -1
- package/templates/.cursor/agents/specline-test-writer.md +1 -1
- package/templates/.cursor/hooks/specline-agent-guard.sh +113 -5
- package/templates/.cursor/hooks/specline-phase-guard.sh +202 -0
- package/templates/.cursor/hooks/specline-pipeline-gate.sh +5 -2
- package/templates/.cursor/hooks/specline-reminder.sh +139 -0
- package/templates/.cursor/hooks/specline-session-start.sh +134 -0
- package/templates/.cursor/hooks.json +22 -1
- package/templates/.cursor/skills/specline-apply-change/SKILL.md +35 -8
- package/templates/.cursor/skills/specline-archive-change/SKILL.md +39 -4
- package/templates/.cursor/skills/specline-explore/SKILL.md +21 -18
- package/templates/.cursor/skills/specline-pipeline/SKILL.md +311 -184
- package/templates/.cursor/skills/specline-propose/SKILL.md +101 -42
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: specline-config-reviewer
|
|
3
|
+
description: 审查 config/docs 类型的代码变更——shell 脚本的安全性、配置文件的语法和一致性、Markdown 文档的结构完整性。产出 code-review.json,区分 error(必须修复)和 warning(建议改进)。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
你是 **specline-config-reviewer**,专门审查 `Type: config` 和 `Type: docs` 任务的产出。
|
|
7
|
+
|
|
8
|
+
## 角色定位
|
|
9
|
+
|
|
10
|
+
- 审查 shell 脚本、JSON/YAML 配置文件、Markdown 文档的变更
|
|
11
|
+
- 在 CODE REVIEW 阶段被调用
|
|
12
|
+
- 产出结构化 `code-review.json`,格式与 `specline-code-reviewer` 保持一致
|
|
13
|
+
|
|
14
|
+
## 审查维度
|
|
15
|
+
|
|
16
|
+
### Shell 脚本(`.sh`)
|
|
17
|
+
- **语法正确性**:检查 `bash -n` 是否有语法错误
|
|
18
|
+
- **安全性**:
|
|
19
|
+
- 检查命令注入风险(未转义的用户输入、eval 调用)
|
|
20
|
+
- 检查未引用的变量(导致路径注入)
|
|
21
|
+
- 危险的路径操作(`rm -rf /` 等)
|
|
22
|
+
- 不安全的权限设置(`chmod 777`)
|
|
23
|
+
- **可维护性**:set 选项使用、错误处理是否完整
|
|
24
|
+
|
|
25
|
+
### 配置文件(`.json`、`.yaml`、`.yml`)
|
|
26
|
+
- **语法有效性**:JSON/YAML 解析是否成功
|
|
27
|
+
- **字段完整性**:必备字段是否存在
|
|
28
|
+
- **一致性**:与现有配置的值是否冲突
|
|
29
|
+
- **安全性**:是否包含硬编码的密钥、令牌
|
|
30
|
+
|
|
31
|
+
### Markdown 文档(`.md`)
|
|
32
|
+
- **结构完整性**:必需章节是否存在
|
|
33
|
+
- **链接有效性**:相对路径引用是否正确
|
|
34
|
+
- **与 Spec 一致性**:文档描述是否与 spec.md 中的要求匹配
|
|
35
|
+
|
|
36
|
+
## 工作方式
|
|
37
|
+
|
|
38
|
+
1. 查看本次变更中 config/docs 任务修改的文件(从 git diff 或文件内容对比)
|
|
39
|
+
2. 对照 tasks.md 的 `Covers` 追溯链,确认每个变更对应的 Requirement 和 Scenario
|
|
40
|
+
3. 逐一审查文件,按审查维度发现的问题归类
|
|
41
|
+
4. 产出 `code-review.json` 到 `specline/changes/<change>/.tmp/code-review.json`
|
|
42
|
+
|
|
43
|
+
## 输出
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"findings": [
|
|
48
|
+
{
|
|
49
|
+
"severity": "error",
|
|
50
|
+
"file": "path/to/script.sh",
|
|
51
|
+
"line": 42,
|
|
52
|
+
"task_id": "3",
|
|
53
|
+
"covers": "Requirement: 安全配置",
|
|
54
|
+
"message": "未引用的变量 $DIR 可能导致路径注入"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"severity": "warning",
|
|
58
|
+
"file": "path/to/config.json",
|
|
59
|
+
"task_id": "4",
|
|
60
|
+
"covers": "Requirement: Hook 注册",
|
|
61
|
+
"message": "建议添加字段 'description' 提高可读性"
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
- `severity`: `"error"`(必须修复)或 `"warning"`(建议改进)
|
|
68
|
+
- `file`: 问题所在文件路径
|
|
69
|
+
- `line`: 问题所在行号(如果适用,否则省略)
|
|
70
|
+
- `task_id`: 关联的任务 ID
|
|
71
|
+
- `covers`: 关联的 Requirement/Scenario 引用
|
|
72
|
+
- `message`: 问题和修复建议
|
|
73
|
+
|
|
74
|
+
## 约束
|
|
75
|
+
|
|
76
|
+
1. 只审查 `Type: config` 或 `Type: docs` 任务的文件
|
|
77
|
+
2. 不审查前端或后端代码(留给 specline-code-reviewer)
|
|
78
|
+
3. error 级别的问题在 `specline-pipeline-gate.sh lint` 中会被计数并阻塞流水线
|
|
79
|
+
4. 如果无 config/docs 变更,直接返回空 findings 数组
|
|
@@ -1,15 +1,123 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# agent-guard.sh — subagentStart Hook
|
|
3
|
-
|
|
2
|
+
# specline-agent-guard.sh — subagentStart Hook(增强版)
|
|
3
|
+
# 白名单校验 + 流水线阶段匹配校验
|
|
4
|
+
#
|
|
5
|
+
# Input (stdin JSON):
|
|
6
|
+
# { "subagent_type": "...", "subagent_id": "...", "task": "...", ... }
|
|
7
|
+
#
|
|
8
|
+
# Output (stdout JSON):
|
|
9
|
+
# { "permission": "allow" } 或 { "permission": "deny", "user_message": "..." }
|
|
10
|
+
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
4
13
|
input=$(cat)
|
|
5
14
|
subagent_type=$(echo "$input" | jq -r '.subagent_type // empty')
|
|
6
|
-
|
|
15
|
+
|
|
16
|
+
# 非 specline agent → 放行(不受 Specline 管控)
|
|
17
|
+
if ! echo "$subagent_type" | grep -qE "^specline-"; then
|
|
7
18
|
echo '{"permission": "allow"}'
|
|
8
19
|
exit 0
|
|
9
20
|
fi
|
|
10
|
-
|
|
21
|
+
|
|
22
|
+
# ===== 1. 白名单校验 =====
|
|
23
|
+
ALLOWED_AGENTS="specline-spec-creator|specline-spec-reviewer|specline-frontend-dev|specline-backend-dev|specline-config-dev|specline-code-reviewer|specline-config-reviewer|specline-test-writer|specline-test-runner"
|
|
24
|
+
|
|
25
|
+
if ! echo "$subagent_type" | grep -qE "^($ALLOWED_AGENTS)$"; then
|
|
26
|
+
echo "{\"permission\": \"deny\", \"user_message\": \"子Agent类型 '$subagent_type' 不在 Specline 允许列表中。允许的类型: spec-creator, spec-reviewer, frontend-dev, backend-dev, config-dev, code-reviewer, config-reviewer, test-writer, test-runner\"}"
|
|
27
|
+
exit 0
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# ===== 2. 流水线阶段匹配校验 =====
|
|
31
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
32
|
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
33
|
+
CHANGES_DIR="$PROJECT_ROOT/specline/changes"
|
|
34
|
+
|
|
35
|
+
# 查找活跃 pipeline
|
|
36
|
+
find_active_pipeline() {
|
|
37
|
+
if [ ! -d "$CHANGES_DIR" ]; then
|
|
38
|
+
echo ""
|
|
39
|
+
return
|
|
40
|
+
fi
|
|
41
|
+
for f in "$CHANGES_DIR"/*/.pipeline-state.json; do
|
|
42
|
+
[ -f "$f" ] || continue
|
|
43
|
+
if echo "$f" | grep -q "/archive/"; then continue; fi
|
|
44
|
+
local ph
|
|
45
|
+
ph=$(jq -r '.current_phase // ""' "$f" 2>/dev/null)
|
|
46
|
+
if [ "$ph" != "archive" ] && [ "$ph" != "" ]; then
|
|
47
|
+
echo "$f"
|
|
48
|
+
return
|
|
49
|
+
fi
|
|
50
|
+
done
|
|
51
|
+
echo ""
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
STATE_FILE=$(find_active_pipeline)
|
|
55
|
+
|
|
56
|
+
# 无活跃 pipeline → 放行(不在流水线中的会话可以使用任何 specline agent)
|
|
57
|
+
if [ -z "$STATE_FILE" ]; then
|
|
11
58
|
echo '{"permission": "allow"}'
|
|
12
59
|
exit 0
|
|
13
60
|
fi
|
|
14
|
-
|
|
61
|
+
|
|
62
|
+
phase=$(jq -r '.current_phase' "$STATE_FILE")
|
|
63
|
+
change=$(jq -r '.change_name' "$STATE_FILE")
|
|
64
|
+
|
|
65
|
+
# 判断 Agent 类型分类
|
|
66
|
+
is_spec_agent=$(echo "$subagent_type" | grep -qE "specline-spec-creator|specline-spec-reviewer" && echo "true" || echo "false")
|
|
67
|
+
is_coding_agent=$(echo "$subagent_type" | grep -qE "^(specline-frontend-dev|specline-backend-dev|specline-config-dev)$" && echo "true" || echo "false")
|
|
68
|
+
is_review_agent=$(echo "$subagent_type" | grep -qE "^(specline-code-reviewer|specline-config-reviewer)$" && echo "true" || echo "false")
|
|
69
|
+
is_test_agent=$(echo "$subagent_type" | grep -qE "specline-test-writer|specline-test-runner" && echo "true" || echo "false")
|
|
70
|
+
|
|
71
|
+
case "$phase" in
|
|
72
|
+
spec)
|
|
73
|
+
if [ "$is_coding_agent" = "true" ]; then
|
|
74
|
+
echo "{\"permission\": \"deny\", \"user_message\": \"🚫 SPEC 阶段不能启动编码 Agent: $subagent_type。变更: $change。请先完成 SPEC → CODING 阶段切换。\"}"
|
|
75
|
+
exit 0
|
|
76
|
+
fi
|
|
77
|
+
if [ "$is_test_agent" = "true" ]; then
|
|
78
|
+
echo "{\"permission\": \"deny\", \"user_message\": \"🚫 SPEC 阶段不能启动测试 Agent: $subagent_type。变更: $change。\"}"
|
|
79
|
+
exit 0
|
|
80
|
+
fi
|
|
81
|
+
if [ "$is_review_agent" = "true" ]; then
|
|
82
|
+
echo "{\"permission\": \"deny\", \"user_message\": \"🚫 SPEC 阶段不能启动代码审查 Agent: $subagent_type。变更: $change。请使用 specline-spec-reviewer。\"}"
|
|
83
|
+
exit 0
|
|
84
|
+
fi
|
|
85
|
+
# spec-creator, spec-reviewer → 放行
|
|
86
|
+
;;
|
|
87
|
+
|
|
88
|
+
coding)
|
|
89
|
+
if [ "$is_spec_agent" = "true" ]; then
|
|
90
|
+
echo "{\"permission\": \"deny\", \"user_message\": \"🚫 CODING 阶段不能启动 Spec Agent: $subagent_type。变更: $change。如需修改 Spec,请手动编辑文件。\"}"
|
|
91
|
+
exit 0
|
|
92
|
+
fi
|
|
93
|
+
# 编码/测试/审查 agent → 放行
|
|
94
|
+
;;
|
|
95
|
+
|
|
96
|
+
code_review)
|
|
97
|
+
if [ "$is_spec_agent" = "true" ]; then
|
|
98
|
+
echo "{\"permission\": \"deny\", \"user_message\": \"🚫 CODE REVIEW 阶段不能启动 Spec Agent: $subagent_type。变更: $change。\"}"
|
|
99
|
+
exit 0
|
|
100
|
+
fi
|
|
101
|
+
if [ "$is_test_agent" = "true" ] && [ "$subagent_type" != "specline-test-writer" ]; then
|
|
102
|
+
echo "{\"permission\": \"deny\", \"user_message\": \"🚫 CODE REVIEW 阶段不能启动测试运行 Agent。变更: $change。Test 在下一阶段。\"}"
|
|
103
|
+
exit 0
|
|
104
|
+
fi
|
|
105
|
+
;;
|
|
106
|
+
|
|
107
|
+
test)
|
|
108
|
+
if [ "$is_spec_agent" = "true" ]; then
|
|
109
|
+
echo "{\"permission\": \"deny\", \"user_message\": \"🚫 TEST 阶段不能启动 Spec Agent: $subagent_type。变更: $change。\"}"
|
|
110
|
+
exit 0
|
|
111
|
+
fi
|
|
112
|
+
# 编码/测试/审查 agent → 放行(测试阶段可能需要编码修复)
|
|
113
|
+
;;
|
|
114
|
+
|
|
115
|
+
archive)
|
|
116
|
+
echo "{\"permission\": \"deny\", \"user_message\": \"🚫 变更 $change 已归档,不能启动任何子 Agent。\"}"
|
|
117
|
+
exit 0
|
|
118
|
+
;;
|
|
119
|
+
esac
|
|
120
|
+
|
|
121
|
+
# 阶段匹配 + 白名单都通过
|
|
122
|
+
echo '{"permission": "allow"}'
|
|
15
123
|
exit 0
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# specline-phase-guard.sh — preToolUse Hook
|
|
3
|
+
# 操作前按流水线阶段校验并拦截违规行为
|
|
4
|
+
#
|
|
5
|
+
# Input (stdin JSON):
|
|
6
|
+
# { "tool_name": "Write"|"Task"|"Shell"|"Delete"|..., "tool_input": {...}, ... }
|
|
7
|
+
#
|
|
8
|
+
# Output (stdout JSON):
|
|
9
|
+
# { "permission": "allow" } 或 { "permission": "deny", "user_message": "...", "agent_message": "..." }
|
|
10
|
+
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
input=$(cat)
|
|
14
|
+
tool_name=$(echo "$input" | jq -r '.tool_name // empty')
|
|
15
|
+
tool_input=$(echo "$input" | jq -r '.tool_input // "{}"')
|
|
16
|
+
|
|
17
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
18
|
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
19
|
+
CHANGES_DIR="$PROJECT_ROOT/specline/changes"
|
|
20
|
+
|
|
21
|
+
# ===== 辅助函数 =====
|
|
22
|
+
|
|
23
|
+
# 查找第一个活跃 pipeline(排除 archive/)
|
|
24
|
+
find_active_pipeline() {
|
|
25
|
+
if [ ! -d "$CHANGES_DIR" ]; then
|
|
26
|
+
echo ""
|
|
27
|
+
return
|
|
28
|
+
fi
|
|
29
|
+
for f in "$CHANGES_DIR"/*/.pipeline-state.json; do
|
|
30
|
+
[ -f "$f" ] || continue
|
|
31
|
+
if echo "$f" | grep -q "/archive/"; then continue; fi
|
|
32
|
+
local ph
|
|
33
|
+
ph=$(jq -r '.current_phase // ""' "$f" 2>/dev/null)
|
|
34
|
+
if [ "$ph" != "archive" ] && [ "$ph" != "" ]; then
|
|
35
|
+
echo "$f"
|
|
36
|
+
return
|
|
37
|
+
fi
|
|
38
|
+
done
|
|
39
|
+
echo ""
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
deny() {
|
|
43
|
+
local user_msg="$1"
|
|
44
|
+
local agent_msg="$2"
|
|
45
|
+
# JSON 转义
|
|
46
|
+
user_msg=$(echo "$user_msg" | jq -Rs '.')
|
|
47
|
+
agent_msg=$(echo "$agent_msg" | jq -Rs '.')
|
|
48
|
+
cat << EOF
|
|
49
|
+
{
|
|
50
|
+
"permission": "deny",
|
|
51
|
+
"user_message": ${user_msg},
|
|
52
|
+
"agent_message": ${agent_msg}
|
|
53
|
+
}
|
|
54
|
+
EOF
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
allow() {
|
|
58
|
+
echo '{"permission": "allow"}'
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# ===== 主逻辑 =====
|
|
62
|
+
|
|
63
|
+
STATE_FILE=$(find_active_pipeline)
|
|
64
|
+
|
|
65
|
+
# 无活跃 pipeline → 透明放行
|
|
66
|
+
if [ -z "$STATE_FILE" ]; then
|
|
67
|
+
allow
|
|
68
|
+
exit 0
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
phase=$(jq -r '.current_phase' "$STATE_FILE")
|
|
72
|
+
change=$(jq -r '.change_name' "$STATE_FILE")
|
|
73
|
+
tasks_file="$CHANGES_DIR/$change/tasks.md"
|
|
74
|
+
|
|
75
|
+
# ---- Write / StrReplace / EditNotebook ----
|
|
76
|
+
case "$tool_name" in
|
|
77
|
+
Write|StrReplace|EditNotebook)
|
|
78
|
+
# SPEC 阶段:禁止编辑非 specline 的源代码文件
|
|
79
|
+
if [ "$phase" = "spec" ]; then
|
|
80
|
+
file_path=$(echo "$tool_input" | jq -r '.path // .file_path // empty')
|
|
81
|
+
|
|
82
|
+
if [ -n "$file_path" ]; then
|
|
83
|
+
# 允许 specline 规划文件
|
|
84
|
+
if echo "$file_path" | grep -q "specline/"; then
|
|
85
|
+
allow
|
|
86
|
+
exit 0
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
# 允许 hook/config 文件(流水线基础设施)
|
|
90
|
+
if echo "$file_path" | grep -qE "(\.cursor/|hooks\.json|package\.json)"; then
|
|
91
|
+
allow
|
|
92
|
+
exit 0
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
# 代码文件 → 拦截
|
|
96
|
+
if echo "$file_path" | grep -qE '\.(ts|tsx|js|jsx|py|go|rs|java|rb|php|css|html|vue|svelte)$'; then
|
|
97
|
+
deny \
|
|
98
|
+
"🚫 SPEC 阶段不能编辑代码文件: $(basename "$file_path")" \
|
|
99
|
+
"当前处于 SPEC 阶段(变更:$change)。不能直接编辑应用代码文件。只有在进入 CODING 阶段后才能修改代码。如需修改规划文件,只能编辑 specline/changes/ 下的内容。"
|
|
100
|
+
exit 0
|
|
101
|
+
fi
|
|
102
|
+
fi
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
# ARCHIVE 阶段:禁止任何编辑
|
|
106
|
+
if [ "$phase" = "archive" ]; then
|
|
107
|
+
deny \
|
|
108
|
+
"🚫 变更 $change 已归档,不能修改文件" \
|
|
109
|
+
"变更 $change 已归档。不能修改任何文件。如需继续工作,请创建新的变更。"
|
|
110
|
+
exit 0
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
# CODING/CODE_REVIEW/TEST 阶段:放行
|
|
114
|
+
# (子 Agent 需要编辑代码文件,Orchestrator 的行为靠 SKILL + sessionStart 上下文约束)
|
|
115
|
+
allow
|
|
116
|
+
;;
|
|
117
|
+
|
|
118
|
+
# ---- Task (子Agent) ----
|
|
119
|
+
Task)
|
|
120
|
+
subagent_type=$(echo "$tool_input" | jq -r '.subagent_type // empty')
|
|
121
|
+
|
|
122
|
+
# 不是 specline agent → 放行
|
|
123
|
+
if ! echo "$subagent_type" | grep -qE "^specline-"; then
|
|
124
|
+
allow
|
|
125
|
+
exit 0
|
|
126
|
+
fi
|
|
127
|
+
|
|
128
|
+
is_coding_agent=$(echo "$subagent_type" | grep -qE "^(specline-frontend-dev|specline-backend-dev|specline-config-dev)$" && echo "true" || echo "false")
|
|
129
|
+
is_spec_agent=$(echo "$subagent_type" | grep -qE "^(specline-spec-creator|specline-spec-reviewer)$" && echo "true" || echo "false")
|
|
130
|
+
is_test_agent=$(echo "$subagent_type" | grep -qE "^(specline-test-writer|specline-test-runner)$" && echo "true" || echo "false")
|
|
131
|
+
is_review_agent=$(echo "$subagent_type" | grep -qE "^(specline-code-reviewer|specline-config-reviewer)$" && echo "true" || echo "false")
|
|
132
|
+
|
|
133
|
+
case "$phase" in
|
|
134
|
+
spec)
|
|
135
|
+
if [ "$is_coding_agent" = "true" ]; then
|
|
136
|
+
deny \
|
|
137
|
+
"🚫 SPEC 阶段不能启动编码 Agent ($subagent_type)" \
|
|
138
|
+
"当前在 SPEC 阶段(变更:$change)。编码 Agent($subagent_type)只能在 CODING 阶段启动。如需编码,请先完成 Spec Gate 和人工确认。"
|
|
139
|
+
exit 0
|
|
140
|
+
fi
|
|
141
|
+
if [ "$is_test_agent" = "true" ]; then
|
|
142
|
+
deny \
|
|
143
|
+
"🚫 SPEC 阶段不能启动测试 Agent ($subagent_type)" \
|
|
144
|
+
"当前在 SPEC 阶段(变更:$change)。测试 Agent 只能在 CODING 或 TEST 阶段启动。"
|
|
145
|
+
exit 0
|
|
146
|
+
fi
|
|
147
|
+
# spec-creator / spec-reviewer → 放行
|
|
148
|
+
;;
|
|
149
|
+
|
|
150
|
+
coding)
|
|
151
|
+
if [ "$is_spec_agent" = "true" ]; then
|
|
152
|
+
deny \
|
|
153
|
+
"🚫 CODING 阶段不能启动 Spec Agent ($subagent_type)" \
|
|
154
|
+
"当前在 CODING 阶段(变更:$change)。Spec Agent 只能在 SPEC 阶段启动。如需修改 Spec 文档,请手动编辑 specline/changes/ 下的文件后恢复流水线。"
|
|
155
|
+
exit 0
|
|
156
|
+
fi
|
|
157
|
+
# 编码/测试/审查 agent → 放行
|
|
158
|
+
;;
|
|
159
|
+
|
|
160
|
+
code_review)
|
|
161
|
+
if [ "$is_spec_agent" = "true" ]; then
|
|
162
|
+
deny \
|
|
163
|
+
"🚫 CODE REVIEW 阶段不能启动 Spec Agent ($subagent_type)" \
|
|
164
|
+
"当前在 CODE REVIEW 阶段(变更:$change)。Spec Agent 只能在 SPEC 阶段启动。"
|
|
165
|
+
exit 0
|
|
166
|
+
fi
|
|
167
|
+
;;
|
|
168
|
+
|
|
169
|
+
test)
|
|
170
|
+
if [ "$is_spec_agent" = "true" ]; then
|
|
171
|
+
deny \
|
|
172
|
+
"🚫 TEST 阶段不能启动 Spec Agent ($subagent_type)" \
|
|
173
|
+
"当前在 TEST 阶段(变更:$change)。Spec Agent 只能在 SPEC 阶段启动。"
|
|
174
|
+
exit 0
|
|
175
|
+
fi
|
|
176
|
+
;;
|
|
177
|
+
esac
|
|
178
|
+
|
|
179
|
+
allow
|
|
180
|
+
;;
|
|
181
|
+
|
|
182
|
+
# ---- Delete ----
|
|
183
|
+
Delete)
|
|
184
|
+
# SPEC 阶段禁止删除代码文件
|
|
185
|
+
if [ "$phase" = "spec" ]; then
|
|
186
|
+
file_path=$(echo "$tool_input" | jq -r '.path // empty')
|
|
187
|
+
if [ -n "$file_path" ] && ! echo "$file_path" | grep -q "specline/"; then
|
|
188
|
+
deny \
|
|
189
|
+
"🚫 SPEC 阶段不能删除代码文件" \
|
|
190
|
+
"当前在 SPEC 阶段,不能删除应用代码文件。"
|
|
191
|
+
exit 0
|
|
192
|
+
fi
|
|
193
|
+
fi
|
|
194
|
+
allow
|
|
195
|
+
;;
|
|
196
|
+
|
|
197
|
+
*)
|
|
198
|
+
allow
|
|
199
|
+
;;
|
|
200
|
+
esac
|
|
201
|
+
|
|
202
|
+
exit 0
|
|
@@ -88,6 +88,7 @@ gate_new() {
|
|
|
88
88
|
fi
|
|
89
89
|
|
|
90
90
|
mkdir -p "$change_dir/specs"
|
|
91
|
+
mkdir -p "$change_dir/.tmp"
|
|
91
92
|
|
|
92
93
|
# 写入 .specline.yaml
|
|
93
94
|
cat > "$change_dir/.specline.yaml" << YAML
|
|
@@ -363,8 +364,8 @@ gate_lint() {
|
|
|
363
364
|
fi
|
|
364
365
|
fi
|
|
365
366
|
|
|
366
|
-
# code-review.json error
|
|
367
|
-
local review_file="$PROJECT_ROOT/code-review.json"
|
|
367
|
+
# code-review.json error 计数(位于 change 的 .tmp/ 目录下)
|
|
368
|
+
local review_file="$PROJECT_ROOT/specline/changes/$CHANGE/.tmp/code-review.json"
|
|
368
369
|
if [ -f "$review_file" ]; then
|
|
369
370
|
local error_count
|
|
370
371
|
error_count=$(jq '[.findings[] | select(.severity=="error")] | length' "$review_file" 2>/dev/null || echo "0")
|
|
@@ -584,6 +585,8 @@ gate_archive() {
|
|
|
584
585
|
mv "$src_dir" "$dest"
|
|
585
586
|
echo "✅ 已归档到: $dest"
|
|
586
587
|
|
|
588
|
+
# 临时文件(.tmp/)随 change 目录一起归档,无需单独清理
|
|
589
|
+
|
|
587
590
|
# 更新状态文件
|
|
588
591
|
if [ -f "$dest/.pipeline-state.json" ]; then
|
|
589
592
|
local now
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# specline-reminder.sh — postToolUse Hook
|
|
3
|
+
# 关键操作后注入下一步提醒到 Agent 上下文
|
|
4
|
+
#
|
|
5
|
+
# Input (stdin JSON):
|
|
6
|
+
# { "tool_name": "Task"|"Write"|..., "tool_input": {...}, "tool_output": "...", ... }
|
|
7
|
+
#
|
|
8
|
+
# Output (stdout JSON):
|
|
9
|
+
# { "additional_context": "<提醒文本>" } 或 {}
|
|
10
|
+
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
input=$(cat)
|
|
14
|
+
tool_name=$(echo "$input" | jq -r '.tool_name // empty')
|
|
15
|
+
tool_input=$(echo "$input" | jq -r '.tool_input // "{}"')
|
|
16
|
+
|
|
17
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
18
|
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
19
|
+
CHANGES_DIR="$PROJECT_ROOT/specline/changes"
|
|
20
|
+
|
|
21
|
+
# ===== 辅助函数 =====
|
|
22
|
+
|
|
23
|
+
find_active_pipeline() {
|
|
24
|
+
if [ ! -d "$CHANGES_DIR" ]; then
|
|
25
|
+
echo ""
|
|
26
|
+
return
|
|
27
|
+
fi
|
|
28
|
+
for f in "$CHANGES_DIR"/*/.pipeline-state.json; do
|
|
29
|
+
[ -f "$f" ] || continue
|
|
30
|
+
if echo "$f" | grep -q "/archive/"; then continue; fi
|
|
31
|
+
local ph
|
|
32
|
+
ph=$(jq -r '.current_phase // ""' "$f" 2>/dev/null)
|
|
33
|
+
if [ "$ph" != "archive" ] && [ "$ph" != "" ]; then
|
|
34
|
+
echo "$f"
|
|
35
|
+
return
|
|
36
|
+
fi
|
|
37
|
+
done
|
|
38
|
+
echo ""
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
STATE_FILE=$(find_active_pipeline)
|
|
42
|
+
|
|
43
|
+
# 无活跃 pipeline → 透明
|
|
44
|
+
if [ -z "$STATE_FILE" ]; then
|
|
45
|
+
echo '{}'
|
|
46
|
+
exit 0
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
phase=$(jq -r '.current_phase' "$STATE_FILE")
|
|
50
|
+
change=$(jq -r '.change_name' "$STATE_FILE")
|
|
51
|
+
|
|
52
|
+
# ---- Task (子Agent) 完成后 ----
|
|
53
|
+
if [ "$tool_name" = "Task" ]; then
|
|
54
|
+
subagent_type=$(echo "$tool_input" | jq -r '.subagent_type // ""')
|
|
55
|
+
|
|
56
|
+
if [ "$phase" = "coding" ] && echo "$subagent_type" | grep -qE "^specline-"; then
|
|
57
|
+
# 检查是否还有同批次未完成的任务
|
|
58
|
+
pending_in_batch="?"
|
|
59
|
+
pending_tasks=$(jq -r '[.phases.coding.tasks[]? | select(.status == "pending")] | length' "$STATE_FILE" 2>/dev/null || echo "0")
|
|
60
|
+
|
|
61
|
+
reminder="🔔 **[Pipeline] 子 Agent ($subagent_type) 已完成**\n"
|
|
62
|
+
reminder+="- 变更: $change | 阶段: CODING\n"
|
|
63
|
+
|
|
64
|
+
if [ "$pending_tasks" != "0" ] && [ "$pending_tasks" != "?" ]; then
|
|
65
|
+
reminder+="- 还有 ${pending_tasks} 个任务未完成\n"
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
reminder+="- ⚠️ 请更新 tasks.md 中对应任务的 checkbox:[ ] → [x]\n"
|
|
69
|
+
reminder+="- ⚠️ 当前批次全部完成后,运行 Build Gate:\n"
|
|
70
|
+
reminder+=" \`.cursor/hooks/specline-pipeline-gate.sh build --change \"$change\"\`"
|
|
71
|
+
|
|
72
|
+
escaped=$(echo -e "$reminder" | jq -Rs '.')
|
|
73
|
+
cat << EOF
|
|
74
|
+
{
|
|
75
|
+
"additional_context": ${escaped}
|
|
76
|
+
}
|
|
77
|
+
EOF
|
|
78
|
+
exit 0
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
if [ "$phase" = "test" ] && echo "$subagent_type" | grep -qE "^specline-"; then
|
|
82
|
+
reminder="🔔 **[Pipeline] 测试修复 Agent ($subagent_type) 已完成**\n"
|
|
83
|
+
reminder+="- 变更: $change | 阶段: TEST\n"
|
|
84
|
+
reminder+="- ⚠️ 请重新运行对应的测试 Gate:\n"
|
|
85
|
+
reminder+=" \`.cursor/hooks/specline-pipeline-gate.sh test-unit --change \"$change\"\`"
|
|
86
|
+
|
|
87
|
+
escaped=$(echo -e "$reminder" | jq -Rs '.')
|
|
88
|
+
cat << EOF
|
|
89
|
+
{
|
|
90
|
+
"additional_context": ${escaped}
|
|
91
|
+
}
|
|
92
|
+
EOF
|
|
93
|
+
exit 0
|
|
94
|
+
fi
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
# ---- Write / StrReplace 完成后 ----
|
|
98
|
+
if [ "$tool_name" = "Write" ] || [ "$tool_name" = "StrReplace" ]; then
|
|
99
|
+
file_path=$(echo "$tool_input" | jq -r '.path // .file_path // empty')
|
|
100
|
+
|
|
101
|
+
if [ "$phase" = "coding" ] && [ -n "$file_path" ]; then
|
|
102
|
+
# 编辑 tasks.md → 不提醒(这是正确的行为)
|
|
103
|
+
if echo "$file_path" | grep -q "tasks.md"; then
|
|
104
|
+
echo '{}'
|
|
105
|
+
exit 0
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
# 编辑代码文件 → 提醒(Orchestrator 不应该直接编辑)
|
|
109
|
+
if echo "$file_path" | grep -qE '\.(ts|tsx|js|jsx|py|go|rs)$'; then
|
|
110
|
+
reminder="🔔 **[Pipeline] 检测到直接编辑代码文件**\n"
|
|
111
|
+
reminder+="- 文件: $(basename "$file_path")\n"
|
|
112
|
+
reminder+="- 编码应通过子 Agent 完成,而非直接编辑\n"
|
|
113
|
+
reminder+="- 如果这是 Orchestrator 的错误行为,请改用 Task 工具启动对应子 Agent"
|
|
114
|
+
|
|
115
|
+
escaped=$(echo -e "$reminder" | jq -Rs '.')
|
|
116
|
+
cat << EOF
|
|
117
|
+
{
|
|
118
|
+
"additional_context": ${escaped}
|
|
119
|
+
}
|
|
120
|
+
EOF
|
|
121
|
+
exit 0
|
|
122
|
+
fi
|
|
123
|
+
fi
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
# ---- Shell 执行后检查 Gate ----
|
|
127
|
+
if [ "$tool_name" = "Shell" ]; then
|
|
128
|
+
command=$(echo "$tool_input" | jq -r '.command // empty')
|
|
129
|
+
|
|
130
|
+
# 如果是执行 gate 脚本 → 不提醒
|
|
131
|
+
if echo "$command" | grep -q "specline-pipeline-gate.sh"; then
|
|
132
|
+
echo '{}'
|
|
133
|
+
exit 0
|
|
134
|
+
fi
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
# 默认:无提醒
|
|
138
|
+
echo '{}'
|
|
139
|
+
exit 0
|