そのバイブスの効率を後から見直す無粋なCursor用ツール

## はじめに

唐突ですがAIにいくら課金していますか？
自分は月額が 100$ を超えてきました。

最近はCursorのリクエストの消費が早すぎて
「頼む...次で....次でいい感じのやつを出力してくれっ....！！」
というカイジ的な気持ちでエンターキーを打鍵しています。

そして巷には
「こんなプロンプトが良いよ！」
「こんなフローがいいよ！」という情報が溢れていますが、

- それらがどれだけ効率的だったのか
- もっと良い他の方法があったのか
- 上手くいなかいプロンプトはどこで間違えていたのか

計測や反省する為のデータが不足しているなと思っていました。

さらに大規模なプロジェクトだとチーム全体のAI課金はバカにならない金額になってきていますよね。

リポジトリにコミットされている `.cursor` のルールが「本当に正解なのか？」という疑問がありつつ、手軽に書き換えたら知らぬ間にチーム全体のトークン消費が増える危険もあり定量的にデータを取る仕組みが欲しくなっていました。

という事で生ログを追うには辛い Cursor の大きなログをサマライズして出力するツールを作りました。

https://github.com/pppp606/cursor-efficiency

- Cursorを使っていて月500回のリクエストがすぐに無くなる人
- もっとAIと仲良くなりたい人
- チーム全体のAI利用効率を見たい人

などにオススメです。


## 2. ツールの概要
Cursorを通して自分がAIにリクエストした内容、生成されたコードの情報、使用したリクエストやトークン量などをログから拾います。

「こんなにリクエストしてるのに、結局コミットされたコードはこれだけ...私のプロンプト reject 率高すぎ！！」とかが分かるツールです。

各項目の説明は後述しますが、こんな感じのログが出力されます。

