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
package/README.md CHANGED
@@ -36,19 +36,26 @@ AI から使う場合は、`/spec-runner` のように「spec-runner を実行
36
36
 
37
37
  ---
38
38
 
39
+ ## フロー(全体像)
40
+
41
+ 設計書(`docs/01..06`)と UC 仕様(`docs/02_ユースケース仕様/`)をどんな順で作っていくかは `docs/flow.md` にまとめています。
42
+
43
+ ---
44
+
39
45
  ## 導入後にできるもの
40
46
 
41
47
  ```
42
48
  <プロジェクトルート>/
43
- └── .spec-runner/
44
- ├── spec-runner.sh # 入口(次のステップ --json)
45
- ├── project.json # 設定(ブランチ命名・必須ドキュメント・テストコマンド等)
46
- ├── phase-locks.json # フェーズの通過状態
47
- ├── grade-history.json # グレード(LOOP1 / A / B / C)
48
- ├── scripts/ # spec-runner-core.sh, check, branch, test 等
49
- ├── steps/ # 憲章・ドメイン設計・仕様策定・曖昧さ解消・テスト設計・実装 等の .md
50
- ├── templates/ # UC 仕様書ひな形
51
- └── docs/ # フロー全体・振る舞い仕様などの説明
49
+ ├── .spec-runner/
50
+ ├── spec-runner.sh # 入口(次のステップ --json)
51
+ ├── project.json # 設定(ブランチ命名・必須ドキュメント・テストコマンド等)
52
+ ├── phase-locks.json # フェーズの通過状態
53
+ ├── grade-history.json # グレード(LOOP1 / A / B / C)
54
+ ├── scripts/ # spec-runner-core.sh, check, branch, test 等
55
+ ├── steps/ # 憲章・ドメイン設計・仕様策定・曖昧さ解消・テスト設計・実装 等の .md
56
+ │ └── templates/ # UC 仕様書ひな形
57
+ ├── .claude/commands/spec-runner.md # Claude 用コマンド定義(/spec-runner)
58
+ └── (AI は Claude Code 前提)
52
59
  ```
53
60
 
54
61
  ---
@@ -72,6 +79,14 @@ SPEC_RUNNER_FORCE=1 npx spec-runner
72
79
 
73
80
  ---
74
81
 
82
+ ## バージョン運用ルール
83
+
84
+ - このリポジトリでは、今後 **コミットごとに `package.json` の `version` を更新**する。
85
+ - バージョンは原則として SemVer に従い、迷う場合はパッチ(`x.y.Z`)を 1 つ上げる。
86
+ - 1コミット内で複数の変更をまとめた場合も、コミット単位で 1 回だけ更新する。
87
+
88
+ ---
89
+
75
90
  ## ライセンス
76
91
 
77
92
  MIT
@@ -7,13 +7,18 @@
7
7
  * ゴール:
8
8
  * - `npx spec-runner` または `npm exec spec-runner` を実行すると、
9
9
  * プロジェクト直下に `./.spec-runner/` フォルダを作成する。
10
- * - `./.spec-runner/spec-runner.sh 次のステップ --json` を叩けば
10
+ * - Claude Code から `/spec-runner` を実行すれば、
11
11
  * 「現在フェーズ」と「やるべきステップ .md」が 1 本だけ返ってくる。
12
12
  * - メッセージ・ファイルはすべて日本語のみ。
13
13
  *
14
14
  * 重要:
15
15
  * - すでに `.spec-runner/` が存在する場合はデフォルトでは上書きしない。
16
16
  * 上書きしたい場合は、環境変数 `SPEC_RUNNER_FORCE=1` を付けて実行する。
17
+ *
18
+ * 必須テンプレ(パッケージ内):
19
+ * - templates/.spec-runner/project.json.example
20
+ * - templates/.spec-runner/templates/phase-locks.json
21
+ * - templates/.spec-runner/templates/grade-history.json
17
22
  */
18
23
 
19
24
  const fs = require("fs");
@@ -21,10 +26,18 @@ const path = require("path");
21
26
 
22
27
  const CWD = process.cwd();
23
28
  const PKG_DIR = path.resolve(__dirname, "..");
24
- // パッケージ内の公式テンプレート配置場所
25
29
  const TEMPLATE_SPEC_RUNNER_DIR = path.join(PKG_DIR, "templates", ".spec-runner");
26
- // 展開先(ユーザープロジェクト側)
27
30
  const DEST_DIR = path.join(CWD, ".spec-runner");
