spec-runner 1.0.7 → 1.0.9

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 (39) hide show
  1. package/README.md +24 -9
  2. package/bin/spec-runner.js +112 -144
  3. package/package.json +1 -6
  4. package/templates/.spec-runner/hooks/pre-commit +25 -11
  5. package/templates/.spec-runner/project.json.example +10 -8
  6. package/templates/.spec-runner/scripts/branch/uc-next-start.sh +100 -9
  7. package/templates/.spec-runner/scripts/check.sh +396 -13
  8. package/templates/.spec-runner/scripts/spec-runner-core.sh +286 -157
  9. package/templates/.spec-runner/scripts/test/require-tests-green.sh +7 -63
  10. package/templates/.spec-runner/steps/steps.json +171 -0
  11. package/templates/.spec-runner/steps//343/201/235/343/201/256/344/273/226/344/275/234/346/245/255.md +25 -13
  12. package/templates/.spec-runner/steps//343/202/277/343/202/271/343/202/257/344/270/200/350/246/247.md +67 -104
  13. package/templates/.spec-runner/steps//343/203/201/343/202/247/343/203/203/343/202/257/343/203/252/343/202/271/343/203/210.md +52 -78
  14. package/templates/.spec-runner/steps//343/203/206/343/202/271/343/203/210/350/250/255/350/250/210.md +41 -34
  15. package/templates/.spec-runner/steps//343/203/211/343/203/241/343/202/244/343/203/263/350/250/255/350/250/210.md +34 -14
  16. package/templates/.spec-runner/steps//344/273/225/346/247/230/347/255/226/345/256/232.md +161 -207
  17. package/templates/.spec-runner/steps//345/210/206/346/236/220.md +65 -127
  18. package/templates/.spec-runner/steps//345/256/237/350/243/205.md +67 -79
  19. package/templates/.spec-runner/steps//345/256/237/350/243/205/350/250/210/347/224/273.md +56 -56
  20. package/templates/.spec-runner/steps//346/206/262/347/253/240.md +67 -46
  21. package/templates/.spec-runner/steps//346/233/226/346/230/247/343/201/225/350/247/243/346/266/210.md +88 -148
  22. package/templates/.spec-runner/templates/UC-N-MMDD-/345/210/244/346/226/255/350/250/230/351/214/262/343/203/206/343/203/263/343/203/227/343/203/254.md +33 -0
  23. package/templates/.spec-runner/templates/{UC-NNN- → UC-N-}/343/203/246/343/203/274/343/202/271/343/202/261/343/203/274/343/202/271/345/220/215.md +1 -3
  24. package/templates/.spec-runner/templates/grade-history.json +5 -0
  25. package/templates/.spec-runner/templates/phase-locks.json +29 -0
  26. package/templates/.spec-runner/templates//343/203/211/343/203/241/343/202/244/343/203/263/343/203/242/343/203/207/343/203/253.md +21 -0
  27. package/templates/.spec-runner/templates//343/203/246/343/203/223/343/202/255/343/202/277/343/202/271/350/250/200/350/252/236/350/276/236/346/233/270.md +16 -0
  28. package/templates/.spec-runner/templates//346/206/262/347/253/240.md +51 -0
  29. package/templates/.spec-runner/templates//351/233/206/347/264/204.md +46 -0
  30. package/templates/.spec-runner/scripts/branch/create-uc-branch.sh +0 -105
  31. package/templates/.spec-runner/scripts/branch/uc-next-id.sh +0 -17
  32. package/templates/.spec-runner/scripts/check/drift.sh +0 -66
  33. package/templates/.spec-runner/scripts/check/health.sh +0 -103
  34. package/templates/.spec-runner/scripts/check/naming.sh +0 -51
  35. package/templates/.spec-runner/scripts/check/schema-drift.sh +0 -74
  36. package/templates/.spec-runner/scripts/check/schema-sync.sh +0 -153
  37. package/templates/.spec-runner/scripts/lib/uc-context.sh +0 -75
  38. package/templates/.spec-runner/scripts/openapi/openapi-generate.sh +0 -207
  39. package/templates/.spec-runner/scripts/setup/init-project.sh +0 -152
@@ -4,155 +4,255 @@
4
4
  # cmd-dispatch.sh(次のステップ・ゲート確認・ブランチ作成)から呼ばれる。
5
5
 
6
6
  set -e
7
+
8
+ # ============================================================
9
+ # 0) 基本設定
10
+ # ============================================================
7
11
  REPO_ROOT="${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || echo ".")}"
8
12
  cd "$REPO_ROOT"
9
13
  STEPS_DIR="${STEPS_DIR:-$REPO_ROOT/.spec-runner/steps}"
14
+ STEPS_JSON="${STEPS_JSON:-$STEPS_DIR/steps.json}"
10
15
  LOCK_FILE=".spec-runner/phase-locks.json"
16
+ GRADE_FILE=".spec-runner/grade-history.json"
11
17
  PROJECT_JSON=".spec-runner/project.json"
12
18
 
