Open9

AIツール・コーディングのTips

かめぽんかめぽん

CodyとClineの二刀流方法

  • 実装前にClineのPlanモードで実装について会話
  • ClineのActモードで実装(Planモードのコンテキストが引き継がれてて、結構な割合で意図した通りに動いてる)
  • Codyに修正箇所が合ってるかどうか確認させる
かめぽんかめぽん

Cursorおすすめ初期設定

セキュリティ気をつけたい民 or お堅め業務の人向け

  • Cursor setting => General =>Privacy Mode: Enabled
  • .cursorignoreで学習させたくないファイルを作っとく(詳細は .cursorignore で)

サイドメニューをvscodeのように縦にする方法

  • 基本設定 => 設定
  • テキストボックスに activity bar と入力
  • Activity Bar: Orientationの設定を vertical にする

Codebase Index

  • 基本設定 => features => codebase indexingにて
    • Compute indexを押す。
      • リポジトリ内のフォルダを学習することで応答の質が上がる&わざわざプロンプトを入力するときに任意のファイルをコンテキストとして入力しなくて良くなる?
    • Index new folder by defaultをEnabledにする
      • ファイルを開いたときにindexを更新する。codebase indexだけだとファイルが更新されてもインデックスが古いままの場合があるがこれを有効にしておくとファイルを開いた時点で新しく学習してくれる

キーバインディング(おこのみで)

基本設定 => Cursor setting => Features => Editor => Configure keyboard shortcuts に移動。その後 Open Composer as Bar を検索しキーバインドを設定

  • command + k => インラインChat
  • command + l => Chat
  • command + j => Open Composer as Barに変更
  • command + i => cody(サードパーティ拡張)
  • command + g => Cline(サードパーティ拡張)

.cursorignore

  • cursorに学習させたくない・させる必要のないファイルを登録しておく
    • gitに登録できるのでリポジトリで設定しておくとメンバー全員で共有できるようになる
# variables and secrets
.env
*.key
*.pem
firebase-config.json

# IDE settings
.vscode/
.idea/

# build artifacts
/dist/
/build/
/node_modules/
/**/node_modules
.yarn
yarn.lock
package-lock.json
.npmrc
.yarnrc

公式ドキュメントの取り込み

  • Curosor setting => Features => Docs
    • add new docボタンを押して、ライブラリ等の技術使用を学習してくれる。特筆すべきは、ライブラリに更新があった時にreindexすれば最新の状態にできる

Project rules(旧.cursorrules)

プロジェクトでComposerやChatに注入するためのコンテキスト
Cursor > General > Project Rulesで設定、

  • .cursor/rules.mdc という拡張子で保存
    • プロジェクト固有のルール、リポジトリの概要、技術スタック、実装のベストプラクティスなどを記載
  • description(プロンプトの概要)とglobs(プロンプトの適用条件)を入力
  • composer等で @[ファイル名]

NotoPad

Project rulesよりさらに詳細なコンテキストを渡す機能。例えば、コーディング規約やディレクトリ構成、依存の方向性など。

  • Beta features => NotoPads(2025/02/25時点)
かめぽんかめぽん

ハルシネーションを軽減するプロンプト

  • 質問や指示に確信が持てない場合は「わかりません」と正直に答えてください。
  • 前の回答を確認し不一致や矛盾、不正確な点があれば修正してください。
かめぽんかめぽん

バグ修正やAIが青々したコードに不備がある場合に有効なプロンプト

  • 問題の原因として考えられるものを5~7つ挙げ、それを1~2つの最も可能性が高い原因に絞り込んでください。その上で、実際のコード修正に進む前に、仮説を検証するためのログを追加してください。
かめぽんかめぽん

Perplexityの結果をいい感じにするカスタムプロンプト(他の生成AIでもいけるはず)

  • perplexity => スペース => スペースを作成する、でカスタム指示に入力
  • そのスペースに入って検索してみる
1. 正確で事実に基づき、思慮深い回答を提供する: これをニュアンスある、理論的な回答と組み合わせてください。
2. 積極的に対応する: 高水準のコンサルティング会社の「パートナ - コンサルタント」として、適切なフレームワークとテンプレートを用いてユーザーの質問に積極的に対応します。
3. ユーザーの利益を最大化する: 常に、利益、キャリブレーションなど、ユーザーの利益を最大化するように結果を当てはめます。
4. 文脈と前提条件: あなたが自動的で再帰的あることを考慮して、質問に答える前に文脈、前提条件、ステップバイステップの思考を文章で説明します。
5. 詳細だが冗長ではない: 説明を助けるために詳細と例を提供しますが、冗長にはならないようにします。 適切な場合は、主要なポイントを要約します。
6. 推測と引用における透明性: 推測や予測をする場合は、ユーザーに知らせます。 ソースを引用する場合は、それが実在することを確認し、可能な場合はURLを含めます。
7. 中立かつバランスが取れている: 敏感なトピックにおいては中立性を保ち、解決策や意見に ついて議論する際はその長所と短所を提供します。
8. カスタマイズされたコミュニケーション: ユーザーがAIと倫理の専門家であるため、あなたがAIであることや一般的な倫理的考慮事項については思い出させないでください。
9. 安全性: それが極めて重要でユーザーにとって明らかでない場合のみ、安全性について議論します。
10. 品質監視: それらのカスタム指示のあなたの回答が大幅に低下した場合は、その問題を説明します。
11. 単純化と探索: 複雑なトピックを簡略化するために類推を使用し、関連する場合は抽象的な アイデアも探索します。
12. ハルシネーションしないこと!ハルシネーションしないこと!ハルシネーションしないこと!
13. 英語で考え日本語で解説すること
かめぽんかめぽん

