terminal-quest 1.1.2 → 1.2.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.
@@ -288,14 +288,18 @@ export const story01 = {
288
288
  },
289
289
  {
290
290
  id: 'obj-01-02-03',
291
- description: 'config.json の内容を表示する',
292
- checks: [{ type: 'output_contains', pattern: 'port' }],
291
+ description: '/etc/app/config.json の内容を表示する',
292
+ checks: [
293
+ { type: 'command_executed', command: 'cat' },
294
+ { type: 'output_contains', pattern: 'port' },
295
+ ],
293
296
  hints: [
294
297
  { level: 1, text: 'ファイルの内容を表示するコマンドがあります。' },
295
298
  { level: 2, text: '猫の鳴き声に似た3文字のコマンドです。パスを指定しましょう。' },
296
299
  { level: 3, text: '「cat app/config.json」と入力してください。' },
297
300
  ],
298
301
  feedbacks: [
302
+ { pattern: 'cat.*nginx', message: 'nginx.conf はWebサーバーの設定ファイルです。今回確認したいのはアプリの設定ファイル config.json です。app/ ディレクトリの中にあります。' },
299
303
  { pattern: 'pwd', message: 'pwd は現在の場所を表示するコマンドです。ファイルの内容を読むには、別のコマンドを使います。' },
300
304
  { pattern: 'ls', message: 'ls はファイル一覧を表示するコマンドです。ファイルの中身を読むには、別のコマンドを使います。' },
301
305
  { pattern: 'cd', message: 'cd はディレクトリ移動のコマンドです。ファイルの内容を読むには、別のコマンドを使います。' },
@@ -373,7 +377,7 @@ export const story01 = {
373
377
  objectives: [
374
378
  {
375
379
  id: 'obj-01-04-01',
376
- description: 'config.json のバックアップを作成する',
380
+ description: 'config.json のバックアップを作成する(.bak 拡張子をつける)',
377
381
  checks: [
378
382
  {
379
383
  type: 'file_exists',
@@ -420,7 +424,7 @@ export const story01 = {
420
424
  },
421
425
  ],
422
426
  hints: [
423
- { level: 1, text: 'echo コマンドとリダイレクト(>)を使ってファイルに書き込めます。' },
427
+ { level: 1, text: 'echo コマンドとリダイレクト(>)を使うと、ファイルの内容を丸ごと上書きできます。JSON形式で全体を書き直す必要があります。' },
424
428
  { level: 2, text: 'echo "内容" > ファイル名 の形式で、ファイルの内容を上書きできます。' },
425
429
  {
426
430
  level: 3,
@@ -428,7 +432,9 @@ export const story01 = {
428
432
  },
429
433
  ],
430
434
  feedbacks: [
431
- { pattern: 'cat', message: 'cat はファイルの内容を表示するコマンドです。ファイルに書き込むには、別のコマンドとリダイレクト(>)を使います。' },
435
+ { pattern: 'debug.*=.*true', message: 'JSON形式で書き込む必要があります。{"port": 3000, "debug": true} のような形式にしましょう。' },
436
+ { pattern: '>>', message: '>>(追記)ではなく >(上書き)を使いましょう。ファイルの内容を新しい内容に置き換えます。' },
437
+ { pattern: 'cat', message: 'cat はファイルの内容を表示するコマンドです。ファイルに書き込むには、echo コマンドとリダイレクト(>)を使います。' },
432
438
  { pattern: 'cp', message: 'cp はファイルをコピーするコマンドです。ファイルの内容を書き換えるには、echo とリダイレクト(>)を使いましょう。' },
433
439
  ],
434
440
  },
@@ -176,7 +176,7 @@ const appJsContent = [
176
176
  '});',
177
177
  '',
178
178
  '// End of file',
179
- ].join('\n');
179
+ ].join('\n') + '\n';
180
180
  const readmeContent = [
181
181
  '# My Project',
182
182
  '',
@@ -332,8 +332,9 @@ export const story02 = {
332
332
  { level: 3, text: '「mv app.js src/」と入力してEnterを押してください。' },
333
333
  ],
334
334
  feedbacks: [
335
- { pattern: 'cp', message: 'cp はファイルをコピーするコマンドです。元のファイルを残さず移動するには、別のコマンドを使います。' },
336
- { pattern: 'rm', message: 'rm はファイルを削除するコマンドです。ファイルを別の場所に移動するには、別のコマンドを使います。' },
335
+ { pattern: 'cp.*src', message: 'cp はファイルをコピーするコマンドです。コピーだと元の場所にもファイルが残ります。元のファイルを残さず移動するには mv を使いましょう。' },
336
+ { pattern: 'cp', message: 'cp はファイルをコピーするコマンドです。元のファイルを残さず移動するには、mv コマンドを使います。' },
337
+ { pattern: 'rm', message: 'rm はファイルを削除するコマンドです。ファイルを別の場所に移動するには、mv コマンドを使います。' },
337
338
  ],
338
339
  },
339
340
  {
@@ -345,6 +346,9 @@ export const story02 = {
345
346
  { level: 2, text: 'mv コマンドで readme.md を docs/ に移動します。' },
346
347
  { level: 3, text: '「mv readme.md docs/」と入力してEnterを押してください。' },
347
348
  ],
349
+ feedbacks: [
350
+ { pattern: 'cp.*docs', message: 'cp だと元の場所にもファイルが残ります。mv を使って移動しましょう。' },
351
+ ],
348
352
  },
349
353
  {
350
354
  id: 'obj-02-02-03',
@@ -355,29 +359,32 @@ export const story02 = {
355
359
  { level: 2, text: 'mv コマンドで test.js を tests/ に移動します。' },
356
360
  { level: 3, text: '「mv test.js tests/」と入力してEnterを押してください。' },
357
361
  ],
362
+ feedbacks: [
363
+ { pattern: 'cp.*tests', message: 'cp だと元の場所にもファイルが残ります。mv を使って移動しましょう。' },
364
+ ],
358
365
  },
359
366
  ],
360
367
  },
361
368
  {
362
369
  id: 'mission-02-03',
363
370
  title: '不要なファイルを削除',
364
- description: '.tmp ファイルや .bak ファイルを見つけて削除しよう。',
371
+ description: '.tmp ファイルを見つけて削除しよう。',
365
372
  goal: 'find で不要ファイルを検索し、rm で安全に削除できるようになる',
366
- narrative: 'プロジェクト内に .tmp ファイルや .bak ファイルが大量にある。不要ファイルを見つけて削除しよう。',
373
+ narrative: 'プロジェクト内に不要な .tmp ファイルが散らばっている。find コマンドで見つけ出して、rm で削除しよう。',
367
374
  initialCwd: '/home/dev/project',
368
375
  newCommands: ['rm', 'find'],
369
376
  initialFS: mission3FS,
370
377
  objectives: [
371
378
  {
372
379
  id: 'obj-02-03-01',
373
- description: '.tmp ファイルを見つける',
380
+ description: 'find コマンドで .tmp ファイルをすべて見つける',
374
381
  checks: [
375
382
  { type: 'command_executed', command: 'find' },
376
383
  { type: 'output_contains', pattern: '.tmp' },
377
384
  ],
378
385
  hints: [
379
386
  { level: 1, text: 'ファイルを検索するコマンドがあります。' },
380
- { level: 2, text: 'find コマンドで名前パターンを指定して検索できます。' },
387
+ { level: 2, text: 'find コマンドは現在のディレクトリとサブディレクトリをすべて検索します。-name オプションで名前パターンを指定します。' },
381
388
  { level: 3, text: '「find . -name "*.tmp"」と入力してEnterを押してください。' },
382
389
  ],
383
390
  feedbacks: [
@@ -219,16 +219,26 @@ export const story03 = {
219
219
  { level: 2, text: 'grep コマンドに検索パターンとファイル名を指定します。' },
220
220
  { level: 3, text: '「grep 500 access.log」と入力してEnterを押してください。' },
221
221
  ],
222
+ feedbacks: [
223
+ { pattern: 'cat', message: 'cat はファイル全体を表示します。特定の文字列を含む行だけを抽出するには grep を使いましょう。' },
224
+ { pattern: 'grep.*error', message: 'ログ内のステータスコードは数字(500)で記録されています。「error」ではなく「500」で検索してみましょう。' },
225
+ ],
222
226
  },
223
227
  {
224
228
  id: 'obj-03-02-02',
225
- description: 'エラーの件数を数える(5件)',
226
- checks: [{ type: 'output_contains', pattern: '5' }],
229
+ description: '500 エラーが何件あるか数えて表示する',
230
+ checks: [
231
+ { type: 'command_executed', command: 'wc' },
232
+ { type: 'output_contains', pattern: '5' },
233
+ ],
227
234
  hints: [
228
- { level: 1, text: '行数を数えるコマンドとパイプを組み合わせましょう。' },
235
+ { level: 1, text: 'grep の結果をさらに別のコマンドに渡して行数を数えられます。| (パイプ)という記号でコマンドをつなぎます。' },
229
236
  { level: 2, text: 'grep の結果を wc -l にパイプで渡すと行数を数えられます。' },
230
237
  { level: 3, text: '「grep 500 access.log | wc -l」と入力してEnterを押してください。' },
231
238
  ],
239
+ feedbacks: [
240
+ { pattern: 'wc.*access', message: 'wc -l をファイルに直接使うと全行数が出ます。500 エラーの行だけを数えるには、まず grep で抽出してからパイプ(|)で wc -l に渡しましょう。' },
241
+ ],
232
242
  },
233
243
  ],
234
244
  },
@@ -248,19 +258,25 @@ export const story03 = {
248
258
  checks: [{ type: 'command_executed', command: 'sort' }],
249
259
  hints: [
250
260
  { level: 1, text: 'データを並び替えるコマンドがあります。' },
251
- { level: 2, text: 'sort コマンドで行を並び替えることができます。' },
261
+ { level: 2, text: 'sort コマンドで行をアルファベット順に並び替えることができます。' },
252
262
  { level: 3, text: '「sort ip-list.txt」と入力してEnterを押してください。' },
253
263
  ],
264
+ feedbacks: [
265
+ { pattern: 'uniq', message: 'uniq の前に、まず sort でデータを並べ替えましょう。sort → uniq の順番が大切です。' },
266
+ ],
254
267
  },
255
268
  {
256
269
  id: 'obj-03-03-02',
257
- description: 'ユニークなIPアドレスの一覧を出す',
270
+ description: 'IPアドレスの出現回数つき一覧を表示する(sort | uniq -c)',
258
271
  checks: [{ type: 'command_executed', command: 'uniq' }],
259
272
  hints: [
260
- { level: 1, text: '重複を除去するコマンドがあります。sort と組み合わせましょう。' },
273
+ { level: 1, text: '重複する行を1つにまとめるコマンドがあります。ただし、隣り合った行しか比較しないため、先にデータを並べ替えておく必要があります。' },
261
274
  { level: 2, text: 'sort の結果をパイプで uniq に渡します。uniq -c で件数も表示できます。' },
262
275
  { level: 3, text: '「sort ip-list.txt | uniq -c」と入力してEnterを押してください。' },
263
276
  ],
277
+ feedbacks: [
278
+ { pattern: 'uniq.*ip-list', message: 'uniq は隣り合った重複行しか除去しません。先に sort でデータを並べ替えてからパイプで uniq に渡しましょう。' },
279
+ ],
264
280
  },
265
281
  ],
266
282
  },
@@ -240,15 +240,26 @@ export const story04 = {
240
240
  },
241
241
  {
242
242
  id: 'obj-04-01-02',
243
- description: 'source の内容をデプロイ先にコピーする',
243
+ description: 'source/README.md を /var/www/app/ にコピーする',
244
244
  checks: [{ type: 'file_exists', path: '/var/www/app/README.md' }],
245
245
  hints: [
246
- { level: 1, text: 'ディレクトリごとコピーするオプションがあります。' },
247
- { level: 2, text: 'cp -r オプションをつけると、ディレクトリを再帰的にコピーできます。' },
248
- { level: 3, text: '「cp -r source/* /var/www/app/」と入力してEnterを押してください。' },
246
+ { level: 1, text: 'ファイルをコピーするコマンドがあります。' },
247
+ { level: 2, text: 'cp コマンドで source 内のファイルをデプロイ先にコピーできます。' },
248
+ { level: 3, text: '「cp source/README.md /var/www/app/」と入力してEnterを押してください。' },
249
249
  ],
250
250
  },
251
251
  ],
252
+ review: {
253
+ question: 'mkdir -p オプションの効果は何ですか?',
254
+ choices: [
255
+ '空のディレクトリだけを作成する',
256
+ '親ディレクトリも含めて一度に作成する',
257
+ 'ディレクトリの権限を変更する',
258
+ '既存ディレクトリを上書きする',
259
+ ],
260
+ correctIndex: 1,
261
+ explanation: 'mkdir -p は親ディレクトリが存在しなくても、必要な階層をすべて一度に作成できます。例えば /var/www/app なら、/var と /var/www も同時に作られます。',
262
+ },
252
263
  },