```json
{
  "branch": "main",
  "startTime": "2025-06-01T13:28:02.820Z",
  "endTime": "2025-06-02T01:37:21.871Z",
  "usedTokens": {
    "input": 16819,
    "output": 1987
  },
  "usageRequestAmount": 2,
  "chatCount": {
    "input": 2,
    "output": 6
  },
  "git": {
    "linesChanged": 170,
    "commit": [
      "b5dec83ee341d045d3645b8a9f9f23c5f8af2e4f"
    ],
    "diff": "diff --git a/jps-next/docs/api/openapi.yaml b/jps-next/docs/api/openapi.yaml\nindex ad2c262..ced9463 100644\n--- a/jps-next/docs/api/openapi.yaml\n+++ b/jps-next/docs/api/openapi.yaml\n@@ -142,6 +142,64 @@ components:\n         hasMore:\n           type: boolean\n \n+    Group:\n+      type: object\n+      properties:\n+        id:\n+          type: string\n+          format: cuid\n+        name:\n+          type: string\n+          nullable: true\n+        isDm:\n+          type: boolean\n+        createdAt:\n+          type: string\n+          format: date-time\n+        lastMessageAt:\n+          type: string\n+          format: date-time\n+          description: 最後のメッセージが送信された日時\n+        groupUsers:\n+          type: array\n+          items:\n+            type: object\n+            properties:\n+              id:\n+                type: string\n+                format: cuid\n+              userId:\n+                type: string\n+                format: uuid\n+              groupId:\n+                type: string\n+                format: cuid\n+              isAdmin:\n+                type: boolean\n+              isPending:\n+                type: boolean\n+              user:\n+                $ref: '#/components/schemas/User'\n+\n+    GroupUser:\n+      type: object\n+      properties:\n+        id:\n+          type: string\n+          format: cuid\n+        userId:\n+          type: string\n+          format: uuid\n+        groupId:\n+          type: string\n+          format: cuid\n+        isAdmin:\n+          type: boolean\n+        isPending:\n+          type: boolean\n+        user:\n+          $ref: '#/components/schemas/User'\n+\n paths:\n   /auth/[...nextauth]:\n     get:\n@@ -487,4 +545,114 @@ paths:\n         '401':\n           description: 認証エラー\n         '404':\n-          description: 通知が見つかりません \n\\ No newline at end of file\n+          description: 通知が見つかりません\n+\n+  /groups:\n+    get:\n+      summary: グループ一覧の取得\n+      description: ユーザーが所属するグループの一覧を取得します\n+      parameters:\n+        - name: isDm\n+          in: query\n+          description: DMグループのみを取得する場合はtrueを指定\n+          required: false\n+          schema:\n+            type: boolean\n+      responses:\n+        '200':\n+          description: グループ一覧の取得に成功\n+          content:\n+            application/json:\n+              schema:\n+                type: array\n+                items:\n+                  $ref: '#/components/schemas/Group'\n+        '401':\n+          $ref: '#/components/responses/UnauthorizedError'\n+        '500':\n+          $ref: '#/components/responses/InternalServerError'\n+    post:\n+      summary: グループの作成\n+      description: 新しいグループを作成します\n+      requestBody:\n+        required: true\n+        content:\n+          application/json:\n+            schema:\n+              type: object\n+              required:\n+                - isDm\n+                - userIds\n+              properties:\n+                name:\n+                  type: string\n+                  description: グループ名（DMグループの場合は任意）\n+                isDm:\n+                  type: boolean\n+                  description: DMグループかどうか\n+                userIds:\n+                  type: array\n+                  items:\n+                    type: string\n+                  description: グループに参加させるユーザーIDの配列（DMグループの場合は2人のみ）\n+      responses:\n+        '201':\n+          description: グループの作成に成功\n+          content:\n+            application/json:\n+              schema:\n+                $ref: '#/components/schemas/Group'\n+        '400':\n+          $ref: '#/components/responses/ValidationError'\n+        '401':\n+          $ref: '#/components/responses/UnauthorizedError'\n+        '500':\n+          $ref: '#/components/responses/InternalServerError'\n+  /groups/{groupId}:\n+    put:\n+      summary: グループ名の更新\n+      description: グループ名を更新します（管理者のみ可能、DMグループの場合は全員可能）\n+      parameters:\n+        - name: groupId\n+          in: path\n+          required: true\n+          schema:\n+            type: string\n+          description: グループID\n+      requestBody:\n+        required: true\n+        content:\n+          application/json:\n+            schema:\n+              type: object\n+              required:\n+                - name\n+              properties:\n+                name:\n+                  type: string\n+                  description: 新しいグループ名\n+      responses:\n+        '200':\n+          description: グループ名の更新に成功\n+          content:\n+            application/json:\n+              schema:\n+                $ref: '#/components/schemas/Group'\n+        '400':\n+          $ref: '#/components/responses/ValidationError'\n+        '401':\n+          $ref: '#/components/responses/UnauthorizedError'\n+        '403':\n+          description: 管理者権限がない\n+          content:\n+            application/json:\n+              schema:\n+                $ref: '#/components/schemas/Error'\n+        '404':\n+          description: グループが見つからない\n+          content:\n+            application/json:\n+              schema:\n+                $ref: '#/components/schemas/Error'\n+        '500':\n+          $ref: '#/components/responses/InternalServerError' \n\\ No newline at end of file\n"
  },
  "proposedCodeCount": 2,
  "adoptionRate": 0.5,
  "chatEntries": [
    [
      {
        "type": 1,
        "text": "/groups:のスキーマに問題がないか確認してください",
        "code": []
      },
      {
        "type": 2,
        "text": "OpenAPIの仕様書を確認させていただきます。`/groups`エンドポイントのスキーマについて、以下の点を確認しました：\n\n1. GET エンドポイント\n- 基本的な構造は問題ありません\n- レスポンススキーマは`Group`の配列として適切に定義されています\n- クエリパラメータ`isDm`は任意で、boolean型として適切に定義されています\n\n2. POST エンドポイント\n- リクエストボディの必須フィールド（`isDm`と`userIds`）は適切に定義されています\n- レスポンススキーマは`Group`として適切に定義されています\n\nただし、以下の点で改善の余地があります：\n\n1. エラーレスポンスの参照が不完全です：\n- `$ref: '#/components/responses/ValidationError'`\n- `$ref: '#/components/responses/UnauthorizedError'`\n- `$ref: '#/components/responses/InternalServerError'`\n\nこれらのエラーレスポンスの定義が`components/responses`セクションに存在しないようです。\n\n修正方針を提案させていただきます：\n\n1. `components/responses`セクションを追加し、共通のエラーレスポンスを定義する\n2. 各エラーレスポンスのスキーマを明確に定義する\n\n以下のように修正を提案させていただきます：\n",
        "code": []
      },
      {
        "type": 2,
        "text": "",
        "code": [
          "components:\n  securitySchemes:\n    BearerAuth:\n      type: http\n      scheme: bearer\n      bearerFormat: JWT\n\n  responses:\n    ValidationError:\n      description: バリデーションエラー\n      content:\n        application/json:\n          schema:\n            type: object\n            properties:\n              message:\n                type: string\n              errors:\n                type: array\n                items:\n                  type: object\n                  properties:\n                    field:\n                      type: string\n                    message:\n                      type: string\n\n    UnauthorizedError:\n      description: 認証エラー\n      content:\n        application/json:\n          schema:\n            type: object\n            properties:\n              message:\n                type: string\n\n    InternalServerError:\n      description: サーバーエラー\n      content:\n        application/json:\n          schema:\n            type: object\n            properties:\n              message:\n                type: string\n\n  schemas:\n// ... existing code ..."
        ]
      },
      {
        "type": 2,
        "text": "これで`/groups`エンドポイントのスキーマは完全になりました。主な改善点は：\n\n1. 共通のエラーレスポンス定義を追加\n   - `ValidationError`: バリデーションエラー用\n   - `UnauthorizedError`: 認証エラー用\n   - `InternalServerError`: サーバーエラー用\n\n2. 各エラーレスポンスに適切なスキーマを定義\n   - エラーメッセージ\n   - バリデーションエラーの場合は詳細なエラー情報も含む\n\nこれにより、APIのエラーハンドリングがより明確になり、クライアント側での実装も容易になります。",
        "code": []
      },
      {
        "type": 1,
        "text": "エラーは一旦大丈夫です。レスポンスの部分でuserのスキーマを定義済みのUser,UserProfileを返す形に変更してください",
        "code": []
      },
      {
        "type": 2,
        "text": "はい、承知しました。`/groups`エンドポイントのレスポンスで、`Group`スキーマ内の`user`プロパティを既存の`User`と`UserProfile`スキーマを使用するように修正します。\n\n現在の`Group`スキーマでは、`groupUsers`配列内の`user`プロパティが`User`スキーマを参照していますが、`UserProfile`も含めるように修正します。\n\n修正方針：\n1. `Group`スキーマの`groupUsers`配列内の`user`プロパティを、既存の`User`スキーマを参照するように変更します\n2. `User`スキーマは既に`profile`プロパティで`UserProfile`を参照しているため、これで両方の情報が含まれることになります\n",
        "code": []
      },
      {
        "type": 2,
        "text": "",
        "code": [
          "    Group:\n      type: object\n      properties:\n        id:\n          type: string\n          format: cuid\n        name:\n          type: string\n          nullable: true\n        isDm:\n          type: boolean\n        createdAt:\n          type: string\n          format: date-time\n        lastMessageAt:\n          type: string\n          format: date-time\n          description: 最後のメッセージが送信された日時\n        groupUsers:\n          type: array\n          items:\n            type: object\n            properties:\n              id:\n                type: string\n                format: cuid\n              userId:\n                type: string\n                format: uuid\n              groupId:\n                type: string\n                format: cuid\n              isAdmin:\n                type: boolean\n              isPending:\n                type: boolean\n              user:\n                $ref: '#/components/schemas/User'\n// ... existing code ..."
        ]
      },
      {
        "type": 2,
        "text": "これで`/groups`エンドポイントのレスポンスに含まれる`user`プロパティは、既存の`User`スキーマを参照するようになりました。`User`スキーマは既に`profile`プロパティで`UserProfile`を参照しているため、ユーザー情報とプロフィール情報の両方が含まれることになります。\n\nこの変更により：\n1. レスポンスの一貫性が保たれます\n2. 既存のスキーマ定義を再利用できます\n3. ユーザー情報とプロフィール情報の両方が適切に含まれます",
        "code": []
      }
    ]
  ]
}
```