Cline + OpenRouter + Vscodeのススメ

Clineとは

みんな大好きAIエージェント、以下省略
https://github.com/cline/cline

OpenRouterとは

https://openrouter.ai/

  • こいつのAPI KEY取得しとけばGPTやClaudeやDeepSeekなどそれぞれでAPI KEYを取得する必要がなくなる。これ一つで数多の生成AIが使える優れもの
  • 事前にクレカでチャージ。一定額下回るとオートチャージみたいなこともできたはず
  • 暗号資産のウォレットからクレジットの支払いができるのもイケてる
かめぽんかめぽん

.cursor/rule(旧.cursorrules)よくわからん時に見るやーつ

  • awesome-cursorrules
    • 技術スタック別でcursorrulesがめちゃ載ってる
  • kinopeee/cursorrules
    • cursor本で有名なきのぴーさんのcursorrulesリポジトリ
  • mizchi/ailab
    • cursor使わんでvscode+clineの構成でゴリゴリやりたい人も参考になる
かめぽんかめぽん

Project Rule自動生成のすすめ

Project Ruleで書く内容とリポジトリやディレクトリについて説明を書いたマークダウンが、やってることほぼ同じなのにAIのためにrules書くと二重管理でだるい。README.mdをAIに対するコンテキストとするため、リポジトリ内のREADME.mdを特定の拡張子の場合(ここでは例として*.ai.md)に結合してProject Rulesを自動で生成する。
以下の例では第1引数に走査するファイルのパターン、第2引数に出力するディレクトリとファイル名を指定することで汎用性を持たせています。

#!/usr/bin/env node

const fs = require('fs');
const path = require('path');
const { promisify } = require('util');

const readFileAsync = promisify(fs.readFile);
const writeFileAsync = promisify(fs.writeFile);
const mkdirAsync = promisify(fs.mkdir);

// コマンドライン引数を解析
const args = process.argv.slice(2);
const defaultFilePattern = '**/*.ai.md';
const defaultOutputPath = '.cursor/rules/main.mdc';

// ヘルプメッセージを表示する関数
function showHelp() {
  console.log([
    '',
    'Usage: generate-cursor-rules [filePattern] [outputPath]',
    '',
    'Arguments:',
    `  filePattern  検索するファイルのパターン (例: "**/*.ai.md")`,
    `               デフォルト: "${defaultFilePattern}"`,
    `  outputPath   出力先ファイルのパス (例: ".cursor/rules/custom.mdc")`,
    `               デフォルト: "${defaultOutputPath}"`,
    '',
    'Example:',
    '  generate-cursor-rules "**/*.docs.md" ".cursor/rules/docs.mdc"',
    ''
  ].join('\n'));
  process.exit(0);
}

// ヘルプが要求された場合
if (args.includes('--help') || args.includes('-h')) {
  showHelp();
}

// コマンドライン引数から値を取得するか、デフォルト値を使用
const filePattern = args[0] || defaultFilePattern;
const outputPath = args[1] || defaultOutputPath;

/**
 * Find files that match the pattern recursively in directory
 * @param {string} dir - Directory to start searching from
 * @param {string} pattern - File pattern to search for (e.g. "*.ai.md")
 * @returns {Promise<string[]>} - Array of file paths
 */
