terminal-quest 1.0.2 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -26,12 +26,24 @@ npm install -g terminal-quest
26
26
  terminal-quest
27
27
  ```
28
28
 
29
+ ### 動作環境
30
+
31
+ - **macOS** / **Linux** / **Windows(WSL)**
32
+ - Node.js v18 以上
33
+ - 外部依存なし(Docker不要、GUI不要、ネットワーク不要)
34
+
35
+ 純粋なnpmパッケージとして動作する**TUIアプリ**です。Ink(React for CLI)で構築されており、ターミナルさえあればどこでも動きます。
36
+
29
37
  ## 特徴
30
38
 
31
- - **安全** - すべてのコマンドは仮想ファイルシステム上で動作。実際のファイルには一切触れません
39
+ - **安全な仮想環境** - すべてのコマンドはインメモリの仮想ファイルシステム上で動作。実際のファイルには一切触れません
32
40
  - **ストーリー駆動** - 物語を進めながら自然にコマンドを習得
33
- - **3つのコース** - レベルに合わせて選べるコース
34
- - **段階的ヒント** - 困ったら3段階のヒントで学習をサポート
41
+ - **3つのコース** - 小学生向け・はじめて・エンジニアの3コースで、レベルに合わせた学習体験
42
+ - **結果ベースの判定** - コマンドの文字列ではなく実行結果で目標達成を判定。`grep foo file` でも `cat file | grep foo` でも正解になります
43
+ - **到達目標** - 各ミッションに「何ができるようになるか」を明示
44
+ - **段階的ヒント** - 困ったら3段階(方向性→コマンド候補→ほぼ答え)のヒントで学習をサポート
45
+ - **間違いフィードバック** - タイプミスには類似コマンドを提案、間違ったコマンドにはミッション固有のガイダンスを表示
46
+ - **ふりかえり問題** - ミッション完了後に4択クイズで理解度チェック
35
47
  - **Tab補完** - コマンド名やファイルパスをTabキーで補完
36
48
  - **達成バッジ** - 学習の進捗に応じてバッジを獲得
37
49
 
@@ -88,6 +100,27 @@ objectives 現在の目標を確認
88
100
  man <cmd> コマンドのマニュアル
89
101
  ```
90
102
 
103
+ ## アーキテクチャ
104
+
105
+ ```
106
+ src/
107
+ ├── engine/ コアエンジン(UI非依存)
108
+ │ ├── VirtualFS インメモリ仮想ファイルシステム
109
+ │ ├── CommandHandler 23コマンド実装(パイプ・リダイレクト対応)
110
+ │ ├── MissionEngine 結果ベースの目標判定
111
+ │ ├── HintEngine 3段階ヒント管理
112
+ │ ├── CommandFeedback タイプミス検出・ミッション固有フィードバック
113
+ │ └── TabCompletion コマンド名・パス補完
114
+ ├── data/stories/ ストーリーデータ(コース別、全9ストーリー)
115
+ ├── screens/ 7画面コンポーネント(Ink/React)
116
+ ├── components/ 再利用UIコンポーネント
117
+ └── state/ ゲーム状態管理(~/.terminal-quest/progress.json に永続化)
118
+ ```
119
+
120
+ - **仮想FS上で完結** — 実際のファイルシステムやシェルは一切使用しません
121
+ - **結果ベースの判定** — ObjectiveCheck がコマンドの出力・FS状態を検査し、解法の自由度を確保
122
+ - **コース別UX** — kids コースはひらがな中心のガイダンスとエラーメッセージ、engineer コースは実務寄りの表現
123
+
91
124
  ## 開発
92
125
 
93
126
  ```bash
@@ -95,10 +128,16 @@ git clone https://github.com/nasuda/terminal-quest.git
95
128
  cd terminal-quest
96
129
  npm install
97
130
  npm run dev # 開発実行
98
- npm test # テスト実行
131
+ npm test # テスト実行(183件)
99
132
  npm run build # ビルド
100
133
  ```
101
134
 
