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.
- package/README.md +24 -9
- package/bin/spec-runner.js +112 -144
- package/package.json +1 -6
- package/templates/.spec-runner/hooks/pre-commit +25 -11
- package/templates/.spec-runner/project.json.example +10 -8
- package/templates/.spec-runner/scripts/branch/uc-next-start.sh +100 -9
- package/templates/.spec-runner/scripts/check.sh +396 -13
- package/templates/.spec-runner/scripts/spec-runner-core.sh +286 -157
- package/templates/.spec-runner/scripts/test/require-tests-green.sh +7 -63
- package/templates/.spec-runner/steps/steps.json +171 -0
- package/templates/.spec-runner/steps//343/201/235/343/201/256/344/273/226/344/275/234/346/245/255.md +25 -13
- package/templates/.spec-runner/steps//343/202/277/343/202/271/343/202/257/344/270/200/350/246/247.md +67 -104
- 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
- package/templates/.spec-runner/steps//343/203/206/343/202/271/343/203/210/350/250/255/350/250/210.md +41 -34
- 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
- package/templates/.spec-runner/steps//344/273/225/346/247/230/347/255/226/345/256/232.md +161 -207
- package/templates/.spec-runner/steps//345/210/206/346/236/220.md +65 -127
- package/templates/.spec-runner/steps//345/256/237/350/243/205.md +67 -79
- package/templates/.spec-runner/steps//345/256/237/350/243/205/350/250/210/347/224/273.md +56 -56
- package/templates/.spec-runner/steps//346/206/262/347/253/240.md +67 -46
- package/templates/.spec-runner/steps//346/233/226/346/230/247/343/201/225/350/247/243/346/266/210.md +88 -148
- 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
- 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
- package/templates/.spec-runner/templates/grade-history.json +5 -0
- package/templates/.spec-runner/templates/phase-locks.json +29 -0
- 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
- 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
- package/templates/.spec-runner/templates//346/206/262/347/253/240.md +51 -0
- package/templates/.spec-runner/templates//351/233/206/347/264/204.md +46 -0
- package/templates/.spec-runner/scripts/branch/create-uc-branch.sh +0 -105
- package/templates/.spec-runner/scripts/branch/uc-next-id.sh +0 -17
- package/templates/.spec-runner/scripts/check/drift.sh +0 -66
- package/templates/.spec-runner/scripts/check/health.sh +0 -103
- package/templates/.spec-runner/scripts/check/naming.sh +0 -51
- package/templates/.spec-runner/scripts/check/schema-drift.sh +0 -74
- package/templates/.spec-runner/scripts/check/schema-sync.sh +0 -153
- package/templates/.spec-runner/scripts/lib/uc-context.sh +0 -75
- package/templates/.spec-runner/scripts/openapi/openapi-generate.sh +0 -207
- 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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
101
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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="憲章策定";
|
|
168
|
-
elif [[ $has_domain_lock -eq 0 ]]; then
|
|
169
|
-
phase=
|
|
170
|
-
elif [[ $has_arch_lock -eq 0 ]]; then
|
|
171
|
-
phase=
|
|
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/
|
|
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=
|
|
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
|
-
|
|
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=
|
|
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="インフラ詳細設計";
|
|
318
|
+
phase=4; phase_name_ja="インフラ詳細設計"; resolve_step "infra_plan"
|
|
188
319
|
else
|
|
189
|
-
|
|
190
|
-
|
|
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=
|
|
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=
|
|
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=
|
|
334
|
+
phase=3; phase_name_ja="アーキテクチャ選択"; resolve_step "architecture_plan"
|
|
204
335
|
else
|
|
205
|
-
phase=
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|