Open3
「CursorのProject Rules運用のベストプラクティスを探る」を試してみる

こんな感じのGitHub Actionsを作り、作ってもらうようにした
name: Update Cursor Rules
on:
push:
paths:
- 'rules/**'
- 'scripts/build_mdc.js'
jobs:
update-rules:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm install
- name: Generate MDC files
run: npm run build:mdc
- name: Check for changes
id: git-check
run: |
git add ".cursor/rules/"*.mdc
if git diff --staged --quiet; then
echo "No changes detected"
echo "changes=false" >> "$GITHUB_OUTPUT"
else
echo "Changes detected"
echo "changes=true" >> "$GITHUB_OUTPUT"
fi
- name: Create Pull Request
if: steps.git-check.outputs.changes == 'true'
uses: peter-evans/create-pull-request@v6
with:
commit-message: "chore: update cursor rules"
title: "chore: update cursor rules from rules directory"
body: |
このPRは、rulesディレクトリの変更に基づいて.cursor/rulesディレクトリのmdcファイルを更新します。
## 変更内容
- rulesディレクトリの変更を.cursor/rulesディレクトリに反映
## 自動生成
このPRは、GitHub Actionsによって自動的に生成されました。
branch: update-cursor-rules
base: ${{ github.ref_name }}
delete-branch: true
一旦これで運用予定

JSはこんな感じで書いた
const fs = require('fs');
const path = require('path');
const { glob } = require('glob');
// mdcファイルとmdディレクトリの対応関係の定義
const mdcConfigurations = [
{
output: ".cursor/rules/00_basic.mdc",
sourceDir: "rules/general",
header: "---\ndescription: セッション開始時に読み込むこと\nglobs: \nalwaysApply: true\n---\n\n",
filePattern: "*.md",
sortBy: "name"
},
{
output: ".cursor/rules/01_project.mdc",
sourceDir: "rules/common",
header: "---\ndescription: \nglobs: \nalwaysApply: false\n---\n",
filePattern: "*.md",
sortBy: "name"
},
{
output: ".cursor/rules/02_memory.mdc",
sourceDir: "rules/memory",
header: "---\ndescription: 今のセッション情報を保存する際に使用\nglobs: \nalwaysApply: true\n---\n",
filePattern: "*.md",
sortBy: "name"
},
{
output: ".cursor/rules/03_backend_development_checklist.mdc",
sourceDir: "rules/backend",
header: "---\ndescription: ドキュメント以外のファイルを修正した際\nglobs: \nalwaysApply: false\n---\n",
filePattern: "*.md",
sortBy: "name"
},
{
output: ".cursor/rules/04_context_window.mdc",
sourceDir: "rules/testing",
header: "---\ndescription: *.jsonなどのファイルを読み込む時\nglobs: \nalwaysApply: false\n---\n",
filePattern: "*.md",
sortBy: "name"
},
{
output: ".cursor/rules/05_git.mdc",
sourceDir: "rules/general",
header: "---\ndescription: \nglobs: \nalwaysApply: false\n---\n",
filePattern: "*.md",
sortBy: "name"
}
];
// ファイル名から数字プレフィックスを抽出してソートするための関数
function extractNumberPrefix(filename) {
const match = filename.match(/^(\d+)_/);
return match ? parseInt(match[1], 10) : Infinity;
}
// mdファイルを検索して結合する関数
async function buildMdcFile(config) {
// ルートディレクトリの取得(スクリプトの実行場所から相対パスで計算)
const rootDir = path.resolve(process.cwd());
// mdファイルのパターンを作成
const pattern = path.join(rootDir, config.sourceDir, config.filePattern);
// mdファイルを検索
const files = await glob(pattern);
// ファイルが見つからない場合は処理をスキップ
if (files.length === 0) {
console.log(`No files found in ${config.sourceDir}, skipping...`);
return;
}
// ファイル名でソート
files.sort((a, b) => {
const numA = extractNumberPrefix(path.basename(a));
const numB = extractNumberPrefix(path.basename(b));
return numA - numB;
});
// コンテンツの初期化
let content = '';
// ヘッダー情報を追加
content += config.header;
// 各mdファイルの内容を結合
for (const file of files) {
console.log(`Processing file: ${file}`);
const fileContent = await fs.promises.readFile(file, 'utf8');
content += fileContent + '\n\n';
}
// mdcファイルを出力
const outputPath = path.join(rootDir, config.output);
// 出力ディレクトリが存在することを確認
const outputDir = path.dirname(outputPath);
try {
await fs.promises.mkdir(outputDir, { recursive: true });
} catch (error) {
// ディレクトリが既に存在する場合は無視
}
// ファイルに書き込み
await fs.promises.writeFile(outputPath, content);
console.log(`Generated ${config.output} from ${files.length} files in ${config.sourceDir}`);
}
// 既存のMDCファイルの中身を空にする関数
async function cleanMdcFiles() {
const rootDir = path.resolve(process.cwd());
// .cursor/rules ディレクトリの存在確認
const rulesDir = path.join(rootDir, '.cursor/rules');
try {
await fs.promises.access(rulesDir);
} catch (error) {
// ディレクトリが存在しない場合は作成
await fs.promises.mkdir(rulesDir, { recursive: true });
return;
}
// .mdc ファイルを検索して中身を空にする
const mdcFiles = await glob(path.join(rulesDir, '*.mdc'));
for (const file of mdcFiles) {
console.log(`Clearing content of MDC file: ${file}`);
await fs.promises.writeFile(file, ''); // ファイルの中身を空にする
}
}
// メイン処理
async function main() {
try {
// 既存のMDCファイルを削除
await cleanMdcFiles();
// 各設定に対してmdcファイルを生成
for (const config of mdcConfigurations) {
await buildMdcFile(config);
}
console.log('All mdc files have been successfully generated!');
} catch (error) {
console.error('Error generating mdc files:', error);
process.exit(1);
}
}
// スクリプトの実行
main();
ログインするとコメントできます