# エラーメッセージ出力仕様

## 概要

`article-lint` のチェック結果を、ユーザーが問題を理解し、修正方法を把握できるよう、詳細で実用的なメッセージ形式で出力する。

## 現状の課題

現在のエラーメッセージは簡潔すぎて、以下の情報が不足している：

```
Issues found:
Rule 1: Frontmatter is missing
Rule 4: Missing "### 学習目標" section
Rule 6: Missing "## まとめ" section
```

**問題点:**
- どこに問題があるか（行番号）が不明
- なぜそれが問題なのかが不明
- どう修正すればよいかが不明
- 重要度（エラー/警告/情報）が区別されていない

## メッセージ構造

### LintResultインターフェース（拡張版）

```typescript
export interface LintResult {
  // 識別情報
  ruleId: string;           // ルールID（例: "text/frontmatter/missing-title"）
  ruleName: string;         // ルール名（日本語）

  // 重要度
  severity: 'error' | 'warning' | 'info';

  // 位置情報
  location?: {
    line?: number;          // 開始行番号
    endLine?: number;       // 終了行番号（範囲指定の場合）
    column?: number;        // 開始列番号
    endColumn?: number;     // 終了列番号
  };

  // メッセージ
  message: string;          // 問題の概要（1行）
  detail?: string;          // 詳細説明（複数行可）
  suggestion?: string;      // 修正方法の提案

  // コンテキスト情報
  context?: {
    found?: string;         // 実際に見つかった値
    expected?: string;      // 期待される値
    codeSnippet?: string;   // 問題のあるコード断片
  };

  // 参照情報
  reference?: {
    ruleUrl?: string;       // ルール詳細ドキュメントへのURL
    relatedRules?: string[]; // 関連するルールID
  };
}
```

## 出力形式

### コンソール出力（デフォルト）

```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📄 文書タイプ: テキスト教材
📁 ファイル: text/chapter-01.md
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

✖ エラー: 3件 | ⚠ 警告: 1件 | ℹ 情報: 0件

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

✖ [text/frontmatter/missing] frontmatterがありません

  📍 位置: 1行目

  📝 説明:
     Markdownファイルの先頭にYAML形式のfrontmatterが必要です。
     frontmatterは文書のメタデータ（タイトル、公開状態など）を定義します。

  💡 修正方法:
     ファイルの先頭に以下のfrontmatterを追加してください:

     ```yaml
     ---
     title: [章のタイトル]
     draft: false
     ---
     ```

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

✖ [text/structure/missing-learning-goals] 「### 学習目標」セクションがありません

  📍 位置: ファイル全体

  📝 説明:
     テキスト教材には「### 学習目標」セクションが必須です。
     このセクションでは、この章で学ぶ内容を受講者に明示します。

  💡 修正方法:
     {{ toc }} の後、概要文章の次に以下のセクションを追加してください:

     ```markdown
     ### 学習目標

     [この章の学習目標を説明する段落文章]

     :::note この章で学ぶこと

     - [学習目標1]
     - [学習目標2]
     - [学習目標3]

     :::
     ```

  📚 参考: _rules/text.md「ページ構成要素」セクション

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

✖ [text/structure/missing-summary] 「## まとめ」セクションがありません

  📍 位置: ファイル全体

  📝 説明:
     テキスト教材の最後には「## まとめ」セクションが必須です。
     このセクションでは、学習した内容の要点をまとめ、次の章への展開を行います。

  💡 修正方法:
     ファイルの最後に以下のセクションを追加してください:

     ```markdown
     ## まとめ

     [このページで学習した内容を要約して読者の定着を図る]

     :::note 要点のまとめ

     - [要点1]
     - [要点2]
     - [要点3]

     :::

     [次のページの内容を簡潔に紹介する]

     [次のページへのリンク](./next-page)
     ```

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

⚠ [text/code-block/missing-filepath] コードブロックにファイルパスがありません

  📍 位置: 45行目

  📝 説明:
     プログラムファイルを追加・更新するコードブロックの前には、
     ファイルパスを `_` で囲って記述する必要があります。

  🔍 検出内容:
     ```typescript
     export function hello() {
       console.log("Hello");
     }
     ```

  💡 修正方法:
     コードブロックの直前に以下の形式でファイルパスを追加してください:

     _src/utils/hello.ts_
     ```typescript
     export function hello() {
       console.log("Hello");
     }
     ```

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

📊 チェック完了: 4件の問題が見つかりました（エラー: 3, 警告: 1）
```

