spec-runner 1.0.24 → 1.1.2

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 (32) hide show
  1. package/README.md +15 -7
  2. package/bin/spec-runner.js +28 -23
  3. package/docs/flow.md +59 -201
  4. package/package.json +1 -1
  5. package/templates/.spec-runner/project.json.example +1 -1
  6. package/templates/.spec-runner/scripts/check.sh +2 -38
  7. package/templates/.spec-runner/scripts/spec-runner-core.sh +137 -348
  8. package/templates/.spec-runner/scripts/uc-next-start.sh +154 -0
  9. package/templates/.spec-runner/spec-runner.sh +2 -3
  10. package/templates/.spec-runner/steps/steps.json +22 -97
  11. package/templates/.spec-runner/steps//343/203/206/343/202/271/343/203/210/350/250/255/350/250/210.md +10 -9
  12. package/templates/.spec-runner/steps//343/203/211/343/203/241/343/202/244/343/203/263/350/250/255/350/250/210.md +2 -2
  13. package/templates/.spec-runner/steps//344/273/225/346/247/230/347/255/226/345/256/232.md +16 -14
  14. package/templates/.spec-runner/steps//345/210/206/346/236/220.md +2 -2
  15. package/templates/.spec-runner/steps//345/256/237/350/243/205.md +8 -7
  16. package/templates/.spec-runner/steps//345/256/237/350/243/205/350/250/210/347/224/273.md +10 -10
  17. package/templates/.spec-runner/steps//346/206/262/347/253/240.md +1 -1
  18. package/templates/.spec-runner/steps//346/233/226/346/230/247/343/201/225/350/247/243/346/266/210.md +1 -1
  19. package/templates/mkdocs-scaffold/docs/index.md +3 -3
  20. package/templates/skills/uc-k1-work-card-init/SKILL.md +76 -0
  21. package/templates/skills/uc-k2-pre-commit-check/SKILL.md +57 -0
  22. package/templates/skills/uc-k3-spec-impl-diff-review/SKILL.md +57 -0
  23. package/templates/spec-runner-command.md +4 -3
  24. package/templates/.spec-runner/hooks/pre-commit +0 -47
  25. package/templates/.spec-runner/hooks/pre-push +0 -9
  26. package/templates/.spec-runner/scripts/branch/uc-next-start.sh +0 -224
  27. package/templates/.spec-runner/scripts/docs-serve.sh +0 -21
  28. package/templates/.spec-runner/scripts/test/require-tests-green.sh +0 -27
  29. package/templates/.spec-runner/steps//343/201/235/343/201/256/344/273/226/344/275/234/346/245/255.md +0 -34
  30. package/templates/.spec-runner/steps//343/202/277/343/202/271/343/202/257/344/270/200/350/246/247.md +0 -95
  31. 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 +0 -80
  32. package/templates/.spec-runner/templates/grade-history.json +0 -5
@@ -1,33 +1,20 @@
1
1
  #!/usr/bin/env bash
2
- # spec-runner 判定の単一入口。振る舞い仕様に沿って「フェーズ・ゲート・状態・グレード」を扱う。
3
- # 使用: spec-runner-core.sh [--phase] [--json] | --gate [GRADE] | --status | --grade
4
- # cmd-dispatch.sh(次のステップ・ゲート確認・ブランチ作成)から呼ばれる。
2
+ # spec-runner 判定の単一入口(薄いオーケストレータ)
3
+ # 使用: spec-runner-core.sh [--phase] [--json] | --status
5
4
 
6
5
  set -e
7
6
 
8
- # ============================================================
9
- # 0) 基本設定
10
- # ============================================================
11
7
  REPO_ROOT="${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || echo ".")}"
12
8
  cd "$REPO_ROOT"
13
9
  STEPS_DIR="${STEPS_DIR:-$REPO_ROOT/.spec-runner/steps}"
14
10
  STEPS_JSON="${STEPS_JSON:-$STEPS_DIR/steps.json}"
15
11
  LOCK_FILE=".spec-runner/phase-locks.json"
16
- GRADE_FILE=".spec-runner/grade-history.json"
17
12
  PROJECT_JSON=".spec-runner/project.json"
18
13
 
19
- # ============================================================
20
- # 1) ユーティリティ
21
- # ============================================================
22
14
  die() { echo "$1" >&2; exit 1; }
23
15
 
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
- }
16
+ require_cmd() { command -v "$1" >/dev/null 2>&1 || die "spec-runner-core: $1 が必要です"; }
17
+ require_file() { [[ -f "$1" ]] || die "spec-runner-core: ファイルがありません: $1"; }
31
18
 
