specline 1.4.0 → 2.0.0
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 +132 -125
- package/adapters/claude/deploy.json +12 -0
- package/adapters/claude/hooks/hooks.json +12 -0
- package/adapters/claude/hooks.json +12 -0
- package/adapters/claude/orchestration.md +17 -0
- package/adapters/codex/agent.toml.hbs +7 -0
- package/adapters/codex/deploy.json +12 -0
- package/adapters/codex/hooks.json +12 -0
- package/adapters/codex/orchestration.md +18 -0
- package/adapters/cursor/deploy.json +12 -0
- package/adapters/cursor/hooks.json +9 -0
- package/adapters/cursor/orchestration.md +17 -0
- package/adapters/opencode/deploy.json +12 -0
- package/adapters/opencode/orchestration.md +18 -0
- package/adapters/opencode/plugin.js +10 -0
- package/cli.mjs +161 -558
- package/core/agents/specline-backend-dev.yaml +45 -0
- package/core/agents/specline-code-reviewer.yaml +67 -0
- package/core/agents/specline-config-dev.yaml +50 -0
- package/core/agents/specline-config-reviewer.yaml +70 -0
- package/core/agents/specline-explore-assistant.yaml +79 -0
- package/core/agents/specline-frontend-dev.yaml +45 -0
- package/core/agents/specline-spec-creator.yaml +58 -0
- package/core/agents/specline-spec-reviewer.yaml +58 -0
- package/core/agents/specline-test-runner.yaml +62 -0
- package/core/agents/specline-test-writer.yaml +67 -0
- package/core/bootstrap/using-specline.md +14 -0
- package/core/gates/pipeline-gate-checks/a1-covers-ref.sh +125 -0
- package/core/gates/pipeline-gate-checks/a2-a3-reverse.sh +171 -0
- package/core/gates/pipeline-gate-checks/c1-exception.sh +71 -0
- package/core/gates/pipeline-gate-checks/c2-vague.sh +60 -0
- package/core/gates/pipeline-gate-checks/common.sh +68 -0
- package/core/gates/pipeline-gate-checks/d1-cycle.sh +149 -0
- package/core/gates/pipeline-gate-checks/d3-type-file.sh +260 -0
- package/core/gates/pipeline-gate.sh +1456 -0
- package/core/hooks/session-start.sh +259 -0
- package/core/skills/specline-apply-change/SKILL.md +197 -0
- package/core/skills/specline-archive-change/SKILL.md +173 -0
- package/core/skills/specline-explore/SKILL.md +504 -0
- package/core/skills/specline-knowledge/SKILL.md +539 -0
- package/core/skills/specline-pipeline/SKILL.md +604 -0
- package/core/skills/specline-pipeline/references/error-recovery-details.md +49 -0
- package/core/skills/specline-pipeline/references/event-log-spec.md +59 -0
- package/core/skills/specline-pipeline/references/pipeline-state-schema.md +87 -0
- package/core/skills/specline-pipeline/templates/subagent-prompts.md +397 -0
- package/core/skills/specline-propose/SKILL.md +186 -0
- package/core/skills/specline-quickfix/SKILL.md +289 -0
- package/core/templates/AGENTS.md.hbs +5 -0
- package/core/templates/specline/config.yaml +15 -0
- package/lib/deploy-claude.mjs +80 -0
- package/lib/deploy-codex.mjs +77 -0
- package/lib/deploy-opencode.mjs +93 -0
- package/lib/deploy.mjs +668 -0
- package/lib/gate.mjs +103 -0
- package/lib/hash.mjs +13 -0
- package/lib/hook.mjs +105 -0
- package/lib/init.mjs +122 -0
- package/lib/lock.mjs +99 -0
- package/lib/merge.mjs +184 -0
- package/lib/paths.mjs +40 -0
- package/lib/platforms.mjs +74 -0
- package/lib/render-agents.mjs +88 -0
- package/lib/render.mjs +126 -0
- package/lib/sync.mjs +253 -0
- package/lib/tty-select.mjs +89 -0
- package/package.json +4 -1
- package/templates/.cursor/README.md +18 -0
- package/templates/.cursor/agents/specline-code-reviewer.md +18 -2
- package/templates/.cursor/agents/specline-spec-creator.md +51 -2
- package/templates/.cursor/agents/specline-test-runner.md +10 -1
- package/templates/.cursor/agents/specline-test-writer.md +58 -7
- package/templates/.cursor/hooks/specline-pipeline-gate-checks/a2-a3-reverse.sh +1 -1
- package/templates/.cursor/hooks/specline-pipeline-gate.sh +118 -0
- package/templates/.cursor/skills/specline-pipeline/SKILL.md +10 -4
- package/templates/.cursor/skills/specline-propose/SKILL.md +3 -3
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# d3-type-file.sh - D3: Type-File 一致性检测
|
|
4
|
+
#
|
|
5
|
+
# 检测 tasks.md 中每个任务的 Type 字段与 Files 字段的扩展名是否一致。
|
|
6
|
+
# 通过 source 加载,定义 run_d3_type_file() 函数。
|
|
7
|
+
#
|
|
8
|
+
# 依赖:
|
|
9
|
+
# - common.sh(提供 semantic_warn / semantic_info 和全局计数器)
|
|
10
|
+
# - 环境变量 TASKS_FILE(由 gate_semantic 设置)
|
|
11
|
+
#
|
|
12
|
+
# 兼容性: bash 3.2+ (macOS 默认 bash)
|
|
13
|
+
|
|
14
|
+
set -euo pipefail
|
|
15
|
+
|
|
16
|
+
# ===== Type -> 期望扩展名匹配函数(兼容 bash 3.2,无关联数组)=====
|
|
17
|
+
#
|
|
18
|
+
# is_extension_for_type <type> <extension>
|
|
19
|
+
# 返回: 0 = 匹配, 1 = 不匹配
|
|
20
|
+
is_extension_for_type() {
|
|
21
|
+
local t="$1"
|
|
22
|
+
local e="$2"
|
|
23
|
+
|
|
24
|
+
case "$t" in
|
|
25
|
+
frontend)
|
|
26
|
+
case "$e" in
|
|
27
|
+
tsx|jsx|css|scss|less|html|vue|svelte) return 0 ;;
|
|
28
|
+
*) return 1 ;;
|
|
29
|
+
esac
|
|
30
|
+
;;
|
|
31
|
+
backend)
|
|
32
|
+
case "$e" in
|
|
33
|
+
py|go|rs|java|rb|php) return 0 ;;
|
|
34
|
+
*) return 1 ;;
|
|
35
|
+
esac
|
|
36
|
+
;;
|
|
37
|
+
infra)
|
|
38
|
+
case "$e" in
|
|
39
|
+
yaml|yml|tf|toml) return 0 ;;
|
|
40
|
+
*) return 1 ;;
|
|
41
|
+
esac
|
|
42
|
+
;;
|
|
43
|
+
db)
|
|
44
|
+
case "$e" in
|
|
45
|
+
sql|prisma) return 0 ;;
|
|
46
|
+
*) return 1 ;;
|
|
47
|
+
esac
|
|
48
|
+
;;
|
|
49
|
+
config)
|
|
50
|
+
case "$e" in
|
|
51
|
+
json|yaml|yml|toml|cfg|env) return 0 ;;
|
|
52
|
+
*) return 1 ;;
|
|
53
|
+
esac
|
|
54
|
+
;;
|
|
55
|
+
docs)
|
|
56
|
+
case "$e" in
|
|
57
|
+
md|rst|txt) return 0 ;;
|
|
58
|
+
*) return 1 ;;
|
|
59
|
+
esac
|
|
60
|
+
;;
|
|
61
|
+
*)
|
|
62
|
+
return 1
|
|
63
|
+
;;
|
|
64
|
+
esac
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# ===== 特殊文件名匹配 =====
|
|
68
|
+
# infra 类型下的无扩展名特殊文件
|
|
69
|
+
is_infra_special_name() {
|
|
70
|
+
local name="$1"
|
|
71
|
+
local lc_name
|
|
72
|
+
lc_name=$(echo "$name" | tr '[:upper:]' '[:lower:]')
|
|
73
|
+
case "$lc_name" in
|
|
74
|
+
dockerfile|docker-compose|docker-compose.yml|docker-compose.yaml) return 0 ;;
|
|
75
|
+
*) return 1 ;;
|
|
76
|
+
esac
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# ===== check_file_against_type =====
|
|
80
|
+
# 参数: $1 = task_id, $2 = type, $3 = file_path
|
|
81
|
+
# 返回: 0 = 一致, 1 = 不匹配
|
|
82
|
+
check_file_against_type() {
|
|
83
|
+
local task_id="$1"
|
|
84
|
+
local task_type="$2"
|
|
85
|
+
local file_path="$3"
|
|
86
|
+
|
|
87
|
+
# 提取扩展名
|
|
88
|
+
local ext=""
|
|
89
|
+
local basename
|
|
90
|
+
basename=$(basename "$file_path")
|
|
91
|
+
|
|
92
|
+
# 判断是否有扩展名(文件名包含点号且点号不在开头)
|
|
93
|
+
case "$basename" in
|
|
94
|
+
*.*)
|
|
95
|
+
ext="${basename##*.}"
|
|
96
|
+
ext=$(echo "$ext" | tr '[:upper:]' '[:lower:]')
|
|
97
|
+
;;
|
|
98
|
+
*)
|
|
99
|
+
ext=""
|
|
100
|
+
;;
|
|
101
|
+
esac
|
|
102
|
+
|
|
103
|
+
# --- 特殊规则 1: infra 类型下,无扩展名或特殊名称文件 ---
|
|
104
|
+
if [ "$task_type" = "infra" ]; then
|
|
105
|
+
if is_infra_special_name "$basename"; then
|
|
106
|
+
return 0
|
|
107
|
+
fi
|
|
108
|
+
# 也检查扩展名以 yml/yaml 结尾的 docker-compose 变体
|
|
109
|
+
local lc_basename
|
|
110
|
+
lc_basename=$(echo "$basename" | tr '[:upper:]' '[:lower:]')
|
|
111
|
+
case "$lc_basename" in
|
|
112
|
+
docker-compose.yml|docker-compose.yaml) return 0 ;;
|
|
113
|
+
esac
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
# --- 特殊规则 2: .ts + server/ 路径 + Type: backend -> 一致 ---
|
|
117
|
+
if [ "$ext" = "ts" ] && [ "$task_type" = "backend" ]; then
|
|
118
|
+
local lc_path
|
|
119
|
+
lc_path=$(echo "$file_path" | tr '[:upper:]' '[:lower:]')
|
|
120
|
+
case "$lc_path" in
|
|
121
|
+
*server/*|*/server|*server*) return 0 ;;
|
|
122
|
+
esac
|
|
123
|
+
# 如果 .ts 文件不在 server/ 路径下且 Type 是 backend,不算匹配
|
|
124
|
+
# 继续执行下面的通用检查(.ts 不在 backend 列表中,会失败)
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
# --- 特殊规则 3: db 类型下,路径包含 migration 或 schema 关键词 -> 一致 ---
|
|
128
|
+
if [ "$task_type" = "db" ]; then
|
|
129
|
+
local lc_path
|
|
130
|
+
lc_path=$(echo "$file_path" | tr '[:upper:]' '[:lower:]')
|
|
131
|
+
case "$lc_path" in
|
|
132
|
+
*migration*|*schema*) return 0 ;;
|
|
133
|
+
esac
|
|
134
|
+
fi
|
|
135
|
+
|
|
136
|
+
# --- 特殊规则 4: config 类型下,.env.xxx 变体文件(如 .env.example)-> 一致 ---
|
|
137
|
+
if [ "$task_type" = "config" ]; then
|
|
138
|
+
local lc_basename
|
|
139
|
+
lc_basename=$(echo "$basename" | tr '[:upper:]' '[:lower:]')
|
|
140
|
+
case "$lc_basename" in
|
|
141
|
+
.env|.env.*|*.env) return 0 ;;
|
|
142
|
+
esac
|
|
143
|
+
fi
|
|
144
|
+
|
|
145
|
+
# --- 通用规则: 检查扩展名 ---
|
|
146
|
+
if [ -z "$ext" ]; then
|
|
147
|
+
return 1
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
if is_extension_for_type "$task_type" "$ext"; then
|
|
151
|
+
return 0
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
return 1
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
# ===== run_d3_type_file =====
|
|
158
|
+
# 主入口函数,由 gate_semantic 在 source 后调用
|
|
159
|
+
run_d3_type_file() {
|
|
160
|
+
if [ -z "${TASKS_FILE:-}" ] || [ ! -f "$TASKS_FILE" ]; then
|
|
161
|
+
echo "[WARN] TASKS_FILE 未设置或不存在,跳过 D3 检查" >&2
|
|
162
|
+
return 0
|
|
163
|
+
fi
|
|
164
|
+
|
|
165
|
+
local all_pass=true
|
|
166
|
+
local skipped_count=0
|
|
167
|
+
local mismatch_count=0
|
|
168
|
+
|
|
169
|
+
# 用 awk 解析 tasks.md,输出结构化的任务信息
|
|
170
|
+
# 输出格式:
|
|
171
|
+
# FILE|<task_id>|<type>|<file_path>
|
|
172
|
+
# NOFILES|<task_id>|<type>
|
|
173
|
+
local parsed_data
|
|
174
|
+
parsed_data=$(awk '
|
|
175
|
+
/^## [0-9]+\./ {
|
|
176
|
+
# 新任务开始前,处理上一个任务
|
|
177
|
+
if (task_id != "" && has_type == 1 && has_files == 0) {
|
|
178
|
+
print "NOFILES|" task_id "|" task_type
|
|
179
|
+
}
|
|
180
|
+
# 提取新任务编号
|
|
181
|
+
task_id = $2
|
|
182
|
+
gsub(/\..*/, "", task_id)
|
|
183
|
+
task_type = ""
|
|
184
|
+
has_type = 0
|
|
185
|
+
has_files = 0
|
|
186
|
+
}
|
|
187
|
+
/\*\*Type\*\*:/ {
|
|
188
|
+
task_type = $0
|
|
189
|
+
sub(/.*\*\*Type\*\*:[ \t]*/, "", task_type)
|
|
190
|
+
sub(/[ \t]+$/, "", task_type)
|
|
191
|
+
has_type = 1
|
|
192
|
+
}
|
|
193
|
+
/\*\*Files\*\*:/ {
|
|
194
|
+
has_files = 1
|
|
195
|
+
files_str = $0
|
|
196
|
+
sub(/.*\*\*Files\*\*:[ \t]*/, "", files_str)
|
|
197
|
+
# 分割逗号分隔的文件列表
|
|
198
|
+
n = split(files_str, file_list, /,[ \t]*/)
|
|
199
|
+
for (i = 1; i <= n; i++) {
|
|
200
|
+
gsub(/^[ \t]+|[ \t]+$/, "", file_list[i])
|
|
201
|
+
if (file_list[i] != "") {
|
|
202
|
+
print "FILE|" task_id "|" task_type "|" file_list[i]
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
END {
|
|
207
|
+
# 处理最后一个任务
|
|
208
|
+
if (task_id != "" && has_type == 1 && has_files == 0) {
|
|
209
|
+
print "NOFILES|" task_id "|" task_type
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
' "$TASKS_FILE")
|
|
213
|
+
|
|
214
|
+
# 逐行处理解析结果
|
|
215
|
+
while IFS= read -r line; do
|
|
216
|
+
[ -z "$line" ] && continue
|
|
217
|
+
|
|
218
|
+
local field1 field2 field3 field4
|
|
219
|
+
field1=$(echo "$line" | cut -d'|' -f1)
|
|
220
|
+
field2=$(echo "$line" | cut -d'|' -f2)
|
|
221
|
+
field3=$(echo "$line" | cut -d'|' -f3)
|
|
222
|
+
field4=$(echo "$line" | cut -d'|' -f4)
|
|
223
|
+
|
|
224
|
+
case "$field1" in
|
|
225
|
+
FILE)
|
|
226
|
+
local tid="$field2"
|
|
227
|
+
local ttype="$field3"
|
|
228
|
+
local fpath="$field4"
|
|
229
|
+
|
|
230
|
+
if ! check_file_against_type "$tid" "$ttype" "$fpath"; then
|
|
231
|
+
semantic_warn "D3" "任务 ${tid} (Type: ${ttype}) 的 Files 可能不匹配 — ${fpath}"
|
|
232
|
+
all_pass=false
|
|
233
|
+
mismatch_count=$((mismatch_count + 1))
|
|
234
|
+
fi
|
|
235
|
+
;;
|
|
236
|
+
NOFILES)
|
|
237
|
+
local tid="$field2"
|
|
238
|
+
local ttype="$field3"
|
|
239
|
+
semantic_info "D3" "任务 ${tid} 无 Files 字段,跳过 Type-File 一致性检查"
|
|
240
|
+
skipped_count=$((skipped_count + 1))
|
|
241
|
+
;;
|
|
242
|
+
esac
|
|
243
|
+
done <<< "$parsed_data"
|
|
244
|
+
|
|
245
|
+
# 输出汇总信息
|
|
246
|
+
if [ "$all_pass" = true ]; then
|
|
247
|
+
echo "[OK] D3 Type-File 一致性检查全部通过"
|
|
248
|
+
fi
|
|
249
|
+
|
|
250
|
+
return 0
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
# 如果直接执行此脚本(非 source),运行检查
|
|
254
|
+
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
|
|
255
|
+
if [ -z "${TASKS_FILE:-}" ]; then
|
|
256
|
+
echo "用法: TASKS_FILE=<path> bash $0"
|
|
257
|
+
exit 1
|
|
258
|
+
fi
|
|
259
|
+
run_d3_type_file
|
|
260
|
+
fi
|