@k2works/claude-code-booster 1.9.1 → 1.10.0

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 (94) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +42 -42
  3. package/bin/claude-code-booster +79 -79
  4. package/lib/assets/.claude/README.md +162 -162
  5. package/lib/assets/.claude/SKILLS_TEMPLATE.md +100 -100
  6. package/lib/assets/.claude/scripts/generate-inception-deck.mjs +911 -911
  7. package/lib/assets/.claude/settings.json +11 -11
  8. package/lib/assets/.claude/skills/ai-agent-guidelines/SKILL.md +119 -119
  9. package/lib/assets/.claude/skills/analyzing-architecture/SKILL.md +87 -87
  10. package/lib/assets/.claude/skills/analyzing-business/SKILL.md +117 -117
  11. package/lib/assets/.claude/skills/analyzing-data-model/SKILL.md +80 -80
  12. package/lib/assets/.claude/skills/analyzing-domain-model/SKILL.md +88 -88
  13. package/lib/assets/.claude/skills/analyzing-inception-deck/SKILL.md +137 -137
  14. package/lib/assets/.claude/skills/analyzing-non-functional/SKILL.md +91 -91
  15. package/lib/assets/.claude/skills/analyzing-operation/SKILL.md +91 -91
  16. package/lib/assets/.claude/skills/analyzing-requirements/SKILL.md +87 -87
  17. package/lib/assets/.claude/skills/analyzing-tech-stack/SKILL.md +102 -102
  18. package/lib/assets/.claude/skills/analyzing-test-strategy/SKILL.md +87 -87
  19. package/lib/assets/.claude/skills/analyzing-ui-design/SKILL.md +86 -86
  20. package/lib/assets/.claude/skills/analyzing-usecases/SKILL.md +87 -87
  21. package/lib/assets/.claude/skills/creating-adr/SKILL.md +115 -115
  22. package/lib/assets/.claude/skills/developing-backend/SKILL.md +106 -106
  23. package/lib/assets/.claude/skills/developing-frontend/SKILL.md +96 -96
  24. package/lib/assets/.claude/skills/developing-release/SKILL.md +154 -154
  25. package/lib/assets/.claude/skills/generating-slides/SKILL.md +136 -136
  26. package/lib/assets/.claude/skills/git-commit/SKILL.md +106 -106
  27. package/lib/assets/.claude/skills/killing-processes/SKILL.md +98 -98
  28. package/lib/assets/.claude/skills/managing-docs/SKILL.md +200 -200
  29. package/lib/assets/.claude/skills/managing-operations/DEPLOY.md +77 -77
  30. package/lib/assets/.claude/skills/managing-operations/SETUP_CSHARP.md +80 -80
  31. package/lib/assets/.claude/skills/managing-operations/SETUP_FRONTEND.md +84 -84
  32. package/lib/assets/.claude/skills/managing-operations/SETUP_JAVA.md +75 -75
  33. package/lib/assets/.claude/skills/managing-operations/SKILL.md +156 -156
  34. package/lib/assets/.claude/skills/orchestrating-analysis/SKILL.md +134 -134
  35. package/lib/assets/.claude/skills/orchestrating-development/SKILL.md +243 -243
  36. package/lib/assets/.claude/skills/orchestrating-project/SKILL.md +193 -193
  37. package/lib/assets/.claude/skills/planning-releases/SKILL.md +222 -222
  38. package/lib/assets/.claude/skills/syncing-github-project/SKILL.md +181 -69
  39. package/lib/assets/.claude/skills/tracking-progress/SKILL.md +164 -164
  40. package/lib/assets/.devcontainer/devcontainer.json +34 -34
  41. package/lib/assets/.env.example +17 -17
  42. package/lib/assets/.gitattributes +4 -4
  43. package/lib/assets/.github/workflows/docker-publish.yml +77 -77
  44. package/lib/assets/.github/workflows/mkdocs.yml +39 -39
  45. package/lib/assets/AGENTS.md +94 -94
  46. package/lib/assets/CLAUDE.md +162 -162
  47. package/lib/assets/README.md +269 -269
  48. package/lib/assets/docker-compose.yml +33 -33
  49. package/lib/assets/docs/assets/css/extra.css +29 -29
  50. package/lib/assets/docs/assets/js/extra.js +44 -44
  51. package/lib/assets/docs/index.md +14 -14
  52. package/lib/assets/docs/reference/CodexCLIMCP/343/202/242/343/203/227/343/203/252/343/202/261/343/203/274/343/202/267/343/203/247/343/203/263/351/226/213/347/231/272/343/203/225/343/203/255/343/203/274.md +532 -532
  53. package/lib/assets/docs/reference/CodexCLIMCP/343/202/265/343/203/274/343/203/220/343/203/274/350/250/255/345/256/232/346/211/213/351/240/206.md +341 -341
  54. package/lib/assets/docs/reference/Java/343/202/242/343/203/227/343/203/252/343/202/261/343/203/274/343/202/267/343/203/247/343/203/263/347/222/260/345/242/203/346/247/213/347/257/211/343/202/254/343/202/244/343/203/211.md +578 -578
  55. package/lib/assets/docs/reference/TypeScript/343/202/242/343/203/227/343/203/252/343/202/261/343/203/274/343/202/267/343/203/247/343/203/263/347/222/260/345/242/203/346/247/213/347/257/211/343/202/254/343/202/244/343/203/211.md +465 -465
  56. package/lib/assets/docs/reference/UI/350/250/255/350/250/210/343/202/254/343/202/244/343/203/211.md +448 -448
  57. package/lib/assets/docs/reference//343/202/210/343/201/204/343/202/275/343/203/225/343/203/210/343/202/246/343/202/247/343/202/242/343/201/250/343/201/257.md +242 -242
  58. package/lib/assets/docs/reference//343/202/242/343/203/274/343/202/255/343/203/206/343/202/257/343/203/201/343/203/243/350/250/255/350/250/210/343/202/254/343/202/244/343/203/211.md +2216 -2216
  59. package/lib/assets/docs/reference//343/202/244/343/203/263/343/203/225/343/203/251/350/250/255/350/250/210/343/202/254/343/202/244/343/203/211.md +1878 -1878
  60. package/lib/assets/docs/reference//343/202/250/343/202/257/343/202/271/343/203/210/343/203/252/343/203/274/343/203/240/343/203/227/343/203/255/343/202/260/343/203/251/343/203/237/343/203/263/343/202/260.md +554 -554
  61. package/lib/assets/docs/reference//343/202/263/343/203/274/343/203/207/343/202/243/343/203/263/343/202/260/343/201/250/343/203/206/343/202/271/343/203/210/343/202/254/343/202/244/343/203/211.md +705 -705
  62. package/lib/assets/docs/reference//343/203/206/343/202/271/343/203/210/346/210/246/347/225/245/343/202/254/343/202/244/343/203/211.md +1313 -1313
  63. package/lib/assets/docs/reference//343/203/207/343/203/274/343/202/277/343/203/242/343/203/207/343/203/253/350/250/255/350/250/210/343/202/254/343/202/244/343/203/211.md +311 -311
  64. package/lib/assets/docs/reference//343/203/211/343/203/241/343/202/244/343/203/263/343/203/242/343/203/207/343/203/253/350/250/255/350/250/210/343/202/254/343/202/244/343/203/211.md +599 -599
  65. package/lib/assets/docs/reference//343/203/223/343/202/270/343/203/215/343/202/271/343/202/242/343/203/274/343/202/255/343/203/206/343/202/257/343/203/201/343/203/243/345/210/206/346/236/220/343/202/254/343/202/244/343/203/211.md +528 -528
  66. package/lib/assets/docs/reference//343/203/246/343/203/274/343/202/271/343/202/261/343/203/274/343/202/271/344/275/234/346/210/220/343/202/254/343/202/244/343/203/211.md +682 -682
  67. package/lib/assets/docs/reference//343/203/252/343/203/252/343/203/274/343/202/271/343/202/254/343/202/244/343/203/211.md +442 -442
  68. package/lib/assets/docs/reference//343/203/252/343/203/252/343/203/274/343/202/271/343/203/273/343/202/244/343/203/206/343/203/254/343/203/274/343/202/267/343/203/247/343/203/263/350/250/210/347/224/273/343/202/254/343/202/244/343/203/211.md +558 -558
  69. package/lib/assets/docs/reference//347/222/260/345/242/203/345/244/211/346/225/260/347/256/241/347/220/206/343/202/254/343/202/244/343/203/211.md +663 -663
  70. package/lib/assets/docs/reference//350/246/201/344/273/266/345/256/232/347/276/251/343/202/254/343/202/244/343/203/211.md +1248 -1248
  71. package/lib/assets/docs/reference//351/201/213/347/224/250/350/246/201/344/273/266/345/256/232/347/276/251/343/202/254/343/202/244/343/203/211.md +392 -392
  72. package/lib/assets/docs/reference//351/226/213/347/231/272/343/202/254/343/202/244/343/203/211.md +235 -235
  73. package/lib/assets/docs/reference//351/235/236/346/251/237/350/203/275/350/246/201/344/273/266/345/256/232/347/276/251/343/202/254/343/202/244/343/203/211.md +1236 -1236
  74. package/lib/assets/docs/template/ADR.md +30 -30
  75. package/lib/assets/docs/template/README.md +50 -50
  76. package/lib/assets/docs/template//343/201/276/343/201/232/343/201/223/343/202/214/343/202/222/350/252/255/343/202/202/343/201/206/343/203/252/343/202/271/343/203/210.md +12 -12
  77. package/lib/assets/docs/template//343/202/244/343/203/206/343/203/254/343/203/274/343/202/267/343/203/247/343/203/263/345/256/214/344/272/206/345/240/261/345/221/212/346/233/270.md +58 -58
  78. package/lib/assets/docs/template//343/202/244/343/203/263/343/202/273/343/203/227/343/202/267/343/203/247/343/203/263/343/203/207/343/203/203/343/202/255.md +13 -13
  79. package/lib/assets/docs/template//343/203/223/343/202/270/343/203/215/343/202/271/343/202/242/343/203/274/343/202/255/343/203/206/343/202/257/343/203/201/343/203/243.md +379 -379
  80. package/lib/assets/docs/template//345/256/214/345/205/250/345/275/242/345/274/217/343/201/256/343/203/246/343/203/274/343/202/271/343/202/261/343/203/274/343/202/271.md +68 -68
  81. package/lib/assets/docs/template//350/246/201/344/273/266/345/256/232/347/276/251.md +669 -669
  82. package/lib/assets/docs/template//350/250/255/350/250/210.md +163 -163
  83. package/lib/assets/gulpfile.js +23 -23
  84. package/lib/assets/mkdocs.yml +65 -65
  85. package/lib/assets/ops/docker/mkdoc/Dockerfile +19 -19
  86. package/lib/assets/ops/scripts/journal.js +180 -180
  87. package/lib/assets/ops/scripts/mkdocs.js +82 -82
  88. package/lib/assets/ops/scripts/release.js +431 -431
  89. package/lib/assets/ops/scripts/ssh.js +190 -190
  90. package/lib/assets/ops/scripts/vault.js +299 -299
  91. package/lib/assets/package-lock.json +1653 -1653
  92. package/lib/assets/package.json +40 -40
  93. package/lib/gulpfile.js +37 -37
  94. package/package.json +41 -41
