specline 1.3.4 → 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.
Files changed (82) hide show
  1. package/README.md +132 -125
  2. package/adapters/claude/deploy.json +12 -0
  3. package/adapters/claude/hooks/hooks.json +12 -0
  4. package/adapters/claude/hooks.json +12 -0
  5. package/adapters/claude/orchestration.md +17 -0
  6. package/adapters/codex/agent.toml.hbs +7 -0
  7. package/adapters/codex/deploy.json +12 -0
  8. package/adapters/codex/hooks.json +12 -0
  9. package/adapters/codex/orchestration.md +18 -0
  10. package/adapters/cursor/deploy.json +12 -0
  11. package/adapters/cursor/hooks.json +9 -0
  12. package/adapters/cursor/orchestration.md +17 -0
  13. package/adapters/opencode/deploy.json +12 -0
  14. package/adapters/opencode/orchestration.md +18 -0
  15. package/adapters/opencode/plugin.js +10 -0
  16. package/cli.mjs +161 -558
  17. package/core/agents/specline-backend-dev.yaml +45 -0
  18. package/core/agents/specline-code-reviewer.yaml +67 -0
  19. package/core/agents/specline-config-dev.yaml +50 -0
  20. package/core/agents/specline-config-reviewer.yaml +70 -0
  21. package/core/agents/specline-explore-assistant.yaml +79 -0
  22. package/core/agents/specline-frontend-dev.yaml +45 -0
  23. package/core/agents/specline-spec-creator.yaml +58 -0
  24. package/core/agents/specline-spec-reviewer.yaml +58 -0
  25. package/core/agents/specline-test-runner.yaml +62 -0
  26. package/core/agents/specline-test-writer.yaml +67 -0
  27. package/core/bootstrap/using-specline.md +14 -0
  28. package/core/gates/pipeline-gate-checks/a1-covers-ref.sh +125 -0
  29. package/core/gates/pipeline-gate-checks/a2-a3-reverse.sh +171 -0
  30. package/core/gates/pipeline-gate-checks/c1-exception.sh +71 -0
  31. package/core/gates/pipeline-gate-checks/c2-vague.sh +60 -0
  32. package/core/gates/pipeline-gate-checks/common.sh +68 -0
  33. package/core/gates/pipeline-gate-checks/d1-cycle.sh +149 -0
  34. package/core/gates/pipeline-gate-checks/d3-type-file.sh +260 -0
  35. package/core/gates/pipeline-gate.sh +1456 -0
  36. package/core/hooks/session-start.sh +259 -0
  37. package/core/skills/specline-apply-change/SKILL.md +197 -0
  38. package/core/skills/specline-archive-change/SKILL.md +173 -0
  39. package/core/skills/specline-explore/SKILL.md +504 -0
  40. package/core/skills/specline-knowledge/SKILL.md +539 -0
  41. package/core/skills/specline-pipeline/SKILL.md +604 -0
  42. package/core/skills/specline-pipeline/references/error-recovery-details.md +49 -0
  43. package/core/skills/specline-pipeline/references/event-log-spec.md +59 -0
  44. package/core/skills/specline-pipeline/references/pipeline-state-schema.md +87 -0
  45. package/core/skills/specline-pipeline/templates/subagent-prompts.md +397 -0
  46. package/core/skills/specline-propose/SKILL.md +186 -0
  47. package/core/skills/specline-quickfix/SKILL.md +289 -0
  48. package/core/templates/AGENTS.md.hbs +5 -0
  49. package/core/templates/specline/config.yaml +15 -0
  50. package/lib/deploy-claude.mjs +80 -0
  51. package/lib/deploy-codex.mjs +77 -0
  52. package/lib/deploy-opencode.mjs +93 -0
  53. package/lib/deploy.mjs +668 -0
  54. package/lib/gate.mjs +103 -0
  55. package/lib/hash.mjs +13 -0
  56. package/lib/hook.mjs +105 -0
  57. package/lib/init.mjs +122 -0
  58. package/lib/lock.mjs +99 -0
  59. package/lib/merge.mjs +184 -0
  60. package/lib/paths.mjs +40 -0
  61. package/lib/platforms.mjs +74 -0
  62. package/lib/render-agents.mjs +88 -0
  63. package/lib/render.mjs +126 -0
  64. package/lib/sync.mjs +253 -0
  65. package/lib/tty-select.mjs +89 -0
  66. package/package.json +4 -1
  67. package/templates/.cursor/README.md +18 -0
  68. package/templates/.cursor/agents/specline-code-reviewer.md +63 -4
  69. package/templates/.cursor/agents/specline-spec-creator.md +120 -1
  70. package/templates/.cursor/agents/specline-spec-reviewer.md +21 -2
  71. package/templates/.cursor/agents/specline-test-runner.md +10 -1
  72. package/templates/.cursor/agents/specline-test-writer.md +58 -7
  73. package/templates/.cursor/hooks/specline-pipeline-gate-checks/a2-a3-reverse.sh +1 -1
  74. package/templates/.cursor/hooks/specline-pipeline-gate.sh +118 -0
  75. package/templates/.cursor/skills/specline-apply-change/SKILL.md +26 -0
  76. package/templates/.cursor/skills/specline-archive-change/SKILL.md +24 -0
  77. package/templates/.cursor/skills/specline-explore/SKILL.md +17 -0
  78. package/templates/.cursor/skills/specline-knowledge/SKILL.md +539 -0
  79. package/templates/.cursor/skills/specline-pipeline/SKILL.md +102 -3
  80. package/templates/.cursor/skills/specline-pipeline/templates/subagent-prompts.md +32 -0
  81. package/templates/.cursor/skills/specline-propose/SKILL.md +34 -3
  82. package/templates/.cursor/skills/specline-quickfix/SKILL.md +26 -0
@@ -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