31
+ const TEMPLATES_DIR = path.join(TEMPLATE_SPEC_RUNNER_DIR, "templates");
32
+ const PHASE_LOCKS_TEMPLATE = path.join(TEMPLATES_DIR, "phase-locks.json");
33
+ const GRADE_HISTORY_TEMPLATE = path.join(TEMPLATES_DIR, "grade-history.json");
34
+
35
+ /** コピー時はスキップし、FORCE 時は消さない(ユーザー状態を保持) */
36
+ const USER_STATE_BASENAMES = new Set([
37
+ "project.json",
38
+ "phase-locks.json",
39
+ "grade-history.json",
40
+ ]);
28
41
 
29
42
  function log(msg) {
30
43
  console.log(msg);
@@ -51,6 +64,47 @@ function exists(p) {
51
64
  }
52
65
  }
53
66
 
67
+ function ensureTemplateDirOrExit() {
68
+ if (!exists(TEMPLATE_SPEC_RUNNER_DIR)) {
69
+ error("パッケージ内に templates/.spec-runner テンプレートが見つかりません。");
70
+ process.exit(1);
71
+ }
72
+ if (!exists(path.join(TEMPLATE_SPEC_RUNNER_DIR, "project.json.example"))) {
73
+ error("必須テンプレ project.json.example が見つかりません。");
74
+ process.exit(1);
75
+ }
76
+ if (!exists(PHASE_LOCKS_TEMPLATE)) {
77
+ error("必須テンプレ templates/phase-locks.json が見つかりません。");
78
+ process.exit(1);
79
+ }
80
+ if (!exists(GRADE_HISTORY_TEMPLATE)) {
81
+ error("必須テンプレ templates/grade-history.json が見つかりません。");
82
+ process.exit(1);
83
+ }
84
+ }
85
+
86
+ function assertDestInstallableOrExit() {
87
+ const force = process.env.SPEC_RUNNER_FORCE === "1";
88
+ if (!exists(DEST_DIR)) return;
89
+ if (force) {
90
+ info("既存の .spec-runner/ を上書きします(SPEC_RUNNER_FORCE=1)。");
91
+ return;
92
+ }
93
+ error(".spec-runner/ フォルダがすでに存在します。");
94
+ info("上書きする場合は、環境変数 SPEC_RUNNER_FORCE=1 を付けて実行してください。");
95
+ info("例: SPEC_RUNNER_FORCE=1 npx spec-runner");
96
+ process.exit(1);
97
+ }
98
+
99
+ /** FORCE 時: テンプレから消えたファイルも反映できるよう、保持対象以外を削除 */
100
+ function wipeDestExceptUserState() {
101
+ if (!exists(DEST_DIR) || process.env.SPEC_RUNNER_FORCE !== "1") return;
102
+ for (const entry of fs.readdirSync(DEST_DIR, { withFileTypes: true })) {
103
+ if (USER_STATE_BASENAMES.has(entry.name)) continue;
104
+ fs.rmSync(path.join(DEST_DIR, entry.name), { recursive: true, force: true });
105
+ }
106
+ }
107
+
54
108
  function copyDirRecursively(src, dest, options = {}) {
55
109
  const { skipNames = new Set() } = options;
56
110
  if (!exists(src)) return;
@@ -76,160 +130,74 @@ function copyDirRecursively(src, dest, options = {}) {
76
130
  }
77
131
  }
78
132
 