32
19
  get_steps_common_doc() {
33
20
  local key="$1"
@@ -37,228 +24,112 @@ get_steps_common_doc() {
37
24
  echo "$v"
38
25
  }
39
26
 
40
- # ============================================================
41
- # 2) 引数解析
42
- # ============================================================
43
27
  MODE="phase"
44
28
  JSON_MODE=false
45
- GATE_GRADE=""
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 "$@"
29
+ while [[ $# -gt 0 ]]; do
30
+ case "$1" in
31
+ --phase) MODE="phase" ;;
32
+ --json) JSON_MODE=true ;;
33
+ --status) MODE="status" ;;
34
+ esac
35
+ shift
36
+ done
60
37
 
61
- # ============================================================
62
- # 3) 前提チェック & 状態ロード(1回だけ)
63
- # ============================================================
64
38
  require_cmd jq
65
39
  require_file "$PROJECT_JSON"
66
40
  require_file "$STEPS_JSON"
67
41
  require_file "$LOCK_FILE"
68
- require_file "$GRADE_FILE"
69
42
 
70
43
  has_charter_lock=0
71
44
  has_domain_lock=0
72
45
  has_arch_lock=0
73
- has_infra_lock=0
74
46
  uc_discovery_completed=0
75
- grade=""
76
- branch=""
77
47
  test_dir=""
78
48
  test_pattern=""
