@k2works/claude-code-booster 1.10.0 → 1.11.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 (93) 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 +89 -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/tracking-progress/SKILL.md +164 -164
  39. package/lib/assets/.devcontainer/devcontainer.json +34 -34
  40. package/lib/assets/.env.example +17 -17
  41. package/lib/assets/.gitattributes +4 -4
  42. package/lib/assets/.github/workflows/docker-publish.yml +77 -77
  43. package/lib/assets/.github/workflows/mkdocs.yml +39 -39
  44. package/lib/assets/AGENTS.md +94 -94
  45. package/lib/assets/CLAUDE.md +162 -162
  46. package/lib/assets/README.md +285 -269
  47. package/lib/assets/docker-compose.yml +33 -33
  48. package/lib/assets/docs/assets/css/extra.css +29 -29
  49. package/lib/assets/docs/assets/js/extra.js +44 -44
  50. package/lib/assets/docs/index.md +14 -14
  51. 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
  52. 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
  53. 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
  54. 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
  55. package/lib/assets/docs/reference/UI/350/250/255/350/250/210/343/202/254/343/202/244/343/203/211.md +448 -448
  56. 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
  57. 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
  58. 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
  59. 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
  60. 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
  61. 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
  62. 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
  63. 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
  64. 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
  65. 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
  66. 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
  67. 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
  68. 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
  69. 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
  70. 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
  71. package/lib/assets/docs/reference//351/226/213/347/231/272/343/202/254/343/202/244/343/203/211.md +235 -235
  72. 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
  73. package/lib/assets/docs/template/ADR.md +30 -30
  74. package/lib/assets/docs/template/README.md +50 -50
  75. 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
  76. 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
  77. 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
  78. 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
  79. 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
  80. package/lib/assets/docs/template//350/246/201/344/273/266/345/256/232/347/276/251.md +669 -669
  81. package/lib/assets/docs/template//350/250/255/350/250/210.md +163 -163
  82. package/lib/assets/gulpfile.js +23 -23
  83. package/lib/assets/mkdocs.yml +65 -65
  84. package/lib/assets/ops/docker/mkdoc/Dockerfile +19 -19
  85. package/lib/assets/ops/scripts/journal.js +180 -180
  86. package/lib/assets/ops/scripts/mkdocs.js +82 -82
  87. package/lib/assets/ops/scripts/release.js +431 -431
  88. package/lib/assets/ops/scripts/ssh.js +190 -190
  89. package/lib/assets/ops/scripts/vault.js +299 -299
  90. package/lib/assets/package-lock.json +1653 -1653
  91. package/lib/assets/package.json +40 -40
  92. package/lib/gulpfile.js +37 -37
  93. 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);