Cursorのログの仕様としは、キーバリューの形でローカルにあるSQLiteのファイル `state.vscdb` に書き込まれている感じです。

追うのが大変でAIからの返答メッセージや提案コードの在処を探し回ったので、同じ様にログ探してここに辿り着いた人は以下を参考にしてください！
https://github.com/pppp606/cursor-efficiency/blob/main/src/utils/chatLogs.ts


## 3. インストール

```bash
git clone https://github.com/pppp606/cursor-efficiency.git
cd cursor-efficiency

npm install
npm run build
npm install -g .
```

まだnpmに公開しておらず、ビルドして使ってください🙏
自分でも使っていってツールが小慣れてきたらnpmに登録予定です。

## 4. 使い方

基本的に、ブランチを切って新たな開発を始めるタイミングで計測を開始、コミットを積んでいって計測終了というフローを想定しています。
一つのリポジトリ、同じディレクトリで複数のブランチを切り替えながら、開発を進める事は想定していません。

### 4.1 計測開始：`cursor-efficiency start`

```bash
cursor-efficiency start
```

`cursor-efficiency/.log/${リポジトリのディレクトリ名}/.cursor-efficiency.json` が生成され、計測開始時刻、計測開始時のコミットSHAなどを書き込みます。


### 4.2 コーディング＆コミット