19
+ # ============================================================
20
+ # 1) ユーティリティ
21
+ # ============================================================
22
+ die() { echo "$1" >&2; exit 1; }
23
+
24
+ require_cmd() {
25
+ command -v "$1" >/dev/null 2>&1 || die "spec-runner-core: $1 が必要です(例: brew install $1)"
26
+ }
27
+
28
+ require_file() {
29
+ [[ -f "$1" ]] || die "spec-runner-core: ファイルがありません: $1"
30
+ }
31
+
32
+ get_steps_common_doc() {
33
+ local key="$1"
34
+ local v
35
+ v="$(jq -r --arg k "$key" '.common.docs[$k] // empty' "$STEPS_JSON" 2>/dev/null)"
36
+ [[ -n "$v" && "$v" != "null" ]] || die "spec-runner-core: steps.json に common.docs.$key がありません"
37
+ echo "$v"
38
+ }
39
+
40
+ # ============================================================
41
+ # 2) 引数解析
42
+ # ============================================================
13
43
  MODE="phase"
14
44
  JSON_MODE=false
15
45
  GATE_GRADE=""
16
- while [[ $# -gt 0 ]]; do
17
- case "$1" in
18
- --phase) MODE="phase" ;;
19
- --json) JSON_MODE=true ;;
20
- --gate) MODE="gate"; GATE_GRADE="${2:-}"; shift ;;
21
- --status) MODE="status" ;;
22
- --grade) MODE="grade" ;;
23
- *) [[ "$MODE" == "gate" && -z "$GATE_GRADE" ]] && GATE_GRADE="$1" ;;
24
- esac
25
- shift
26
- done
27
-
28
- # === 共通: lock / grade / branch を一度だけ読む ===
46
+ parse_args() {
47
+ while [[ $# -gt 0 ]]; do
48
+ case "$1" in
49
+ --phase) MODE="phase" ;;
50
+ --json) JSON_MODE=true ;;
51
+ --gate) MODE="gate"; GATE_GRADE="${2:-}"; shift ;;
52
+ --status) MODE="status" ;;
53
+ --grade) MODE="grade" ;;
54
+ *) [[ "$MODE" == "gate" && -z "$GATE_GRADE" ]] && GATE_GRADE="$1" ;;
55
+ esac
56
+ shift
57
+ done
58
+ }
59
+ parse_args "$@"
60
+
61
+ # ============================================================
62
+ # 3) 前提チェック & 状態ロード(1回だけ)
63
+ # ============================================================
64
+ require_cmd jq
65
+ require_file "$PROJECT_JSON"
66
+ require_file "$STEPS_JSON"
67
+ require_file "$LOCK_FILE"
68
+ require_file "$GRADE_FILE"
69
+
29
70
  has_charter_lock=0
30
71
  has_domain_lock=0
31
72
  has_arch_lock=0
32
73
  has_infra_lock=0
33
- if [[ -f "$LOCK_FILE" ]] && command -v jq >/dev/null 2>&1; then
34
- [[ "$(jq -r '.charter.completed // false' "$LOCK_FILE" 2>/dev/null)" == "true" ]] && has_charter_lock=1
35
- [[ "$(jq -r '.domain.completed // false' "$LOCK_FILE" 2>/dev/null)" == "true" ]] && has_domain_lock=1
36
- [[ "$(jq -r '.architecture.completed // false' "$LOCK_FILE" 2>/dev/null)" == "true" ]] && has_arch_lock=1
37
- [[ "$(jq -r '.infra.completed // false' "$LOCK_FILE" 2>/dev/null)" == "true" ]] && has_infra_lock=1
38
- fi
39
-
40
- grade="LOOP1"
41
- if [[ -f ".spec-runner/grade-history.json" ]] && command -v jq >/dev/null 2>&1; then
42
- grade=$(jq -r '.current_grade // "LOOP1"' .spec-runner/grade-history.json 2>/dev/null || echo "LOOP1")
43
- fi
44
-
45
- branch=$(git branch --show-current 2>/dev/null || echo "")
46
- branch_prefix="feature"
47
- test_dir="tests"
48
- test_pattern="*.spec.*"
49
- if [[ -f "$PROJECT_JSON" ]] && command -v jq >/dev/null 2>&1; then
50
- p=$(jq -r '.naming.branch_prefix // empty' "$PROJECT_JSON" 2>/dev/null)
51
- [[ -n "$p" ]] && branch_prefix="$p"
52
- d=$(jq -r '.test_design.dir // empty' "$PROJECT_JSON" 2>/dev/null)
53
- [[ -n "$d" ]] && test_dir="$d"
54
- pat=$(jq -r '.test_design.pattern // empty' "$PROJECT_JSON" 2>/dev/null)
55
- [[ -n "$pat" ]] && test_pattern="$pat"
56
- fi
74
+ grade=""
75
+ branch=""
76
+ test_dir=""
77
+ test_pattern=""
78
+ branch_prefix=""
79
+ uc_id_re=""
80
+ other_work_pattern=""
57
81
  on_uc_branch=0
58
82
  on_other_work_branch=0
59
83
  current_uc_id=""
60
- other_work_pattern="work|infra|cicd"
61
- if [[ -f "$PROJECT_JSON" ]] && command -v jq >/dev/null 2>&1; then
62
- ow=$(jq -r '.naming.other_work_prefixes[]? // empty' "$PROJECT_JSON" 2>/dev/null | tr '\n' '|' | sed 's/|$//')
63
- [[ -n "$ow" ]] && other_work_pattern="$ow"
64
- fi
65
- if [[ "$branch" =~ ^${branch_prefix}/UC-[0-9]{3}- ]]; then
66
- on_uc_branch=1
67
- current_uc_id="${branch#*/}"
68
- elif [[ "$branch" =~ ^${branch_prefix}/(${other_work_pattern})/ ]]; then
69
- on_other_work_branch=1
70
- fi
71
84
 