79
- branch_prefix=""
80
- uc_id_re=""
81
- other_work_pattern=""
82
- on_uc_branch=0
83
- on_other_work_branch=0
84
- current_uc_id=""
85
-
86
- load_state() {
87
- jq -e '.charter.completed == true' "$LOCK_FILE" >/dev/null 2>&1 && has_charter_lock=1
88
- jq -e '.domain.completed == true' "$LOCK_FILE" >/dev/null 2>&1 && has_domain_lock=1
89
- jq -e '.architecture.completed == true' "$LOCK_FILE" >/dev/null 2>&1 && has_arch_lock=1
90
- jq -e '.infra.completed == true' "$LOCK_FILE" >/dev/null 2>&1 && has_infra_lock=1
91
- jq -e '.uc_discovery.completed == true' "$LOCK_FILE" >/dev/null 2>&1 && uc_discovery_completed=1
92
-
93
- grade=$(jq -r '.current_grade' "$GRADE_FILE")
94
- [[ -n "$grade" && "$grade" != "null" ]] || die "spec-runner-core: grade-history.json の current_grade が未設定です"
95
-
96
- branch=$(git branch --show-current 2>/dev/null || echo "")
49
+ require_uc_prefixed_tests=0
50
+
51
+ jq -e '.charter.completed == true' "$LOCK_FILE" >/dev/null 2>&1 && has_charter_lock=1
52
+ jq -e '.domain.completed == true' "$LOCK_FILE" >/dev/null 2>&1 && has_domain_lock=1
53
+ jq -e '.architecture.completed == true' "$LOCK_FILE" >/dev/null 2>&1 && has_arch_lock=1
54
+ jq -e '.uc_discovery.completed == true' "$LOCK_FILE" >/dev/null 2>&1 && uc_discovery_completed=1
55
+
56
+ test_dir=$(jq -r '.test_design.dir' "$PROJECT_JSON")
57
+ [[ -n "$test_dir" && "$test_dir" != "null" ]] || die "spec-runner-core: project.json test_design.dir が未設定です"
58
+ test_pattern=$(jq -r '.test_design.pattern' "$PROJECT_JSON")
59
+ [[ -n "$test_pattern" && "$test_pattern" != "null" ]] || die "spec-runner-core: project.json test_design.pattern が未設定です"
60
+ rq="$(jq -r '.test_design.require_uc_prefixed_tests // false' "$PROJECT_JSON")"
61
+ [[ "$rq" == "true" || "$rq" == "1" ]] && require_uc_prefixed_tests=1
62
+
63
+ quality_done() {
64
+ local kind="$1"
65
+ local scope="$2"
66
+ local key="$3"
67
+ local key_md="${key}.md"
68
+ jq -e --arg k "$key" --arg km "$key_md" --arg s "$scope" \
69
+ ".quality.${kind}[\$s][]? | select(. == \$k or . == \$km)" \
70
+ "$LOCK_FILE" >/dev/null 2>&1
71
+ }
97
72
 
98
- branch_prefix=$(jq -r '.naming.branch_prefix' "$PROJECT_JSON")
99
- [[ -n "$branch_prefix" && "$branch_prefix" != "null" ]] || die "spec-runner-core: project.json naming.branch_prefix が未設定です"
100
- uc_id_re=$(jq -r '.naming.uc_id_pattern' "$PROJECT_JSON")
101
- [[ -n "$uc_id_re" && "$uc_id_re" != "null" ]] || die "spec-runner-core: project.json naming.uc_id_pattern が未設定です"
102
- other_work_pattern=$(jq -r '.naming.other_work_prefixes | join("|")' "$PROJECT_JSON")
103
- [[ -n "$other_work_pattern" ]] || die "spec-runner-core: project.json naming.other_work_prefixes が空です"
73
+ resolve_step() {
74
+ local sid="$1"
75
+ step_id="$sid"
76
+ command=$(jq -r --arg id "$sid" '.steps[]? | select(.id==$id) | .name_ja' "$STEPS_JSON")
77
+ local md
78
+ md=$(jq -r --arg id "$sid" '.steps[]? | select(.id==$id) | .md_file' "$STEPS_JSON")
79
+ [[ -n "$command" && "$command" != "null" ]] || die "spec-runner-core: steps.json に id=$sid の name_ja がありません"
80
+ [[ -n "$md" && "$md" != "null" ]] || die "spec-runner-core: steps.json に id=$sid の md_file がありません"
81
+ command_file="$STEPS_DIR/$md"
82
+ step_commands=$(jq -c --arg id "$sid" '.steps[]? | select(.id==$id) | .commands' "$STEPS_JSON")
83
+ check_command=$(jq -r '.common.commands.check' "$STEPS_JSON")
84
+ }
104
85
 
105
- test_dir=$(jq -r '.test_design.dir' "$PROJECT_JSON")
106
- [[ -n "$test_dir" && "$test_dir" != "null" ]] || die "spec-runner-core: project.json test_design.dir が未設定です"
107
- test_pattern=$(jq -r '.test_design.pattern' "$PROJECT_JSON")
108
- [[ -n "$test_pattern" && "$test_pattern" != "null" ]] || die "spec-runner-core: project.json test_design.pattern が未設定です"
109
- require_uc_prefixed_tests=1
110
- rq="$(jq -r '.test_design.require_uc_prefixed_tests // true' "$PROJECT_JSON")"
111
- [[ "$rq" == "false" || "$rq" == "0" ]] && require_uc_prefixed_tests=0
86
+ latest_uc_spec() {
87
+ find docs/02_ユースケース仕様 -mindepth 2 -maxdepth 2 -type f -name "UC-*.md" 2>/dev/null | sort -V | tail -1
88
+ }
112
89
 
113
- if [[ "$branch" =~ ^${branch_prefix}/(${uc_id_re})- ]]; then
114
- on_uc_branch=1
115
- current_uc_id="${BASH_REMATCH[1]}"
116
- elif [[ "$branch" =~ ^${branch_prefix}/(${other_work_pattern})/ ]]; then
117
- on_other_work_branch=1
118
- fi
90
+ latest_unreviewed_uc_spec() {
91
+ local f key
92
+ while IFS= read -r f; do
93
+ key="$(basename "$f" .md)"
94
+ jq -e --arg u "$key" 'any(.uc_reviewed[]?; . == $u)' "$LOCK_FILE" >/dev/null 2>&1 && continue
95
+ echo "$f"
96
+ return 0
97
+ done < <(find docs/02_ユースケース仕様 -mindepth 2 -maxdepth 2 -type f -name "UC-*.md" 2>/dev/null | sort -V)
98
+ return 1
119
99
  }
120
- load_state
121
100
 
122
- # UC ブランチで「実装に進める」ためのテスト存在判定(TDD: 当該 UC 用 spec を先に書かせる)
123
- uc_branch_has_tests_ready_for_implement() {
101
+ uc_has_tests_ready_for_implement() {
102
+ local uc_key="$1"
103
+ local uc_id=""
124
104
  [[ -d "$test_dir" ]] || return 1
125
105
  if [[ $require_uc_prefixed_tests -eq 0 ]]; then
126
106
  [[ -n "$(find "$test_dir" -type f -name "$test_pattern" 2>/dev/null | head -1)" ]]
127
107
  return $?
128
108
  fi
129
- [[ -n "$current_uc_id" ]] || return 1
109
+ [[ "$uc_key" =~ ^(UC-[0-9]+)- ]] && uc_id="${BASH_REMATCH[1]}"
110
+ [[ -n "$uc_id" ]] || return 1
130
111
  local f bn
131
112
  while IFS= read -r f; do
132
- [[ -z "$f" ]] && continue
133
113
  bn=$(basename "$f")
134
- [[ "$bn" == "${current_uc_id}-"* ]] || continue
114
+ [[ "$bn" == "${uc_id}-"* ]] || continue
135
115
  [[ "$bn" == $test_pattern ]] || continue
136
116
  return 0
137
117
  done < <(find "$test_dir" -type f 2>/dev/null)
138
118
  return 1
139
119
  }
140
120
 
141
- # ============================================================
142
- # 4) ゲート関連(小関数)
143
- # ============================================================
144
- gate_error() { echo "GATE: $1" >&2; exit 1; }
145
-
146
- resolve_steps_token() {
147
- local p="$1"
148
- [[ "$p" != steps:* ]] && { echo "$p"; return 0; }
149
- local token keypart suffix base
150
- token="${p#steps:}"
151
- keypart="${token%%/*}"
152
- suffix=""
153
- [[ "$token" == *"/"* ]] && suffix="/${token#*/}"
154
- base="$(jq -r --arg k "$keypart" '.common.docs[$k] // empty' "$STEPS_JSON")"
155
- [[ -n "$base" && "$base" != "null" ]] || gate_error "steps.json に common.docs.$keypart がありません(required_docs の $p を解決できません)"
156
- echo "${base}${suffix}"
157
- }
158
-
159
- get_required_docs_list() {
160
- local key="$1"
161
- jq -r --arg k "$key" '.required_docs[$k][]?' "$PROJECT_JSON"
162
- }
163
-
164
- assert_paths_exist() {
165
- local paths="$1"
166
- while IFS= read -r p; do
167
- [[ -z "$p" || "$p" == "null" ]] && continue
168
- p="$(resolve_steps_token "$p")"
169
- if [[ -f "$p" ]]; then
170
- :
171
- elif [[ -d "$p" ]]; then
172
- count=$(find "$p" -maxdepth 1 -name "*.md" 2>/dev/null | wc -l | tr -d ' ')
173
- [[ "${count:-0}" -ge 1 ]] || gate_error "必須: $p に 1 件以上の .md がありません"
121
+ run_status() {
122
+ echo "=== spec-runner フェーズ状況 ==="
123
+ echo "Lock(.spec-runner/phase-locks.json):"
124
+ for sec in charter domain architecture uc_discovery test_design; do
125
+ if jq -e --arg s "$sec" '.[$s].completed == true' "$LOCK_FILE" >/dev/null 2>&1; then
126
+ echo " ✓ $sec"
174
127
  else
175
- gate_error "必須: $p が存在しません"
128
+ echo " - $sec"
176
129
  fi
177
- done <<< "$paths"
178
- }
179
-
180
- gate_charter() {
181
- local list
182
- list="$(get_required_docs_list "charter")"
183
- [[ -n "$list" ]] || gate_error "project.json の required_docs.charter が未設定です"
184
- assert_paths_exist "$list"
185
- if [[ "$1" == "LOOP1" ]]; then
186
- jq -e '.charter.completed == true' "$LOCK_FILE" >/dev/null 2>&1 || gate_error "Phase 0 未完了(phase-locks.json の charter.completed)"
187
- jq -e '.charter.reviewed_by' "$LOCK_FILE" >/dev/null 2>&1 || gate_error "憲章に署名がありません(charter.reviewed_by)"
188
- echo "Phase 0: OK"
189
- fi
190
- }
191
-
192
- gate_domain_and_arch() {
193
- local grd="$1"
194
- [[ "$grd" == "LOOP1" ]] && return 0
195
-
196
- jq -e '.domain.completed == true' "$LOCK_FILE" >/dev/null 2>&1 || gate_error "Phase 1 未完了"
197
- list="$(get_required_docs_list "domain")"
198
- [[ -n "$list" ]] || gate_error "project.json の required_docs.domain が未設定です"
199
- assert_paths_exist "$list"
200
- echo "Phase 1: OK"
201
-
202
- jq -e '.architecture.completed == true' "$LOCK_FILE" >/dev/null 2>&1 || gate_error "Phase 2 未完了"
203
- list="$(get_required_docs_list "architecture")"
204
- [[ -n "$list" ]] || gate_error "project.json の required_docs.architecture が未設定です"
205
- assert_paths_exist "$list"
206
- echo "Phase 2: OK"
207
- }
208
-
209
- gate_infra_grade_a() {
210
- [[ "$1" != "A" ]] && return 0
211
- jq -e '.infra.completed == true' "$LOCK_FILE" >/dev/null 2>&1 || gate_error "Grade A: インフラ設計未完了"
212
- list="$(get_required_docs_list "grade_a")"
213
- [[ -n "$list" ]] || gate_error "project.json の required_docs.grade_a が未設定です(Grade A 時必須)"
214
- assert_paths_exist "$list"
215
- echo "Phase 4 (Grade A): OK"
216
- }
217
-
218
- gate_uc_openapi_and_tests() {
219
- local grd="$1"
220
- [[ "$grd" != "A" && "$grd" != "B" ]] && return 0
221
-
222
- uc_count=$(find docs/02_ユースケース仕様 -mindepth 2 -maxdepth 2 -name "UC-*.md" 2>/dev/null | wc -l)
223
- if [[ "${uc_count:-0}" -gt 0 ]]; then
224
- uc_reviewed_count=$(jq '.uc_reviewed | length' "$LOCK_FILE")
225
- [[ "${uc_reviewed_count:-0}" -gt 0 ]] || gate_error "Gate 3: uc_reviewed に少なくとも1件の UC 識別子を登録してください"
226
- list="$(get_required_docs_list "gate3_openapi")"
227
- [[ -n "$list" ]] || gate_error "project.json の required_docs.gate3_openapi が未設定です"
228
- assert_paths_exist "$list"
229
- echo "Gate 3 (UC+OpenAPI): OK"
230
- fi
231
-
232
- test_design_ok=0
233
- jq -e '.test_design.completed == true' "$LOCK_FILE" >/dev/null 2>&1 && test_design_ok=1
234
- if [[ $test_design_ok -eq 1 ]] || [[ -d "$test_dir" && -n "$(find "$test_dir" -type f -name "$test_pattern" 2>/dev/null | head -1)" ]]; then
235
- echo "Gate 5 (テスト設計): OK"
236
- fi
237
- }
238
-
239
- gate_tests_green_soft() {
240
- local grd="$1"
241
- [[ "$grd" != "A" && "$grd" != "B" && "$grd" != "C" ]] && return 0
242
-
243
- if [[ -f ".spec-runner/scripts/test/require-tests-green.sh" ]]; then
244
- .spec-runner/scripts/test/require-tests-green.sh 2>/dev/null && echo "Gate 6 (テスト通過): OK" || true
245
- fi
246
- }
247
-
248
- # === ゲート確認モード ===
249
- run_gate() {
250
- GRD="${GATE_GRADE:-$grade}"
251
-
252
- gate_charter "$GRD"
253
- gate_domain_and_arch "$GRD"
254
- gate_infra_grade_a "$GRD"
255
- gate_uc_openapi_and_tests "$GRD"
256
- gate_tests_green_soft "$GRD"
257
-
258
- echo "ゲート確認: 通過"
130
+ done
259
131
  }
260
132
 
261
- # === フェーズ判定モード ===
262
133
  run_phase() {
263
134
  phase=0
264
135
  phase_name_ja=""
@@ -272,52 +143,24 @@ run_phase() {
272
143
  charter_doc="$(get_steps_common_doc "charter")"
273
144
  domain_root="$(get_steps_common_doc "domain_root")"
274
145
  architecture_root="$(get_steps_common_doc "architecture_root")"
146
+ domain_spec_present=0
147
+ arch_spec_present=0
275
148
 
149
+ doc_key() { basename "$1" .md; }
276
150
  first_md_in_dir() {
277
151
  local d="$1"
278
152
  [[ -d "$d" ]] || return 1
279
153
  find "$d" -type f -name "*.md" 2>/dev/null | sort | head -1
280
154
  }
281
155
 
282
- doc_key() {
283
- local f="$1"
284
- basename "$f" .md
285
- }
286
-
287
- quality_done() {
288
- local kind="$1" # clarified | analyzed
289
- local scope="$2" # charter | domain | architecture | uc
290
- local key="$3"
291
- # 互換性のため、quality には「ベース名」と「.md 付き」の両方を許容する
292
- local key_md="${key}.md"
293
- jq -e --arg k "$key" --arg km "$key_md" --arg s "$scope" \
294
- ".quality.${kind}[\$s][]? | select(. == \$k or . == \$km)" \
295
- "$LOCK_FILE" >/dev/null 2>&1
296
- }
297
-
298
- resolve_step() {
299
- local sid="$1"
300
- step_id="$sid"
301
- command=$(jq -r --arg id "$sid" '.steps[]? | select(.id==$id) | .name_ja' "$STEPS_JSON")
302
- local md
303
- md=$(jq -r --arg id "$sid" '.steps[]? | select(.id==$id) | .md_file' "$STEPS_JSON")
304
- [[ -n "$command" && "$command" != "null" ]] || die "spec-runner-core: steps.json に id=$sid の name_ja がありません"
305
- [[ -n "$md" && "$md" != "null" ]] || die "spec-runner-core: steps.json に id=$sid の md_file がありません"
306
- command_file="$STEPS_DIR/$md"
307
- step_commands=$(jq -c --arg id "$sid" '.steps[]? | select(.id==$id) | .commands' "$STEPS_JSON")
308
- [[ -n "$step_commands" && "$step_commands" != "null" ]] || die "spec-runner-core: steps.json に id=$sid の commands がありません"
309
- check_command=$(jq -r '.common.commands.check' "$STEPS_JSON")
310
- [[ -n "$check_command" && "$check_command" != "null" ]] || die "spec-runner-core: steps.json に common.commands.check がありません"
311
- }
312
-
313
- if [[ $on_uc_branch -eq 1 ]] && [[ -n "$current_uc_id" ]]; then
314
- for f in docs/02_ユースケース仕様/*/"${current_uc_id}-"*.md; do
315
- [[ -f "$f" ]] && feature_spec="$f" && feature_dir="$(dirname "$f")" && break
316
- done
317
- fi
318
-
319
156
  uc_count_total=$(find docs/02_ユースケース仕様 -mindepth 2 -maxdepth 2 -name "UC-*.md" 2>/dev/null | wc -l | tr -d ' ')
320
157
  uc_count_total=${uc_count_total:-0}
158
+ [[ -n "$(first_md_in_dir "$domain_root" || true)" ]] && domain_spec_present=1
159
+ [[ -n "$(first_md_in_dir "$architecture_root" || true)" ]] && arch_spec_present=1
160
+
161
+ # lock が先に立っていても、設計成果物が無い場合は必ず設計フェーズへ戻す
162
+ [[ $domain_spec_present -eq 0 ]] && has_domain_lock=0
163
+ [[ $arch_spec_present -eq 0 ]] && has_arch_lock=0
321
164
 
322
165
  if [[ $has_charter_lock -eq 0 ]]; then
323
166
  if [[ -f "$charter_doc" ]]; then
@@ -334,165 +177,111 @@ run_phase() {
334
177
  else
335
178
  phase=0; phase_name_ja="憲章策定"; resolve_step "charter"
336
179
  fi
337
- elif [[ $has_domain_lock -eq 0 && $on_uc_branch -eq 0 ]]; then
338
- # UC を洗い出している途中(uc_discovery.completed=false)の間はドメインへ進まない
339
- if [[ $uc_discovery_completed -eq 0 ]]; then
180
+ elif [[ $uc_discovery_completed -eq 0 ]]; then
181
+ uc_spec="$(latest_unreviewed_uc_spec || true)"
182
+ if [[ -n "$uc_spec" ]]; then
183
+ feature_spec="$uc_spec"
184
+ feature_dir="$(dirname "$uc_spec")"
185
+ uc_key="$(doc_key "$uc_spec")"
186
+ if ! quality_done "clarified" "uc" "$uc_key"; then
187
+ phase=1; phase_name_ja="ユースケース洗い出し中(曖昧さ解消)"; resolve_step "clarify"
188
+ elif ! quality_done "analyzed" "uc" "$uc_key"; then
189
+ phase=1; phase_name_ja="ユースケース洗い出し中(分析)"; resolve_step "analyze"
190
+ else
191
+ phase=1; phase_name_ja="ユースケース洗い出し中(レビュー通過待ち)"; resolve_step "clarify"
192
+ fi
193
+ else
340
194
  phase=1; phase_name_ja="ユースケース洗い出し中(次UC作成)"; resolve_step "uc_spec"
195
+ fi
196
+ elif [[ $has_domain_lock -eq 0 ]]; then
197
+ uc_spec="$(latest_unreviewed_uc_spec || true)"
198
+ if [[ -n "$uc_spec" ]]; then
199
+ feature_spec="$uc_spec"
200
+ feature_dir="$(dirname "$uc_spec")"
201
+ uc_key="$(doc_key "$uc_spec")"
202
+ if ! quality_done "clarified" "uc" "$uc_key"; then
203
+ phase=1; phase_name_ja="ユースケース仕様(曖昧さ解消)"; resolve_step "clarify"
204
+ elif ! quality_done "analyzed" "uc" "$uc_key"; then
205
+ phase=1; phase_name_ja="ユースケース仕様(分析)"; resolve_step "analyze"
206
+ else
207
+ phase=1; phase_name_ja="ユースケース仕様(レビュー通過まで)"; resolve_step "clarify"
208
+ fi
341
209
  else
342
- # UC 1 件以上ある場合のみ、ドメイン側の質フローを回す
343
- if [[ ${uc_count_total} -gt 0 ]]; then
344
- domain_spec="$(first_md_in_dir "$domain_root" || true)"
345
- if [[ -n "$domain_spec" ]]; then
346
- feature_spec="$domain_spec"
347
- feature_dir="$(dirname "$domain_spec")"
348
- dkey="$(doc_key "$domain_spec")"
349
- if ! quality_done "clarified" "domain" "$dkey"; then
350
- phase=2; phase_name_ja="ドメイン設計(曖昧さ解消)"; resolve_step "clarify"
351
- elif ! quality_done "analyzed" "domain" "$dkey"; then
352
- phase=2; phase_name_ja="ドメイン設計(分析)"; resolve_step "analyze"
353
- else
354
- phase=2; phase_name_ja="ドメイン設計"; resolve_step "domain"
355
- fi
210
+ domain_spec="$(first_md_in_dir "$domain_root" || true)"
211
+ if [[ -n "$domain_spec" ]]; then
212
+ feature_spec="$domain_spec"
213
+ feature_dir="$(dirname "$domain_spec")"
214
+ dkey="$(doc_key "$domain_spec")"
215
+ if ! quality_done "clarified" "domain" "$dkey"; then
216
+ phase=2; phase_name_ja="ドメイン設計(曖昧さ解消)"; resolve_step "clarify"
217
+ elif ! quality_done "analyzed" "domain" "$dkey"; then
218
+ phase=2; phase_name_ja="ドメイン設計(分析)"; resolve_step "analyze"
356
219
  else
357
220
  phase=2; phase_name_ja="ドメイン設計"; resolve_step "domain"
358
221
  fi
359
222
  else
360
- phase=1; phase_name_ja="ユースケース洗い出し中(次UC作成)"; resolve_step "uc_spec"
223
+ phase=2; phase_name_ja="ドメイン設計"; resolve_step "domain"
361
224
  fi
362
225
  fi
363
- elif [[ $has_arch_lock -eq 0 && $has_domain_lock -eq 1 ]]; then
226
+ elif [[ $has_arch_lock -eq 0 ]]; then
364
227
  arch_spec="$(first_md_in_dir "$architecture_root" || true)"
365
228
  if [[ -n "$arch_spec" ]]; then
366
229
  feature_spec="$arch_spec"
367
230
  feature_dir="$(dirname "$arch_spec")"
368
231
  akey="$(doc_key "$arch_spec")"
369
232
  if ! quality_done "clarified" "architecture" "$akey"; then
370
- phase=3; phase_name_ja="アーキテクチャ選択(曖昧さ解消)"; resolve_step "clarify"
233
+ phase=3; phase_name_ja="実装計画(曖昧さ解消)"; resolve_step "clarify"
371
234
  elif ! quality_done "analyzed" "architecture" "$akey"; then
372
- phase=3; phase_name_ja="アーキテクチャ選択(分析)"; resolve_step "analyze"
235
+ phase=3; phase_name_ja="実装計画(分析)"; resolve_step "analyze"
373
236
  else
374
- phase=3; phase_name_ja="アーキテクチャ選択"; resolve_step "architecture_plan"
237
+ phase=3; phase_name_ja="実装計画"; resolve_step "architecture_plan"
375
238
  fi
376
239
  else
377
- phase=3; phase_name_ja="アーキテクチャ選択"; resolve_step "architecture_plan"
378
- fi
379
- elif [[ $on_uc_branch -eq 1 ]]; then
380
- uc_spec=""
381
- if [[ -n "$current_uc_id" ]]; then
382
- for f in docs/02_ユースケース仕様/*/"${current_uc_id}-"*.md; do [[ -f "$f" ]] && uc_spec="$f" && break; done
240
+ phase=3; phase_name_ja="実装計画"; resolve_step "architecture_plan"
383
241
  fi
242
+ else
243
+ uc_spec="$(latest_uc_spec || true)"
384
244
  if [[ -z "$uc_spec" ]]; then
385
245
  phase=1; phase_name_ja="ユースケース仕様"; resolve_step "uc_spec"
386
246
  else
387
247
  feature_spec="$uc_spec"
388
248
  feature_dir="$(dirname "$uc_spec")"
389
- uc_dir=$(basename "$uc_spec" .md)
249
+ uc_key="$(doc_key "$uc_spec")"
390
250
  reviewed=0
391
- jq -e --arg u "$uc_dir" '.uc_reviewed[]? == $u' "$LOCK_FILE" 2>/dev/null | grep -q true && reviewed=1
251
+ jq -e --arg u "$uc_key" 'any(.uc_reviewed[]?; . == $u)' "$LOCK_FILE" >/dev/null 2>&1 && reviewed=1
392
252
  if [[ $reviewed -eq 0 ]]; then
393
- if ! quality_done "clarified" "uc" "$uc_dir"; then
253
+ if ! quality_done "clarified" "uc" "$uc_key"; then
394
254
  phase=1; phase_name_ja="ユースケース仕様(曖昧さ解消)"; resolve_step "clarify"
395
- elif ! quality_done "analyzed" "uc" "$uc_dir"; then
255
+ elif ! quality_done "analyzed" "uc" "$uc_key"; then
396
256
  phase=1; phase_name_ja="ユースケース仕様(分析)"; resolve_step "analyze"
397
257
  else
398
258
  phase=1; phase_name_ja="ユースケース仕様(レビュー通過まで)"; resolve_step "clarify"
399
259
  fi
400
260
  else
401
- # UC 洗い出し中は、レビュー済みでも次の UC 作成へ戻す(TDD/実装に進まない)
402
- if [[ $uc_discovery_completed -eq 0 ]]; then
403
- phase=1; phase_name_ja="ユースケース洗い出し中(次UC作成)"; resolve_step "uc_spec"
404
- elif [[ "$grade" == "A" ]] && [[ $has_infra_lock -eq 0 ]]; then
405
- phase=4; phase_name_ja="インフラ詳細設計"; resolve_step "infra_plan"
261
+ if uc_has_tests_ready_for_implement "$uc_key"; then
262
+ phase=6; phase_name_ja="実装"; resolve_step "implement"
406
263
  else
407
- if uc_branch_has_tests_ready_for_implement; then
408
- phase=6; phase_name_ja="実装"; resolve_step "implement"
409
- else
410
- phase=5; phase_name_ja="テスト設計(当該 UC の spec 必須)"; resolve_step "test_design"
411
- fi
264
+ phase=5; phase_name_ja="テスト設計"; resolve_step "test_design"
412
265
  fi
413
266
  fi
414
267
  fi
415
- else
416
- if [[ $on_other_work_branch -eq 1 ]]; then
417
- phase=1; phase_name_ja="その他作業(CI/CD・インフラ等)"; resolve_step "other_work"
418
- elif [[ $has_domain_lock -eq 0 ]]; then
419
- phase=1; phase_name_ja="ユースケース開始(仕様策定)"; resolve_step "uc_spec"
420
- elif [[ $has_arch_lock -eq 0 ]]; then
421
- phase=3; phase_name_ja="アーキテクチャ選択"; resolve_step "architecture_plan"
422
- else
423
- phase=1; phase_name_ja="ユースケース開始(仕様策定)"; resolve_step "uc_spec"
424
- fi
425
268
  fi
426
269
 
427
270
  if [[ "$JSON_MODE" == true ]]; then
428
- 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" \
271
+ jq -cn --argjson phase "$phase" --arg name "$phase_name_ja" --arg step_id "$step_id" --arg cmd "$command" --arg file "$command_file" \
429
272
  --arg check "$check_command" --argjson cmds "$step_commands" \
430
273
  --arg feature_dir "$feature_dir" --arg feature_spec "$feature_spec" \
431
- '{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}'
274
+ '{phase:$phase, phase_name_ja:$name, step_id:$step_id, command:$cmd, command_file:$file, check_command:$check, step_commands:$cmds, feature_dir:$feature_dir, feature_spec:$feature_spec}'
432
275
  else
433
276
  echo "現在フェーズ: Phase $phase($phase_name_ja)"
434
277
  echo "推奨コマンド: $command"
435
278
  echo "コマンドファイル: $command_file"
436
- echo "グレード: $grade"
437
279
  echo "チェック(毎回): $check_command"
438
- if [[ $on_uc_branch -eq 0 ]] && [[ -n "$branch" ]]; then
439
- echo ""
440
- echo "注意: main 等のままの修正は危険です。UC 用ブランチを作成してから作業してください(仕様策定ステップでブランチ作成)。"
441
- fi
442
280
  fi
443
281
  }
444
282
 
445
- # === 状態表示(lock 一覧)===
446
- run_status() {
447
- echo "=== spec-runner フェーズ状況 ==="
448
- echo "グレード: $grade"
449
- echo ""
450
- echo "Lock(.spec-runner/phase-locks.json):"
451
- for sec in charter domain architecture infra uc_discovery test_design; do
452
- if jq -e --arg s "$sec" '.[$s].completed == true' "$LOCK_FILE" >/dev/null 2>&1; then
453
- echo " ✓ $sec"
454
- else
455
- echo " - $sec"
456
- fi
457
- done
458
- }
459
-
460
- # === グレード判定チェックリスト ===
461
- run_grade() {
462
- echo "=== spec-runner グレード判定 ==="
463
- echo ""
464
- echo "STEP 1: 作業の種別を判定する"
465
- echo " → 既存UCの修正・バグfix: Grade C 確定(以降の判定不要)"
466
- echo " → 新規UC: STEP 2 へ"
467
- echo ""
468
- echo "STEP 2: Grade B 仮置きで UC 草稿を作成する"
469
- echo " → AI と対話して UC 仕様書の草稿を作成する"
470
- echo " → この時点ではまだブランチ作成・コミット不要"
471
- echo ""
472
- echo "STEP 3: UC 草稿を見て Grade を確定する"
473
- echo " 以下のいずれかが草稿に含まれていれば Grade A:"
474
- echo " □ 新しいDB・テーブル・コレクションの追加が必要"
475
- echo " □ 外部API・SaaS との新規連携が必要"
476
- echo " □ 新規クラウドサービスの追加が必要(S3バケット・Redisクラスタ等)"
477
- echo " □ ネットワーク構成の変更が必要"
478
- echo " □ CI/CDパイプラインの変更が必要"
479
- echo " → 一つでも該当: Grade A 確定 → ブランチ作成 → Phase 3(UC仕様書)へ"
480
- echo " → 全て非該当: Grade B 確定 → ブランチ作成 → Phase 3(UC仕様書)へ(草稿を流用)"
481
- echo ""
482
- echo "判定結果は .spec-runner/grade-history.json に記録する(ブランチ名には含めない)。"
483
- echo "迷ったら上位グレードを選ぶ。"
484
- echo ""
485
- echo "現在の記録: current_grade = $grade"
486
- }
487
-
488
- # === 実行 ===
489
- if [[ "$MODE" == "gate" ]]; then
490
- echo "=== ゲート確認(グレード: ${GATE_GRADE:-$grade}) ==="
491
- run_gate
492
- elif [[ "$MODE" == "status" ]]; then
283
+ if [[ "$MODE" == "status" ]]; then
493
284
  run_status
494
- elif [[ "$MODE" == "grade" ]]; then
495
- run_grade
496
285
  else
497
286
  run_phase
498
287
  fi