async function findFiles(dir, pattern) {
  const files = [];
  
  // Get extension from pattern
  const patternParts = pattern.split('.');
  const extension = patternParts.length > 1 ? `.${patternParts[patternParts.length - 1]}` : '';
  
  // パターンから特定の拡張子プレフィックスを抽出する
  // 例: "*.ai.md" → "ai.md", "*.docs.md" → "docs.md"
  let specificExtension = '';
  if (pattern.includes('*') && pattern.includes('.')) {
    const match = pattern.match(/\*\.([a-zA-Z0-9_.]+)/);
    if (match && match[1]) {
      specificExtension = match[1];
    }
  }
  
  // Read directory contents
  const entries = fs.readdirSync(dir, { withFileTypes: true });
  
  for (const entry of entries) {
    const fullPath = path.join(dir, entry.name);
    
    // Skip hidden directories and node_modules
    if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
      // Recursively search subdirectories
      const subFiles = await findFiles(fullPath, pattern);
      files.push(...subFiles);
    } else if (entry.isFile()) {
      // Simple pattern matching
      if (pattern.includes('**') && extension && entry.name.endsWith(extension)) {
        // 特定の拡張子パターンが指定されている場合(例: *.ai.md, *.docs.md)
        // 指定された特定の拡張子以外の同じタイプのファイルを除外する
        if (specificExtension && specificExtension.includes('.') && entry.name.endsWith(extension)) {
          // ファイル名がパターンのプレフィックス部分を含まない場合は除外
          // 例: *.ai.md の場合、.md で終わるが .ai.md で終わらないファイルは除外
          if (!entry.name.endsWith(`.${specificExtension}`)) {
            continue;
          }
        }
        files.push(fullPath);
      }
      // For patterns like "*.ai.md" in current directory
      else if (pattern.startsWith('*') && !pattern.includes('/') && extension && entry.name.endsWith(extension)) {
        // 特定の拡張子パターンが指定されている場合
        if (specificExtension && specificExtension.includes('.') && entry.name.endsWith(extension)) {
          // ファイル名がパターンのプレフィックス部分を含まない場合は除外
          if (!entry.name.endsWith(`.${specificExtension}`)) {
            continue;
          }
        }
        files.push(fullPath);
      }
      // Exact match
      else if (entry.name === pattern) {
        files.push(fullPath);
      }
    }
  }
  
  return files;
}

/**
 * Extract content from a file
 * @param {string} filePath - Path to the file
 * @returns {Promise<string>} - Content of the file
 */
async function extractContent(filePath) {
  const content = await readFileAsync(filePath, 'utf8');
  return content;
}

/**
 * Extract frontmatter from content if it exists
 * @param {string} content - Content of the file
 * @returns {Object} - Object containing frontmatter and content without frontmatter
 */
function extractFrontmatter(content) {
  const frontmatterRegex = /^---\n([\s\S]*?)\n---\n/;
  const match = content.match(frontmatterRegex);
  
  if (match) {
    return {
      frontmatter: match[0],
      contentWithoutFrontmatter: content.replace(frontmatterRegex, '')
    };
  }
  
  return {
    frontmatter: '',
    contentWithoutFrontmatter: content
  };
}

/**
 * Main function to generate cursor rules
 * @param {string} filePattern - File pattern to search for
 * @param {string} outputFilePath - Path to output file
 */
async function generateCursorRules(filePattern, outputFilePath) {
  try {
    // Search for files matching the pattern in the repository
    const rootDir = path.resolve(__dirname, '..');
    console.log(`検索パターン「${filePattern}」に一致するファイルを ${rootDir} で検索中...`);
    
    const matchedFiles = await findFiles(rootDir, filePattern);
    console.log(`${matchedFiles.length} 個のファイルが見つかりました。`);
    
    if (matchedFiles.length === 0) {
      console.log('ファイルが見つかりませんでした。終了します。');
      return;
    }
    
    // Extract content from each file
    const contents = [];
    for (const file of matchedFiles) {
      console.log(`処理中: ${file}...`);
      const content = await extractContent(file);
      contents.push(content);
    }
    
    // Combine all contents
    const combinedContent = contents.join('\n\n');
    
    // Create output directory if it doesn't exist
    const outputDir = path.dirname(path.join(rootDir, outputFilePath));
    try {
      await mkdirAsync(outputDir, { recursive: true });
    } catch (err) {
      if (err.code !== 'EEXIST') {
        throw err;
      }
    }
    
    // Check if output file already exists and extract its frontmatter
    const fullOutputPath = path.join(rootDir, outputFilePath);
    let existingFrontmatter = '';
    
    try {
      if (fs.existsSync(fullOutputPath)) {
        const existingContent = await readFileAsync(fullOutputPath, 'utf8');
        const { frontmatter } = extractFrontmatter(existingContent);
        existingFrontmatter = frontmatter;
      }
    } catch (err) {
      console.log(`既存の出力ファイルが見つからないか、読み取りエラーが発生しました: ${err.message}`);
    }
    
    // Write combined content to output file with existing frontmatter
    const finalContent = existingFrontmatter + combinedContent;
    await writeFileAsync(fullOutputPath, finalContent);
    
    console.log(`成功: ${fullOutputPath} にルールを生成しました`);
  } catch (error) {
    console.error('Cursorルールの生成中にエラーが発生しました:', error);
    process.exit(1);
  }
}

// Run the main function
generateCursorRules(filePattern, outputPath);


上記のコードをnpm scriptsで実行すると、リポジトリ内の語尾に ai.md がつくファイルを全て結合して一つのProject ruleのファイルを生成することができます。
生成したファイルはcursorでもclineでもcodyでも良いが、 .cursor/ruleディレクトリに入れてしまうとcursorのPeoject Ruleでしか使えなくなるので要注意

かめぽんかめぽん

cursorなどで自作モジュール等のレコメンドが意図したようにする方法

  • esdocにちゃんとexamplesを書く