135
+ ### 技術スタック
136
+
137
+ - TypeScript (ESM) + [Ink 5](https://github.com/vadimdemedes/ink) (React for CLI)
138
+ - Vitest(テスト)
139
+ - npm配布(`bin/terminal-quest.js` → `dist/index.js` 直接import、tsx不要)
140
+
102
141
  ## ライセンス
103
142
 
104
143
  MIT
package/dist/App.js CHANGED
@@ -21,7 +21,7 @@ export function App() {
21
21
  case 'terminal':
22
22
  return (_jsx(TerminalScreen, { storyId: screen.storyId, missionIndex: screen.missionIndex, onNavigate: navigateTo, onMissionComplete: completeMission, onStoryComplete: completeStory, onCommandExecuted: incrementCommands }));
23
23
  case 'missionComplete':
24
- return (_jsx(MissionCompleteScreen, { storyId: screen.storyId, missionIndex: screen.missionIndex, onNavigate: navigateTo }));
24
+ return (_jsx(MissionCompleteScreen, { storyId: screen.storyId, missionIndex: screen.missionIndex, commandCount: screen.commandCount, onNavigate: navigateTo }));
25
25
  case 'progress':
26
26
  return _jsx(ProgressScreen, { progress: progress, onNavigate: navigateTo });
27
27
  case 'settings':
@@ -449,6 +449,13 @@ export const story00 = {
449
449
  id: 'mission-00-01',
450
450
  title: 'ここはどこ?',
451
451
  description: '新しいパソコンを開いたら、まずは今いる場所を確認して、どんなファイルがあるか見てみよう。',
452
+ goal: 'pwd と ls を使って、今いる場所とファイル構成を把握できるようになる',
453
+ review: {
454
+ question: '今いるフォルダの場所を表示するコマンドはどれですか?',
455
+ choices: ['ls', 'cd', 'pwd', 'cat'],
456
+ correctIndex: 2,
457
+ explanation: 'pwd (Print Working Directory) は、今いるフォルダのパスを画面に表示するコマンドです。',
458
+ },
452
459
  narrative: 'やったー!新しいパソコンが届いた!さっそくターミナルを開いてみたけど、真っ黒な画面が出てきた。ここはどこだろう?まずは自分がどこにいるのか確認して、周りに何があるのか見てみよう。',
453
460
  initialCwd: '/home/watashi',
454
461
  initialFS: mission1FS,
@@ -481,6 +488,13 @@ export const story00 = {
481
488
  id: 'mission-00-02',
482
489
  title: 'フォルダの中を見て回ろう',
483
490
  description: 'フォルダを移動して、中にあるファイルを読んでみよう。',
491
+ goal: 'cd でフォルダを移動し、cat でファイルの中身を読めるようになる',
492
+ review: {
493
+ question: 'フォルダを移動するコマンドはどれですか?',
494
+ choices: ['ls', 'cd', 'cat', 'pwd'],
495
+ correctIndex: 1,
496
+ explanation: 'cd (Change Directory) は、別のフォルダに移動するコマンドです。',
497
+ },
484
498
  narrative: 'パソコンの中には「写真」「音楽」「レシピ」などのフォルダがあるみたい。フォルダの中に移動して、どんなファイルがあるか見てみよう!',
485
499
  initialCwd: '/home/watashi',
486
500
  initialFS: mission2FS,
@@ -523,6 +537,13 @@ export const story00 = {
523
537
  id: 'mission-00-03',
524
538
  title: '写真アルバムを作ろう',
525
539
  description: '写真を整理するために、新しいフォルダやファイルを作ってみよう。',
540
+ goal: 'mkdir でフォルダを作り、touch で空のファイルを作れるようになる',
541
+ review: {
542
+ question: '新しいフォルダを作るコマンドはどれですか?',
543
+ choices: ['touch', 'mkdir', 'cp', 'mv'],
544
+ correctIndex: 1,
545
+ explanation: 'mkdir (Make Directory) は、新しいフォルダ(ディレクトリ)を作成するコマンドです。',
546
+ },
526
547
  narrative: '写真フォルダの中がごちゃごちゃしてきた。「旅行」や「ペット」のフォルダを作って、写真を整理しよう!',
527
548
  initialCwd: '/home/watashi/写真',
528
549
  initialFS: mission3FS,
@@ -565,6 +586,13 @@ export const story00 = {
565
586
  id: 'mission-00-04',
566
587
  title: 'ファイルを整理しよう',
567
588
  description: '間違った場所にあるファイルをコピーしたり、移動したりして整理しよう。',
589
+ goal: 'cp でファイルをコピーし、mv や rm で整理できるようになる',
590
+ review: {
591
+ question: 'ファイルをコピーするコマンドはどれですか?',
592
+ choices: ['mv', 'cp', 'rm', 'cat'],
593
+ correctIndex: 1,
594
+ explanation: 'cp (Copy) は、ファイルやフォルダをコピーするコマンドです。mv は移動、rm は削除です。',
595
+ },
568
596
  narrative: 'あれ?「大事なメモ.txt」がホームフォルダに置きっぱなしだ。これは「メモ」フォルダに入れるべきだよね。ファイルをコピーしたり移動したりして、きちんと整理しよう。',
569
597
  initialCwd: '/home/watashi',
570
598
  initialFS: mission4FS,
@@ -597,6 +625,13 @@ export const story00 = {
597
625
  id: 'mission-00-05',
598
626
  title: 'いらないファイルを片付けよう',
599
627
  description: '不要な一時ファイルを探し出して、削除しよう。',
628
+ goal: 'find でファイルを検索し、rm で不要ファイルを削除できるようになる',
629
+ review: {
630
+ question: 'ファイルを名前で検索するコマンドはどれですか?',
631
+ choices: ['grep', 'find', 'ls', 'cat'],
632
+ correctIndex: 1,
633
+ explanation: 'find は、ファイルやフォルダを名前やタイプで検索するコマンドです。grep はファイルの中身を検索します。',
634
+ },
600
635
  narrative: 'パソコンの中に「.tmp」という拡張子の一時ファイルがたまっている。これは不要なファイルなので、探し出して片付けよう!',
601
636
  initialCwd: '/home/watashi',
602
637
  initialFS: mission5FS,
@@ -649,6 +684,13 @@ export const story00 = {
649
684
  id: 'mission-00-06',
650
685
  title: '日記を書こう',
651
686
  description: '新しい日記を書いて、過去の日記から特定の言葉を探してみよう。',
687
+ goal: 'echo でファイルに書き込み、grep で特定の言葉を検索できるようになる',
688
+ review: {
689
+ question: 'ファイルの中から特定の文字列を検索するコマンドはどれですか?',
690
+ choices: ['find', 'cat', 'grep', 'echo'],
691
+ correctIndex: 2,
692
+ explanation: 'grep は、ファイルの中身から指定した文字列を含む行を検索するコマンドです。find はファイル名で検索します。',
693
+ },
652
694
  narrative: '日記フォルダに過去の日記がある。今日の日記を書いて、それから過去の日記から気になる言葉を探してみよう!',
653
695
  initialCwd: '/home/watashi/日記',
654
696
  initialFS: mission6FS,
@@ -681,6 +723,13 @@ export const story00 = {
681
723
  id: 'mission-00-07',
682
724
  title: '長いレポートを確認しよう',
683
725
  description: '長いレポートの最初と最後だけを見たり、行数を数えたりしてみよう。',
726
+ goal: 'head と tail でファイルの一部を確認し、wc で行数を数えられるようになる',
727
+ review: {
728
+ question: 'ファイルの最後の数行だけを表示するコマンドはどれですか?',
729
+ choices: ['head', 'tail', 'cat', 'wc'],
730
+ correctIndex: 1,
731
+ explanation: 'tail はファイルの末尾(最後の部分)を表示するコマンドです。head は最初の部分を表示します。',
732
+ },
684
733
  narrative: '旅行のレポートが長くて全部読むのは大変。最初の部分と最後の部分だけ確認して、全体の行数も数えてみよう。',
685
734
  initialCwd: '/home/watashi',
686
735
  initialFS: mission7FS,
@@ -725,6 +774,13 @@ export const story00 = {
725
774
  id: 'mission-00-08',
726
775
  title: '連絡先を整理しよう',
727
776
  description: '連絡先データを並べ替えたり、重複を取り除いたりしてみよう。',
777
+ goal: 'sort、uniq、cut を使ってデータを加工・整理できるようになる',
778
+ review: {
779
+ question: '重複した行を取り除くために sort と組み合わせて使うコマンドはどれですか?',
780
+ choices: ['grep', 'cut', 'uniq', 'wc'],
781
+ correctIndex: 2,
782
+ explanation: 'uniq は、隣接する重複行を取り除くコマンドです。sort で並べ替えてから使うのがポイントです。',
783
+ },
728
784
  narrative: '連絡先のファイルがぐちゃぐちゃで、同じ人が何回も登録されている。きれいに並べ替えて、重複を取り除こう!',
729
785
  initialCwd: '/home/watashi',
730
786
  initialFS: mission8FS,
@@ -770,6 +826,13 @@ export const story00 = {
770
826
  id: 'mission-00-09',
771
827
  title: '共有ファイルの設定',
772
828
  description: 'ファイルの権限を設定して、スクリプトを実行できるようにしよう。',
829
+ goal: 'chmod で権限を変更し、パイプでコマンドを連携できるようになる',
830
+ review: {
831
+ question: 'ファイルに実行権限を追加するコマンドはどれですか?',
832
+ choices: ['chown +x', 'chmod +x', 'cp +x', 'mv +x'],
833
+ correctIndex: 1,
834
+ explanation: 'chmod (Change Mode) はファイルの権限を変更するコマンドです。+x で実行権限を追加します。',
835
+ },
773
836
  narrative: '共有フォルダにある集計スクリプトを実行したいけど、実行する権限がない。権限を設定して使えるようにしよう!',
774
837
  initialCwd: '/home/watashi',
775
838
  initialFS: mission9FS,
@@ -802,6 +865,13 @@ export const story00 = {
802
865
  id: 'mission-00-10',
803
866
  title: '変更履歴を管理しよう',
804
867
  description: 'git を使ってレポートの変更履歴を確認してみよう。',
868
+ goal: 'git status と git log で変更の状態と履歴を確認できるようになる',
869
+ review: {
870
+ question: 'ファイルの変更状態を確認する git コマンドはどれですか?',
871
+ choices: ['git log', 'git status', 'git diff', 'git add'],
872
+ correctIndex: 1,
873
+ explanation: 'git status は、どのファイルが変更されたか、ステージされているかなどの現在の状態を表示します。',
874
+ },
805
875
  narrative: 'レポートフォルダでは git を使って変更履歴を管理している。どんな変更が行われたか確認してみよう!',
806
876
  initialCwd: '/home/watashi/レポート',
807
877
  initialFS: mission10FS,
@@ -196,6 +196,13 @@ export const story01 = {
196
196
  id: 'mission-01-01',
197
197
  title: '現在地を確認しよう',
198
198
  description: '基本中の基本、現在のディレクトリを確認し、ファイルの一覧を表示してみよう。',
199
+ goal: 'pwd と ls を使って、今いる場所とファイル構成を把握できるようになる',
200
+ review: {
201
+ question: '現在のディレクトリを表示するコマンドはどれですか?',
202
+ choices: ['ls', 'cd', 'pwd', 'cat'],
203
+ correctIndex: 2,
204
+ explanation: 'pwd (Print Working Directory) は現在いるディレクトリのパスを表示するコマンドです。',
205
+ },
199
206
  narrative: '先輩からの引き継ぎメモには「まずは現在いるディレクトリを確認」と書いてある。ターミナルを開いたら、まず自分がどこにいるのか把握しよう。',
200
207
  initialCwd: '/home/admin',
201
208
  newCommands: ['pwd', 'ls'],
@@ -210,6 +217,11 @@ export const story01 = {
210
217
  { level: 2, text: '"Print Working Directory" の略で、3文字のコマンドです。' },
211
218
  { level: 3, text: '「pwd」と入力してEnterを押してください。' },
212
219
  ],
220
+ feedbacks: [
221
+ { pattern: 'ls', message: 'ls はディレクトリの中身を一覧表示するコマンドです。現在の場所を確認するには、別のコマンドを使います。' },
222
+ { pattern: 'cd', message: 'cd はディレクトリを移動するコマンドです。現在の場所を確認するには、別のコマンドを使います。' },
223
+ { pattern: 'cat', message: 'cat はファイルの内容を表示するコマンドです。現在の場所を確認するには、別のコマンドを使います。' },
224
+ ],
213
225
  },
214
226
  {
215
227
  id: 'obj-01-01-02',
@@ -220,6 +232,11 @@ export const story01 = {
220
232
  { level: 2, text: '"List" の略で、2文字のコマンドです。' },
221
233
  { level: 3, text: '「ls」と入力してEnterを押してください。' },
222
234
  ],
235
+ feedbacks: [
236
+ { pattern: 'pwd', message: 'pwd は現在のディレクトリを表示するコマンドです。ファイル一覧を表示するには、別のコマンドを使います。' },
237
+ { pattern: 'cd', message: 'cd はディレクトリを移動するコマンドです。ファイル一覧を表示するには、別のコマンドを使います。' },
238
+ { pattern: 'cat', message: 'cat はファイルの内容を表示するコマンドです。ファイル一覧を表示するには、別のコマンドを使います。' },
239
+ ],
223
240
  },
224
241
  ],
225
242
  },
@@ -227,6 +244,13 @@ export const story01 = {
227
244
  id: 'mission-01-02',
228
245
  title: '設定ファイルを探せ',
229
246
  description: 'サーバーの設定ファイルを見つけて内容を確認しよう。',
247
+ goal: 'cd でディレクトリを移動し、cat でファイル内容を確認できるようになる',
248
+ review: {
249
+ question: 'ディレクトリを移動するコマンドはどれですか?',
250
+ choices: ['ls', 'cd', 'cat', 'pwd'],
251
+ correctIndex: 1,
252
+ explanation: 'cd (Change Directory) はディレクトリを移動するコマンドです。',
253
+ },
230
254
  narrative: 'サーバーの設定ファイルを確認する必要がある。設定ファイルは /etc に保管されている。ディレクトリを移動して、設定内容を確認しよう。',
231
255
  initialCwd: '/home/admin',
232
256
  newCommands: ['cd', 'cat'],
@@ -241,6 +265,11 @@ export const story01 = {
241
265
  { level: 2, text: '"Change Directory" の略のコマンドに、移動先のパスを指定します。' },
242
266
  { level: 3, text: '「cd /etc」と入力してEnterを押してください。' },
243
267
  ],
268
+ feedbacks: [
269
+ { pattern: 'pwd', message: 'pwd は現在の場所を表示するコマンドです。ディレクトリを移動するには、別のコマンドを使います。' },
270
+ { pattern: 'ls', message: 'ls はファイル一覧を表示するコマンドです。ディレクトリを移動するには、別のコマンドを使います。' },
271
+ { pattern: 'cat', message: 'cat はファイルの内容を表示するコマンドです。ディレクトリを移動するには、別のコマンドを使います。' },
272
+ ],
244
273
  },
245
274
  {
246
275
  id: 'obj-01-02-02',
@@ -251,6 +280,11 @@ export const story01 = {
251
280
  { level: 2, text: '2文字のコマンドで、ディレクトリの中身を表示できます。' },
252
281
  { level: 3, text: '「ls」と入力してEnterを押してください。' },
253
282
  ],
283
+ feedbacks: [
284
+ { pattern: 'pwd', message: 'pwd は現在の場所を表示するコマンドです。ファイル一覧を確認するには、別のコマンドを使います。' },
285
+ { pattern: 'cd', message: 'cd はディレクトリ移動のコマンドです。今いる場所の中身を見るには、別のコマンドを使います。' },
286
+ { pattern: 'cat', message: 'cat はファイルの内容を表示するコマンドです。ディレクトリの中身一覧を見るには、別のコマンドを使います。' },
287
+ ],
254
288
  },
255
289
  {
256
290
  id: 'obj-01-02-03',
@@ -261,6 +295,11 @@ export const story01 = {
261
295
  { level: 2, text: '猫の鳴き声に似た3文字のコマンドです。パスを指定しましょう。' },
262
296
  { level: 3, text: '「cat app/config.json」と入力してください。' },
263
297
  ],
298
+ feedbacks: [
299
+ { pattern: 'pwd', message: 'pwd は現在の場所を表示するコマンドです。ファイルの内容を読むには、別のコマンドを使います。' },
300
+ { pattern: 'ls', message: 'ls はファイル一覧を表示するコマンドです。ファイルの中身を読むには、別のコマンドを使います。' },
301
+ { pattern: 'cd', message: 'cd はディレクトリ移動のコマンドです。ファイルの内容を読むには、別のコマンドを使います。' },
302
+ ],
264
303
  },
265
304
  ],
266
305
  },
@@ -268,6 +307,13 @@ export const story01 = {
268
307
  id: 'mission-01-03',
269
308
  title: 'ログからエラーを探せ',
270
309
  description: 'ログファイルからエラーメッセージを見つけ出そう。',
310
+ goal: 'grep を使ってログから特定のパターンを抽出できるようになる',
311
+ review: {
312
+ question: 'ファイルの内容を表示するコマンドはどれですか?',
313
+ choices: ['ls', 'pwd', 'cat', 'cd'],
314
+ correctIndex: 2,
315
+ explanation: 'cat はファイルの内容を表示するコマンドです。concatenate(連結)が語源です。',
316
+ },
271
317
  narrative: 'ユーザーから「エラーが出てる」と報告が。ログファイルを調べて、何が起きているか確認しよう。',
272
318
  initialCwd: '/home/admin',
273
319
  newCommands: ['grep'],
@@ -285,6 +331,10 @@ export const story01 = {
285
331
  { level: 2, text: 'ログファイルは /var/log/app.log にあります。' },
286
332
  { level: 3, text: '「cat /var/log/app.log」と入力してください。' },
287
333
  ],
334
+ feedbacks: [
335
+ { pattern: 'grep', message: 'grep は検索コマンドです。まずは cat でログ全体を確認しましょう。' },
336
+ { pattern: 'ls', message: 'ls はファイル一覧を表示するコマンドです。ファイルの内容を読むには、別のコマンドを使います。' },
337
+ ],
288
338
  },
289
339
  {
290
340
  id: 'obj-01-03-02',
@@ -298,6 +348,10 @@ export const story01 = {
298
348
  { level: 2, text: 'grep コマンドに検索パターンとファイル名を指定します。' },
299
349
  { level: 3, text: '「grep ERROR /var/log/app.log」と入力してください。' },
300
350
  ],
351
+ feedbacks: [
352
+ { pattern: 'cat', message: 'cat はファイル全体を表示します。特定の文字列を含む行だけを抽出するには、検索コマンドを使いましょう。' },
353
+ { pattern: 'find', message: 'find はファイル名を検索するコマンドです。ファイルの中身を検索するには、別のコマンドを使います。' },
354
+ ],
301
355
  },
302
356
  ],
303
357
  },
@@ -305,6 +359,13 @@ export const story01 = {
305
359
  id: 'mission-01-04',
306
360
  title: '設定ファイルのバックアップ',
307
361
  description: '設定を変更する前に、安全のためバックアップを取ろう。',
362
+ goal: 'cp でファイルのバックアップを作成する習慣を身につける',
363
+ review: {
364
+ question: '特定の文字列を含む行を検索するコマンドはどれですか?',
365
+ choices: ['cat', 'find', 'grep', 'ls'],
366
+ correctIndex: 2,
367
+ explanation: 'grep はファイル内のテキストをパターンで検索するコマンドです。',
368
+ },
308
369
  narrative: '設定を変更する前に、バックアップを取る習慣をつけよう。何かあったときに元に戻せるように、コピーを作成する。',
309
370
  initialCwd: '/etc/app',
310
371
  newCommands: ['cp'],
@@ -324,6 +385,11 @@ export const story01 = {
324
385
  { level: 2, text: 'cp コマンドで「元ファイル」「コピー先」を指定します。.bak 拡張子をつけるのが慣例です。' },
325
386
  { level: 3, text: '「cp config.json config.json.bak」と入力してください。' },
326
387
  ],
388
+ feedbacks: [
389
+ { pattern: 'mv', message: 'mv はファイルを移動するコマンドです。バックアップを作るには、元のファイルを残したままコピーするコマンドを使いましょう。' },
390
+ { pattern: 'cat', message: 'cat はファイルの内容を表示するコマンドです。ファイルをコピーするには、別のコマンドを使います。' },
391
+ { pattern: 'echo', message: 'echo はテキストを出力するコマンドです。ファイルをそのままコピーするには、別のコマンドを使います。' },
392
+ ],
327
393
  },
328
394
  ],
329
395
  },
@@ -331,6 +397,13 @@ export const story01 = {
331
397
  id: 'mission-01-05',
332
398
  title: '設定を修正せよ',
333
399
  description: 'デバッグモードを有効にして、問題を調査しやすくしよう。',
400
+ goal: 'echo とリダイレクト(>)を使ってファイルを書き換えられるようになる',
401
+ review: {
402
+ question: 'ファイルをコピーするコマンドはどれですか?',
403
+ choices: ['mv', 'cp', 'rm', 'cat'],
404
+ correctIndex: 1,
405
+ explanation: 'cp (copy) はファイルやディレクトリをコピーするコマンドです。',
406
+ },
334
407
  narrative: 'デバッグモードが無効になっている。config.json の debug を true に変更しよう。echo とリダイレクトを使ってファイルを書き換える。',
335
408
  initialCwd: '/etc/app',
336
409
  newCommands: ['echo'],
@@ -354,6 +427,10 @@ export const story01 = {
354
427
  text: '「echo \'{"port": 3000, "debug": true}\' > config.json」と入力してください。',
355
428
  },
356
429
  ],
430
+ feedbacks: [
431
+ { pattern: 'cat', message: 'cat はファイルの内容を表示するコマンドです。ファイルに書き込むには、別のコマンドとリダイレクト(>)を使います。' },
432
+ { pattern: 'cp', message: 'cp はファイルをコピーするコマンドです。ファイルの内容を書き換えるには、echo とリダイレクト(>)を使いましょう。' },
433
+ ],
357
434
  },
358
435
  ],
359
436
  },
@@ -267,6 +267,7 @@ export const story02 = {
267
267
  id: 'mission-02-01',
268
268
  title: 'ディレクトリを作ろう',
269
269
  description: 'mkdir コマンドでディレクトリを作成して、プロジェクト構造を整理しよう。',
270
+ goal: 'mkdir でプロジェクトのディレクトリ構造を設計・作成できるようになる',
270
271
  narrative: 'プロジェクトにはソースコード、ドキュメント、テストが混在している。まずはディレクトリを整理しよう。',
271
272
  initialCwd: '/home/dev/project',
272
273
  newCommands: ['mkdir'],
@@ -281,6 +282,10 @@ export const story02 = {
281
282
  { level: 2, text: '"Make Directory" の略で、mkdir コマンドを使います。' },
282
283
  { level: 3, text: '「mkdir src」と入力してEnterを押してください。' },
283
284
  ],
285
+ feedbacks: [
286
+ { pattern: 'touch', message: 'touch は空のファイルを作成するコマンドです。ディレクトリを作成するには、別のコマンドを使います。' },
287
+ { pattern: 'cd', message: 'cd はディレクトリを移動するコマンドです。新しいディレクトリを作成するには、別のコマンドを使います。' },
288
+ ],
284
289
  },
285
290
  {
286
291
  id: 'obj-02-01-02',
@@ -308,6 +313,7 @@ export const story02 = {
308
313
  id: 'mission-02-02',
309
314
  title: 'ファイルを移動せよ',
310
315
  description: 'mv コマンドでファイルを適切なディレクトリに移動しよう。',
316
+ goal: 'mv でファイルを適切な場所に移動・整理できるようになる',
311
317
  narrative: 'ディレクトリができた。次はファイルを適切な場所に移動しよう。',
312
318
  initialCwd: '/home/dev/project',
313
319
  newCommands: ['mv'],
@@ -325,6 +331,10 @@ export const story02 = {
325
331
  { level: 2, text: '"Move" の略で、mv コマンドを使います。mv 元 先 の形式です。' },
326
332
  { level: 3, text: '「mv app.js src/」と入力してEnterを押してください。' },
327
333
  ],
334
+ feedbacks: [
335
+ { pattern: 'cp', message: 'cp はファイルをコピーするコマンドです。元のファイルを残さず移動するには、別のコマンドを使います。' },
336
+ { pattern: 'rm', message: 'rm はファイルを削除するコマンドです。ファイルを別の場所に移動するには、別のコマンドを使います。' },
337
+ ],
328
338
  },
329
339
  {
330
340
  id: 'obj-02-02-02',
@@ -352,6 +362,7 @@ export const story02 = {
352
362
  id: 'mission-02-03',
353
363
  title: '不要なファイルを削除',
354
364
  description: '.tmp ファイルや .bak ファイルを見つけて削除しよう。',
365
+ goal: 'find で不要ファイルを検索し、rm で安全に削除できるようになる',
355
366
  narrative: 'プロジェクト内に .tmp ファイルや .bak ファイルが大量にある。不要ファイルを見つけて削除しよう。',
356
367
  initialCwd: '/home/dev/project',
357
368
  newCommands: ['rm', 'find'],
@@ -369,6 +380,10 @@ export const story02 = {
369
380
  { level: 2, text: 'find コマンドで名前パターンを指定して検索できます。' },
370
381
  { level: 3, text: '「find . -name "*.tmp"」と入力してEnterを押してください。' },
371
382
  ],
383
+ feedbacks: [
384
+ { pattern: 'ls', message: 'ls は現在のディレクトリの中身を表示するコマンドです。サブディレクトリも含めてファイルを検索するには、別のコマンドを使います。' },
385
+ { pattern: 'grep', message: 'grep はファイルの中身を検索するコマンドです。ファイル名で検索するには、別のコマンドを使います。' },
386
+ ],
372
387
  },
373
388
  {
374
389
  id: 'obj-02-03-02',
@@ -379,6 +394,10 @@ export const story02 = {
379
394
  { level: 2, text: '"Remove" の略で、rm コマンドを使います。' },
380
395
  { level: 3, text: '「rm cache.tmp」と入力してEnterを押してください。' },
381
396
  ],
397
+ feedbacks: [
398
+ { pattern: 'mv', message: 'mv はファイルを移動するコマンドです。ファイルを削除するには、別のコマンドを使います。' },
399
+ { pattern: 'find', message: 'find はファイルを検索するコマンドです。すでに見つけたファイルを削除するには、別のコマンドを使います。' },
400
+ ],
382
401
  },
383
402
  {
384
403
  id: 'obj-02-03-03',
@@ -396,6 +415,7 @@ export const story02 = {
396
415
  id: 'mission-02-04',
397
416
  title: '新しいファイルを作成',
398
417
  description: '新しいファイルを作成して、プロジェクトの状態を確認しよう。',
418
+ goal: 'touch でファイルを作成し、wc でコードの行数を確認できるようになる',
399
419
  narrative: 'プロジェクト構造が整った。新しいファイルを作成して、プロジェクトの状態を確認しよう。',
400
420
  initialCwd: '/home/dev/project',
401
421
  newCommands: ['touch', 'wc'],
@@ -410,6 +430,10 @@ export const story02 = {
410
430
  { level: 2, text: 'touch コマンドでファイルを作成できます。' },
411
431
  { level: 3, text: '「touch CHANGELOG.md」と入力してEnterを押してください。' },
412
432
  ],
433
+ feedbacks: [
434
+ { pattern: 'mkdir', message: 'mkdir はディレクトリを作成するコマンドです。空のファイルを作成するには、別のコマンドを使います。' },
435
+ { pattern: 'echo', message: 'echo はテキストを出力するコマンドです。空のファイルを作成するだけなら、もっと簡単なコマンドがあります。' },
436
+ ],
413
437
  },
414
438
  {
415
439
  id: 'obj-02-04-02',
@@ -167,6 +167,7 @@ export const story03 = {
167
167
  id: 'mission-03-01',
168
168
  title: 'ログファイルの調査',
169
169
  description: 'head と tail コマンドでログファイルの最初と最後を確認しよう。',
170
+ goal: 'head と tail で大きなログファイルの概要を素早く把握できるようになる',
170
171
  narrative: '大量のログがある。まずはファイルの最初と最後を確認しよう。',
171
172
  initialCwd: '/var/log',
172
173
  newCommands: ['head', 'tail'],
@@ -201,6 +202,7 @@ export const story03 = {
201
202
  id: 'mission-03-02',
202
203
  title: 'エラーの分析',
203
204
  description: 'grep と wc コマンドで 500 エラーの件数を数えよう。',
205
+ goal: 'grep でエラーを抽出し、パイプで wc -l に渡して件数を数えられるようになる',
204
206
  narrative: '500エラーが出ている。何件あるか数えよう。',
205
207
  initialCwd: '/var/log',
206
208
  initialFS: mission2FS,
@@ -234,6 +236,7 @@ export const story03 = {
234
236
  id: 'mission-03-03',
235
237
  title: 'アクセスパターンの分析',
236
238
  description: 'sort と uniq コマンドでIPアドレスのアクセス頻度を調べよう。',
239
+ goal: 'sort | uniq -c でデータの出現頻度を集計できるようになる',
237
240
  narrative: 'どのIPアドレスからアクセスが多いか調べよう。',
238
241
  initialCwd: '/var/log',
239
242
  newCommands: ['sort', 'uniq'],
@@ -265,6 +268,7 @@ export const story03 = {
265
268
  id: 'mission-03-04',
266
269
  title: 'レポート作成',
267
270
  description: 'grep とリダイレクトを使って、調査結果をファイルに保存しよう。',
271
+ goal: 'コマンドの出力をリダイレクト(>)でファイルに保存できるようになる',
268
272
  narrative: '調査結果をレポートファイルにまとめよう。',
269
273
  initialCwd: '/var/log',
270
274
  initialFS: mission4FS,
@@ -223,6 +223,7 @@ export const story04 = {
223
223
  id: 'mission-04-01',
224
224
  title: 'デプロイ先の準備',
225
225
  description: 'mkdir -p と cp -r でデプロイ先を準備しよう。',
226
+ goal: 'mkdir -p と cp -r で本番環境のディレクトリ構成を構築できるようになる',
226
227
  narrative: 'デプロイ先のディレクトリを作成して、ソースをコピーしよう。',
227
228
  initialCwd: '/home/deploy',
228
229
  initialFS: mission1FS,
@@ -253,6 +254,7 @@ export const story04 = {
253
254
  id: 'mission-04-02',
254
255
  title: '権限の設定',
255
256
  description: 'chmod コマンドでファイルの実行権限を設定しよう。',
257
+ goal: 'chmod でスクリプトに実行権限を付与できるようになる',
256
258
  narrative: 'デプロイしたファイルの実行権限を設定しよう。',
257
259
  initialCwd: '/var/www/app',
258
260
  newCommands: ['chmod'],
@@ -274,6 +276,7 @@ export const story04 = {
274
276
  id: 'mission-04-03',
275
277
  title: 'バックアップ作成',
276
278
  description: '古いバックアップを削除して、新しいバックアップを作ろう。',
279
+ goal: 'rm -rf で古いディレクトリを削除し、cp -r で新しいバックアップを作れるようになる',
277
280
  narrative: '古いバックアップを削除して、新しいバックアップを作ろう。',
278
281
  initialCwd: '/var/www',
279
282
  initialFS: mission3FS,
@@ -304,6 +307,7 @@ export const story04 = {
304
307
  id: 'mission-04-04',
305
308
  title: '最終確認',
306
309
  description: 'デプロイが完了したか最終確認しよう。',
310
+ goal: 'find と cat でデプロイ結果を検証できるようになる',
307
311
  narrative: 'デプロイが完了した。最終確認をしよう。',
308
312
  initialCwd: '/var/www/app',
309
313
  initialFS: mission4FS,
@@ -379,6 +379,7 @@ export const story05 = {
379
379
  id: 'mission-05-01',
380
380
  title: '状況確認',
381
381
  description: 'git status と git log で現在のリポジトリの状態を把握しよう。',
382
+ goal: 'git status と git log でリポジトリの状態と履歴を把握できるようになる',
382
383
  narrative: '先輩が「リポジトリがおかしい」と焦っている。まずは状況を確認しよう。',
383
384
  initialCwd: '/home/dev/repo',
384
385
  newCommands: ['git'],
@@ -416,6 +417,7 @@ export const story05 = {
416
417
  id: 'mission-05-02',
417
418
  title: '変更の退避',
418
419
  description: 'git stash で作業中の変更を一旦退避しよう。',
420
+ goal: 'git stash で作業中の変更を安全に退避できるようになる',
419
421
  narrative: '作業中の変更を一旦退避して、安全な状態に戻そう。',
420
422
  initialCwd: '/home/dev/repo',
421
423
  initialFS: mission2FS,
@@ -439,6 +441,7 @@ export const story05 = {
439
441
  id: 'mission-05-03',
440
442
  title: 'ブランチ操作',
441
443
  description: 'git branch と git checkout でブランチを操作しよう。',
444
+ goal: 'git branch でブランチ確認、git checkout -b で新しいブランチを作れるようになる',
442
445
  narrative: '修正用のブランチを作って切り替えよう。',
443
446
  initialCwd: '/home/dev/repo',
444
447
  initialFS: mission3FS,
@@ -475,6 +478,7 @@ export const story05 = {
475
478
  id: 'mission-05-04',
476
479
  title: '変更の確認',
477
480
  description: 'git diff で変更内容を確認して問題箇所を特定しよう。',
481
+ goal: 'git diff でコードの変更差分を確認し問題を特定できるようになる',
478
482
  narrative: '変更内容を確認して、問題の箇所を特定しよう。',
479
483
  initialCwd: '/home/dev/repo',
480
484
  initialFS: mission4FS,
@@ -498,6 +502,7 @@ export const story05 = {
498
502
  id: 'mission-05-05',
499
503
  title: 'マージ',
500
504
  description: '修正をメインブランチに統合しよう。',
505
+ goal: 'git checkout と git merge でブランチを統合できるようになる',
501
506
  narrative: '修正が完了した。メインブランチに統合しよう。',
502
507
  initialCwd: '/home/dev/repo',
503
508
  initialFS: mission5FS,
@@ -224,6 +224,7 @@ export const story06 = {
224
224
  id: 'mission-06-01',
225
225
  title: 'パイプ入門',
226
226
  description: 'パイプ(|)を使ってコマンドの出力を次のコマンドに渡そう。',
227
+ goal: 'パイプ(|)でコマンドの出力を別のコマンドに渡せるようになる',
227
228
  narrative: 'パイプ(|)を使うと、コマンドの出力を次のコマンドの入力にできる。',
228
229
  initialCwd: '/home/data',
229
230
  initialFS: mission1FS,
@@ -257,6 +258,7 @@ export const story06 = {
257
258
  id: 'mission-06-02',
258
259
  title: 'データ加工',
259
260
  description: 'sort と uniq、grep と wc をパイプで組み合わせてデータを分析しよう。',
261
+ goal: 'パイプで複数コマンドを連携させてデータ集計・分析ができるようになる',
260
262
  narrative: 'データの集計と分析にパイプを活用しよう。',
261
263
  initialCwd: '/home/data',
262
264
  initialFS: mission2FS,
@@ -290,6 +292,7 @@ export const story06 = {
290
292
  id: 'mission-06-03',
291
293
  title: 'フィールド抽出',
292
294
  description: 'cut コマンドでCSVから特定のフィールドを抽出しよう。',
295
+ goal: 'cut でCSVの特定列を抽出し、sort でフィールド指定ソートができるようになる',
293
296
  narrative: 'CSVデータから特定のフィールドを抽出して分析しよう。',
294
297
  initialCwd: '/home/data',
295
298
  newCommands: ['cut'],
@@ -324,6 +327,7 @@ export const story06 = {
324
327
  id: 'mission-06-04',
325
328
  title: '総合演習',
326
329
  description: '複数のコマンドをパイプで繋いで、データを抽出してファイルに保存しよう。',
330
+ goal: '6段パイプで抽出・加工・集計・ソートを一括実行できるようになる',
327
331
  narrative: '最終課題。複数のコマンドをパイプで繋いで、データから必要な情報を抽出し、ファイルに保存しよう。',
328
332
  initialCwd: '/home/data',
329
333
  initialFS: mission4FS,