Cursorでいつもどおり開発。
AIにお願いしたり、リジェクトしたり編集したりしていきます。

コミットは好きなタイミングで積んでいき「よし作業が終わったぞPR作るか！」というタイミングで計測を終了するのが良さそうです。できるだけ作業の単位を小さくする方が、AI的にも計測結果の精度的にも良いはずです。


### 4.3 計測終了：`cursor-efficiency end`

```bash
# チャット履歴を含めない場合
cursor-efficiency end

# 詳細モードでチャット履歴も出力する場合
cursor-efficiency end -c
```

ファイルへの書き込み等は行なっていません。
ファイルの出力が必要な場合は `> log.json` とかで出力してください！


#### 各フィールド解説と活用イメージ

##### usedTokens.input / usedTokens.output
- AIに投げたプロンプトで消費したトークン数
- 500回りクエストには影響しません。
- トークン（特にinput）が大きすぎると回答の精度が落ちる可能性があります

##### usageRequestAmount
  - トークン消費をコスト換算した数値
  - コードの質を保ちつつ、ここの数値を下げる事ができれば、コストを削減できます

##### chatCount.input / chatCount.output
  - 会話の往復回数
  - チャット回数が多いほど「AIに対して一度で要点を伝えられていない」ことが推測できます
  - ここの回数も下げることが目標です

##### git.linesChanged
  - 実際にコミットされた行数
  - 変更行数だけでは計れませんが、リクエスト回数と併せて効率性を見ることができます

##### git.diff
  - コミットした差分
  - 詳細モードでのみ出力されます

##### proposedCodeCount / adoptionRate
- AIが提案したコードスニペットの総数と、そのうちApprovedした割合
- proposedCodeCountが多く、adoptionRateが低い場合は「プロンプトがあいまい」「提案がズレてる」などの原因を推測できます


## 6. 実践例：ログをLLMに投げて個人的に反省会

上にあげたサンプルのログをAIに渡して、具体的な改善策を提案してもらうとこんな感じになります。


### 6.1 プロンプト

```text
コーディングAIとやりとりをしたログを確認し、ユーザーの改善点を提案してください。

## 目標
- adoptionRate（提案コードが採用される率）を上げる
- chatCount（往復回数）を減らす
- usageRequestAmount（AI へのリクエスト回数）を減らす

## ログ
<サンプルのログ>

```

### 6.2 回答
以下GPT-4oの回答、その通りという感じですね。