72
- # === ゲート確認モード ===
73
- run_gate() {
74
- exit_error() { echo "GATE: $1" >&2; exit 1; }
75
- GRD="${GATE_GRADE:-$grade}"
76
- [[ -f "$LOCK_FILE" ]] || exit_error "phase-locks.json が存在しません"
77
- command -v jq >/dev/null 2>&1 || exit_error "jq がインストールされていません(例: brew install jq)"
78
-
79
- check_required_paths() {
80
- local key="$1"
81
- local list
82
- if [[ -f "$PROJECT_JSON" ]]; then
83
- list=$(jq -r --arg k "$key" '.required_docs[$k][]? // empty' "$PROJECT_JSON" 2>/dev/null)
84
- fi
85
- if [[ -z "$list" ]]; then return 1; fi
86
- while IFS= read -r path; do
87
- [[ -z "$path" ]] && continue
88
- if [[ -f "$path" ]]; then
89
- :
90
- elif [[ -d "$path" ]]; then
91
- count=$(find "$path" -maxdepth 1 -name "*.md" 2>/dev/null | wc -l | tr -d ' ')
92
- [[ "${count:-0}" -ge 1 ]] || exit_error "必須: $path に 1 件以上の .md がありません"
93
- else
94
- exit_error "必須: $path が存在しません"
95
- fi
96
- done <<< "$list"
97
- return 0
98
- }
85
+ load_state() {
86
+ jq -e '.charter.completed == true' "$LOCK_FILE" >/dev/null 2>&1 && has_charter_lock=1
87
+ jq -e '.domain.completed == true' "$LOCK_FILE" >/dev/null 2>&1 && has_domain_lock=1
88
+ jq -e '.architecture.completed == true' "$LOCK_FILE" >/dev/null 2>&1 && has_arch_lock=1
89
+ jq -e '.infra.completed == true' "$LOCK_FILE" >/dev/null 2>&1 && has_infra_lock=1
90
+
91
+ grade=$(jq -r '.current_grade' "$GRADE_FILE")
92
+ [[ -n "$grade" && "$grade" != "null" ]] || die "spec-runner-core: grade-history.json の current_grade が未設定です"
99
93
 
100
- if check_required_paths "charter"; then :; else
101
- [[ -f "docs/01_憲章/憲章.md" ]] || exit_error "憲章.md が存在しません"
94
+ branch=$(git branch --show-current 2>/dev/null || echo "")
95
+
96
+ branch_prefix=$(jq -r '.naming.branch_prefix' "$PROJECT_JSON")
97
+ [[ -n "$branch_prefix" && "$branch_prefix" != "null" ]] || die "spec-runner-core: project.json naming.branch_prefix が未設定です"
98
+ uc_id_re=$(jq -r '.naming.uc_id_pattern' "$PROJECT_JSON")
99
+ [[ -n "$uc_id_re" && "$uc_id_re" != "null" ]] || die "spec-runner-core: project.json naming.uc_id_pattern が未設定です"
100
+ other_work_pattern=$(jq -r '.naming.other_work_prefixes | join("|")' "$PROJECT_JSON")
101
+ [[ -n "$other_work_pattern" ]] || die "spec-runner-core: project.json naming.other_work_prefixes が空です"
102
+
103
+ test_dir=$(jq -r '.test_design.dir' "$PROJECT_JSON")
104
+ [[ -n "$test_dir" && "$test_dir" != "null" ]] || die "spec-runner-core: project.json test_design.dir が未設定です"
105
+ test_pattern=$(jq -r '.test_design.pattern' "$PROJECT_JSON")
106
+ [[ -n "$test_pattern" && "$test_pattern" != "null" ]] || die "spec-runner-core: project.json test_design.pattern が未設定です"
107
+ require_uc_prefixed_tests=1
108
+ rq="$(jq -r '.test_design.require_uc_prefixed_tests // true' "$PROJECT_JSON")"
109
+ [[ "$rq" == "false" || "$rq" == "0" ]] && require_uc_prefixed_tests=0
110
+
111
+ if [[ "$branch" =~ ^${branch_prefix}/(${uc_id_re})- ]]; then
112
+ on_uc_branch=1
113
+ current_uc_id="${BASH_REMATCH[1]}"
114
+ elif [[ "$branch" =~ ^${branch_prefix}/(${other_work_pattern})/ ]]; then
115
+ on_other_work_branch=1
102
116
  fi
103
- if [[ "$GRD" == "LOOP1" ]]; then
104
- [[ "$(jq -r '.charter.completed // false' "$LOCK_FILE" 2>/dev/null)" == "true" ]] || exit_error "Phase 0 未完了(phase-locks.json の charter.completed)"
105
- jq -e '.charter.reviewed_by' "$LOCK_FILE" >/dev/null 2>&1 || exit_error "憲章に署名がありません(charter.reviewed_by)"
106
- echo "Phase 0: OK"
117
+ }
118
+ load_state
119
+
120
+ # UC ブランチで「実装に進める」ためのテスト存在判定(TDD: 当該 UC 用 spec を先に書かせる)
121
+ uc_branch_has_tests_ready_for_implement() {
122
+ [[ -d "$test_dir" ]] || return 1
123
+ if [[ $require_uc_prefixed_tests -eq 0 ]]; then
124
+ [[ -n "$(find "$test_dir" -type f -name "$test_pattern" 2>/dev/null | head -1)" ]]
125
+ return $?
107
126
  fi
108
- if [[ "$GRD" != "LOOP1" ]]; then
109
- [[ "$(jq -r '.domain.completed // false' "$LOCK_FILE" 2>/dev/null)" == "true" ]] || exit_error "Phase 1 未完了"
110
- if check_required_paths "domain"; then :; else
111
- for doc in ユビキタス言語辞書 ドメインモデル 集約; do
112
- [[ -f "docs/02_ドメイン設計/${doc}.md" ]] || exit_error "${doc}.md が存在しません"
113
- done
114
- fi
115
- echo "Phase 1: OK"
116
- [[ "$(jq -r '.architecture.completed // false' "$LOCK_FILE" 2>/dev/null)" == "true" ]] || exit_error "Phase 2 未完了"
117
- if check_required_paths "architecture"; then :; else
118
- [[ -f "docs/03_アーキテクチャ/パターン選定.md" ]] || exit_error "パターン選定.md が存在しません"
119
- [[ -f "docs/03_アーキテクチャ/インフラ方針.md" ]] || exit_error "インフラ方針.md が存在しません"
120
- [[ $(find "docs/03_アーキテクチャ/設計判断記録/" -name "*.md" 2>/dev/null | wc -l) -gt 0 ]] || exit_error "ADR が 1 件もありません"
127
+ [[ -n "$current_uc_id" ]] || return 1
128
+ local f bn
129
+ while IFS= read -r f; do
130
+ [[ -z "$f" ]] && continue
131
+ bn=$(basename "$f")
132
+ [[ "$bn" == "${current_uc_id}-"* ]] || continue
133
+ [[ "$bn" == $test_pattern ]] || continue
134
+ return 0
135
+ done < <(find "$test_dir" -type f 2>/dev/null)
136
+ return 1
137
+ }
138
+
139
+ # ============================================================
140
+ # 4) ゲート関連(小関数)
141
+ # ============================================================
142
+ gate_error() { echo "GATE: $1" >&2; exit 1; }
143
+
144
+ resolve_steps_token() {
145
+ local p="$1"
146
+ [[ "$p" != steps:* ]] && { echo "$p"; return 0; }
147
+ local token keypart suffix base
148
+ token="${p#steps:}"
149
+ keypart="${token%%/*}"
150
+ suffix=""
151
+ [[ "$token" == *"/"* ]] && suffix="/${token#*/}"
152
+ base="$(jq -r --arg k "$keypart" '.common.docs[$k] // empty' "$STEPS_JSON")"
153
+ [[ -n "$base" && "$base" != "null" ]] || gate_error "steps.json に common.docs.$keypart がありません(required_docs の $p を解決できません)"
154
+ echo "${base}${suffix}"
155
+ }
156
+
157
+ get_required_docs_list() {
158
+ local key="$1"
159
+ jq -r --arg k "$key" '.required_docs[$k][]?' "$PROJECT_JSON"
160
+ }
161
+
162
+ assert_paths_exist() {
163
+ local paths="$1"
164
+ while IFS= read -r p; do
165
+ [[ -z "$p" || "$p" == "null" ]] && continue
166
+ p="$(resolve_steps_token "$p")"
167
+ if [[ -f "$p" ]]; then
168
+ :
169
+ elif [[ -d "$p" ]]; then
170
+ count=$(find "$p" -maxdepth 1 -name "*.md" 2>/dev/null | wc -l | tr -d ' ')
171
+ [[ "${count:-0}" -ge 1 ]] || gate_error "必須: $p に 1 件以上の .md がありません"
172
+ else
173
+ gate_error "必須: $p が存在しません"
121
174
  fi
122
- echo "Phase 2: OK"
175
+ done <<< "$paths"
176
+ }
177
+
178
+ gate_charter() {
179
+ local list
180
+ list="$(get_required_docs_list "charter")"
181
+ [[ -n "$list" ]] || gate_error "project.json の required_docs.charter が未設定です"
182
+ assert_paths_exist "$list"
183
+ if [[ "$1" == "LOOP1" ]]; then
184
+ jq -e '.charter.completed == true' "$LOCK_FILE" >/dev/null 2>&1 || gate_error "Phase 0 未完了(phase-locks.json の charter.completed)"
185
+ jq -e '.charter.reviewed_by' "$LOCK_FILE" >/dev/null 2>&1 || gate_error "憲章に署名がありません(charter.reviewed_by)"
186
+ echo "Phase 0: OK"
123
187
  fi
124
- if [[ "$GRD" == "A" ]]; then
125
- [[ "$(jq -r '.infra.completed // false' "$LOCK_FILE" 2>/dev/null)" == "true" ]] || exit_error "Grade A: インフラ設計未完了"
126
- if check_required_paths "grade_a"; then :; else
127
- [[ -f "docs/04_インフラ設計/schema.dbml" ]] || exit_error "Grade A 必須: docs/04_インフラ設計/schema.dbml が存在しません"
128
- fi
129
- echo "Phase 4 (Grade A): OK"
188
+ }
189
+
190
+ gate_domain_and_arch() {
191
+ local grd="$1"
192
+ [[ "$grd" == "LOOP1" ]] && return 0
193
+
194
+ jq -e '.domain.completed == true' "$LOCK_FILE" >/dev/null 2>&1 || gate_error "Phase 1 未完了"
195
+ list="$(get_required_docs_list "domain")"
196
+ [[ -n "$list" ]] || gate_error "project.json の required_docs.domain が未設定です"
197
+ assert_paths_exist "$list"
198
+ echo "Phase 1: OK"
199
+
200
+ jq -e '.architecture.completed == true' "$LOCK_FILE" >/dev/null 2>&1 || gate_error "Phase 2 未完了"
201
+ list="$(get_required_docs_list "architecture")"
202
+ [[ -n "$list" ]] || gate_error "project.json の required_docs.architecture が未設定です"
203
+ assert_paths_exist "$list"
204
+ echo "Phase 2: OK"
205
+ }
206
+
207
+ gate_infra_grade_a() {
208
+ [[ "$1" != "A" ]] && return 0
209
+ jq -e '.infra.completed == true' "$LOCK_FILE" >/dev/null 2>&1 || gate_error "Grade A: インフラ設計未完了"
210
+ list="$(get_required_docs_list "grade_a")"
211
+ [[ -n "$list" ]] || gate_error "project.json の required_docs.grade_a が未設定です(Grade A 時必須)"
212
+ assert_paths_exist "$list"
213
+ echo "Phase 4 (Grade A): OK"
214
+ }
215
+
216
+ gate_uc_openapi_and_tests() {
217
+ local grd="$1"
218
+ [[ "$grd" != "A" && "$grd" != "B" ]] && return 0
219
+
220
+ uc_count=$(find docs/02_ユースケース仕様 -mindepth 2 -maxdepth 2 -name "UC-*.md" 2>/dev/null | wc -l)
221
+ if [[ "${uc_count:-0}" -gt 0 ]]; then
222
+ uc_reviewed_count=$(jq '.uc_reviewed | length' "$LOCK_FILE")
223
+ [[ "${uc_reviewed_count:-0}" -gt 0 ]] || gate_error "Gate 3: uc_reviewed に少なくとも1件の UC 識別子を登録してください"
224
+ list="$(get_required_docs_list "gate3_openapi")"
225
+ [[ -n "$list" ]] || gate_error "project.json の required_docs.gate3_openapi が未設定です"
226
+ assert_paths_exist "$list"
227
+ echo "Gate 3 (UC+OpenAPI): OK"
130
228
  fi
131
- if [[ "$GRD" == "A" || "$GRD" == "B" ]]; then
132
- uc_count=$(find docs/05_ユースケース仕様 -mindepth 2 -maxdepth 2 -name "UC-*.md" 2>/dev/null | wc -l)
133
- if [[ "${uc_count:-0}" -gt 0 ]]; then
134
- uc_reviewed_count=$(jq -r '.uc_reviewed // [] | length' "$LOCK_FILE" 2>/dev/null || echo 0)
135
- [[ "${uc_reviewed_count:-0}" -gt 0 ]] || exit_error "Gate 3: uc_reviewed に少なくとも1件の UC 識別子を登録してください"
136
- if check_required_paths "gate3_openapi"; then :; else
137
- [[ -f "docs/06_API仕様/openapi.yaml" ]] || exit_error "Gate 3: docs/06_API仕様/openapi.yaml が存在しません"
138
- fi
139
- echo "Gate 3 (UC+OpenAPI): OK"
140
- fi
141
- test_design_ok=0
142
- [[ "$(jq -r '.test_design.completed // false' "$LOCK_FILE" 2>/dev/null)" == "true" ]] && test_design_ok=1
143
- if [[ $test_design_ok -eq 1 ]] || [[ -d "$test_dir" && -n "$(find "$test_dir" -type f -name "$test_pattern" 2>/dev/null | head -1)" ]]; then
144
- echo "Gate 5 (テスト設計): OK"
145
- fi
229
+
230
+ test_design_ok=0
231
+ jq -e '.test_design.completed == true' "$LOCK_FILE" >/dev/null 2>&1 && test_design_ok=1
232
+ if [[ $test_design_ok -eq 1 ]] || [[ -d "$test_dir" && -n "$(find "$test_dir" -type f -name "$test_pattern" 2>/dev/null | head -1)" ]]; then
233
+ echo "Gate 5 (テスト設計): OK"
146
234
  fi
147
- if [[ "$GRD" == "A" || "$GRD" == "B" || "$GRD" == "C" ]]; then
148
- if [[ -f ".spec-runner/scripts/test/require-tests-green.sh" ]]; then
149
- .spec-runner/scripts/test/require-tests-green.sh 2>/dev/null && echo "Gate 6 (テスト通過): OK" || true
150
- elif [[ -f "docker-compose.yml" ]] || [[ -f "compose.yml" ]]; then
151
- docker compose run --rm app npm test 2>/dev/null && echo "Gate 6 (テスト通過): OK" || true
152
- elif [[ -f "package.json" ]]; then
153
- npm test 2>/dev/null && echo "Gate 6 (テスト通過): OK" || true
154
- fi
235
+ }
236
+
237
+ gate_tests_green_soft() {
238
+ local grd="$1"
239
+ [[ "$grd" != "A" && "$grd" != "B" && "$grd" != "C" ]] && return 0
240
+
241
+ if [[ -f ".spec-runner/scripts/test/require-tests-green.sh" ]]; then
242
+ .spec-runner/scripts/test/require-tests-green.sh 2>/dev/null && echo "Gate 6 (テスト通過): OK" || true
155
243
  fi
244
+ }
245
+
246
+ # === ゲート確認モード ===
247
+ run_gate() {
248
+ GRD="${GATE_GRADE:-$grade}"
249
+
250
+ gate_charter "$GRD"
251
+ gate_domain_and_arch "$GRD"
252
+ gate_infra_grade_a "$GRD"
253
+ gate_uc_openapi_and_tests "$GRD"
254
+ gate_tests_green_soft "$GRD"
255
+
156
256
  echo "ゲート確認: 通過"
157
257
  }
158
258
 
@@ -162,62 +262,92 @@ run_phase() {
162
262
  phase_name_ja=""
163
263
  command=""
164
264
  command_file=""
265
+ step_id=""
266
+ step_commands="[]"
267
+ check_command=""
268
+ feature_dir=""
269
+ feature_spec=""
270
+
271
+ resolve_step() {
272
+ local sid="$1"
273
+ step_id="$sid"
274
+ command=$(jq -r --arg id "$sid" '.steps[]? | select(.id==$id) | .name_ja' "$STEPS_JSON")
275
+ local md
276
+ md=$(jq -r --arg id "$sid" '.steps[]? | select(.id==$id) | .md_file' "$STEPS_JSON")
277
+ [[ -n "$command" && "$command" != "null" ]] || die "spec-runner-core: steps.json に id=$sid の name_ja がありません"
278
+ [[ -n "$md" && "$md" != "null" ]] || die "spec-runner-core: steps.json に id=$sid の md_file がありません"
279
+ command_file="$STEPS_DIR/$md"
280
+ step_commands=$(jq -c --arg id "$sid" '.steps[]? | select(.id==$id) | .commands' "$STEPS_JSON")
281
+ [[ -n "$step_commands" && "$step_commands" != "null" ]] || die "spec-runner-core: steps.json に id=$sid の commands がありません"
282
+ check_command=$(jq -r '.common.commands.check' "$STEPS_JSON")
283
+ [[ -n "$check_command" && "$check_command" != "null" ]] || die "spec-runner-core: steps.json に common.commands.check がありません"
284
+ }
285
+
286
+ if [[ $on_uc_branch -eq 1 ]] && [[ -n "$current_uc_id" ]]; then
287
+ for f in docs/02_ユースケース仕様/*/"${current_uc_id}-"*.md; do
288
+ [[ -f "$f" ]] && feature_spec="$f" && feature_dir="$(dirname "$f")" && break
289
+ done
290
+ fi
291
+
292
+ uc_count_total=$(find docs/02_ユースケース仕様 -mindepth 2 -maxdepth 2 -name "UC-*.md" 2>/dev/null | wc -l | tr -d ' ')
293
+ uc_count_total=${uc_count_total:-0}
165
294
 
166
295
  if [[ $has_charter_lock -eq 0 ]]; then
167
- phase=0; phase_name_ja="憲章策定"; command="憲章"; command_file="$STEPS_DIR/憲章.md"
168
- elif [[ $has_domain_lock -eq 0 ]]; then
169
- phase=1; phase_name_ja="ドメイン設計"; command="ドメイン設計"; command_file="$STEPS_DIR/ドメイン設計.md"
170
- elif [[ $has_arch_lock -eq 0 ]]; then
171
- phase=2; phase_name_ja="アーキテクチャ選択"; command="実装計画"; command_file="$STEPS_DIR/実装計画.md"
296
+ phase=0; phase_name_ja="憲章策定"; resolve_step "charter"
297
+ elif [[ $has_domain_lock -eq 0 && ${uc_count_total} -gt 0 && $on_uc_branch -eq 0 ]]; then
298
+ phase=2; phase_name_ja="ドメイン設計"; resolve_step "domain"
299
+ elif [[ $has_arch_lock -eq 0 && $has_domain_lock -eq 1 ]]; then
300
+ phase=3; phase_name_ja="アーキテクチャ選択"; resolve_step "architecture_plan"
172
301
  elif [[ $on_uc_branch -eq 1 ]]; then
173
302
  uc_spec=""
174
303
  if [[ -n "$current_uc_id" ]]; then
175
- for f in docs/05_ユースケース仕様/*/"${current_uc_id}.md"; do [[ -f "$f" ]] && uc_spec="$f" && break; done
304
+ for f in docs/02_ユースケース仕様/*/"${current_uc_id}-"*.md; do [[ -f "$f" ]] && uc_spec="$f" && break; done
176
305
  fi
177
306
  if [[ -z "$uc_spec" ]]; then
178
- phase=3; phase_name_ja="UC 仕様書"; command="仕様策定"; command_file="$STEPS_DIR/仕様策定.md"
307
+ phase=1; phase_name_ja="ユースケース仕様"; resolve_step "uc_spec"
179
308
  else
309
+ feature_spec="$uc_spec"
310
+ feature_dir="$(dirname "$uc_spec")"
180
311
  uc_dir=$(basename "$uc_spec" .md)
181
312
  reviewed=0
182
- [[ -f "$LOCK_FILE" ]] && command -v jq >/dev/null 2>&1 && jq -e --arg u "$uc_dir" '.uc_reviewed[]? == $u' "$LOCK_FILE" 2>/dev/null | grep -q true && reviewed=1
313
+ jq -e --arg u "$uc_dir" '.uc_reviewed[]? == $u' "$LOCK_FILE" 2>/dev/null | grep -q true && reviewed=1
183
314
  if [[ $reviewed -eq 0 ]]; then
184
- phase=3; phase_name_ja="UC 仕様書(レビュー通過まで)"; command="曖昧さ解消"; command_file="$STEPS_DIR/曖昧さ解消.md"
315
+ phase=1; phase_name_ja="ユースケース仕様(レビュー通過まで)"; resolve_step "clarify"
185
316
  else
186
317
  if [[ "$grade" == "A" ]] && [[ $has_infra_lock -eq 0 ]]; then
187
- phase=4; phase_name_ja="インフラ詳細設計"; command="実装計画"; command_file="$STEPS_DIR/実装計画.md"
318
+ phase=4; phase_name_ja="インフラ詳細設計"; resolve_step "infra_plan"
188
319
  else
189
- has_tests=0
190
- [[ -d "$test_dir" ]] && [[ -n "$(find "$test_dir" -type f -name "$test_pattern" 2>/dev/null | head -1)" ]] && has_tests=1
191
- if [[ $has_tests -eq 0 ]]; then
192
- phase=5; phase_name_ja="テスト設計"; command="テスト設計"; command_file="$STEPS_DIR/テスト設計.md"
320
+ if uc_branch_has_tests_ready_for_implement; then
321
+ phase=6; phase_name_ja="実装"; resolve_step "implement"
193
322
  else
194
- phase=6; phase_name_ja="実装"; command="実装"; command_file="$STEPS_DIR/実装.md"
323
+ phase=5; phase_name_ja="テスト設計(当該 UC の spec 必須)"; resolve_step "test_design"
195
324
  fi
196
325
  fi
197
326
  fi
198
327
  fi
199
328
  else
200
329
  if [[ $on_other_work_branch -eq 1 ]]; then
201
- phase=3; phase_name_ja="その他作業(CI/CD・インフラ等)"; command="その他作業"; command_file="$STEPS_DIR/その他作業.md"
330
+ phase=1; phase_name_ja="その他作業(CI/CD・インフラ等)"; resolve_step "other_work"
331
+ elif [[ $has_domain_lock -eq 0 ]]; then
332
+ phase=1; phase_name_ja="ユースケース開始(仕様策定)"; resolve_step "uc_spec"
202
333
  elif [[ $has_arch_lock -eq 0 ]]; then
203
- phase=2; phase_name_ja="アーキテクチャ選択"; command="実装計画"; command_file="$STEPS_DIR/実装計画.md"
334
+ phase=3; phase_name_ja="アーキテクチャ選択"; resolve_step "architecture_plan"
204
335
  else
205
- phase=3; phase_name_ja="UC 開始(仕様策定)"; command="仕様策定"; command_file="$STEPS_DIR/仕様策定.md"
336
+ phase=1; phase_name_ja="ユースケース開始(仕様策定)"; resolve_step "uc_spec"
206
337
  fi
207
338
  fi
208
339
 
209
340
  if [[ "$JSON_MODE" == true ]]; then
210
- if command -v jq >/dev/null 2>&1; then
211
- jq -cn --argjson phase $phase --arg name "$phase_name_ja" --arg cmd "$command" --arg file "$command_file" --arg grade "$grade" \
212
- '{phase:$phase, phase_name_ja:$name, command:$cmd, command_file:$file, grade:$grade}'
213
- else
214
- echo "{\"phase\":$phase,\"phase_name_ja\":\"$phase_name_ja\",\"command\":\"$command\",\"command_file\":\"$command_file\",\"grade\":\"$grade\"}"
215
- fi
341
+ jq -cn --argjson phase "$phase" --arg name "$phase_name_ja" --arg step_id "$step_id" --arg cmd "$command" --arg file "$command_file" --arg grade "$grade" \
342
+ --arg check "$check_command" --argjson cmds "$step_commands" \
343
+ --arg feature_dir "$feature_dir" --arg feature_spec "$feature_spec" \
344
+ '{phase:$phase, phase_name_ja:$name, step_id:$step_id, command:$cmd, command_file:$file, grade:$grade, check_command:$check, step_commands:$cmds, feature_dir:$feature_dir, feature_spec:$feature_spec}'
216
345
  else
217
346
  echo "現在フェーズ: Phase $phase($phase_name_ja)"
218
347
  echo "推奨コマンド: $command"
219
348
  echo "コマンドファイル: $command_file"
220
349
  echo "グレード: $grade"
350
+ echo "チェック(毎回): $check_command"
221
351
  if [[ $on_uc_branch -eq 0 ]] && [[ -n "$branch" ]]; then
222
352
  echo ""
223
353
  echo "注意: main 等のままの修正は危険です。UC 用ブランチを作成してから作業してください(仕様策定ステップでブランチ作成)。"
@@ -231,14 +361,13 @@ run_status() {
231
361
  echo "グレード: $grade"
232
362
  echo ""
233
363
  echo "Lock(.spec-runner/phase-locks.json):"
234
- if [[ -f "$LOCK_FILE" ]] && command -v jq >/dev/null 2>&1; then
235
- for sec in charter domain architecture infra test_design; do
236
- val=$(jq -r --arg s "$sec" '.[$s].completed // false' "$LOCK_FILE" 2>/dev/null)
237
- [[ "$val" == "true" ]] && echo " ✓ $sec" || echo " - $sec"
238
- done
239
- else
240
- echo " phase-locks.json が存在しないか jq がインストールされていません"
241
- fi
364
+ for sec in charter domain architecture infra test_design; do
365
+ if jq -e --arg s "$sec" '.[$s].completed == true' "$LOCK_FILE" >/dev/null 2>&1; then
366
+ echo " ✓ $sec"
367
+ else
368
+ echo " - $sec"
369
+ fi
370
+ done
242
371
  }
243
372
 
244
373
  # === グレード判定チェックリスト ===
@@ -250,7 +379,7 @@ run_grade() {
250
379
  echo " → 新規UC: STEP 2 へ"
251
380
  echo ""
252
381
  echo "STEP 2: Grade B 仮置きで UC 草稿を作成する"
253
- echo " → AI と対話して UC 仕様書の草稿(status: draft)を作成する"
382
+ echo " → AI と対話して UC 仕様書の草稿を作成する"
254
383
  echo " → この時点ではまだブランチ作成・コミット不要"
255
384
  echo ""
256
385
  echo "STEP 3: UC 草稿を見て Grade を確定する"
@@ -1,75 +1,21 @@
1
1
  #!/usr/bin/env bash
2
2
  # テストコマンドを実行し、すべてグリーンでないと終了コード 1 を返す。
3
- # 実装ステップの「完了検証」で必須。コマンドは .spec-runner/project.json の test_command.run で指定。
4
- # 未設定時はプロジェクトを検出して候補を実行し、設定を促す。
3
+ # コマンドは .spec-runner/project.json の test_command.run のみ(必須)。
5
4
 
6
5
  set -e
7
6
  REPO_ROOT="${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || echo ".")}"
8
7
  cd "$REPO_ROOT"
9
8
  SPEC_RUNNER="${REPO_ROOT}/.spec-runner"
10
9
  PROJECT_JSON="${SPEC_RUNNER}/project.json"
11
- LEGACY_TEST_JSON="${SPEC_RUNNER}/test-command.json"
10
+ command -v jq >/dev/null 2>&1 || { echo "require-tests-green: jq が必要です(brew install jq)" >&2; exit 1; }
11
+ [[ -f "$PROJECT_JSON" ]] || { echo "require-tests-green: $PROJECT_JSON がありません" >&2; exit 1; }
12
12
 
13
- # 設定を読む: project.json の test_command.run → 後方互換で test-command.json の run
14
- get_run_command() {
15
- local run=""
16
- if [[ -f "$PROJECT_JSON" ]]; then
17
- if command -v jq >/dev/null 2>&1; then
18
- run=$(jq -r '.test_command.run // empty' "$PROJECT_JSON" 2>/dev/null)
19
- else
20
- run=$(grep -o '"run"[[:space:]]*:[[:space:]]*"[^"]*"' "$PROJECT_JSON" 2>/dev/null | head -1 | sed 's/.*"\([^"]*\)" *$/\1/')
21
- fi
22
- fi
23
- if [[ -z "$run" ]] && [[ -f "$LEGACY_TEST_JSON" ]]; then
24
- if command -v jq >/dev/null 2>&1; then
25
- run=$(jq -r '.run // empty' "$LEGACY_TEST_JSON" 2>/dev/null)
26
- else
27
- run=$(grep -o '"run"[[:space:]]*:[[:space:]]*"[^"]*"' "$LEGACY_TEST_JSON" 2>/dev/null | sed 's/.*"\([^"]*\)" *$/\1/')
28
- fi
29
- fi
30
- echo -n "$run"
31
- }
32
-
33
- # 未設定時に検出して実行するコマンドを決める
34
- detect_and_run() {
35
- if [[ -f "package.json" ]] && grep -q '"test"' package.json 2>/dev/null; then
36
- echo "npm test"
37
- return
38
- fi
39
- if [[ -f "pyproject.toml" ]]; then
40
- if command -v poetry >/dev/null 2>&1; then
41
- echo "poetry run pytest"
42
- else
43
- echo "pytest"
44
- fi
45
- return
46
- fi
47
- if [[ -f "go.mod" ]]; then
48
- echo "go test ./..."
49
- return
50
- fi
51
- if [[ -f "docker-compose.yml" ]] || [[ -f "docker-compose.yaml" ]]; then
52
- # サービス名は app を仮定(未設定時は検出のみで実行しない)
53
- echo ""
54
- return
55
- fi
56
- echo ""
13
+ RUN_CMD=$(jq -r '.test_command.run' "$PROJECT_JSON")
14
+ [[ -n "$RUN_CMD" && "$RUN_CMD" != "null" ]] || {
15
+ echo "require-tests-green: project.json に test_command.run を設定してください。" >&2
16
+ exit 1
57
17
  }
58
18
 
59
- RUN_CMD=$(get_run_command)
60
-
61
- # 未設定なら検出
62
- if [[ -z "$RUN_CMD" ]]; then
63
- RUN_CMD=$(detect_and_run)
64
- if [[ -z "$RUN_CMD" ]]; then
65
- echo "require-tests-green: テストコマンドが未設定です。.spec-runner/project.json の test_command.run を設定するか、初期化(init-project.sh)を実行してください。" >&2
66
- echo " 例: project.json に \"test_command\": {\"run\": \"npm test\"} を追加" >&2
67
- exit 1
68
- fi
69
- echo "require-tests-green: project.json に test_command がありません。検出したコマンドで実行します: $RUN_CMD" >&2
70
- echo " 恒久設定: 初期化で設定するか、project.json に test_command.run を追加してください。" >&2
71
- fi
72
-
73
19
  echo "require-tests-green: テストを実行しています($RUN_CMD)..."
74
20
  if eval "$RUN_CMD"; then
75
21
  echo "require-tests-green: すべてグリーンです。実装完了の条件(テスト)を満たしています。"
@@ -77,7 +23,5 @@ if eval "$RUN_CMD"; then
77
23
  else
78
24
  echo "" >&2
79
25
  echo "require-tests-green: テストが失敗しました。実装完了とみなせません。" >&2
80
- echo " テストを修正し、再度 .spec-runner/scripts/test/require-tests-green.sh を実行してください。" >&2
81
- echo " コマンドを変更する場合は .spec-runner/project.json の test_command.run を編集するか、初期化(init-project.sh)を実行してください。" >&2
82
26
  exit 1
83
27
  fi