### 簡易出力モード（`--quiet`）

```
text/chapter-01.md:1:1: error [text/frontmatter/missing] frontmatterがありません
text/chapter-01.md: error [text/structure/missing-learning-goals] 「### 学習目標」セクションがありません
text/chapter-01.md: error [text/structure/missing-summary] 「## まとめ」セクションがありません
text/chapter-01.md:45:1: warning [text/code-block/missing-filepath] コードブロックにファイルパスがありません

4 problems (3 errors, 1 warning)
```

### JSON出力（`--format json`）

```json
{
  "filePath": "text/chapter-01.md",
  "documentType": "text",
  "documentTypeName": "テキスト教材",
  "success": false,
  "summary": {
    "total": 4,
    "errors": 3,
    "warnings": 1,
    "infos": 0
  },
  "results": [
    {
      "ruleId": "text/frontmatter/missing",
      "ruleName": "frontmatter必須チェック",
      "severity": "error",
      "location": {
        "line": 1,
        "column": 1
      },
      "message": "frontmatterがありません",
      "detail": "Markdownファイルの先頭にYAML形式のfrontmatterが必要です。frontmatterは文書のメタデータ（タイトル、公開状態など）を定義します。",
      "suggestion": "ファイルの先頭に以下のfrontmatterを追加してください:\n\n---\ntitle: [章のタイトル]\ndraft: false\n---",
      "reference": {
        "ruleUrl": "docs/rules/text-frontmatter.md"
      }
    }
  ]
}
```

## 文書タイプ別メッセージ定義

### テキスト教材（text）

| ルールID | 重要度 | メッセージ | 修正方法 |
|---------|--------|-----------|---------|
| `text/frontmatter/missing` | error | frontmatterがありません | ファイル先頭にfrontmatterを追加 |
| `text/frontmatter/missing-title` | error | frontmatterに「title」フィールドがありません | `title: [タイトル]` を追加 |
| `text/frontmatter/missing-draft` | error | frontmatterに「draft」フィールドがありません | `draft: false` を追加 |
| `text/frontmatter/invalid-yaml` | error | frontmatterのYAML構文が不正です | YAML構文を修正 |
| `text/toc/missing` | error | 目次プレースホルダー「{{ toc }}」がありません | frontmatter直後に `{{ toc }}` を追加 |
| `text/toc/wrong-position` | warning | 「{{ toc }}」がfrontmatter直後にありません | `{{ toc }}` をfrontmatter直後に移動 |
| `text/structure/missing-learning-goals` | error | 「### 学習目標」セクションがありません | 学習目標セクションを追加 |
| `text/structure/missing-summary` | error | 「## まとめ」セクションがありません | まとめセクションを追加 |
| `text/structure/summary-not-last` | warning | 「## まとめ」が最後のH2セクションではありません | まとめを最後に移動 |
| `text/structure/invalid-order` | error | セクションの順序が不正です（学習目標がまとめより後） | セクション順序を修正 |
| `text/code-block/missing-language` | warning | コードブロックに言語指定がありません | 言語指定を追加（例: ```typescript） |
| `text/code-block/missing-filepath` | warning | プログラムコードブロックの前にファイルパスがありません | `_path/to/file_` 形式で追加 |
| `text/step/empty` | warning | :::step ブロックが空です | ハンズオン手順を追加 |
| `text/step/missing-numbered-list` | info | :::step ブロック内に番号付きリストがありません | 手順を番号付きリストで記述 |

### 理解度確認問題（quiz）

| ルールID | 重要度 | メッセージ | 修正方法 |
|---------|--------|-----------|---------|
| `quiz/frontmatter/missing` | error | frontmatterがありません | ファイル先頭にfrontmatterを追加 |
| `quiz/frontmatter/missing-title` | error | frontmatterに「title」フィールドがありません | `title: [タイトル]` を追加 |
| `quiz/toc/missing` | error | 目次プレースホルダー「{{ toc }}」がありません | frontmatter直後に `{{ toc }}` を追加 |
| `quiz/structure/missing-quiz` | error | :::quiz ブロックがありません | 問題を :::quiz で囲む |
| `quiz/structure/missing-question` | error | :::question ブロックがありません | 問題文を :::question で囲む |
| `quiz/structure/missing-select` | error | :::select ブロックがありません | 選択肢を :::select で囲む |
| `quiz/structure/missing-correct` | error | :::correct ブロックがありません | 正解と解説を :::correct で囲む |
| `quiz/structure/invalid-quiz-structure` | error | :::quiz ブロックの構造が不正です | question, select, correct の順で配置 |
| `quiz/select/insufficient-options` | warning | 選択肢が少なすぎます（現在: X個） | 4つ程度の選択肢を用意 |
| `quiz/correct/invalid-answer` | error | 正解番号が選択肢の範囲外です | 選択肢の番号に対応する数値を指定 |
| `quiz/correct/missing-explanation` | warning | 解説文がありません | 正解の理由を説明する解説を追加 |

### スライド教材（slides）

| ルールID | 重要度 | メッセージ | 修正方法 |
|---------|--------|-----------|---------|
| `slides/frontmatter/missing-marp` | error | frontmatterに「marp: true」がありません | `marp: true` を追加 |
| `slides/frontmatter/missing-title` | error | frontmatterに「title」フィールドがありません | `title: [タイトル]` を追加 |
| `slides/structure/missing-title-slide` | error | タイトルスライド（H1見出し）がありません | 先頭に `# タイトル` を追加 |
| `slides/structure/missing-summary-slide` | warning | 「## この章のまとめ」スライドがありません | 最後にまとめスライドを追加 |
| `slides/structure/missing-learning-content-slide` | warning | 「## この章で学ぶこと」スライドがありません | タイトル後に学習内容スライドを追加 |
| `slides/delimiter/missing` | error | スライド区切り「---」がありません | 各スライド間に `---` を追加 |
| `slides/content/too-dense` | warning | 1スライドあたりの項目数が多すぎます（X項目） | 3-7項目を目安に分割 |
| `slides/content/missing-bullet-points` | info | 箇条書きがありません | 箇条書きで情報を整理 |