```md
以下のログから、ユーザー側のやり取りで改善できるポイントを挙げます。目標は adoptionRate を上げ、chatCount と usageRequestAmount を減らすことです。

---

## 1. 要求する内容を一度にまとめる

* **現状**

  * 最初のリクエスト（「/groups のスキーマに問題がないか確認」）で、AI は「エラーレスポンス定義の追加」を提案しました。しかし、ユーザーはその提案を採用せず、別の要望（「User/UserProfile スキーマを使う形に変更」）を提示。結果、AI が最初に出したコードの半分は採用されず、提案数は2、採用数は1（adoptionRate 0.5）に留まりました。
* **改善案**

  1. 最初のプロンプトで「エラー定義は不要なのでスキップしてください」「レスポンスの user 部分だけ見てほしい」など、不要な提案を事前に除外する。
  2. 「エラーレスポンス関連の修正は要らない」「User/UserProfile を参照する変更も同時にお願いします」というように、必要な修正範囲をすべてまとめて伝える。

  * ⇒ AI は一度に最適な提案を返しやすくなり、無駄な提案を省けることで採用率が上がり、往復回数も減る。

---

## 2. 出力形式を明確に指定する

* **現状**

  * AI が最初の返信で「改善方針」→「コードブロック」→「説明」という流れで複数メッセージに分けて送信。ユーザーが「エラー部分は大丈夫です」と返すまで、説明や提案が冗長になりがちでした。
* **改善案**

  1. 「コードだけを出力してください」「説明は要点だけで構いません」「出力は YAML の差分形式でお願いします」など、出力の粒度・フォーマットを明示しておく。
  2. たとえば：

     ```
     /groups スキーマの user 部分を既存 User/UserProfile を参照するように変更してください。追加説明不要なので、該当部分の YAML コードのみ提示してください。
     ```

  * ⇒ AI は余計な言葉を省き、すぐに必要なコードを返せるため、やり取りが短くなり、chatCount・usageRequestAmount が減少しやすい。

---

## 3. 期待するスキーマの具体例を併記する

* **現状**

  * 「既存 User, UserProfile を参照するように変更してほしい」と伝えただけで、どのように組み合わせたいのか細かいイメージは示されていませんでした。そのため、AI は一般的な修正方針を提示し、結果的に何度か細かいやり取りが発生しました。
* **改善案**

  1. 「Group スキーマ中の groupUsers 配列内の user プロパティを以下のように変更してください」と、変更後のプロパティ例（`$ref: '#/components/schemas/User'` を使うイメージ）を簡単にコピペで示す。
  2. あるいは「最終的にこのような JSON を返す形が理想です：

     ```json
     {
       "id": "cuid123",
       "name": "チームA",
       "groupUsers": [
         {
           "id": "cuid456",
           "user": { "id":"uuid789", "name":"山田太郎", "profile":{...} },
           "isAdmin": false,
           "isPending": true
         }
       ]
     }
     ```

     という要件をかなり具体的に示す。

  * ⇒ AI は「これが最終形だな」と把握してコードを生成できるため、微調整の往復を減らせる。

---

## 4. 不要な機能や提案はあらかじめ否定する

* **現状**

  * AI が「エラーレスポンス定義を追加しましょう」とまず提案したものの、ユーザーはあえて導入しない方針でした。
* **改善案**

  1. プロンプトに「エラーレスポンス部分はすでに別所で定義済みのため、追加不要です」と明記する。
  2. 「エラーハンドリングに関しては言及不要」「GET/POST のみ見てほしい」など、見ないでほしい範囲を明示する。

  * ⇒ AI が不要な修正を省略し、ユーザーが本当に必要な部分だけを出力してくれるので、提案の採用率が上がる。

---

## 5. 質問は「なぜそうなるのか」ではなく「どうすれば良いか」を中心にする

* **現状**

  * AI の返信では「エラーレスポンスがないようです」と原因指摘から入り、その後コード例を提示。ユーザーは「なぜなくていいか？」ではなく「どう作ればいいか」が知りたかったので、原因を深掘りするよりも実装例が欲しかった側面があります。
* **改善案**

  1. 質問を「なぜエラーレスポンスが不足しているか」ではなく、「エラーレスポンスを定義済みのものに差し替えるにはどうすればよいか」とストレートに書く。
  2. 必要があれば「YAML の差分形式で」「コンポーネント部分以下だけ抜粋で」と具体的に示す。

  * ⇒ AI は原因分析に時間を使わず、すぐ実装例を出せるため、不要な解説を省いてやり取りを短縮できる。

---

## 6. 会話の前提を共有して無駄な誤解を防ぐ

* **現状**

  * そもそも「components/responses の各エラー定義はすでに別のファイルで定義済みなので不要」といった前提が共有されておらず、AI は“ないもの”として提案してしまいました。
* **改善案**

  1. 「既存リポジトリでは以下のファイル（例：`components/responses.yaml`）にすでに全エラーレスポンスが定義されています。そのため `/groups` 内での `$ref: '#/components/responses/...Error'` はすでに解決済みで、ここでは見なくて構いません」というように、会話の前提を最初に書く。
  2. 「現状の openapi.yaml 全体はすでにリポジトリにあるので、変更はこの diff 部分だけで構いません」と伝える。

  * ⇒ AI は不要な部分に手を付けず、提案の的外れを防げるため、採用率が上がる。

---

## 7. 出力ボリュームを抑える工夫をする

* **現状**

  * AI の返信が複数ステップに分かれたり、説明が長くなることで往復が増えています。
* **改善案**

  1. 「コードのみ」「説明は一行だけ」「YAML 全体ではなく、このセクションだけ」と要求の粒度を細かく指定する。
  2. 「動作確認用のサンプルリクエスト/レスポンスは不要です。最小限の変更差分だけ示してください」と伝える。

  * ⇒ AI が余計な情報を省き、最小限の出力で済むため、chatCount が減少する。

---

## まとめ

1. **最初のプロンプトで不要部分を明確に否定し、必要な変更をすべてまとめる。**
2. **出力フォーマット（差分形式／コードのみ／YAML 局所など）を具体的に指定する。**
3. **期待する最終形イメージやスキーマ例を提示し、AI が迷わないようにする。**
4. **会話の前提（既存定義の有無など）を共有し、的外れな提案を防ぐ。**
5. **「どう実装すればよいか」を中心に質問し、原因分析や余計な説明を不要と明示する。**

これらを踏まえてプロンプトを書き直せば、AI は最初から適切な提案を返しやすくなり、採用率（adoptionRate）は上がり、往復回数（chatCount）やリクエスト回数（usageRequestAmount）は削減されるはずです。

```