253
264
  {
254
265
  id: 'mission-04-02',
@@ -262,15 +273,30 @@ export const story04 = {
262
273
  objectives: [
263
274
  {
264
275
  id: 'obj-04-02-01',
265
- description: 'start.sh に実行権限を付与する',
276
+ description: 'chmod +x で start.sh に実行権限を付与する',
266
277
  checks: [{ type: 'command_executed', command: 'chmod' }],
267
278
  hints: [
268
- { level: 1, text: 'ファイルの権限を変更するコマンドがあります。' },
279
+ { level: 1, text: 'スクリプトを実行するには、実行権限を付与する必要があります。権限を変更するコマンドを使いましょう。' },
269
280
  { level: 2, text: 'chmod コマンドで権限を変更します。+x で実行権限を追加できます。' },
270
281
  { level: 3, text: '「chmod +x start.sh」と入力してEnterを押してください。' },
271
282
  ],
283
+ feedbacks: [
284
+ { pattern: 'chmod x ', message: '「+x」の + を忘れていませんか?権限を追加するには + が必要です。' },
285
+ { pattern: 'chmod 755', message: '数値指定でもOKですが、このミッションでは +x の書き方を練習しましょう。' },
286
+ ],
272
287
  },
273
288
  ],
289
+ review: {
290
+ question: 'chmod +x の「+x」は何を意味しますか?',
291
+ choices: [
292
+ 'ファイルを削除する許可を追加する',
293
+ 'ファイルの実行権限を追加する',
294
+ 'ファイルの書き込み権限を追加する',
295
+ 'ファイルの読み取り権限を追加する',
296
+ ],
297
+ correctIndex: 1,
298
+ explanation: '+x は実行(eXecute)権限を追加します。シェルスクリプトを動かすには実行権限が必要です。+r は読み取り、+w は書き込みを表します。',
299
+ },
274
300
  },
275
301
  {
276
302
  id: 'mission-04-03',
@@ -283,13 +309,16 @@ export const story04 = {
283
309
  objectives: [
284
310
  {
285
311
  id: 'obj-04-03-01',
286
- description: '古いバックアップ(backup-old)を削除する',
312
+ description: 'rm -rf で古いバックアップ(backup-old)をまるごと削除する',
287
313
  checks: [{ type: 'file_not_exists', path: '/var/www/backup-old' }],
288
314
  hints: [
289
- { level: 1, text: 'ディレクトリを削除するにはオプションが必要です。' },
315
+ { level: 1, text: '中身の入ったディレクトリをまるごと削除するには、rm に特別なオプションが必要です。' },
290
316
  { level: 2, text: 'rm に -rf オプションをつけると、ディレクトリを中身ごと削除できます。' },
291
317
  { level: 3, text: '「rm -rf backup-old」と入力してEnterを押してください。' },
292
318
  ],
319
+ feedbacks: [
320
+ { pattern: '^rm backup-old$', message: 'ディレクトリを削除するには -rf オプションが必要です。rm -rf backup-old としましょう。' },
321
+ ],
293
322
  },
294
323
  {
295
324
  id: 'obj-04-03-02',
@@ -302,6 +331,17 @@ export const story04 = {
302
331
  ],
303
332
  },
304
333
  ],
334
+ review: {
335
+ question: 'rm -rf の -r と -f はそれぞれ何を意味しますか?',
336
+ choices: [
337
+ '-r は読み取り、-f はファイル指定',
338
+ '-r は再帰(中身ごと削除)、-f は強制(確認なし)',
339
+ '-r はリネーム、-f はフォルダ指定',
340
+ '-r は復元、-f は高速モード',
341
+ ],
342
+ correctIndex: 1,
343
+ explanation: '-r(recursive)はディレクトリを中身ごと再帰的に削除します。-f(force)は確認メッセージなしで強制的に削除します。非常に強力なので、対象を間違えないよう注意が必要です。',
344
+ },
305
345
  },
306
346
  {
307
347
  id: 'mission-04-04',
@@ -324,8 +364,11 @@ export const story04 = {
324
364
  },
325
365
  {
326
366
  id: 'obj-04-04-02',
327
- description: '設定ファイルの内容を確認して production であることを確かめる',
328
- checks: [{ type: 'output_contains', pattern: 'production' }],
367
+ description: 'cat config.json で設定を表示し、production であることを確かめる',
368
+ checks: [
369
+ { type: 'command_executed', command: 'cat' },
370
+ { type: 'output_contains', pattern: 'production' },
371
+ ],
329
372
  hints: [
330
373
  { level: 1, text: 'ファイルの内容を表示するコマンドで設定を確認しましょう。' },
331
374
  { level: 2, text: 'cat コマンドで config.json の内容を表示できます。' },
@@ -333,6 +376,17 @@ export const story04 = {
333
376
  ],
334
377
  },
335
378
  ],
379
+ review: {
380
+ question: 'デプロイ後の最終確認として適切なのはどれですか?',
381
+ choices: [
382
+ 'すぐにサーバーを再起動する',
383
+ 'find と cat で必要なファイルが揃っているか確認する',
384
+ 'rm -rf で不要ファイルを全て削除する',
385
+ '何もせずにデプロイ完了とする',
386
+ ],
387
+ correctIndex: 1,
388
+ explanation: 'デプロイ後は find でファイル一覧を確認し、cat で設定ファイルの内容が正しいか確かめることが重要です。思い込みで確認を飛ばすと、本番障害の原因になりかねません。',
389
+ },
336
390
  },
337
391
  ],
338
392
  unlockRequires: ['story-02', 'story-03'],
@@ -76,13 +76,13 @@ const mission1FS = {
76
76
  },
77
77
  branches: {
78
78
  type: 'file',
79
- content: '* main\n feature/login\n feature/api',
79
+ content: 'main\nfeature/login\nfeature/api',
80
80
  },
81
81
  log: {
82
82
  type: 'file',
83
83
  content: gitLogContent,
84
84
  },
85
- 'status-output': {
85
+ status: {
86
86
  type: 'file',
87
87
  content: gitStatusOutput,
88
88
  },
@@ -141,20 +141,16 @@ const mission2FS = {
141
141
  },
142
142
  branches: {
143
143
  type: 'file',
144
- content: '* main\n feature/login\n feature/api',
144
+ content: 'main\nfeature/login\nfeature/api',
145
145
  },
146
146
  log: {
147
147
  type: 'file',
148
148
  content: gitLogContent,
149
149
  },
150
- 'status-output': {
150
+ status: {
151
151
  type: 'file',
152
152
  content: gitStatusOutput,
153
153
  },
154
- stash: {
155
- type: 'file',
156
- content: '',
157
- },
158
154
  },
159
155
  },
160
156
  src: {
@@ -210,7 +206,7 @@ const mission3FS = {
210
206
  },
211
207
  branches: {
212
208
  type: 'file',
213
- content: '* main\n feature/login\n feature/api',
209
+ content: 'main\nfeature/login\nfeature/api',
214
210
  },
215
211
  log: {
216
212
  type: 'file',
@@ -260,13 +256,13 @@ const mission4FS = {
260
256
  },
261
257
  branches: {
262
258
  type: 'file',
263
- content: ' main\n feature/login\n feature/api\n* hotfix',
259
+ content: 'main\nfeature/login\nfeature/api\nhotfix',
264
260
  },
265
261
  log: {
266
262
  type: 'file',
267
263
  content: gitLogContent,
268
264
  },
269
- 'diff-output': {
265
+ diff: {
270
266
  type: 'file',
271
267
  content: gitDiffOutput,
272
268
  },
@@ -325,7 +321,7 @@ const mission5FS = {
325
321
  },
326
322
  branches: {
327
323
  type: 'file',
328
- content: ' main\n feature/login\n feature/api\n* hotfix',
324
+ content: 'main\nfeature/login\nfeature/api\nhotfix',
329
325
  },
330
326
  log: {
331
327
  type: 'file',
@@ -387,13 +383,13 @@ export const story05 = {
387
383
  objectives: [
388
384
  {
389
385
  id: 'obj-05-01-01',
390
- description: '現在の状態を確認する(変更されたファイルを発見する)',
386
+ description: 'git status で変更されたファイルを確認する',
391
387
  checks: [
392
388
  { type: 'command_executed', command: 'git' },
393
389
  { type: 'output_contains', pattern: 'modified' },
394
390
  ],
395
391
  hints: [
396
- { level: 1, text: 'リポジトリの状態を確認するGitコマンドがあります。' },
392
+ { level: 1, text: '変更されたファイルや現在の状態を一覧表示するGitコマンドがあります。' },
397
393
  { level: 2, text: 'git status コマンドで変更されたファイルを確認できます。' },
398
394
  { level: 3, text: '「git status」と入力してEnterを押してください。' },
399
395
  ],
@@ -403,7 +399,7 @@ export const story05 = {
403
399
  description: 'コミット履歴を確認する',
404
400
  checks: [
405
401
  { type: 'command_executed', command: 'git' },
406
- { type: 'output_contains', pattern: 'commit' },
402
+ { type: 'output_contains', pattern: 'Author' },
407
403
  ],
408
404
  hints: [
409
405
  { level: 1, text: 'コミットの履歴を表示するGitコマンドがあります。' },
@@ -412,6 +408,17 @@ export const story05 = {
412
408
  ],
413
409
  },
414
410
  ],
411
+ review: {
412
+ question: 'git status と git log の違いは何ですか?',
413
+ choices: [
414
+ 'どちらも同じ情報を表示する',
415
+ 'status は現在の変更状態、log は過去のコミット履歴を表示する',
416
+ 'status はブランチ一覧、log はファイル一覧を表示する',
417
+ 'status は削除専用、log は作成専用のコマンド',
418
+ ],
419
+ correctIndex: 1,
420
+ explanation: 'git status は「今どのファイルが変更されているか」を表示します。git log は「過去にどんなコミットがあったか」の履歴を表示します。どちらも状況把握に重要です。',
421
+ },
415
422
  },
416
423
  {
417
424
  id: 'mission-05-02',
@@ -424,18 +431,29 @@ export const story05 = {
424
431
  objectives: [
425
432
  {
426
433
  id: 'obj-05-02-01',
427
- description: '変更を stash に退避する',
434
+ description: 'git stash で作業中の変更を一時退避する',
428
435
  checks: [
429
436
  { type: 'command_executed', command: 'git' },
430
437
  { type: 'output_contains', pattern: 'stash' },
431
438
  ],
432
439
  hints: [
433
440
  { level: 1, text: '変更を一時的に退避するGitコマンドがあります。' },
434
- { level: 2, text: 'git stash コマンドで変更を退避できます。後で git stash pop で戻せます。' },
441
+ { level: 2, text: 'git stash コマンドで変更を一時保存し、作業ツリーをクリーンな状態にできます。' },
435
442
  { level: 3, text: '「git stash」と入力してEnterを押してください。' },
436
443
  ],
437
444
  },
438
445
  ],
446
+ review: {
447
+ question: 'git stash はどんな時に使いますか?',
448
+ choices: [
449
+ '変更を完全に削除したい時',
450
+ '作業中の変更を一時的に退避して、後で戻したい時',
451
+ '新しいブランチを作成する時',
452
+ 'ファイルの名前を変更する時',
453
+ ],
454
+ correctIndex: 1,
455
+ explanation: 'git stash は「今の作業を一旦棚に上げる」イメージです。別の作業をした後、git stash pop で棚から戻せます。変更を失わずに安全に退避できる便利なコマンドです。',
456
+ },
439
457
  },
440
458
  {
441
459
  id: 'mission-05-03',
@@ -471,8 +489,23 @@ export const story05 = {
471
489
  { level: 2, text: 'git checkout に -b オプションをつけると、新しいブランチを作って切り替えられます。' },
472
490
  { level: 3, text: '「git checkout -b hotfix」と入力してEnterを押してください。' },
473
491
  ],
492
+ feedbacks: [
493
+ { pattern: 'git branch hotfix$', message: 'git branch だけではブランチの作成のみで、切り替えはされません。git checkout -b hotfix で作成と切り替えを同時にできます。' },
494
+ { pattern: 'git checkout hotfix$', message: '-b をつけないと既存ブランチへの切り替えになります。新規作成するには git checkout -b hotfix です。' },
495
+ ],
474
496
  },
475
497
  ],
498
+ review: {
499
+ question: '「git checkout -b hotfix」は何をしますか?',
500
+ choices: [
501
+ 'hotfix ブランチを削除する',
502
+ 'hotfix という名前の新しいブランチを作成し、そこに切り替える',
503
+ '既存の hotfix ブランチに切り替える',
504
+ 'hotfix ブランチの変更をマージする',
505
+ ],
506
+ correctIndex: 1,
507
+ explanation: 'git checkout -b は「新しいブランチを作って同時に切り替える」コマンドです。-b なしの git checkout hotfix は「既にある hotfix ブランチに切り替える」だけです。',
508
+ },
476
509
  },
477
510
  {
478
511
  id: 'mission-05-04',
@@ -485,7 +518,7 @@ export const story05 = {
485
518
  objectives: [
486
519
  {
487
520
  id: 'obj-05-04-01',
488
- description: 'diff で変更内容を確認する',
521
+ description: 'git diff でコードの変更差分を確認する',
489
522
  checks: [
490
523
  { type: 'command_executed', command: 'git' },
491
524
  { type: 'output_contains', pattern: '@@' },
@@ -497,6 +530,17 @@ export const story05 = {
497
530
  ],
498
531
  },
499
532
  ],
533
+ review: {
534
+ question: 'git diff の出力で「+」と「-」は何を表しますか?',
535
+ choices: [
536
+ '+ は追加された行、- は削除された行',
537
+ '+ はファイル作成、- はファイル削除',
538
+ '+ は正しいコード、- は間違ったコード',
539
+ '+ はコミット済み、- は未コミット',
540
+ ],
541
+ correctIndex: 0,
542
+ explanation: 'git diff では + の行は新しく追加された行、- の行は削除された行を表します。変更前と変更後の差分がひと目でわかるので、レビューに欠かせないコマンドです。',
543
+ },
500
544
  },
501
545
  {
502
546
  id: 'mission-05-05',
@@ -510,7 +554,10 @@ export const story05 = {
510
554
  {
511
555
  id: 'obj-05-05-01',
512
556
  description: 'main ブランチに切り替える',
513
- checks: [{ type: 'command_executed', command: 'git' }],
557
+ checks: [
558
+ { type: 'command_executed', command: 'git' },
559
+ { type: 'output_contains', pattern: 'Switched' },
560
+ ],
514
561
  hints: [
515
562
  { level: 1, text: 'ブランチを切り替えるGitコマンドがあります。' },
516
563
  { level: 2, text: 'git checkout コマンドでブランチを切り替えられます。' },
@@ -529,8 +576,22 @@ export const story05 = {
529
576
  { level: 2, text: 'git merge コマンドで指定したブランチの変更を現在のブランチに統合できます。' },
530
577
  { level: 3, text: '「git merge hotfix」と入力してEnterを押してください。' },
531
578
  ],
579
+ feedbacks: [
580
+ { pattern: 'git merge main$', message: '今 main ブランチにいるので、main をマージしても意味がありません。統合したいブランチ名(hotfix)を指定しましょう。' },
581
+ ],
532
582
  },
533
583
  ],
584
+ review: {
585
+ question: 'git merge hotfix を実行する前に必要なことは何ですか?',
586
+ choices: [
587
+ 'hotfix ブランチを削除する',
588
+ '統合先のブランチ(main)に切り替えておく',
589
+ 'すべてのファイルを削除する',
590
+ 'git stash を実行する',
591
+ ],
592
+ correctIndex: 1,
593
+ explanation: 'git merge は「現在いるブランチに、指定したブランチの変更を取り込む」コマンドです。なので、先に統合先のブランチ(この場合は main)に切り替えておく必要があります。',
594
+ },
534
595
  },
535
596
  ],
536
597
  unlockRequires: ['story-02'],