### 演習問題（practice）

| ルールID | 重要度 | メッセージ | 修正方法 |
|---------|--------|-----------|---------|
| `practice/frontmatter/missing` | error | frontmatterがありません | ファイル先頭にfrontmatterを追加 |
| `practice/toc/missing` | error | 目次プレースホルダー「{{ toc }}」がありません | frontmatter直後に `{{ toc }}` を追加 |
| `practice/structure/missing-overview` | error | 「### 概要」セクションがありません | 演習の概要セクションを追加 |
| `practice/structure/missing-problem` | error | 「### 問題」セクションがありません | 問題セクションを追加 |
| `practice/structure/missing-answer-format` | error | 「### 解答形式」セクションがありません | 解答形式セクションを追加 |
| `practice/structure/missing-solution` | error | 「### 解答例」セクションがありません | 解答例セクションを追加 |
| `practice/solution/missing-code` | warning | 解答例にコードブロックがありません | サンプルコードを追加 |
| `practice/solution/missing-add-markers` | info | コード追加部分に「//addstart」「//addend」マーカーがありません | 追加部分をマーカーで囲む |

### 研修要件定義書（requirements）

| ルールID | 重要度 | メッセージ | 修正方法 |
|---------|--------|-----------|---------|
| `requirements/frontmatter/missing` | error | frontmatterがありません | ファイル先頭にfrontmatterを追加 |
| `requirements/frontmatter/missing-title` | error | frontmatterに「title」フィールドがありません | `title: [タイトル]` を追加 |
| `requirements/frontmatter/missing-duration` | warning | frontmatterに「duration」フィールドがありません | `duration: [研修期間]` を追加 |
| `requirements/structure/missing-overview` | error | 「## 研修概要」セクションがありません | 研修概要セクションを追加 |
| `requirements/structure/missing-features` | error | 「## 研修の特徴」セクションがありません | 研修の特徴セクションを追加 |
| `requirements/structure/missing-learning-goals` | error | 「## 学習目標」セクションがありません | 学習目標セクションを追加 |
| `requirements/structure/missing-curriculum` | error | 「## カリキュラム」セクションがありません | カリキュラムセクションを追加 |
| `requirements/structure/missing-details` | error | 「## 研修詳細」セクションがありません | 研修詳細セクションを追加 |
| `requirements/learning-goals/insufficient` | warning | 学習目標が少なすぎます（現在: X個） | 3-5個の学習目標を設定 |
| `requirements/curriculum/missing-items` | warning | カリキュラムに学習項目がありません | 各章の学習項目を箇条書きで追加 |