## 7. チームでの活用例：Gitフック経由で一括収集

個人だけでなく、チーム全体のAI利用状況を把握したいときは、Gitフックに組み込むのが良さそうです。ここでは `pre-push` フックの例を示します。

#### 7.1 フックスクリプト例（`.git/hooks/pre-push`）

```bash
#!/bin/bash
# 例: .git/hooks/pre-push

# 1. 現在のブランチと短いコミットSHAを取得
BRANCH=$(git rev-parse --abbrev-ref HEAD)
SHA=$(git rev-parse --short HEAD)
LOG_FILE="cursor_log_${BRANCH}_${SHA}.json"

# 2. Cursor-efficiency終了コマンドを実行してログを生成
cursor-efficiency end -c > "$LOG_FILE"

# 3. ログをサーバーやS3にアップロード（例：curlでPOST）
curl -X POST \
  -H "Content-Type: application/json" \
  -d @"$LOG_FILE" \
  https://example.com/api/team-logs/upload

exit 0
```

収集したデータをBigQueryに投入して解析すると

- このファイル弄ろうとすると途端に精度が落ちてる、リファクタした方が良さそう
- 〇〇さんむちゃくちゃAI使うの上手いな
- ルール変更したらりクエスト数が10%下がった！！

とかトレンドが見えてくるんじゃないかなと思っています。
（実際にやってみたらどうなるかはわかりません🙏）


## 8. 今後の展望
- BigQuery等に投げ込む用にサマライズしていない生log出力モードを追加する
- AIが解析するのに有用なフィールドを選んでサマライズデータに追加する
- Cursornのログデータの構造がちょいちょい変わるのでCIで変更を検知できるようにする
- Codex等のコーディングエージェントや、作業中に並行して使うチャット型AIも計測対象にする


## 最後に
何かあったらissueに書いてもらえると嬉しいです！