79
- function writeJsonPretty(destPath, obj) {
80
- fs.mkdirSync(path.dirname(destPath), { recursive: true });
81
- fs.writeFileSync(destPath, JSON.stringify(obj, null, 2) + "\n", "utf8");
82
- ok(path.relative(CWD, destPath));
133
+ function expandTemplateTree() {
134
+ copyDirRecursively(TEMPLATE_SPEC_RUNNER_DIR, DEST_DIR, {
135
+ skipNames: USER_STATE_BASENAMES,
136
+ });
83
137
  }
84
138
 
85
- function main() {
86
- log("");
87
- log("╔════════════════════════════════════════╗");
88
- log("║ spec-runner インストーラ ║");
89
- log("║ フェーズ駆動 / 次のステップ方式 ║");
90
- log("╚════════════════════════════════════════╝");
91
- log("");
92
-
93
- if (!exists(TEMPLATE_SPEC_RUNNER_DIR)) {
94
- error("パッケージ内に templates/.spec-runner テンプレートが見つかりません。");
95
- process.exit(1);
96
- }
97
-
98
- if (exists(DEST_DIR) && process.env.SPEC_RUNNER_FORCE !== "1") {
99
- error(".spec-runner/ フォルダがすでに存在します。");
100
- info("上書きする場合は、環境変数 SPEC_RUNNER_FORCE=1 を付けて実行してください。");
101
- info("例: SPEC_RUNNER_FORCE=1 npx spec-runner");
102
- process.exit(1);
103
- }
104
-
105
- if (exists(DEST_DIR) && process.env.SPEC_RUNNER_FORCE === "1") {
106
- info("既存の .spec-runner/ を上書きします(SPEC_RUNNER_FORCE=1)。");
107
- }
108
-
109
- info(".spec-runner/ を展開しています...");
110
- const skipNames = new Set([
111
- "project.json",
112
- "phase-locks.json",
113
- "grade-history.json",
114
- ]);
115
- copyDirRecursively(TEMPLATE_SPEC_RUNNER_DIR, DEST_DIR, { skipNames });
116
-
117
- // 2. project.json を project.json.example から生成(存在すれば)
139
+ function bootstrapProjectJson() {
118
140
  const examplePath = path.join(TEMPLATE_SPEC_RUNNER_DIR, "project.json.example");
119
141
  const projectJsonDest = path.join(DEST_DIR, "project.json");
120
- if (exists(examplePath)) {
121
- const content = fs.readFileSync(examplePath, "utf8");
122
- fs.writeFileSync(projectJsonDest, content, "utf8");
123
- ok(path.relative(CWD, projectJsonDest));
124
- } else {
125
- // フォールバック(最低限の既定値)
126
- const fallback = {
127
- naming: {
128
- branch_prefix: "feature",
129
- uc_id_pattern: "UC-[0-9]{3}",
130
- uc_spec_basename: "{uc_id}-{slug}.md",
131
- adr_basename: "MMDD-{title}.md",
132
- docs_05_categories: true,
133
- other_work_prefixes: ["work", "infra", "cicd"],
134
- },
135
- required_docs: {
136
- charter: ["docs/01_憲章/憲章.md"],
137
- },
138
- test_design: {
139
- dir: "tests",
140
- pattern: "*.spec.*",
141
- },
142
- test_command: {
143
- run: "npm test",
144
- },
145
- };
146
- writeJsonPretty(projectJsonDest, fallback);
142
+ fs.writeFileSync(projectJsonDest, fs.readFileSync(examplePath, "utf8"), "utf8");
143
+ ok(path.relative(CWD, projectJsonDest));
144
+ }
145
+
146
+ /**
147
+ * フェーズ用 JSON は templates/.spec-runner/templates/*.json を正とする。
148
+ * .spec-runner/ 直下に無いときだけコピー(既存のロックは上書きしない)。
149
+ */
150
+ function writeInitialLocksAndGrade() {
151
+ const locksDest = path.join(DEST_DIR, "phase-locks.json");
152
+ if (!exists(locksDest)) {
153
+ fs.copyFileSync(PHASE_LOCKS_TEMPLATE, locksDest);
154
+ ok(path.relative(CWD, locksDest));
147
155
  }
148
156
 
149
- // 3. phase-locks.json / grade-history.json を初期状態で作成
150
- const phaseLocksDest = path.join(DEST_DIR, "phase-locks.json");
151
- const gradeHistoryDest = path.join(DEST_DIR, "grade-history.json");
152
-
153
- const phaseLocks = {
154
- _comment:
155
- "フェーズ完了状態の単一ソース。初期状態ではすべて未完了。レビュー状態もここで管理する。",
156
- charter: {
157
- completed: false,
158
- phase: 0,
159
- phase_name: "憲章策定",
160
- locked_at: null,
161
- reviewed_by: null,
162
- document: "docs/01_憲章/憲章.md",
163
- version: "v0.0.0",
164
- },
165
- domain: {
166
- completed: false,
167
- phase: 1,
168
- phase_name: "ドメイン設計",
169
- locked_at: null,
170
- reviewed_by: null,
171
- documents: [
172
- "docs/02_ドメイン設計/ユビキタス言語辞書.md",
173
- "docs/02_ドメイン設計/ドメインモデル.md",
174
- "docs/02_ドメイン設計/集約.md",
175
- ],
176
- },
177
- architecture: {
178
- completed: false,
179
- phase: 2,
180
- phase_name: "アーキテクチャ選択",
181
- locked_at: null,
182
- reviewed_by: null,
183
- documents: [
184
- "docs/03_アーキテクチャ/パターン選定.md",
185
- "docs/03_アーキテクチャ/インフラ方針.md",
186
- "docs/03_アーキテクチャ/設計判断記録",
187
- ],
188
- },
189
- infra: {
190
- completed: false,
191
- phase: 4,
192
- phase_name: "インフラ詳細設計",
193
- locked_at: null,
194
- },
195
- test_design: {
196
- completed: false,
197
- },
198
- uc_reviewed: [],
199
- };
200
-
201
- const gradeHistory = {
202
- current_grade: "LOOP1",
203
- history: [],
204
- };
205
-
206
- writeJsonPretty(phaseLocksDest, phaseLocks);
207
- writeJsonPretty(gradeHistoryDest, gradeHistory);
157
+ const gradeDest = path.join(DEST_DIR, "grade-history.json");
158
+ if (!exists(gradeDest)) {
159
+ fs.copyFileSync(GRADE_HISTORY_TEMPLATE, gradeDest);
160
+ ok(path.relative(CWD, gradeDest));
161
+ }
162
+ }
208
163
 
164
+ function installClaudeCommandIfPresent() {
209
165
  const commandTmpl = path.join(PKG_DIR, "templates", "spec-runner-command.md");
210
- if (exists(commandTmpl)) {
211
- const cmdContent = fs.readFileSync(commandTmpl, "utf8");
212
- const claudeCmd = path.join(CWD, ".claude", "commands", "spec-runner.md");
213
- const cursorCmd = path.join(CWD, ".cursor", "commands", "spec-runner.md");
214
- fs.mkdirSync(path.dirname(claudeCmd), { recursive: true });
215
- fs.mkdirSync(path.dirname(cursorCmd), { recursive: true });
216
- fs.writeFileSync(claudeCmd, cmdContent, "utf8");
217
- fs.writeFileSync(cursorCmd, cmdContent, "utf8");
218
- ok(path.relative(CWD, claudeCmd));
219
- ok(path.relative(CWD, cursorCmd));
220
- }
166
+ if (!exists(commandTmpl)) return;
167
+ const claudeCmd = path.join(CWD, ".claude", "commands", "spec-runner.md");
168
+ fs.mkdirSync(path.dirname(claudeCmd), { recursive: true });
169
+ fs.writeFileSync(claudeCmd, fs.readFileSync(commandTmpl, "utf8"), "utf8");
170
+ ok(path.relative(CWD, claudeCmd));
171
+ }
221
172
 
173
+ function printBanner() {
222
174
  log("");
223
- log("次のステップ:");
224
- log(" 1. プロジェクトルートで:");
225
- log("");
226
- log(" ./.spec-runner/spec-runner.sh 次のステップ --json");
175
+ log("╔════════════════════════════════════════╗");
176
+ log("║ spec-runner インストーラ ║");
177
+ log("║ フェーズ駆動 / 次のステップ方式 ║");
178
+ log("╚════════════════════════════════════════╝");
227
179
  log("");
228
- log(" 2. 出力の command_file(.spec-runner/steps/*.md)を開き、その指示に従う。");
229
- log(" 3. 終わったら再度上記を実行して次に進む。");
180
+ }
181
+
182
+ function printFooter() {
230
183
  log("");
231
- log(" AI では「/spec-runner を実行して」と伝えればよい。");
184
+ log("次のステップ(Claude Code 専用):");
185
+ log(" /spec-runner を実行して");
232
186
  log("");
233
187
  }
234
188
 
189
+ function main() {
190
+ printBanner();
191
+ ensureTemplateDirOrExit();
192
+ assertDestInstallableOrExit();
193
+
194
+ info(".spec-runner/ を展開しています...");
195
+ wipeDestExceptUserState();
196
+ expandTemplateTree();
197
+ bootstrapProjectJson();
198
+ writeInitialLocksAndGrade();
199
+ installClaudeCommandIfPresent();
200
+ printFooter();
201
+ }
202
+
235
203
  main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spec-runner",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "フェーズ駆動で設計先行を強制。npx で .spec-runner を展開し、次のステップ .md に従って進める",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -30,11 +30,6 @@
30
30
  "README.md",
31
31
  "LICENSE"
32
32
  ],
33
- "dependencies": {
34
- "chalk": "^4.1.2",
35
- "enquirer": "^2.3.6",
36
- "ora": "^5.4.1"
37
- },
38
33
  "repository": {
39
34
  "type": "git",
40
35
  "url": "git+https://github.com/TEEE88/spec-runner.git"
@@ -6,28 +6,42 @@ REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)"
6
6
  [[ -n "$REPO_ROOT" ]] || exit 0
7
7
 
8
8
  branch=$(git branch --show-current)
9
- bp="feature"
10
- other_work="work/.+|infra/.+|cicd/.+"
11
- if [[ -f "$REPO_ROOT/.spec-runner/project.json" ]] && command -v jq >/dev/null 2>&1; then
12
- p=$(jq -r '.naming.branch_prefix // empty' "$REPO_ROOT/.spec-runner/project.json" 2>/dev/null); [[ -n "$p" ]] && bp="$p"
13
- ow=$(jq -r '.naming.other_work_prefixes[]? | . + "/.+"' "$REPO_ROOT/.spec-runner/project.json" 2>/dev/null | tr '\n' '|' | sed 's/|$//'); [[ -n "$ow" ]] && other_work="$ow"
14
- fi
15
- valid_pattern="^(main|develop|${bp}/(UC-[0-9]{3}-.+|${other_work})|fix/UC-[0-9]{3}-.+|release/[0-9]+\.[0-9]+\.[0-9]+.*|hotfix/[0-9]+\.[0-9]+\.[0-9]+-.+)$"
9
+
10
+ pj="$REPO_ROOT/.spec-runner/project.json"
11
+ [[ -f "$pj" ]] || { echo "pre-commit: project.json がありません: $pj" >&2; exit 1; }
12
+ command -v jq >/dev/null 2>&1 || { echo "pre-commit: jq が必要です(brew install jq)" >&2; exit 1; }
13
+ bp="$(jq -r '.naming.branch_prefix // "feature"' "$pj" 2>/dev/null)"
14
+ uc_id_pat="$(jq -r '.naming.uc_id_pattern // "UC-[0-9]+"' "$pj" 2>/dev/null)"
15
+ other_work="$(jq -r '.naming.other_work_prefixes[]? | . + "/.+"' "$pj" 2>/dev/null | tr '\n' '|' | sed 's/|$//')"
16
+ [[ -z "$other_work" ]] && other_work="work/.+|infra/.+|cicd/.+"
17
+ valid_pattern="^(main|develop|${bp}/(${uc_id_pat}-.+|${other_work})|fix/${uc_id_pat}-.+|release/[0-9]+\\.[0-9]+\\.[0-9]+.*|hotfix/[0-9]+\\.[0-9]+\\.[0-9]+-.+)$"
16
18
 
17
19
  if ! echo "$branch" | grep -qE "$valid_pattern"; then
18
20
  echo "❌ ブランチ名が規則違反: $branch"
19
21
  echo " 正しい形式:"
20
- echo " ${bp}/UC-{NNN}-{kebab-description} (新規UC、接頭辞は project.json branch_prefix)"
22
+ if [[ -f "$REPO_ROOT/.spec-runner/project.json" ]] && command -v jq >/dev/null 2>&1; then
23
+ bp=$(jq -r '.naming.branch_prefix // "feature"' "$REPO_ROOT/.spec-runner/project.json" 2>/dev/null || echo "feature")
24
+ else
25
+ bp="feature"
26
+ fi
27
+ echo " ${bp}/{UC-ID}-{kebab-description} (新規UC、UC-IDは project.json の uc_id_pattern)"
21
28
  echo " ${bp}/cicd/xxx ${bp}/infra/xxx (その他作業、project.json の other_work_prefixes)"
22
- echo " fix/UC-{NNN}-{kebab-description} (修正)"
29
+ echo " fix/{UC-ID}-{kebab-description} (修正)"
23
30
  echo " release/{semver} (リリース)"
24
31
  echo " hotfix/{semver}-{kebab-description} (緊急修正)"
25
32
  exit 1
26
33
  fi
27
34
 
28
35
  # 命名規則チェック(.spec-runner スクリプトがある場合のみ実行)
29
- if [[ -x "$REPO_ROOT/.spec-runner/scripts/check/naming.sh" ]]; then
30
- "$REPO_ROOT/.spec-runner/scripts/check/naming.sh" || exit 1
36
+ if [[ -d "$REPO_ROOT/src" ]]; then
37
+ while IFS= read -r dir; do
38
+ [[ -z "$dir" ]] && continue
39
+ base=$(basename "$dir")
40
+ if ! echo "$base" | grep -qE '^[a-z][a-z0-9-]*$'; then
41
+ echo "NAMING: フォルダ名「$dir」はkebab-caseで命名してください" >&2
42
+ exit 1
43
+ fi
44
+ done < <(find "$REPO_ROOT/src/" -type d 2>/dev/null || true)
31
45
  fi
32
46
 
33
47
  exit 0
@@ -1,23 +1,25 @@
1
1
  {
2
- "_comment": "init-project.sh または AI との対話で作成。other_work_prefixes は UC 以外の作業用ブランチ(例: feature/cicd/xxx)。",
2
+ "_comment": "AI が編集して作成。other_work_prefixes は UC 以外の作業用ブランチ(例: feature/cicd/xxx)。",
3
+ "_comment_required_docs": "required_docs は steps.json を単一ソースにするため、\"steps:<common.docsキー>[/suffix]\" を指定できる(例: steps:openapi, steps:domain_root/集約.md)。",
3
4
  "naming": {
4
5
  "branch_prefix": "feature",
5
- "uc_id_pattern": "UC-[0-9]{3}",
6
+ "uc_id_pattern": "UC-[0-9]+",
6
7
  "uc_spec_basename": "{uc_id}-{slug}.md",
7
8
  "adr_basename": "MMDD-{title}.md",
8
9
  "docs_05_categories": true,
9
10
  "other_work_prefixes": ["work", "infra", "cicd"]
10
11
  },
11
12
  "required_docs": {
12
- "charter": ["docs/01_憲章/憲章.md"],
13
- "domain": ["docs/02_ドメイン設計/ユビキタス言語辞書.md", "docs/02_ドメイン設計/ドメインモデル.md", "docs/02_ドメイン設計/集約.md"],
14
- "architecture": ["docs/03_アーキテクチャ/パターン選定.md", "docs/03_アーキテクチャ/インフラ方針.md", "docs/03_アーキテクチャ/設計判断記録"],
15
- "grade_a": ["docs/04_インフラ設計/schema.dbml"],
16
- "gate3_openapi": ["docs/06_API仕様/openapi.yaml"]
13
+ "charter": ["steps:charter"],
14
+ "domain": ["steps:domain_root/ユビキタス言語辞書.md", "steps:domain_root/ドメインモデル.md", "steps:domain_root/集約.md"],
15
+ "architecture": ["steps:architecture_root/パターン選定.md", "steps:architecture_root/インフラ方針.md", "steps:architecture_root/設計判断記録"],
16
+ "grade_a": ["steps:infra_root/schema.dbml"],
17
+ "gate3_openapi": ["steps:openapi"]
17
18
  },
18
19
  "test_design": {
19
20
  "dir": "tests",
20
- "pattern": "*.spec.*"
21
+ "pattern": "*.spec.*",
22
+ "require_uc_prefixed_tests": true
21
23
  },
22
24
  "test_command": {
23
25
  "run": "npm test"
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env bash
2
- # 次の UC を開始する: main に切り替え、feature/UC-NNN-xxx ブランチを作成する。
2
+ # 次の UC を開始する: main に切り替え、feature/UC-N-xxx ブランチを作成する。
3
3
  # 使用例:
4
4
  # ./uc-next-start.sh # 次 UC 番号を自動検出。説明はプロンプトまたは "next-uc" で作成
5
5
  # ./uc-next-start.sh task-update # 次 UC 番号 + 説明 "task-update" でブランチ作成
6
- # ./uc-next-start.sh UC-002 task-update # 指定 UC + 説明
6
+ # ./uc-next-start.sh task-update 認証 # UC 番号 + 説明 + カテゴリ
7
+ # ./uc-next-start.sh UC-2 task-update # 指定 UC + 説明
8
+ # ./uc-next-start.sh UC-2 task-update 認証 # 指定 UC + 説明 + カテゴリ
7
9
  # ./uc-next-start.sh --yes task-update # 確認なしで実行
8
10
  # 実行後は次のステップに進む旨を案内する。
9
11
 
@@ -21,15 +23,50 @@ for a in "$@"; do
21
23
  esac
22
24
  done
23
25
 
24
- # 次 UC 番号を取得
25
- NEXT_UC=$("$BRANCH_DIR/uc-next-id.sh")
26
+ PROJECT_JSON="$REPO_ROOT/.spec-runner/project.json"
27
+ [[ -f "$PROJECT_JSON" ]] || { echo "uc-next-start: project.json がありません: $PROJECT_JSON" >&2; exit 1; }
28
+ command -v jq >/dev/null 2>&1 || { echo "uc-next-start: jq が必要です(brew install jq)" >&2; exit 1; }
29
+ UC_ID_RE="$(jq -r '.naming.uc_id_pattern' "$PROJECT_JSON")"
30
+ BRANCH_PREFIX="$(jq -r '.naming.branch_prefix' "$PROJECT_JSON")"
31
+ [[ -n "$UC_ID_RE" && "$UC_ID_RE" != "null" && -n "$BRANCH_PREFIX" && "$BRANCH_PREFIX" != "null" ]] || {
32
+ echo "uc-next-start: project.json の naming.uc_id_pattern / branch_prefix が未設定です" >&2
33
+ exit 1
34
+ }
35
+
36
+ next_uc_id() {
37
+ # docs/02_ユースケース仕様/<カテゴリ>/UC-*.md から次に使う UC-N を返す
38
+ local dir="$REPO_ROOT/docs/02_ユースケース仕様"
39
+ mkdir -p "$dir"
40
+ local max=0
41
+ for f in "$dir"/*/UC-*.md; do
42
+ [[ -e "$f" ]] || continue
43
+ base=$(basename "$f" .md)
44
+ if [[ "$base" =~ ^(${UC_ID_RE})- ]]; then
45
+ uc_id="${BASH_REMATCH[1]}"
46
+ digits=$(echo "$uc_id" | tr -cd '0-9')
47
+ [[ -z "$digits" ]] && continue
48
+ n=$((10#$digits))
49
+ [[ $n -gt $max ]] && max=$n
50
+ fi
51
+ done
52
+ printf "UC-%d\n" $((max + 1))
53
+ }
54
+
55
+ NEXT_UC="$(next_uc_id)"
26
56
  DESC=""
57
+ CATEGORY=""
27
58
 
28
- if [[ ${#ARGS[@]} -ge 2 ]] && [[ "${ARGS[0]}" =~ ^UC-[0-9]{3}$ ]]; then
59
+ # ファイル題名は日本語優先にするため、スクリプト引数の「生の説明」を保持する
60
+ RAW_DESC=""
61
+ if [[ ${#ARGS[@]} -ge 2 ]] && [[ "${ARGS[0]}" =~ ^${UC_ID_RE}$ ]]; then
29
62
  NEXT_UC="${ARGS[0]}"
30
63
  DESC="${ARGS[1]}"
64
+ RAW_DESC="${ARGS[1]}"
65
+ CATEGORY="${ARGS[2]:-}"
31
66
  elif [[ ${#ARGS[@]} -ge 1 ]]; then
32
67
  DESC="${ARGS[0]}"
68
+ RAW_DESC="${ARGS[0]}"
69
+ CATEGORY="${ARGS[1]:-}"
33
70
  fi
34
71
 
35
72
  # 説明が無ければデフォルト(next-uc や UC 番号ベース)
@@ -39,14 +76,37 @@ if [[ -z "$DESC" ]]; then
39
76
  else
40
77
  echo "次の UC 用ブランチを作成します。"
41
78
  echo " UC: $NEXT_UC"
42
- echo -n " 説明(英小文字・ハイフン。Enter で \"next-uc\"): "
79
+ echo -n " 説明(英数字・ハイフン推奨。日本語のみの場合は uc-001 等にフォールバック。Enter で \"next-uc\"): "
43
80
  read -r DESC
44
81
  DESC="${DESC:-next-uc}"
45
82
  fi
46
83
  fi
47
84
 
48
- DESC=$(echo "$DESC" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/-\+/-/g' | sed 's/^-//' | sed 's/-$//')
49
- BRANCH_NAME="feature/${NEXT_UC}-${DESC}"
85
+ # 説明は英小文字・ハイフン(Git ブランチ名は ASCII のみ)。日本語など非 ASCII はサニタイズで除去される
86
+ # macOS(BSD sed) でも動くように、連続ハイフンは 's/--*/-/g' で圧縮する
87
+ DESC=$(echo "$DESC" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-*//' | sed 's/-*$//')
88
+ # サニタイズ後に空(日本語のみの説明など)の場合は UC 番号ベースのスラグでフォールバック
89
+ [[ -z "$DESC" ]] && DESC="uc-$(echo "$NEXT_UC" | sed 's/^UC-//')"
90
+ BRANCH_NAME="${BRANCH_PREFIX}/${NEXT_UC}-${DESC}"
91
+
92
+ # UC 仕様書ファイル名は必ず日本語にする(題名が ASCII のみなら「要確認」にする)
93
+ DOC_TITLE="$RAW_DESC"
94
+ if [[ -z "$DOC_TITLE" ]]; then
95
+ DOC_TITLE="要確認"
96
+ else
97
+ # 非ASCIIが無い(= 英数字/記号のみ)なら、日本語題名が無い扱いにして要確認へ
98
+ if echo "$DOC_TITLE" | LC_ALL=C grep -q '^[ -~]*$'; then
99
+ DOC_TITLE="要確認"
100
+ fi
101
+ fi
102
+ # ファイル名に危険な文字が入らないように除去(日本語は許可)
103
+ DOC_TITLE=$(echo "$DOC_TITLE" | sed 's/[\\\\\\/\\:\\*\\?\\\"\\<\\>\\|]/ /g' | sed 's/[[:space:]]\\+/ /g' | sed 's/^ *//; s/ *$//')
104
+ [[ -z "$DOC_TITLE" ]] && DOC_TITLE="要確認"
105
+
106
+ # カテゴリ(省略時はデフォルト)。日本語カテゴリは許可し、危険文字だけ除去。
107
+ CATEGORY="${CATEGORY:-ユースケース}"
108
+ CATEGORY=$(echo "$CATEGORY" | sed 's/[^a-zA-Z0-9_ーぁ-んァ-ン一-龥\-]//g')
109
+ [[ -z "$CATEGORY" ]] && CATEGORY="ユースケース"
50
110
 
51
111
  if git rev-parse --verify "$BRANCH_NAME" >/dev/null 2>&1; then
52
112
  echo "Error: ブランチ '$BRANCH_NAME' は既に存在します。" >&2
@@ -76,6 +136,37 @@ fi
76
136
 
77
137
  git checkout "$MAIN_BRANCH"
78
138
  git pull --ff-only 2>/dev/null || true
79
- "$BRANCH_DIR/create-uc-branch.sh" "$NEXT_UC" "$DESC"
139
+
140
+ # ブランチ作成 + UC 仕様書作成(統合)
141
+ UC_ID_PATTERN="^${UC_ID_RE}$"
142
+ if ! echo "$NEXT_UC" | grep -qE "$UC_ID_PATTERN"; then
143
+ echo "Error: UC-ID が命名規則に合いません: $NEXT_UC(期待: $UC_ID_PATTERN)" >&2
144
+ exit 1
145
+ fi
146
+ valid_uc_pattern="^${BRANCH_PREFIX}/${UC_ID_RE}-[a-z0-9-]+\$"
147
+ if ! echo "$BRANCH_NAME" | grep -qE "$valid_uc_pattern"; then
148
+ echo "Error: ブランチ名が命名規則に合いません: $BRANCH_NAME(期待: ${BRANCH_PREFIX}/<UC-ID>-kebab-description)" >&2
149
+ exit 1
150
+ fi
151
+
152
+ git checkout -b "$BRANCH_NAME"
153
+ echo "Created branch: $BRANCH_NAME"
154
+
155
+ FEATURE_DIR="docs/02_ユースケース仕様/${CATEGORY}"
156
+ UC_DOC="${FEATURE_DIR}/${NEXT_UC}-${DOC_TITLE}.md"
157
+ mkdir -p "$FEATURE_DIR"
158
+ # UC ごとの判断ログ置き場(任意だが、作成しておくと運用が安定する)
159
+ mkdir -p "${FEATURE_DIR}/判断記録"
160
+ # テンプレ: 修正・改善は .spec-runner/templates/UC-N-ユースケース名.md を編集。プレースホルダ UC-N → UC番号, {ユースケース名} → 題名
161
+ TEMPLATE="$REPO_ROOT/.spec-runner/templates/UC-N-ユースケース名.md"
162
+ if [[ -f "$TEMPLATE" ]]; then
163
+ sed "s/UC-N/${NEXT_UC}/g; s/{ユースケース名}/${DOC_TITLE}/g" "$TEMPLATE" > "$UC_DOC"
164
+ echo "Created: $UC_DOC"
165
+ else
166
+ touch "$UC_DOC"
167
+ echo "# ${NEXT_UC}: ${DOC_TITLE}" >> "$UC_DOC"
168
+ echo "Created: $UC_DOC"
169
+ fi
170
+
80
171
  echo ""
81
172
  echo "次の UC 用ブランチの準備ができました。次のステップに進んでください。"