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
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
package/bin/spec-runner.js
CHANGED
|
@@ -7,13 +7,18 @@
|
|
|
7
7
|
* ゴール:
|
|
8
8
|
* - `npx spec-runner` または `npm exec spec-runner` を実行すると、
|
|
9
9
|
* プロジェクト直下に `./.spec-runner/` フォルダを作成する。
|
|
10
|
-
* -
|
|
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
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
133
|
+
function expandTemplateTree() {
|
|
134
|
+
copyDirRecursively(TEMPLATE_SPEC_RUNNER_DIR, DEST_DIR, {
|
|
135
|
+
skipNames: USER_STATE_BASENAMES,
|
|
136
|
+
});
|
|
83
137
|
}
|
|
84
138
|
|
|
85
|
-
function
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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("
|
|
225
|
-
log("");
|
|
226
|
-
log("
|
|
175
|
+
log("╔════════════════════════════════════════╗");
|
|
176
|
+
log("║ spec-runner インストーラ ║");
|
|
177
|
+
log("║ フェーズ駆動 / 次のステップ方式 ║");
|
|
178
|
+
log("╚════════════════════════════════════════╝");
|
|
227
179
|
log("");
|
|
228
|
-
|
|
229
|
-
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function printFooter() {
|
|
230
183
|
log("");
|
|
231
|
-
log("
|
|
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.
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
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-
|
|
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 [[ -
|
|
30
|
-
|
|
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": "
|
|
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]
|
|
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": ["
|
|
13
|
-
"domain": ["
|
|
14
|
-
"architecture": ["
|
|
15
|
-
"grade_a": ["
|
|
16
|
-
"gate3_openapi": ["
|
|
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-
|
|
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
|
|
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
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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 "
|
|
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
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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 用ブランチの準備ができました。次のステップに進んでください。"
|