### 研修設計書（design）

| ルールID | 重要度 | メッセージ | 修正方法 |
|---------|--------|-----------|---------|
| `design/frontmatter/missing` | error | frontmatterがありません | ファイル先頭にfrontmatterを追加 |
| `design/structure/missing-basic-policy` | error | 「## 1. 研修設計の基本方針」セクションがありません | 基本方針セクションを追加 |
| `design/structure/missing-chapter-design` | error | 「## 2. 章構成と学習設計」セクションがありません | 章構成セクションを追加 |
| `design/structure/missing-schedule` | error | 「## 3. タイムスケジュール」セクションがありません | タイムスケジュールセクションを追加 |
| `design/structure/missing-folder-structure` | warning | 「## 4. フォルダ・ファイル構成」セクションがありません | フォルダ構成セクションを追加 |
| `design/chapter/missing-overview` | warning | 章設計に「学習概要」がありません | 各章に学習概要を追加 |
| `design/chapter/missing-learning-goals` | warning | 章設計に「学習目標」がありません | 各章に学習目標を追加 |
| `design/chapter/missing-hands-on` | info | 章設計に「ハンズオン」がありません | ハンズオン内容を追加 |
| `design/schedule/invalid-time-format` | warning | タイムスケジュールの時間形式が不正です | HH:MM–HH:MM 形式で記述 |

### フライヤー要件（flyer）

| ルールID | 重要度 | メッセージ | 修正方法 |
|---------|--------|-----------|---------|
| `flyer/frontmatter/missing` | error | frontmatterがありません | ファイル先頭にfrontmatterを追加 |
| `flyer/frontmatter/missing-title` | error | frontmatterに「title」フィールドがありません | `title: [タイトル]` を追加 |
| `flyer/frontmatter/missing-logo` | warning | frontmatterに「logo」フィールドがありません | `logo: "/assets/logo.png"` を追加 |
| `flyer/structure/missing-catch` | error | 「## 研修のキャッチ文章」セクションがありません | キャッチ文章セクションを追加 |
| `flyer/structure/missing-features` | error | 「## 研修の特徴」セクションがありません | 研修の特徴セクションを追加 |
| `flyer/structure/missing-curriculum` | error | 「## カリキュラム」セクションがありません | カリキュラムセクションを追加 |
| `flyer/catch/too-long` | warning | キャッチ文章が長すぎます（現在: X文字、推奨: 100文字程度） | 100文字程度に短縮 |
| `flyer/features/too-long` | warning | 研修の特徴が長すぎます（現在: X文字、推奨: 400文字程度） | 400文字程度に短縮 |
| `flyer/learning-goals/too-many` | warning | 学習目標が多すぎます（現在: X個、推奨: 5個以内） | 5個以内に絞る |

## メッセージ作成ガイドライン

### 1. 明確性

- **何が問題か**を具体的に記述する
- 曖昧な表現（「問題があります」）を避ける
- 技術用語は初出時に説明を付ける

### 2. 位置情報

- 可能な限り行番号を含める
- 範囲がある場合は開始・終了行を示す
- 位置が特定できない場合は「ファイル全体」と明記

### 3. 修正方法

- 具体的なコード例を含める
- 複数の修正方法がある場合は推奨を示す
- 参照すべきドキュメントへのリンクを提供

### 4. 重要度の基準

| 重要度 | 説明 | 例 |
|--------|------|-----|
| `error` | 必須要件の違反、文書として機能しない | frontmatter欠落、必須セクション欠落 |
| `warning` | 推奨事項の違反、品質に影響 | ファイルパス欠落、文字数超過 |
| `info` | ベストプラクティスの提案 | 箇条書きの使用推奨、マーカー追加 |

### 5. 日本語スタイル

- 敬体（です・ます調）を使用
- 簡潔かつ丁寧な表現
- 命令形は避け、提案形（〜してください）を使用

## 実装例

### Validatorでのメッセージ生成