@@ -1,911 +1,911 @@
1
- /**
2
- * インセプションデッキ PowerPoint 生成スクリプト(汎用テンプレート)
3
- *
4
- * テンプレート: docs/template/インセプションデッキ.pptx のスライド構成に準拠
5
- * データソース: docs/analysis/inception-deck.md, docs/analysis/business_architecture.md
6
- *
7
- * 使い方:
8
- * 1. SLIDE_DATA セクションのプレースホルダーをプロジェクト固有の内容に書き換える
9
- * 2. node .claude/scripts/generate-inception-deck.mjs を実行
10
- */
11
- import PptxGenJS from "pptxgenjs";
12
- import { writeFileSync } from "fs";
13
- import { resolve } from "path";
14
-
15
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
16
- // SLIDE_DATA: プロジェクト固有データ(ここを書き換える)
17
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
18
- const SLIDE_DATA = {
19
- // ── メタ情報 ──
20
- meta: {
21
- author: "プロジェクトチーム",
22
- title: "プロジェクト名 インセプションデッキ",
23
- version: "v0.1.0",
24
- date: "YYYY-MM-DD",
25
- outputFileName: "PROJECT_v0.1.0.pptx",
26
- },
27
-
28
- // ── Slide 1: タイトル ──
29
- titleSlide: {
30
- projectName: "プロジェクト名",
31
- subtitle: "プロジェクトの説明",
32
- deckLabel: "インセプションデッキ",
33
- organization: "組織名",
34
- },
35
-
36
- // ── Slide 2: 我われはなぜここにいるのか ──
37
- whyAreWeHere: {
38
- subtitle: "プロジェクトが解決すべき課題の概要",
39
- bullets: [
40
- "課題 1: 現状の問題点を記述",
41
- "課題 2: 現状の問題点を記述",
42
- "課題 3: 現状の問題点を記述",
43
- ],
44
- highlight: "課題を一文で要約するメッセージ",
45
- },
46
-
47
- // ── Slide 3: エレベーターピッチ ──
48
- elevatorPitch: {
49
- want: "〇〇を実現",
50
- targetUser: "ターゲットユーザーの説明",
51
- productName: "プロダクト名",
52
- category: "プロダクトカテゴリ",
53
- keyBenefit: "主要な機能・利点の説明",
54
- competitor: "既存の代替手段",
55
- differentiator: "差別化ポイント",
56
- },
57
-
58
- // ── Slide 4: どんな価値をもたらすのか? ──
59
- values: {
60
- rows: [
61
- // [番号, ビジネス目標, 期待される効果]
62
- ["1", "ビジネス目標 1", "期待される効果 1"],
63
- ["2", "ビジネス目標 2", "期待される効果 2"],
64
- ["3", "ビジネス目標 3", "期待される効果 3"],
65
- ],
66
- },
67
-
68
- // ── Slide 5: やらないことリスト ──
69
- scope: {
70
- inScope: [
71
- "スコープ内の機能 1",
72
- "スコープ内の機能 2",
73
- "スコープ内の機能 3",
74
- ],
75
- outOfScope: [
76
- "スコープ外の項目 1",
77
- "スコープ外の項目 2",
78
- ],
79
- decideLater: [
80
- "後で決める項目 1",
81
- "後で決める項目 2",
82
- ],
83
- },
84
-
85
- // ── Slide 6: プロジェクトコミュニティ ──
86
- stakeholders: {
87
- rows: [
88
- // [ステークホルダー, 役割, 主な関心事]
89
- ["ステークホルダー 1", "役割", "関心事"],
90
- ["ステークホルダー 2", "役割", "関心事"],
91
- ["ステークホルダー 3", "役割", "関心事"],
92
- ],
93
- },
94
-
95
- // ── Slide 7: 技術的な解決策の概要 ──
96
- technicalSolution: {
97
- // 上部の外部チャネル(モール、外部 API など)
98
- externalChannels: [
99
- { name: "チャネル 1" },
100
- { name: "チャネル 2" },
101
- { name: "チャネル 3" },
102
- ],
103
- // 中央のシステム
104
- systemName: "システム名",
105
- modules: [
106
- "モジュール 1",
107
- "モジュール 2",
108
- "モジュール 3",
109
- "モジュール 4",
110
- ],
111
- // 下部の外部連携先
112
- externalServices: [
113
- { name: "外部サービス 1" },
114
- { name: "外部サービス 2" },
115
- { name: "外部サービス 3" },
116
- ],
117
- techNote: "技術方針の概要を一文で記述",
118
- },
119
-
120
- // ── Slide 8: 夜も眠れなくなるような問題 ──
121
- risks: {
122
- rows: [
123
- // [番号, リスク, 影響度, 対策]
124
- ["1", "リスク 1", "高", "対策 1"],
125
- ["2", "リスク 2", "中", "対策 2"],
126
- ["3", "リスク 3", "低", "対策 3"],
127
- ],
128
- },
129
-
130
- // ── Slide 9: 俺たちの "A チーム" ──
131
- team: {
132
- rows: [
133
- // [役割, 人数, 備考]
134
- ["役割 1", "N 名", "備考"],
135
- ["役割 2", "N 名", "備考"],
136
- ],
137
- highlight: "チーム体制の特徴やポイントを一文で記述",
138
- },
139
-
140
- // ── Slide 10: 期間を見極める ──
141
- timeline: {
142
- totalWeeks: 12,
143
- phases: [
144
- // weeks: ガントバー上の幅(週数)
145
- {
146
- name: "Phase 1: フェーズ名",
147
- desc: "主な作業内容",
148
- weeksLabel: "N 週間",
149
- weeks: 3,
150
- },
151
- {
152
- name: "Phase 2: フェーズ名",
153
- desc: "主な作業内容",
154
- weeksLabel: "N 週間",
155
- weeks: 4,
156
- },
157
- {
158
- name: "Phase 3: フェーズ名",
159
- desc: "主な作業内容",
160
- weeksLabel: "N 週間",
161
- weeks: 3,
162
- },
163
- {
164
- name: "Phase 4: フェーズ名",
165
- desc: "主な作業内容",
166
- weeksLabel: "N 週間",
167
- weeks: 2,
168
- },
169
- ],
170
- // MVP マーカーを表示するフェーズ番号(0 始まり。先頭から N フェーズ完了時点)
171
- // null の場合はマーカーを表示しない
172
- mvpAfterPhase: 1,
173
- },
174
-
175
- // ── Slide 11: トレードオフ・スライダー ──
176
- tradeoffs: {
177
- // level: 1=MIN 〜 4=MAX
178
- sliders: [
179
- { label: "機能をぜんぶ揃える(スコープ)", level: 2 },
180
- { label: "予算内に収める(予算)", level: 2 },
181
- { label: "期日を死守する(時間)", level: 2 },
182
- { label: "高い品質、少ない欠陥(品質)", level: 3 },
183
- ],
184
- qualityPriorities: [
185
- // [優先度, 品質特性, 理由]
186
- ["1", "品質特性 1", "理由 1"],
187
- ["2", "品質特性 2", "理由 2"],
188
- ["3", "品質特性 3", "理由 3"],
189
- ],
190
- },
191
-
192
- // ── Slide 12: 初回のリリースに必要なもの ──
193
- initialRelease: {
194
- highlight: "MVP の概要を一文で記述",
195
- mvpScope: [
196
- "MVP 機能 1",
197
- "MVP 機能 2",
198
- "MVP 機能 3",
199
- ],
200
- releaseStrategy: [
201
- "リリース戦略 1",
202
- "リリース戦略 2",
203
- ],
204
- },
205
- };
206
-
207
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
208
- // テーマ設定(テンプレートから抽出)
209
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
210
- const COLORS = {
211
- black: "000000",
212
- white: "FFFFFF",
213
- darkBlue: "333399",
214
- teal: "009999",
215
- lightTeal: "BBE0E3",
216
- paleBlue: "DAEDEF",
217
- green: "99CC00",
218
- gray: "808080",
219
- lightGray: "D0D0D0",
220
- orange: "FF6600",
221
- red: "CC3333",
222
- yellow: "FFCC00",
223
- };
224
-
225
- const FONT = {
226
- title: "Yu Gothic",
227
- body: "Yu Gothic",
228
- code: "Courier New",
229
- };
230
-
231
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
232
- // ヘルパー関数
233
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
234
- function addTitle(slide, text) {
235
- slide.addText(text, {
236
- x: 0.5,
237
- y: 0.2,
238
- w: 9.0,
239
- h: 1.0,
240
- fontSize: 28,
241
- fontFace: FONT.title,
242
- bold: true,
243
- color: COLORS.darkBlue,
244
- });
245
- }
246
-
247
- function addSubtitle(slide, text) {
248
- slide.addText(text, {
249
- x: 0.5,
250
- y: 1.1,
251
- w: 9.0,
252
- h: 0.5,
253
- fontSize: 14,
254
- fontFace: FONT.body,
255
- color: COLORS.gray,
256
- });
257
- }
258
-
259
- function addBullets(slide, items, opts = {}) {
260
- const top = opts.y ?? 1.6;
261
- const textRows = items.map((item) => ({
262
- text: item,
263
- options: {
264
- fontSize: opts.fontSize ?? 14,
265
- fontFace: FONT.body,
266
- color: opts.color ?? COLORS.black,
267
- bullet: { type: "bullet" },
268
- paraSpaceAfter: 6,
269
- },
270
- }));
271
- slide.addText(textRows, {
272
- x: opts.x ?? 0.7,
273
- y: top,
274
- w: opts.w ?? 8.6,
275
- h: opts.h ?? 5.5 - (top - 1.0),
276
- valign: "top",
277
- });
278
- }
279
-
280
- function addTable(slide, header, rows, opts = {}) {
281
- const top = opts.y ?? 1.8;
282
- const tableData = [
283
- header.map((h) => ({
284
- text: h,
285
- options: {
286
- bold: true,
287
- fontSize: 11,
288
- fontFace: FONT.body,
289
- color: COLORS.white,
290
- fill: { color: COLORS.darkBlue },
291
- align: "left",
292
- valign: "middle",
293
- },
294
- })),
295
- ...rows.map((row) =>
296
- row.map((cell) => ({
297
- text: cell,
298
- options: {
299
- fontSize: 10,
300
- fontFace: FONT.body,
301
- color: COLORS.black,
302
- align: "left",
303
- valign: "top",
304
- },
305
- }))
306
- ),
307
- ];
308
- slide.addTable(tableData, {
309
- x: opts.x ?? 0.5,
310
- y: top,
311
- w: opts.w ?? 9.0,
312
- colW: opts.colW,
313
- border: { type: "solid", pt: 0.5, color: COLORS.lightGray },
314
- rowH: opts.rowH,
315
- autoPage: false,
316
- });
317
- }
318
-
319
- function addHighlightBox(slide, text, opts = {}) {
320
- slide.addText(text, {
321
- x: opts.x ?? 0.5,
322
- y: opts.y ?? 1.5,
323
- w: opts.w ?? 9.0,
324
- h: opts.h ?? 1.0,
325
- fontSize: opts.fontSize ?? 16,
326
- fontFace: FONT.body,
327
- color: COLORS.darkBlue,
328
- bold: true,
329
- fill: { color: COLORS.paleBlue },
330
- align: "center",
331
- valign: "middle",
332
- });
333
- }
334
-
335
- function addSliderBar(slide, label, level, y) {
336
- const barX = 3.5;
337
- const barW = 5.0;
338
- const segW = barW / 4;
339
-
340
- slide.addText(label, {
341
- x: 0.5,
342
- y,
343
- w: 3.0,
344
- h: 0.45,
345
- fontSize: 11,
346
- fontFace: FONT.body,
347
- color: COLORS.black,
348
- valign: "middle",
349
- });
350
-
351
- for (let i = 0; i < 4; i++) {
352
- const isFilled = i < level;
353
- slide.addShape("rect", {
354
- x: barX + i * segW,
355
- y: y + 0.05,
356
- w: segW - 0.05,
357
- h: 0.35,
358
- fill: { color: isFilled ? COLORS.darkBlue : COLORS.lightGray },
359
- line: { color: COLORS.gray, width: 0.5 },
360
- });
361
- }
362
-
363
- slide.addText("MIN", {
364
- x: barX - 0.05,
365
- y: y + 0.38,
366
- w: 0.5,
367
- h: 0.2,
368
- fontSize: 7,
369
- fontFace: FONT.body,
370
- color: COLORS.gray,
371
- });
372
- slide.addText("MAX", {
373
- x: barX + barW - 0.45,
374
- y: y + 0.38,
375
- w: 0.5,
376
- h: 0.2,
377
- fontSize: 7,
378
- fontFace: FONT.body,
379
- color: COLORS.gray,
380
- });
381
- }
382
-
383
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
384
- // スライド生成
385
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
386
- async function main() {
387
- const { meta } = SLIDE_DATA;
388
-
389
- const pptx = new PptxGenJS();
390
- pptx.defineLayout({ name: "SCREEN_4x3", width: 10, height: 7.5 });
391
- pptx.layout = "SCREEN_4x3";
392
- pptx.author = meta.author;
393
- pptx.title = meta.title;
394
-
395
- // ─── Slide 1: タイトル ───
396
- {
397
- const d = SLIDE_DATA.titleSlide;
398
- const slide = pptx.addSlide();
399
- slide.background = { color: COLORS.darkBlue };
400
- slide.addText(
401
- [
402
- {
403
- text: d.projectName,
404
- options: {
405
- fontSize: 48,
406
- fontFace: FONT.title,
407
- bold: true,
408
- color: COLORS.white,
409
- breakLine: true,
410
- },
411
- },
412
- {
413
- text: d.subtitle,
414
- options: {
415
- fontSize: 28,
416
- fontFace: FONT.title,
417
- color: COLORS.lightTeal,
418
- breakLine: true,
419
- },
420
- },
421
- {
422
- text: d.deckLabel,
423
- options: {
424
- fontSize: 24,
425
- fontFace: FONT.title,
426
- color: COLORS.white,
427
- breakLine: true,
428
- },
429
- },
430
- ],
431
- { x: 1.0, y: 1.5, w: 8.0, h: 3.5, align: "center", valign: "middle" }
432
- );
433
- slide.addText(d.organization, {
434
- x: 1.0,
435
- y: 5.5,
436
- w: 8.0,
437
- h: 0.5,
438
- fontSize: 16,
439
- fontFace: FONT.body,
440
- color: COLORS.lightTeal,
441
- align: "center",
442
- });
443
- slide.addText(`${meta.version} | ${meta.date}`, {
444
- x: 1.0,
445
- y: 6.2,
446
- w: 8.0,
447
- h: 0.4,
448
- fontSize: 12,
449
- fontFace: FONT.body,
450
- color: COLORS.gray,
451
- align: "center",
452
- });
453
- }
454
-
455
- // ─── Slide 2: 我われはなぜここにいるのか ───
456
- {
457
- const d = SLIDE_DATA.whyAreWeHere;
458
- const slide = pptx.addSlide();
459
- addTitle(slide, "我われはなぜここにいるのか");
460
- addSubtitle(slide, d.subtitle);
461
- addBullets(slide, d.bullets);
462
- addHighlightBox(slide, d.highlight, {
463
- y: 5.8,
464
- h: 0.7,
465
- fontSize: 14,
466
- });
467
- }
468
-
469
- // ─── Slide 3: エレベーターピッチ ───
470
- {
471
- const d = SLIDE_DATA.elevatorPitch;
472
- const slide = pptx.addSlide();
473
- addTitle(slide, "エレベーターピッチ");
474
-
475
- const bold = {
476
- fontSize: 14,
477
- fontFace: FONT.body,
478
- color: COLORS.darkBlue,
479
- bold: true,
480
- };
481
- const normal = {
482
- fontSize: 14,
483
- fontFace: FONT.body,
484
- color: COLORS.black,
485
- };
486
-
487
- const pitchParts = [
488
- { text: d.want, options: bold },
489
- { text: " したい\n", options: normal },
490
- { text: d.targetUser, options: bold },
491
- { text: " 向けの、\n", options: normal },
492
- { text: d.productName, options: bold },
493
- { text: " というプロダクトは、\n", options: normal },
494
- { text: d.category, options: bold },
495
- { text: " です。\nこれは ", options: normal },
496
- { text: d.keyBenefit, options: bold },
497
- { text: " ができ、\n", options: normal },
498
- { text: d.competitor, options: bold },
499
- { text: " とは違って、\n", options: normal },
500
- { text: d.differentiator, options: bold },
501
- { text: " が備わっている。", options: normal },
502
- ];
503
-
504
- slide.addText(pitchParts, {
505
- x: 0.8,
506
- y: 1.5,
507
- w: 8.4,
508
- h: 4.5,
509
- fill: { color: COLORS.paleBlue },
510
- valign: "middle",
511
- paraSpaceAfter: 8,
512
- });
513
- }
514
-
515
- // ─── Slide 4: どんな価値をもたらすのか? ───
516
- {
517
- const d = SLIDE_DATA.values;
518
- const slide = pptx.addSlide();
519
- addTitle(slide, "どんな価値をもたらすのか?");
520
- addTable(
521
- slide,
522
- ["#", "ビジネス目標", "期待される効果"],
523
- d.rows,
524
- { colW: [0.4, 3.0, 5.6] }
525
- );
526
- }
527
-
528
- // ─── Slide 5: やらないことリスト ───
529
- {
530
- const d = SLIDE_DATA.scope;
531
- const slide = pptx.addSlide();
532
- addTitle(slide, "やらないことリスト");
533
- addSubtitle(slide, "スコープの範囲");
534
-
535
- const colW = 2.85;
536
- const colGap = 0.15;
537
- const cols = [
538
- { title: "やる(スコープ内)", color: COLORS.teal, items: d.inScope },
539
- { title: "やらない(スコープ外)", color: COLORS.red, items: d.outOfScope },
540
- { title: "あとで決める", color: COLORS.orange, items: d.decideLater },
541
- ];
542
-
543
- cols.forEach((col, i) => {
544
- const x = 0.5 + i * (colW + colGap);
545
- slide.addText(col.title, {
546
- x,
547
- y: 1.7,
548
- w: colW,
549
- h: 0.45,
550
- fontSize: 13,
551
- fontFace: FONT.body,
552
- bold: true,
553
- color: COLORS.white,
554
- fill: { color: col.color },
555
- align: "center",
556
- valign: "middle",
557
- });
558
- const bullets = col.items.map((item) => ({
559
- text: item,
560
- options: {
561
- fontSize: 11,
562
- fontFace: FONT.body,
563
- color: COLORS.black,
564
- bullet: { type: "bullet" },
565
- paraSpaceAfter: 4,
566
- },
567
- }));
568
- slide.addText(bullets, {
569
- x,
570
- y: 2.2,
571
- w: colW,
572
- h: 4.8,
573
- valign: "top",
574
- });
575
- });
576
- }
577
-
578
- // ─── Slide 6: プロジェクトコミュニティ ───
579
- {
580
- const d = SLIDE_DATA.stakeholders;
581
- const slide = pptx.addSlide();
582
- addTitle(slide, "プロジェクトコミュニティ");
583
- addSubtitle(slide, "主なステークホルダーと関心事");
584
- addTable(
585
- slide,
586
- ["ステークホルダー", "役割", "主な関心事"],
587
- d.rows,
588
- { y: 1.7, colW: [2.0, 2.2, 4.8] }
589
- );
590
- }
591
-
592
- // ─── Slide 7: 技術的な解決策の概要 ───
593
- {
594
- const d = SLIDE_DATA.technicalSolution;
595
- const slide = pptx.addSlide();
596
- addTitle(slide, "技術的な解決策の概要");
597
-
598
- const boxH = 0.5;
599
-
600
- // 上部: 外部チャネル
601
- const channelCount = d.externalChannels.length;
602
- const channelW = Math.min(2.0, (9.0 - 0.2 * (channelCount - 1)) / channelCount);
603
- const channelGap = channelCount > 1 ? (9.0 - channelW * channelCount) / (channelCount - 1) : 0;
604
- d.externalChannels.forEach((ch, i) => {
605
- const x = 0.5 + i * (channelW + channelGap);
606
- slide.addShape("rect", {
607
- x,
608
- y: 1.5,
609
- w: channelW,
610
- h: boxH,
611
- fill: { color: COLORS.lightTeal },
612
- line: { color: COLORS.teal, width: 1 },
613
- });
614
- slide.addText(ch.name, {
615
- x,
616
- y: 1.5,
617
- w: channelW,
618
- h: boxH,
619
- fontSize: 10,
620
- fontFace: FONT.body,
621
- color: COLORS.black,
622
- align: "center",
623
- valign: "middle",
624
- });
625
- });
626
-
627
- // 接続ラベル
628
- slide.addText("API", {
629
- x: 4.0,
630
- y: 2.05,
631
- w: 1.0,
632
- h: 0.3,
633
- fontSize: 9,
634
- fontFace: FONT.body,
635
- color: COLORS.gray,
636
- align: "center",
637
- });
638
-
639
- // 中央: メインシステム
640
- slide.addShape("rect", {
641
- x: 0.5,
642
- y: 2.5,
643
- w: 9.0,
644
- h: 2.8,
645
- fill: { color: "F8F8FF" },
646
- line: { color: COLORS.darkBlue, width: 2 },
647
- });
648
- slide.addText(d.systemName, {
649
- x: 0.5,
650
- y: 2.5,
651
- w: 9.0,
652
- h: 0.4,
653
- fontSize: 12,
654
- fontFace: FONT.body,
655
- bold: true,
656
- color: COLORS.darkBlue,
657
- align: "center",
658
- });
659
-
660
- // 内部モジュール
661
- d.modules.forEach((name, i) => {
662
- const row = Math.floor(i / 4);
663
- const col = i % 4;
664
- slide.addShape("roundRect", {
665
- x: 0.8 + col * 2.15,
666
- y: 3.0 + row * 0.7,
667
- w: 1.95,
668
- h: 0.55,
669
- fill: { color: COLORS.darkBlue },
670
- rectRadius: 0.05,
671
- });
672
- slide.addText(name, {
673
- x: 0.8 + col * 2.15,
674
- y: 3.0 + row * 0.7,
675
- w: 1.95,
676
- h: 0.55,
677
- fontSize: 10,
678
- fontFace: FONT.body,
679
- color: COLORS.white,
680
- align: "center",
681
- valign: "middle",
682
- });
683
- });
684
-
685
- // 下部: 外部連携先
686
- const svcCount = d.externalServices.length;
687
- const svcW = Math.min(2.0, (9.0 - 0.2 * (svcCount - 1)) / svcCount);
688
- const svcGap = svcCount > 1 ? (9.0 - svcW * svcCount) / (svcCount - 1) : 0;
689
- d.externalServices.forEach((svc, i) => {
690
- const x = 0.5 + i * (svcW + svcGap);
691
- slide.addShape("rect", {
692
- x,
693
- y: 5.6,
694
- w: svcW,
695
- h: boxH,
696
- fill: { color: "FFF3E0" },
697
- line: { color: COLORS.orange, width: 1 },
698
- });
699
- slide.addText(svc.name, {
700
- x,
701
- y: 5.6,
702
- w: svcW,
703
- h: boxH,
704
- fontSize: 10,
705
- fontFace: FONT.body,
706
- color: COLORS.black,
707
- align: "center",
708
- valign: "middle",
709
- });
710
- });
711
-
712
- // 技術方針ノート
713
- slide.addText(d.techNote, {
714
- x: 0.5,
715
- y: 6.4,
716
- w: 9.0,
717
- h: 0.4,
718
- fontSize: 10,
719
- fontFace: FONT.body,
720
- color: COLORS.gray,
721
- align: "center",
722
- });
723
- }
724
-
725
- // ─── Slide 8: 夜も眠れなくなるような問題 ───
726
- {
727
- const d = SLIDE_DATA.risks;
728
- const slide = pptx.addSlide();
729
- addTitle(slide, "夜も眠れなくなるような問題は何だろう?");
730
- addTable(
731
- slide,
732
- ["#", "リスク", "影響度", "対策"],
733
- d.rows,
734
- { colW: [0.4, 3.0, 0.8, 4.8] }
735
- );
736
- }
737
-
738
- // ─── Slide 9: 俺たちの "A チーム" ───
739
- {
740
- const d = SLIDE_DATA.team;
741
- const slide = pptx.addSlide();
742
- addTitle(slide, '俺たちの "A チーム"');
743
- addTable(
744
- slide,
745
- ["役割", "人数", "備考"],
746
- d.rows,
747
- { y: 1.8, colW: [2.5, 1.5, 5.0] }
748
- );
749
- addHighlightBox(slide, d.highlight, { y: 4.0, h: 0.8, fontSize: 14 });
750
- }
751
-
752
- // ─── Slide 10: 期間を見極める ───
753
- {
754
- const d = SLIDE_DATA.timeline;
755
- const slide = pptx.addSlide();
756
- addTitle(slide, "期間を見極める");
757
-
758
- const barStartX = 0.5;
759
- const barMaxW = 9.0;
760
- const phaseWeeks = d.phases.map((p) => p.weeks);
761
-
762
- d.phases.forEach((phase, i) => {
763
- const y = 1.8 + i * 1.0;
764
- const startWeek = phaseWeeks.slice(0, i).reduce((a, b) => a + b, 0);
765
- const x = barStartX + (startWeek / d.totalWeeks) * barMaxW;
766
- const w = (phaseWeeks[i] / d.totalWeeks) * barMaxW;
767
- const fillColor = i % 2 === 0 ? COLORS.lightTeal : COLORS.teal;
768
-
769
- slide.addShape("rect", {
770
- x,
771
- y,
772
- w,
773
- h: 0.45,
774
- fill: { color: fillColor },
775
- line: { color: COLORS.teal, width: 1 },
776
- });
777
- slide.addText(phase.name, {
778
- x,
779
- y,
780
- w,
781
- h: 0.45,
782
- fontSize: 10,
783
- fontFace: FONT.body,
784
- bold: true,
785
- color: COLORS.darkBlue,
786
- align: "center",
787
- valign: "middle",
788
- });
789
- slide.addText(`${phase.desc}(${phase.weeksLabel})`, {
790
- x,
791
- y: y + 0.45,
792
- w,
793
- h: 0.35,
794
- fontSize: 8,
795
- fontFace: FONT.body,
796
- color: COLORS.gray,
797
- align: "center",
798
- });
799
- });
800
-
801
- // MVP マーカー
802
- if (d.mvpAfterPhase != null) {
803
- const mvpWeeks = phaseWeeks
804
- .slice(0, d.mvpAfterPhase + 1)
805
- .reduce((a, b) => a + b, 0);
806
- const mvpX = barStartX + (mvpWeeks / d.totalWeeks) * barMaxW;
807
- slide.addShape("line", {
808
- x: mvpX,
809
- y: 1.5,
810
- w: 0,
811
- h: 5.0,
812
- line: { color: COLORS.red, width: 2, dashType: "dash" },
813
- });
814
- slide.addText("MVP\nリリース", {
815
- x: mvpX - 0.5,
816
- y: 6.5,
817
- w: 1.2,
818
- h: 0.5,
819
- fontSize: 10,
820
- fontFace: FONT.body,
821
- bold: true,
822
- color: COLORS.red,
823
- align: "center",
824
- });
825
- }
826
-
827
- slide.addText("あくまで推測であって、確約するものではありません。", {
828
- x: 0.5,
829
- y: 7.0,
830
- w: 9.0,
831
- h: 0.3,
832
- fontSize: 9,
833
- fontFace: FONT.body,
834
- color: COLORS.gray,
835
- align: "right",
836
- });
837
- }
838
-
839
- // ─── Slide 11: トレードオフ・スライダー ───
840
- {
841
- const d = SLIDE_DATA.tradeoffs;
842
- const slide = pptx.addSlide();
843
- addTitle(slide, "トレードオフ・スライダー");
844
-
845
- d.sliders.forEach((s, i) => {
846
- addSliderBar(slide, s.label, s.level, 1.8 + i * 0.7);
847
- });
848
-
849
- const qualityY = 1.8 + d.sliders.length * 0.7 + 0.3;
850
- slide.addText("品質特性の優先順位", {
851
- x: 0.5,
852
- y: qualityY,
853
- w: 9.0,
854
- h: 0.4,
855
- fontSize: 14,
856
- fontFace: FONT.body,
857
- bold: true,
858
- color: COLORS.darkBlue,
859
- });
860
-
861
- addTable(
862
- slide,
863
- ["優先度", "品質特性", "理由"],
864
- d.qualityPriorities,
865
- { y: qualityY + 0.4, colW: [0.8, 1.8, 6.4] }
866
- );
867
- }
868
-
869
- // ─── Slide 12: 初回のリリースに必要なもの ───
870
- {
871
- const d = SLIDE_DATA.initialRelease;
872
- const slide = pptx.addSlide();
873
- addTitle(slide, "初回のリリースに必要なもの");
874
-
875
- addHighlightBox(slide, d.highlight, { y: 1.5, h: 0.7, fontSize: 16 });
876
-
877
- slide.addText("MVP スコープ", {
878
- x: 0.5,
879
- y: 2.5,
880
- w: 9.0,
881
- h: 0.4,
882
- fontSize: 14,
883
- fontFace: FONT.body,
884
- bold: true,
885
- color: COLORS.darkBlue,
886
- });
887
-
888
- addBullets(slide, d.mvpScope, { y: 2.9, h: 2.5, fontSize: 13 });
889
-
890
- slide.addText("リリース戦略", {
891
- x: 0.5,
892
- y: 5.2,
893
- w: 9.0,
894
- h: 0.4,
895
- fontSize: 14,
896
- fontFace: FONT.body,
897
- bold: true,
898
- color: COLORS.darkBlue,
899
- });
900
-
901
- addBullets(slide, d.releaseStrategy, { y: 5.6, h: 1.5, fontSize: 12 });
902
- }
903
-
904
- // ─── 保存 ───
905
- const outputPath = resolve(`docs/analysis/slide/${meta.outputFileName}`);
906
- const dataBuffer = await pptx.write({ outputType: "nodebuffer" });
907
- writeFileSync(outputPath, dataBuffer);
908
- console.log("Generated:", outputPath);
909
- }
910
-
911
- main().catch(console.error);
1
+ /**
2
+ * インセプションデッキ PowerPoint 生成スクリプト(汎用テンプレート)
3
+ *
4
+ * テンプレート: docs/template/インセプションデッキ.pptx のスライド構成に準拠
5
+ * データソース: docs/analysis/inception-deck.md, docs/analysis/business_architecture.md
6
+ *
7
+ * 使い方:
8
+ * 1. SLIDE_DATA セクションのプレースホルダーをプロジェクト固有の内容に書き換える
9
+ * 2. node .claude/scripts/generate-inception-deck.mjs を実行
10
+ */
11
+ import PptxGenJS from "pptxgenjs";
12
+ import { writeFileSync } from "fs";
13
+ import { resolve } from "path";
14
+
15
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
16
+ // SLIDE_DATA: プロジェクト固有データ(ここを書き換える)
17
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
18
+ const SLIDE_DATA = {
19
+ // ── メタ情報 ──
20
+ meta: {
21
+ author: "プロジェクトチーム",
22
+ title: "プロジェクト名 インセプションデッキ",
23
+ version: "v0.1.0",
24
+ date: "YYYY-MM-DD",
25
+ outputFileName: "PROJECT_v0.1.0.pptx",
26
+ },
27
+
28
+ // ── Slide 1: タイトル ──
29
+ titleSlide: {
30
+ projectName: "プロジェクト名",
31
+ subtitle: "プロジェクトの説明",
32
+ deckLabel: "インセプションデッキ",
33
+ organization: "組織名",
34
+ },
35
+
36
+ // ── Slide 2: 我われはなぜここにいるのか ──
37
+ whyAreWeHere: {
38
+ subtitle: "プロジェクトが解決すべき課題の概要",
39
+ bullets: [
40
+ "課題 1: 現状の問題点を記述",
41
+ "課題 2: 現状の問題点を記述",
42
+ "課題 3: 現状の問題点を記述",
43
+ ],
44
+ highlight: "課題を一文で要約するメッセージ",
45
+ },
46
+
47
+ // ── Slide 3: エレベーターピッチ ──
48
+ elevatorPitch: {
49
+ want: "〇〇を実現",
50
+ targetUser: "ターゲットユーザーの説明",
51
+ productName: "プロダクト名",
52
+ category: "プロダクトカテゴリ",
53
+ keyBenefit: "主要な機能・利点の説明",
54
+ competitor: "既存の代替手段",
55
+ differentiator: "差別化ポイント",
56
+ },
57
+
58
+ // ── Slide 4: どんな価値をもたらすのか? ──
59
+ values: {
60
+ rows: [
61
+ // [番号, ビジネス目標, 期待される効果]
62
+ ["1", "ビジネス目標 1", "期待される効果 1"],
63
+ ["2", "ビジネス目標 2", "期待される効果 2"],
64
+ ["3", "ビジネス目標 3", "期待される効果 3"],
65
+ ],
66
+ },
67
+
68
+ // ── Slide 5: やらないことリスト ──
69
+ scope: {
70
+ inScope: [
71
+ "スコープ内の機能 1",
72
+ "スコープ内の機能 2",
73
+ "スコープ内の機能 3",
74
+ ],
75
+ outOfScope: [
76
+ "スコープ外の項目 1",
77
+ "スコープ外の項目 2",
78
+ ],
79
+ decideLater: [
80
+ "後で決める項目 1",
81
+ "後で決める項目 2",
82
+ ],
83
+ },
84
+
85
+ // ── Slide 6: プロジェクトコミュニティ ──
86
+ stakeholders: {
87
+ rows: [
88
+ // [ステークホルダー, 役割, 主な関心事]
89
+ ["ステークホルダー 1", "役割", "関心事"],
90
+ ["ステークホルダー 2", "役割", "関心事"],
91
+ ["ステークホルダー 3", "役割", "関心事"],
92
+ ],
93
+ },
94
+
95
+ // ── Slide 7: 技術的な解決策の概要 ──
96
+ technicalSolution: {
97
+ // 上部の外部チャネル(モール、外部 API など)
98
+ externalChannels: [
99
+ { name: "チャネル 1" },
100
+ { name: "チャネル 2" },
101
+ { name: "チャネル 3" },
102
+ ],
103
+ // 中央のシステム
104
+ systemName: "システム名",
105
+ modules: [
106
+ "モジュール 1",
107
+ "モジュール 2",
108
+ "モジュール 3",
109
+ "モジュール 4",
110
+ ],
111
+ // 下部の外部連携先
112
+ externalServices: [
113
+ { name: "外部サービス 1" },
114
+ { name: "外部サービス 2" },
115
+ { name: "外部サービス 3" },
116
+ ],
117
+ techNote: "技術方針の概要を一文で記述",
118
+ },
119
+
120
+ // ── Slide 8: 夜も眠れなくなるような問題 ──
121
+ risks: {
122
+ rows: [
123
+ // [番号, リスク, 影響度, 対策]
124
+ ["1", "リスク 1", "高", "対策 1"],
125
+ ["2", "リスク 2", "中", "対策 2"],
126
+ ["3", "リスク 3", "低", "対策 3"],
127
+ ],
128
+ },
129
+
130
+ // ── Slide 9: 俺たちの "A チーム" ──
131
+ team: {
132
+ rows: [
133
+ // [役割, 人数, 備考]
134
+ ["役割 1", "N 名", "備考"],
135
+ ["役割 2", "N 名", "備考"],
136
+ ],
137
+ highlight: "チーム体制の特徴やポイントを一文で記述",
138
+ },
139
+
140
+ // ── Slide 10: 期間を見極める ──
141
+ timeline: {
142
+ totalWeeks: 12,
143
+ phases: [
144
+ // weeks: ガントバー上の幅(週数)
145
+ {
146
+ name: "Phase 1: フェーズ名",
147
+ desc: "主な作業内容",
148
+ weeksLabel: "N 週間",
149
+ weeks: 3,
150
+ },
151
+ {
152
+ name: "Phase 2: フェーズ名",
153
+ desc: "主な作業内容",
154
+ weeksLabel: "N 週間",
155
+ weeks: 4,
156
+ },
157
+ {
158
+ name: "Phase 3: フェーズ名",
159
+ desc: "主な作業内容",
160
+ weeksLabel: "N 週間",
161
+ weeks: 3,
162
+ },
163
+ {
164
+ name: "Phase 4: フェーズ名",
165
+ desc: "主な作業内容",
166
+ weeksLabel: "N 週間",
167
+ weeks: 2,
168
+ },
169
+ ],
170
+ // MVP マーカーを表示するフェーズ番号(0 始まり。先頭から N フェーズ完了時点)
171
+ // null の場合はマーカーを表示しない
172
+ mvpAfterPhase: 1,
173
+ },
174
+
175
+ // ── Slide 11: トレードオフ・スライダー ──
176
+ tradeoffs: {
177
+ // level: 1=MIN 〜 4=MAX
178
+ sliders: [
179
+ { label: "機能をぜんぶ揃える(スコープ)", level: 2 },
180
+ { label: "予算内に収める(予算)", level: 2 },
181
+ { label: "期日を死守する(時間)", level: 2 },
182
+ { label: "高い品質、少ない欠陥(品質)", level: 3 },
183
+ ],
184
+ qualityPriorities: [
185
+ // [優先度, 品質特性, 理由]
186
+ ["1", "品質特性 1", "理由 1"],
187
+ ["2", "品質特性 2", "理由 2"],
188
+ ["3", "品質特性 3", "理由 3"],
189
+ ],
190
+ },
191
+
192
+ // ── Slide 12: 初回のリリースに必要なもの ──
193
+ initialRelease: {
194
+ highlight: "MVP の概要を一文で記述",
195
+ mvpScope: [
196
+ "MVP 機能 1",
197
+ "MVP 機能 2",
198
+ "MVP 機能 3",
199
+ ],
200
+ releaseStrategy: [
201
+ "リリース戦略 1",
202
+ "リリース戦略 2",
203
+ ],
204
+ },
205
+ };
206
+
207
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
208
+ // テーマ設定(テンプレートから抽出)
209
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
210
+ const COLORS = {
211
+ black: "000000",
212
+ white: "FFFFFF",
213
+ darkBlue: "333399",
214
+ teal: "009999",
215
+ lightTeal: "BBE0E3",
216
+ paleBlue: "DAEDEF",
217
+ green: "99CC00",
218
+ gray: "808080",
219
+ lightGray: "D0D0D0",
220
+ orange: "FF6600",
221
+ red: "CC3333",
222
+ yellow: "FFCC00",
223
+ };
224
+
225
+ const FONT = {
226
+ title: "Yu Gothic",
227
+ body: "Yu Gothic",
228
+ code: "Courier New",
229
+ };
230
+
231
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
232
+ // ヘルパー関数
233
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
234
+ function addTitle(slide, text) {
235
+ slide.addText(text, {
236
+ x: 0.5,
237
+ y: 0.2,
238
+ w: 9.0,
239
+ h: 1.0,
240
+ fontSize: 28,
241
+ fontFace: FONT.title,
242
+ bold: true,
243
+ color: COLORS.darkBlue,
244
+ });
245
+ }
246
+
247
+ function addSubtitle(slide, text) {
248
+ slide.addText(text, {
249
+ x: 0.5,
250
+ y: 1.1,
251
+ w: 9.0,
252
+ h: 0.5,
253
+ fontSize: 14,
254
+ fontFace: FONT.body,
255
+ color: COLORS.gray,
256
+ });
257
+ }
258
+
259
+ function addBullets(slide, items, opts = {}) {
260
+ const top = opts.y ?? 1.6;
261
+ const textRows = items.map((item) => ({
262
+ text: item,
263
+ options: {
264
+ fontSize: opts.fontSize ?? 14,
265
+ fontFace: FONT.body,
266
+ color: opts.color ?? COLORS.black,
267
+ bullet: { type: "bullet" },
268
+ paraSpaceAfter: 6,
269
+ },
270
+ }));
271
+ slide.addText(textRows, {
272
+ x: opts.x ?? 0.7,
273
+ y: top,
274
+ w: opts.w ?? 8.6,
275
+ h: opts.h ?? 5.5 - (top - 1.0),
276
+ valign: "top",
277
+ });
278
+ }
279
+
280
+ function addTable(slide, header, rows, opts = {}) {
281
+ const top = opts.y ?? 1.8;
282
+ const tableData = [
283
+ header.map((h) => ({
284
+ text: h,
285
+ options: {
286
+ bold: true,
287
+ fontSize: 11,
288
+ fontFace: FONT.body,
289
+ color: COLORS.white,
290
+ fill: { color: COLORS.darkBlue },
291
+ align: "left",
292
+ valign: "middle",
293
+ },
294
+ })),
295
+ ...rows.map((row) =>
296
+ row.map((cell) => ({
297
+ text: cell,
298
+ options: {
299
+ fontSize: 10,
300
+ fontFace: FONT.body,
301
+ color: COLORS.black,
302
+ align: "left",
303
+ valign: "top",
304
+ },
305
+ }))
306
+ ),
307
+ ];
308
+ slide.addTable(tableData, {
309
+ x: opts.x ?? 0.5,
310
+ y: top,
311
+ w: opts.w ?? 9.0,
312
+ colW: opts.colW,
313
+ border: { type: "solid", pt: 0.5, color: COLORS.lightGray },
314
+ rowH: opts.rowH,
315
+ autoPage: false,
316
+ });
317
+ }
318
+
319
+ function addHighlightBox(slide, text, opts = {}) {
320
+ slide.addText(text, {
321
+ x: opts.x ?? 0.5,
322
+ y: opts.y ?? 1.5,
323
+ w: opts.w ?? 9.0,
324
+ h: opts.h ?? 1.0,
325
+ fontSize: opts.fontSize ?? 16,
326
+ fontFace: FONT.body,
327
+ color: COLORS.darkBlue,
328
+ bold: true,
329
+ fill: { color: COLORS.paleBlue },
330
+ align: "center",
331
+ valign: "middle",
332
+ });
333
+ }
334
+
335
+ function addSliderBar(slide, label, level, y) {
336
+ const barX = 3.5;
337
+ const barW = 5.0;
338
+ const segW = barW / 4;
339
+
340
+ slide.addText(label, {
341
+ x: 0.5,
342
+ y,
343
+ w: 3.0,
344
+ h: 0.45,
345
+ fontSize: 11,
346
+ fontFace: FONT.body,
347
+ color: COLORS.black,
348
+ valign: "middle",
349
+ });
350
+
351
+ for (let i = 0; i < 4; i++) {
352
+ const isFilled = i < level;
353
+ slide.addShape("rect", {
354
+ x: barX + i * segW,
355
+ y: y + 0.05,
356
+ w: segW - 0.05,
357
+ h: 0.35,
358
+ fill: { color: isFilled ? COLORS.darkBlue : COLORS.lightGray },
359
+ line: { color: COLORS.gray, width: 0.5 },
360
+ });
361
+ }
362
+
363
+ slide.addText("MIN", {
364
+ x: barX - 0.05,
365
+ y: y + 0.38,
366
+ w: 0.5,
367
+ h: 0.2,
368
+ fontSize: 7,
369
+ fontFace: FONT.body,
370
+ color: COLORS.gray,
371
+ });
372
+ slide.addText("MAX", {
373
+ x: barX + barW - 0.45,
374
+ y: y + 0.38,
375
+ w: 0.5,
376
+ h: 0.2,
377
+ fontSize: 7,
378
+ fontFace: FONT.body,
379
+ color: COLORS.gray,
380
+ });
381
+ }
382
+
383
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
384
+ // スライド生成
385
+ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
386
+ async function main() {
387
+ const { meta } = SLIDE_DATA;
388
+
389
+ const pptx = new PptxGenJS();
390
+ pptx.defineLayout({ name: "SCREEN_4x3", width: 10, height: 7.5 });
391
+ pptx.layout = "SCREEN_4x3";
392
+ pptx.author = meta.author;
393
+ pptx.title = meta.title;
394
+
395
+ // ─── Slide 1: タイトル ───
396
+ {
397
+ const d = SLIDE_DATA.titleSlide;
398
+ const slide = pptx.addSlide();
399
+ slide.background = { color: COLORS.darkBlue };
400
+ slide.addText(
401
+ [
402
+ {
403
+ text: d.projectName,
404
+ options: {
405
+ fontSize: 48,
406
+ fontFace: FONT.title,
407
+ bold: true,
408
+ color: COLORS.white,
409
+ breakLine: true,
410
+ },
411
+ },
412
+ {
413
+ text: d.subtitle,
414
+ options: {
415
+ fontSize: 28,
416
+ fontFace: FONT.title,
417
+ color: COLORS.lightTeal,
418
+ breakLine: true,
419
+ },
420
+ },
421
+ {
422
+ text: d.deckLabel,
423
+ options: {
424
+ fontSize: 24,
425
+ fontFace: FONT.title,
426
+ color: COLORS.white,
427
+ breakLine: true,
428
+ },
429
+ },
430
+ ],
431
+ { x: 1.0, y: 1.5, w: 8.0, h: 3.5, align: "center", valign: "middle" }
432
+ );
433
+ slide.addText(d.organization, {
434
+ x: 1.0,
435
+ y: 5.5,
436
+ w: 8.0,
437
+ h: 0.5,
438
+ fontSize: 16,
439
+ fontFace: FONT.body,
440
+ color: COLORS.lightTeal,
441
+ align: "center",
442
+ });
443
+ slide.addText(`${meta.version} | ${meta.date}`, {
444
+ x: 1.0,
445
+ y: 6.2,
446
+ w: 8.0,
447
+ h: 0.4,
448
+ fontSize: 12,
449
+ fontFace: FONT.body,
450
+ color: COLORS.gray,
451
+ align: "center",
452
+ });
453
+ }
454
+
455
+ // ─── Slide 2: 我われはなぜここにいるのか ───
456
+ {
457
+ const d = SLIDE_DATA.whyAreWeHere;
458
+ const slide = pptx.addSlide();
459
+ addTitle(slide, "我われはなぜここにいるのか");
460
+ addSubtitle(slide, d.subtitle);
461
+ addBullets(slide, d.bullets);
462
+ addHighlightBox(slide, d.highlight, {
463
+ y: 5.8,
464
+ h: 0.7,
465
+ fontSize: 14,
466
+ });
467
+ }
468
+
469
+ // ─── Slide 3: エレベーターピッチ ───
470
+ {
471
+ const d = SLIDE_DATA.elevatorPitch;
472
+ const slide = pptx.addSlide();
473
+ addTitle(slide, "エレベーターピッチ");
474
+
475
+ const bold = {
476
+ fontSize: 14,
477
+ fontFace: FONT.body,
478
+ color: COLORS.darkBlue,
479
+ bold: true,
480
+ };
481
+ const normal = {
482
+ fontSize: 14,
483
+ fontFace: FONT.body,
484
+ color: COLORS.black,
485
+ };
486
+
487
+ const pitchParts = [
488
+ { text: d.want, options: bold },
489
+ { text: " したい\n", options: normal },
490
+ { text: d.targetUser, options: bold },
491
+ { text: " 向けの、\n", options: normal },
492
+ { text: d.productName, options: bold },
493
+ { text: " というプロダクトは、\n", options: normal },
494
+ { text: d.category, options: bold },
495
+ { text: " です。\nこれは ", options: normal },
496
+ { text: d.keyBenefit, options: bold },
497
+ { text: " ができ、\n", options: normal },
498
+ { text: d.competitor, options: bold },
499
+ { text: " とは違って、\n", options: normal },
500
+ { text: d.differentiator, options: bold },
501
+ { text: " が備わっている。", options: normal },
502
+ ];
503
+
504
+ slide.addText(pitchParts, {
505
+ x: 0.8,
506
+ y: 1.5,
507
+ w: 8.4,
508
+ h: 4.5,
509
+ fill: { color: COLORS.paleBlue },
510
+ valign: "middle",
511
+ paraSpaceAfter: 8,
512
+ });
513
+ }
514
+
515
+ // ─── Slide 4: どんな価値をもたらすのか? ───
516
+ {
517
+ const d = SLIDE_DATA.values;
518
+ const slide = pptx.addSlide();
519
+ addTitle(slide, "どんな価値をもたらすのか?");
520
+ addTable(
521
+ slide,
522
+ ["#", "ビジネス目標", "期待される効果"],
523
+ d.rows,
524
+ { colW: [0.4, 3.0, 5.6] }
525
+ );
526
+ }
527
+
528
+ // ─── Slide 5: やらないことリスト ───
529
+ {
530
+ const d = SLIDE_DATA.scope;
531
+ const slide = pptx.addSlide();
532
+ addTitle(slide, "やらないことリスト");
533
+ addSubtitle(slide, "スコープの範囲");
534
+
535
+ const colW = 2.85;
536
+ const colGap = 0.15;
537
+ const cols = [
538
+ { title: "やる(スコープ内)", color: COLORS.teal, items: d.inScope },
539
+ { title: "やらない(スコープ外)", color: COLORS.red, items: d.outOfScope },
540
+ { title: "あとで決める", color: COLORS.orange, items: d.decideLater },
541
+ ];
542
+
543
+ cols.forEach((col, i) => {
544
+ const x = 0.5 + i * (colW + colGap);
545
+ slide.addText(col.title, {
546
+ x,
547
+ y: 1.7,
548
+ w: colW,
549
+ h: 0.45,
550
+ fontSize: 13,
551
+ fontFace: FONT.body,
552
+ bold: true,
553
+ color: COLORS.white,
554
+ fill: { color: col.color },
555
+ align: "center",
556
+ valign: "middle",
557
+ });
558
+ const bullets = col.items.map((item) => ({
559
+ text: item,
560
+ options: {
561
+ fontSize: 11,
562
+ fontFace: FONT.body,
563
+ color: COLORS.black,
564
+ bullet: { type: "bullet" },
565
+ paraSpaceAfter: 4,
566
+ },
567
+ }));
568
+ slide.addText(bullets, {
569
+ x,
570
+ y: 2.2,
571
+ w: colW,
572
+ h: 4.8,
573
+ valign: "top",
574
+ });
575
+ });
576
+ }
577
+
578
+ // ─── Slide 6: プロジェクトコミュニティ ───
579
+ {
580
+ const d = SLIDE_DATA.stakeholders;
581
+ const slide = pptx.addSlide();
582
+ addTitle(slide, "プロジェクトコミュニティ");
583
+ addSubtitle(slide, "主なステークホルダーと関心事");
584
+ addTable(
585
+ slide,
586
+ ["ステークホルダー", "役割", "主な関心事"],
587
+ d.rows,
588
+ { y: 1.7, colW: [2.0, 2.2, 4.8] }
589
+ );
590
+ }
591
+
592
+ // ─── Slide 7: 技術的な解決策の概要 ───
593
+ {
594
+ const d = SLIDE_DATA.technicalSolution;
595
+ const slide = pptx.addSlide();
596
+ addTitle(slide, "技術的な解決策の概要");
597
+
598
+ const boxH = 0.5;
599
+
600
+ // 上部: 外部チャネル
601
+ const channelCount = d.externalChannels.length;
602
+ const channelW = Math.min(2.0, (9.0 - 0.2 * (channelCount - 1)) / channelCount);
603
+ const channelGap = channelCount > 1 ? (9.0 - channelW * channelCount) / (channelCount - 1) : 0;
604
+ d.externalChannels.forEach((ch, i) => {
605
+ const x = 0.5 + i * (channelW + channelGap);
606
+ slide.addShape("rect", {
607
+ x,
608
+ y: 1.5,
609
+ w: channelW,
610
+ h: boxH,
611
+ fill: { color: COLORS.lightTeal },
612
+ line: { color: COLORS.teal, width: 1 },
613
+ });
614
+ slide.addText(ch.name, {
615
+ x,
616
+ y: 1.5,
617
+ w: channelW,
618
+ h: boxH,
619
+ fontSize: 10,
620
+ fontFace: FONT.body,
621
+ color: COLORS.black,
622
+ align: "center",
623
+ valign: "middle",
624
+ });
625
+ });
626
+
627
+ // 接続ラベル
628
+ slide.addText("API", {
629
+ x: 4.0,
630
+ y: 2.05,
631
+ w: 1.0,
632
+ h: 0.3,
633
+ fontSize: 9,
634
+ fontFace: FONT.body,
635
+ color: COLORS.gray,
636
+ align: "center",
637
+ });
638
+
639
+ // 中央: メインシステム
640
+ slide.addShape("rect", {
641
+ x: 0.5,
642
+ y: 2.5,
643
+ w: 9.0,
644
+ h: 2.8,
645
+ fill: { color: "F8F8FF" },
646
+ line: { color: COLORS.darkBlue, width: 2 },
647
+ });
648
+ slide.addText(d.systemName, {
649
+ x: 0.5,
650
+ y: 2.5,
651
+ w: 9.0,
652
+ h: 0.4,
653
+ fontSize: 12,
654
+ fontFace: FONT.body,
655
+ bold: true,
656
+ color: COLORS.darkBlue,
657
+ align: "center",
658
+ });
659
+
660
+ // 内部モジュール
661
+ d.modules.forEach((name, i) => {
662
+ const row = Math.floor(i / 4);
663
+ const col = i % 4;
664
+ slide.addShape("roundRect", {
665
+ x: 0.8 + col * 2.15,
666
+ y: 3.0 + row * 0.7,
667
+ w: 1.95,
668
+ h: 0.55,
669
+ fill: { color: COLORS.darkBlue },
670
+ rectRadius: 0.05,
671
+ });
672
+ slide.addText(name, {
673
+ x: 0.8 + col * 2.15,
674
+ y: 3.0 + row * 0.7,
675
+ w: 1.95,
676
+ h: 0.55,
677
+ fontSize: 10,
678
+ fontFace: FONT.body,
679
+ color: COLORS.white,
680
+ align: "center",
681
+ valign: "middle",
682
+ });
683
+ });
684
+
685
+ // 下部: 外部連携先
686
+ const svcCount = d.externalServices.length;
687
+ const svcW = Math.min(2.0, (9.0 - 0.2 * (svcCount - 1)) / svcCount);
688
+ const svcGap = svcCount > 1 ? (9.0 - svcW * svcCount) / (svcCount - 1) : 0;
689
+ d.externalServices.forEach((svc, i) => {
690
+ const x = 0.5 + i * (svcW + svcGap);
691
+ slide.addShape("rect", {
692
+ x,
693
+ y: 5.6,
694
+ w: svcW,
695
+ h: boxH,
696
+ fill: { color: "FFF3E0" },
697
+ line: { color: COLORS.orange, width: 1 },
698
+ });
699
+ slide.addText(svc.name, {
700
+ x,
701
+ y: 5.6,
702
+ w: svcW,
703
+ h: boxH,
704
+ fontSize: 10,
705
+ fontFace: FONT.body,
706
+ color: COLORS.black,
707
+ align: "center",
708
+ valign: "middle",
709
+ });
710
+ });
711
+
712
+ // 技術方針ノート
713
+ slide.addText(d.techNote, {
714
+ x: 0.5,
715
+ y: 6.4,
716
+ w: 9.0,
717
+ h: 0.4,
718
+ fontSize: 10,
719
+ fontFace: FONT.body,
720
+ color: COLORS.gray,
721
+ align: "center",
722
+ });
723
+ }
724
+
725
+ // ─── Slide 8: 夜も眠れなくなるような問題 ───
726
+ {
727
+ const d = SLIDE_DATA.risks;
728
+ const slide = pptx.addSlide();
729
+ addTitle(slide, "夜も眠れなくなるような問題は何だろう?");
730
+ addTable(
731
+ slide,
732
+ ["#", "リスク", "影響度", "対策"],
733
+ d.rows,
734
+ { colW: [0.4, 3.0, 0.8, 4.8] }
735
+ );
736
+ }
737
+
738
+ // ─── Slide 9: 俺たちの "A チーム" ───
739
+ {
740
+ const d = SLIDE_DATA.team;
741
+ const slide = pptx.addSlide();
742
+ addTitle(slide, '俺たちの "A チーム"');
743
+ addTable(
744
+ slide,
745
+ ["役割", "人数", "備考"],
746
+ d.rows,
747
+ { y: 1.8, colW: [2.5, 1.5, 5.0] }
748
+ );
749
+ addHighlightBox(slide, d.highlight, { y: 4.0, h: 0.8, fontSize: 14 });
750
+ }
751
+
752
+ // ─── Slide 10: 期間を見極める ───
753
+ {
754
+ const d = SLIDE_DATA.timeline;
755
+ const slide = pptx.addSlide();
756
+ addTitle(slide, "期間を見極める");
757
+
758
+ const barStartX = 0.5;
759
+ const barMaxW = 9.0;
760
+ const phaseWeeks = d.phases.map((p) => p.weeks);
761
+
762
+ d.phases.forEach((phase, i) => {
763
+ const y = 1.8 + i * 1.0;
764
+ const startWeek = phaseWeeks.slice(0, i).reduce((a, b) => a + b, 0);
765
+ const x = barStartX + (startWeek / d.totalWeeks) * barMaxW;
766
+ const w = (phaseWeeks[i] / d.totalWeeks) * barMaxW;
767
+ const fillColor = i % 2 === 0 ? COLORS.lightTeal : COLORS.teal;
768
+
769
+ slide.addShape("rect", {
770
+ x,
771
+ y,
772
+ w,
773
+ h: 0.45,
774
+ fill: { color: fillColor },
775
+ line: { color: COLORS.teal, width: 1 },
776
+ });
777
+ slide.addText(phase.name, {
778
+ x,
779
+ y,
780
+ w,
781
+ h: 0.45,
782
+ fontSize: 10,
783
+ fontFace: FONT.body,
784
+ bold: true,
785
+ color: COLORS.darkBlue,
786
+ align: "center",
787
+ valign: "middle",
788
+ });
789
+ slide.addText(`${phase.desc}(${phase.weeksLabel})`, {
790
+ x,
791
+ y: y + 0.45,
792
+ w,
793
+ h: 0.35,
794
+ fontSize: 8,
795
+ fontFace: FONT.body,
796
+ color: COLORS.gray,
797
+ align: "center",
798
+ });
799
+ });
800
+
801
+ // MVP マーカー
802
+ if (d.mvpAfterPhase != null) {
803
+ const mvpWeeks = phaseWeeks
804
+ .slice(0, d.mvpAfterPhase + 1)
805
+ .reduce((a, b) => a + b, 0);
806
+ const mvpX = barStartX + (mvpWeeks / d.totalWeeks) * barMaxW;
807
+ slide.addShape("line", {
808
+ x: mvpX,
809
+ y: 1.5,
810
+ w: 0,
811
+ h: 5.0,
812
+ line: { color: COLORS.red, width: 2, dashType: "dash" },
813
+ });
814
+ slide.addText("MVP\nリリース", {
815
+ x: mvpX - 0.5,
816
+ y: 6.5,
817
+ w: 1.2,
818
+ h: 0.5,
819
+ fontSize: 10,
820
+ fontFace: FONT.body,
821
+ bold: true,
822
+ color: COLORS.red,
823
+ align: "center",
824
+ });
825
+ }
826
+
827
+ slide.addText("あくまで推測であって、確約するものではありません。", {
828
+ x: 0.5,
829
+ y: 7.0,
830
+ w: 9.0,
831
+ h: 0.3,
832
+ fontSize: 9,
833
+ fontFace: FONT.body,
834
+ color: COLORS.gray,
835
+ align: "right",
836
+ });
837
+ }
838
+
839
+ // ─── Slide 11: トレードオフ・スライダー ───
840
+ {
841
+ const d = SLIDE_DATA.tradeoffs;
842
+ const slide = pptx.addSlide();
843
+ addTitle(slide, "トレードオフ・スライダー");
844
+
845
+ d.sliders.forEach((s, i) => {
846
+ addSliderBar(slide, s.label, s.level, 1.8 + i * 0.7);
847
+ });
848
+
849
+ const qualityY = 1.8 + d.sliders.length * 0.7 + 0.3;
850
+ slide.addText("品質特性の優先順位", {
851
+ x: 0.5,
852
+ y: qualityY,
853
+ w: 9.0,
854
+ h: 0.4,
855
+ fontSize: 14,
856
+ fontFace: FONT.body,
857
+ bold: true,
858
+ color: COLORS.darkBlue,
859
+ });
860
+
861
+ addTable(
862
+ slide,
863
+ ["優先度", "品質特性", "理由"],
864
+ d.qualityPriorities,
865
+ { y: qualityY + 0.4, colW: [0.8, 1.8, 6.4] }
866
+ );
867
+ }
868
+
869
+ // ─── Slide 12: 初回のリリースに必要なもの ───
870
+ {
871
+ const d = SLIDE_DATA.initialRelease;
872
+ const slide = pptx.addSlide();
873
+ addTitle(slide, "初回のリリースに必要なもの");
874
+
875
+ addHighlightBox(slide, d.highlight, { y: 1.5, h: 0.7, fontSize: 16 });
876
+
877
+ slide.addText("MVP スコープ", {
878
+ x: 0.5,
879
+ y: 2.5,
880
+ w: 9.0,
881
+ h: 0.4,
882
+ fontSize: 14,
883
+ fontFace: FONT.body,
884
+ bold: true,
885
+ color: COLORS.darkBlue,
886
+ });
887
+
888
+ addBullets(slide, d.mvpScope, { y: 2.9, h: 2.5, fontSize: 13 });
889
+
890
+ slide.addText("リリース戦略", {
891
+ x: 0.5,
892
+ y: 5.2,
893
+ w: 9.0,
894
+ h: 0.4,
895
+ fontSize: 14,
896
+ fontFace: FONT.body,
897
+ bold: true,
898
+ color: COLORS.darkBlue,
899
+ });
900
+
901
+ addBullets(slide, d.releaseStrategy, { y: 5.6, h: 1.5, fontSize: 12 });
902
+ }
903
+
904
+ // ─── 保存 ───
905
+ const outputPath = resolve(`docs/analysis/slide/${meta.outputFileName}`);
906
+ const dataBuffer = await pptx.write({ outputType: "nodebuffer" });
907
+ writeFileSync(outputPath, dataBuffer);
908
+ console.log("Generated:", outputPath);
909
+ }
910
+
911
+ main().catch(console.error);