```typescript
// src/domain/text/validators/FrontmatterValidator.ts
export class FrontmatterValidator implements Validator {
  readonly id = 'text/frontmatter';
  readonly name = 'Frontmatter検証';

  validate(context: LintContext): LintResult[] {
    const results: LintResult[] = [];
    let foundFrontmatter = false;

    visit(context.tree, 'yaml', (node: any) => {
      foundFrontmatter = true;
      try {
        const fm = yaml.load(node.value) as Record<string, unknown>;

        if (!fm?.title) {
          results.push({
            ruleId: `${this.id}/missing-title`,
            ruleName: 'frontmatter titleフィールド必須',
            severity: 'error',
            location: {
              line: node.position?.start.line,
              column: node.position?.start.column,
            },
            message: 'frontmatterに「title」フィールドがありません',
            detail: 'titleフィールドは章のタイトルを定義し、目次や検索で使用されます。',
            suggestion: 'frontmatterに以下を追加してください:\n\ntitle: [章のタイトル]',
            context: {
              found: JSON.stringify(fm),
              expected: 'title フィールドを含むオブジェクト',
            },
          });
        }
      } catch {
        results.push({
          ruleId: `${this.id}/invalid-yaml`,
          ruleName: 'frontmatter YAML構文',
          severity: 'error',
          message: 'frontmatterのYAML構文が不正です',
          detail: 'YAML構文エラーがあります。インデント、コロン、引用符を確認してください。',
          suggestion: 'YAMLの構文を確認し、修正してください。\n\n正しい形式の例:\n---\ntitle: "章タイトル"\ndraft: false\n---',
        });
      }
    });

    if (!foundFrontmatter) {
      results.push({
        ruleId: `${this.id}/missing`,
        ruleName: 'frontmatter必須',
        severity: 'error',
        location: { line: 1, column: 1 },
        message: 'frontmatterがありません',
        detail: 'Markdownファイルの先頭にYAML形式のfrontmatterが必要です。frontmatterは文書のメタデータ（タイトル、公開状態など）を定義します。',
        suggestion: 'ファイルの先頭に以下のfrontmatterを追加してください:\n\n---\ntitle: [章のタイトル]\ndraft: false\n---',
      });
    }

    return results;
  }
}
```

### ConsoleFormatterでの表示

```typescript
// src/presentation/cli/formatters/ConsoleFormatter.ts
export class ConsoleFormatter {
  format(output: LintUseCaseOutput): void {
    this.printHeader(output);
    this.printSummary(output);

    for (const result of output.results) {
      this.printResult(result);
    }

    this.printFooter(output);
  }

  private printResult(result: LintResult): void {
    const icon = this.getSeverityIcon(result.severity);
    const color = this.getSeverityColor(result.severity);

    console.log(chalk.gray('━'.repeat(60)));
    console.log(`${icon} ${color(`[${result.ruleId}]`)} ${result.message}`);

    if (result.location?.line) {
      console.log(`  📍 位置: ${result.location.line}行目`);
    }

    if (result.detail) {
      console.log(`  📝 説明:\n     ${result.detail.split('\n').join('\n     ')}`);
    }

    if (result.context?.found) {
      console.log(`  🔍 検出内容: ${result.context.found}`);
    }

    if (result.suggestion) {
      console.log(`  💡 修正方法:\n     ${result.suggestion.split('\n').join('\n     ')}`);
    }

    console.log('');
  }

  private getSeverityIcon(severity: string): string {
    switch (severity) {
      case 'error': return chalk.red('✖');
      case 'warning': return chalk.yellow('⚠');
      case 'info': return chalk.blue('ℹ');
      default: return '•';
    }
  }

  private getSeverityColor(severity: string): chalk.Chalk {
    switch (severity) {
      case 'error': return chalk.red;
      case 'warning': return chalk.yellow;
      case 'info': return chalk.blue;
      default: return chalk.white;
    }
  }
}
```

## CLIオプション

```bash
# 詳細表示（デフォルト）
article-lint text <file>

# 簡易表示
article-lint text <file> --quiet

# JSON出力
article-lint text <file> --format json

# 特定の重要度のみ表示
article-lint text <file> --severity error      # エラーのみ
article-lint text <file> --severity warning    # 警告以上

# カラー無効化
article-lint text <file> --no-color
```

## まとめ

本仕様により、以下を実現する：

1. **問題の特定が容易**: 行番号と具体的なコンテキストを提供
2. **修正方法が明確**: 具体的なコード例と手順を提示
3. **重要度の区別**: error/warning/infoで優先度を明示
4. **複数の出力形式**: コンソール（詳細/簡易）、JSONに対応
5. **拡張性**: 新しい文書タイプやルールの追加が容易
