From 2debf09bf247cdca5d727c4851af0bef4017cb21 Mon Sep 17 00:00:00 2001 From: mattyatea Date: Mon, 29 Jun 2026 22:23:26 +0900 Subject: [PATCH] Tighten user entity test types --- .agents/skills/add-api-endpoint/SKILL.md | 253 ++++++++++++++++++ .agents/skills/add-i18n-key/SKILL.md | 117 ++++++++ .agents/skills/add-mk-component/SKILL.md | 174 ++++++++++++ .agents/skills/context-budget/SKILL.md | 148 ++++++++++ .agents/skills/create-migration/SKILL.md | 156 +++++++++++ .../source-command-harness-audit/SKILL.md | 152 +++++++++++ .../source-command-quality-gate/SKILL.md | 128 +++++++++ .codex/agents/misskey-api-reviewer.toml | 164 ++++++++++++ .codex/agents/vue-component-reviewer.toml | 173 ++++++++++++ .serena/.gitignore | 2 + .serena/project.yml | 133 +++++++++ packages/backend/test/e2e/users.ts | 12 +- .../test/unit/entities/UserEntityService.ts | 24 +- 13 files changed, 1620 insertions(+), 16 deletions(-) create mode 100644 .agents/skills/add-api-endpoint/SKILL.md create mode 100644 .agents/skills/add-i18n-key/SKILL.md create mode 100644 .agents/skills/add-mk-component/SKILL.md create mode 100644 .agents/skills/context-budget/SKILL.md create mode 100644 .agents/skills/create-migration/SKILL.md create mode 100644 .agents/skills/source-command-harness-audit/SKILL.md create mode 100644 .agents/skills/source-command-quality-gate/SKILL.md create mode 100644 .codex/agents/misskey-api-reviewer.toml create mode 100644 .codex/agents/vue-component-reviewer.toml create mode 100644 .serena/.gitignore create mode 100644 .serena/project.yml diff --git a/.agents/skills/add-api-endpoint/SKILL.md b/.agents/skills/add-api-endpoint/SKILL.md new file mode 100644 index 0000000000..7fa1d8eeea --- /dev/null +++ b/.agents/skills/add-api-endpoint/SKILL.md @@ -0,0 +1,253 @@ +--- +name: add-api-endpoint +description: Misskey の REST API エンドポイント (/api//) を NestJS DI + meta/paramDef 規約で追加する。バックエンドに新しい API ルートを足す時に必ず使う。endpoint-list.ts への手動登録、e2e テスト、misskey-js 再生成、CHANGELOG までの一連の手順を含む。 +--- + +# Misskey API エンドポイント追加スキル + +`packages/backend/src/server/api/endpoints//.ts` に新規エンドポイントを追加するためのワークフロー。**手順 4 (endpoint-list.ts 登録) を忘れると 404 になる** 点に最大の注意を払う。 + +## 最重要事実 (見落とすと壊れる) + +1. エンドポイントは **glob 自動収集されない**。[packages/backend/src/server/api/endpoint-list.ts](../../../packages/backend/src/server/api/endpoint-list.ts) への 1 行追加が必須。 +2. `meta` / `paramDef` を変えたら **misskey-js の再生成が必須**。`pnpm build-misskey-js-with-types` を忘れると CI の `check-misskey-js-autogen` で必ず落ちる。 +3. `meta.errors` の各 `id` は **UUID**。重複させない (既存全 UUID と衝突確認)。 + +## ステップ 1: ファイル配置と SPDX + +`packages/backend/src/server/api/endpoints//.ts` に新規作成する。`` は機能領域 (例: `notes`, `users`, `admin/announcements`)。 + +冒頭に SPDX ヘッダーを必ず付ける: + +```ts +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ +``` + +## ステップ 2: 最小テンプレート (シンプル read 系) + +[endpoints/ping.ts](../../../packages/backend/src/server/api/endpoints/ping.ts) をベースに書く。認証不要・パラメータなし・小さなレスポンスの例: + +```ts +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; + +export const meta = { + tags: [''], + requireCredential: false, + + res: { + type: 'object', + optional: false, nullable: false, + properties: { + // ... + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: {}, + required: [], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + ) { + super(meta, paramDef, async (ps, me) => { + // 実装 + }); + } +} +``` + +## ステップ 3: 認証付き / DI / errors を含むテンプレート + +[endpoints/notes/create.ts](../../../packages/backend/src/server/api/endpoints/notes/create.ts) を参照する。要点: + +```ts +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import { ApiError } from '@/server/api/error.js'; +import { DI } from '@/di-symbols.js'; +// import ms from 'ms'; // limit.duration に ms('1hour') 等を渡すとき (default import) + +export const meta = { + tags: ['notes'], + requireCredential: true, // 認証必須なら true + prohibitMoved: false, // moved user を拒否するか + kind: 'write:notes', // OAuth scope (requireCredential 時に必須) + limit: { + duration: 3600000, // ms('1hour') + max: 300, + }, + errors: { + noSuchNote: { + message: 'No such note.', + code: 'NO_SUCH_NOTE', + id: 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx', // ★ UUID v4 を必ず生成 (`x`=hex, `y`=8/9/a/b)。下の「UUID 生成」を参照 + }, + }, + res: { + type: 'object', + optional: false, nullable: false, + ref: 'Note', // packed entity に揃える場合 + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + noteId: { type: 'string', format: 'misskey:id' }, + }, + required: ['noteId'], +} as const; + +@Injectable() +export default class extends Endpoint { // eslint-disable-line import/no-default-export + constructor( + @Inject(DI.notesRepository) + private notesRepository: NotesRepository, + ) { + super(meta, paramDef, async (ps, me) => { + const note = await this.notesRepository.findOneBy({ id: ps.noteId }); + if (note == null) throw new ApiError(meta.errors.noSuchNote); + // 実装 + }); + } +} +``` + +### meta フィールド早見表 + +| フィールド | 用途 | +|---|---| +| `tags` | OpenAPI タグ (機能領域) | +| `requireCredential` | 認証必須か | +| `requireModerator` / `requireAdmin` | 権限制限 | +| `prohibitMoved` | アカウント移行済ユーザーを拒否 | +| `kind` | OAuth scope (`read:notes` / `write:notes` 等)。`requireCredential: true` 時必須 | +| `limit` | レート制限 (`{ duration, max, key?, minInterval? }`) | +| `errors` | エラー定義。各要素に `message` / `code` / `id` (UUID v4) 必須 | +| `res` | JSON Schema or `ref: ''` (packed entity 参照) | +| `requireFile` | ファイルアップロード必須 | +| `secure` | secure cookie 必要 | +| `allowGet` | GET メソッド許可 | +| `cacheSec` | レスポンスキャッシュ秒数 | +| `description` | OpenAPI 説明 | + +詳細は [endpoints.ts](../../../packages/backend/src/server/api/endpoints.ts) の型定義 (lines 11-125) を参照。 + +### paramDef の特殊フォーマット + +JSON Schema (AJV) ベースだが、Misskey 拡張を使える: + +- `format: 'misskey:id'` — ID 文字列バリデーション +- `allOf` / `anyOf` / `oneOf` — 複合条件 +- `default` — デフォルト値 + +詳細は [endpoint-base.ts](../../../packages/backend/src/server/api/endpoint-base.ts) を参照。 + +### エラー throw + +**「公開 API エラーとして API クライアントに返したいもの」は必ず `throw new ApiError(meta.errors.)` を使う**。`meta.errors` に列挙した上で `ApiError` でラップしないと、misskey-js 側の型情報に出ず、レスポンスも 500 になる。第 2 引数で追加情報を渡せる: + +```ts +throw new ApiError(meta.errors.invalidParam, { reason: 'too short' }); +``` + +一方で、**想定外の例外 (DB 不整合 / 下層サービスの bug など) を握り潰すために `try/catch` で `ApiError` に変換するのは避ける**。既存 endpoint も「期待される業務エラーは `ApiError` に変換し、それ以外は `throw err;` で再 throw する」という二段構えになっている。`packages/backend/src/server/api/endpoints/notes/create.ts` の `catch` 節 (末尾の `throw err;`) を参照。生の `throw` を全面禁止すると未知例外も 200 で潰れて debug が困難になるので、このバランスを保つ。 + +詳細は [error.ts](../../../packages/backend/src/server/api/error.ts) の `ApiError` クラスを参照。 + +### UUID 生成 + +```bash +node -e "console.log(crypto.randomUUID())" +``` + +その UUID が他のエンドポイントの `id` と衝突していないか必ず確認: + +```bash +grep -r "id: '<生成した UUID>'" packages/backend/src/server/api/endpoints/ +``` + +## ステップ 4: ★必須 — endpoint-list.ts に登録 + +[packages/backend/src/server/api/endpoint-list.ts](../../../packages/backend/src/server/api/endpoint-list.ts) の同カテゴリ末尾に 1 行追加する(既存の並びを崩さない): + +```ts +export * as '/' from './endpoints//.js'; +``` + +ファイル冒頭のコメント (`When you add new endpoint, you should add it to this file.`) の通り、このリストが API ルーティングの単一の真実。**忘れると 404**。 + +`EndpointsModule.ts` がこのファイルの全エクスポートを `Object.entries()` で反復し、NestJS provider (`provide: 'ep:'`) を生成する。 + +## ステップ 5: e2e テスト追加 + +[packages/backend/test/e2e/endpoints.ts](../../../packages/backend/test/e2e/endpoints.ts) に対応する `describe` / `test` を追加する。`api()` ヘルパーで叩く: + +```ts +describe('/', () => { + test('正常系', async () => { + const res = await api('/', { /* params */ }, alice); + assert.strictEqual(res.status, 200); + }); +}); +``` + +実行: `pnpm --filter backend test:e2e` + +## ステップ 6: misskey-js 再生成 (★必須) + +`meta` / `paramDef` / `res` を変えたら必ず実行する: + +```bash +pnpm build-misskey-js-with-types +``` + +これで以下が更新される: + +- `packages/backend/built/api.json` (OpenAPI spec) +- `packages/misskey-js/generator/api.json` +- `packages/misskey-js/src/autogen/*.ts` (TypeScript 型) + +PR に `packages/misskey-js/src/autogen/` 配下の差分が含まれていないと、CI の `check-misskey-js-autogen` で落ちる。 + +## ステップ 7: Lint と typecheck + +```bash +pnpm --filter backend lint +``` + +(typecheck = `tsgo --noEmit` / ESLint = `eslint`) + +## ステップ 8: CHANGELOG + +ユーザー影響がある (新機能 / 既存挙動変更) なら、`CHANGELOG.md` の `## Unreleased` → `### Server` に 1 行追加する ([AGENTS.md §CHANGELOG](../../../AGENTS.md#changelog) 参照): + +``` +- Feat: /api// を追加 +``` + +純粋なリファクタや内部用なら不要。 + +## 参照ファイル + +- [endpoints.ts (meta/paramDef 型定義)](../../../packages/backend/src/server/api/endpoints.ts) +- [endpoint-base.ts (Endpoint 基底クラス)](../../../packages/backend/src/server/api/endpoint-base.ts) +- [endpoint-list.ts (★ ここに登録)](../../../packages/backend/src/server/api/endpoint-list.ts) +- [error.ts (ApiError)](../../../packages/backend/src/server/api/error.ts) +- [endpoints/ping.ts (最小例)](../../../packages/backend/src/server/api/endpoints/ping.ts) +- [endpoints/notes/create.ts (DI + errors の典型)](../../../packages/backend/src/server/api/endpoints/notes/create.ts) +- [test/e2e/endpoints.ts (テスト例)](../../../packages/backend/test/e2e/endpoints.ts) +- [scripts/generate_api_json.js (misskey-js 生成元)](../../../packages/backend/scripts/generate_api_json.js) diff --git a/.agents/skills/add-i18n-key/SKILL.md b/.agents/skills/add-i18n-key/SKILL.md new file mode 100644 index 0000000000..899c61826d --- /dev/null +++ b/.agents/skills/add-i18n-key/SKILL.md @@ -0,0 +1,117 @@ +--- +name: add-i18n-key +description: Misskey の i18n キーを追加・修正する。locales/ja-JP.yml のみ編集可能で、他言語ファイル (en-US.yml 等 39 言語) は Crowdin の自動配信先のため絶対に触らない。型は packages/i18n が ja-JP.yml から自動再生成する。frontend からは i18n.ts. または i18n.tsx.(...) で参照する。 +--- + +# Misskey i18n キー追加スキル + +UI 文言の追加・変更を行う際の規約。**手動編集して良いのは `locales/ja-JP.yml` のみ。** + +## 大前提 (絶対 NG) + +- **`locales/.yml` (ja-JP.yml 以外) の編集は禁止**。これらは Crowdin の自動配信先で、手動編集すると次の同期で上書き喪失する ([locales/README.md](../../../locales/README.md), [crowdin.yml](../../../crowdin.yml))。 +- 文字列リテラルを SFC に直書きしない (`こんにちは` 等)。必ず `i18n.ts.` を経由する。 +- 既存キーの破壊的リネームは Crowdin 翻訳資産も道連れになるので慎重に。追加・改名併用 (新キー追加 → 移行 → 旧キー削除) を検討する。 + +## ステップ 1: ja-JP.yml にキーを追加 + +[locales/ja-JP.yml](../../../locales/ja-JP.yml) を編集する。YAML の階層構造を維持し、関連するセクションに配置する: + +```yaml +# トップレベル単純キー +save: "保存" + +# ネストしたカテゴリ (アンダースコア接頭辞は内部カテゴリ) +_settings: + general: "全般" + appearance: "外観" + +# パラメータ付き (単純なプレースホルダ置換) +# ICU MessageFormat の plural / select / number / date などは非対応 +# 使えるのは `{name}` のような単純な置換のみ +greeting: "こんにちは、{name}さん" +``` + +### 命名のお作法 + +- 単純キー: lowerCamelCase (例: `saveChanges`, `confirmDelete`)。 +- カテゴリ: アンダースコア接頭辞 (例: `_settings`, `_abuseUserReport`)。 +- 既存セクション内に置く場合はアルファベット順を維持する (新セクション全体を末尾に追加するのは可)。 + +## ステップ 2: 型定義の自動再生成 + +`packages/i18n/build.ts` が `ja-JP.yml` を解析し、TypeScript インターフェースを [packages/i18n/src/autogen/locale.ts](../../../packages/i18n/src/autogen/locale.ts) に出力する。 + +### 自動 (推奨) + +`pnpm dev` 実行中なら、`packages/i18n` の watch スクリプトが yml の変更を検知して自動再生成する。 + +### 手動 + +```bash +pnpm --filter i18n generate +``` + +実体は `tsx scripts/generateLocaleInterface.ts`。 + +### 失敗パターン + +これを実行せずに frontend 側で `i18n.ts.` を参照すると、`Locale` インターフェースに追加されていないため、typecheck で「Property '' does not exist on type 'Locale'」というエラーになる。`pnpm --filter frontend lint` で発覚する。 + +## ステップ 3: frontend での参照 + +```ts +import { i18n } from '@/i18n.js'; +``` + +| 用途 | 書き方 | +|---|---| +| 単純文字列 | `i18n.ts.save` | +| ネスト | `i18n.ts._settings.general` | +| パラメータ付き | `i18n.tsx.greeting({ name: userName })` | +| Vue テンプレート内 | `{{ i18n.ts.save }}` / `{{ i18n.tsx.greeting({ name }) }}` | + +`i18n.ts` は型付き文字列、`i18n.tsx` は MessageFormat 関数。 + +## ステップ 4: 検証 + +```bash +# i18n パッケージの型再生成 + typecheck +pnpm --filter i18n lint + +# frontend で新キー参照箇所の型チェック +pnpm --filter frontend lint +``` + +## 例: 「ノートを削除しますか?」確認ダイアログを追加する + +1. `locales/ja-JP.yml`: + ```yaml + _notes: + deleteConfirm: "このノートを削除しますか?" + ``` +2. `pnpm --filter i18n generate` (または `pnpm dev` で watch 中) +3. SFC: + ```vue + + ``` + +## 参照ファイル + +- [locales/README.md (★ 編集ポリシー根拠)](../../../locales/README.md) +- [locales/ja-JP.yml](../../../locales/ja-JP.yml) +- [packages/i18n/build.ts](../../../packages/i18n/build.ts) +- [packages/i18n/src/autogen/locale.ts (生成物)](../../../packages/i18n/src/autogen/locale.ts) +- [packages/frontend/src/i18n.ts](../../../packages/frontend/src/i18n.ts) diff --git a/.agents/skills/add-mk-component/SKILL.md b/.agents/skills/add-mk-component/SKILL.md new file mode 100644 index 0000000000..fd35c5dd3b --- /dev/null +++ b/.agents/skills/add-mk-component/SKILL.md @@ -0,0 +1,174 @@ +--- +name: add-mk-component +description: Misskey フロントエンドの新規 Vue 3 コンポーネントを追加する。Mk* 命名 / SPDX (HTML コメント) / + + +``` + +### 規約ポイント + +| 項目 | 規約 | +|---|---| +| ` +``` + +### `os` の主なヘルパー (詳細は [os.ts](../../../packages/frontend/src/os.ts)) + +| 関数 | 用途 | +|---|---| +| `os.alert({ type, title?, text })` | 単方向アラート | +| `os.confirm({ type, title, text })` | yes/no 確認 (`{ canceled }` を返す) | +| `os.toast(message)` | 一時通知 | +| `os.popup(component, props, handlers)` | 任意コンポーネントの非同期ポップアップ | +| `os.popupMenu(items, anchor?)` | コンテキストメニュー | +| `os.form(title, fields)` | フォームダイアログ | +| `os.apiWithDialog(endpoint, data)` | API 呼出し + エラー時ダイアログ表示 | + +## ステップ 5: Storybook ストーリー併設 + +[MkButton.stories.impl.ts](../../../packages/frontend/src/components/MkButton.stories.impl.ts) を雛形として参考にする。`.stories.impl.ts` も `packages/frontend/src/` 配下の `.ts` ファイルなので [AGENTS.md §1 SPDX ヘッダー必須](../../../AGENTS.md#1-spdx-ヘッダー必須) の対象であり、冒頭に SPDX ヘッダーを必ず付ける (HTML コメント形式ではなく `/* */` 形式)。形式 (以下の `MkXxx` は実際のコンポーネント名に置換する): + +```ts +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +/* eslint-disable import/no-default-export */ +import type { StoryObj } from '@storybook/vue3'; +import MkXxx from './MkXxx.vue'; + +export const Default = { + render(args) { + return { + components: { MkXxx }, + setup() { + return { args }; + }, + template: 'slot content', + }; + }, + args: { + variant: 'primary', + }, + parameters: { + layout: 'centered', + }, +} satisfies StoryObj; +``` + +`Vue` SFC は default export なので、`import MkXxx from './MkXxx.vue';` のように名前付き import ではなく default import で書く。実行確認は `pnpm --filter frontend storybook-dev`。 + +## ステップ 6: Lint と typecheck + +```bash +pnpm --filter frontend lint +``` + +(typecheck = vue-tsc 等、ESLint = `@misskey-dev/eslint-plugin` 含む) + +ESLint --fix をピンポイントで: + +```bash +pnpm exec eslint --fix packages/frontend/src/components/Mk.vue +``` + +## ステップ 7: 既存コンポーネントとの整合性確認 + +- 似た用途の既存 `Mk*` コンポーネントを参考に、スタイルやプロップ命名を揃える。 +- `_button` / `_panel` / `_selectable` などの **共通 utility class** (グローバルスタイルにある) を活用できるか確認する。 +- 大きな機能なら、Storybook stories で各バリエーションを網羅する。 + +## 参照ファイル + +- [MkInfo.vue (シンプル例)](../../../packages/frontend/src/components/MkInfo.vue) +- [MkButton.vue (汎用ボタン例)](../../../packages/frontend/src/components/MkButton.vue) +- [MkInput.vue (generics + 多機能例)](../../../packages/frontend/src/components/MkInput.vue) +- [MkButton.stories.impl.ts (Storybook 雛形)](../../../packages/frontend/src/components/MkButton.stories.impl.ts) +- [packages/frontend/src/os.ts](../../../packages/frontend/src/os.ts) +- [packages/frontend/src/i18n.ts](../../../packages/frontend/src/i18n.ts) diff --git a/.agents/skills/context-budget/SKILL.md b/.agents/skills/context-budget/SKILL.md new file mode 100644 index 0000000000..97bc3ea444 --- /dev/null +++ b/.agents/skills/context-budget/SKILL.md @@ -0,0 +1,148 @@ +--- +name: context-budget +description: Codex セッションのコンテキスト窓消費を agents/skills/MCP/rules/AGENTS.md ごとに見える化し、肥大化と冗長コンポーネントを検出して節約候補を提示する。"コンテキスト消費を見せて"、"context budget"、"context audit"、"トークン内訳"、"これ以上 MCP 入る?" 等の発話で起動する。 +--- + + + +# Context Budget + +セッション内に読み込まれるコンポーネント (agents / skills / rules / MCP servers / AGENTS.md) の token overhead を分析し、空き context を回復する具体策を提示する。 + +## 使う場面 + +- セッションが重い・出力品質が落ちてきた感覚がある +- 直近で skills / agents / MCP server を多数追加した +- 残りの context headroom を知りたい +- 追加コンポーネントを入れる前に空きを確認したい +- 「context-budget」「token 内訳」等のキーワードでユーザーが明示的に要請した時 (Misskey リポジトリにはこの名前のスラッシュコマンドは登録していない — 本 skill は名前 / description マッチで auto-invoke される想定。実装済の slash command 一覧は [.Codex/commands/](../../commands/) を参照) + +## 仕組み + +### Phase 1: Inventory + +各コンポーネントを走査して token を推定する。 + +**Agents** (`.Codex/agents/*.md`) +- 行数とトークン数 (`words × 1.3`) を計算 +- frontmatter `description` の長さを抽出 +- フラグ: 200 行超 (重い)、description 30 word 超 (frontmatter 肥大) + +**Skills** (`.Codex/skills/*/SKILL.md`) +- SKILL.md ごとに token を計算 +- フラグ: 400 行超 +- `.agents/skills/` 等の重複コピーは除外 + +**Rules** (リポジトリルートの `AGENTS.md` + `.Codex/` から `@-import` されるファイル) +- ファイル単位で token 計算 +- フラグ: 100 行超 +- 同一言語モジュール内の内容重複を検出 + +**MCP Servers** (`.mcp.json` または有効 MCP 設定) +- server 数と総 tool 数 +- schema overhead をツールあたり ~500 token で見積もる +- フラグ: 20 tool 超のサーバー、`gh` / `git` / `npm` 等の CLI を単純ラップしただけのサーバー + +**AGENTS.md** (project + user-level) +- ファイルごとに token を計算 +- フラグ: 合計 300 行超 + +### Phase 2: Classify + +| バケット | 判定基準 | 行動 | +|--------------------|-------------------------------------------------------------|-----------------------------------| +| **Always needed** | AGENTS.md から参照されている / 有効コマンドの裏 / 現プロジェクトと一致 | 維持 | +| **Sometimes needed** | ドメイン依存 (例: 言語パターン)、AGENTS.md 参照なし | オンデマンド有効化を検討 | +| **Rarely needed** | コマンド参照なし、内容重複、明確な用途なし | 削除または lazy-load | + +### Phase 3: Detect Issues + +- **Bloated agent description** — frontmatter description が 30 word 超だと、Task ツール起動のたびに毎回ロードされる +- **Heavy agents** — 200 行超は Task ツールの context を毎回膨らませる +- **Redundant components** — agent ロジックを重複する skill、AGENTS.md と重複する rule +- **MCP over-subscription** — 10 server 超、または CLI 代用可能なサーバー +- **AGENTS.md bloat** — 冗長説明、古いセクション、rule に移すべき指示 + +### Phase 4: Report + +``` +Context Budget Report +═══════════════════════════════════════ + +Total estimated overhead: ~XX,XXX tokens +Context model: <現在モデル名> (K window) ← 例: Codex Opus 4.7 (1M), Codex Sonnet (200K) +Effective available context: ~XXX,XXX tokens (XX%) + +Component Breakdown: +┌─────────────────┬────────┬───────────┐ +│ Component │ Count │ Tokens │ +├─────────────────┼────────┼───────────┤ +│ Agents │ N │ ~X,XXX │ +│ Skills │ N │ ~X,XXX │ +│ Rules │ N │ ~X,XXX │ +│ MCP tools │ N │ ~XX,XXX │ +│ AGENTS.md │ N │ ~X,XXX │ +└─────────────────┴────────┴───────────┘ + +WARNING: Issues Found (N): +[token 節約量の降順] + +Top 3 Optimizations: +1. [action] → save ~X,XXX tokens +2. [action] → save ~X,XXX tokens +3. [action] → save ~X,XXX tokens + +Potential savings: ~XX,XXX tokens (XX% of current overhead) +``` + +verbose mode ではさらにファイルごとの token 内訳、最重ファイルの行単位ブレークダウン、重複行の対比、MCP tool 一覧 + tool ごとの schema サイズ推定を出す。 + +## 例 + +**基本監査** +``` +User: コンテキスト消費を見せて +Skill: 16 agents (12,400 tokens), 28 skills (6,200), 87 MCP tools (43,500), 2 AGENTS.md (1,200) + Flags: 重い agent 3 個、CLI 代用可能な MCP 3 個 + Top saving: MCP 3 個削除 → -27,500 tokens (overhead の 47% 削減) +``` + +**Verbose** +``` +User: トークン内訳をファイル単位で +Skill: 上記レポートに加えて、planner.md (213 lines, 1,840 tokens) のような + per-file 行内訳、MCP tool ごとのサイズ、rule の重複行を side-by-side で表示 +``` + +**追加前チェック** +``` +User: MCP server を 5 個追加したいが、空きある? +Skill: 現状 33% → 5 server (≈ 50 tools) 追加で +25,000 tokens → 45% に到達 + 推奨: CLI 代用可能な server 2 個を先に外して 40% 以下を維持 +``` + +## ベストプラクティス + +- **トークン推定**: prose は `words × 1.3`、code 主体は `chars / 4` +- **MCP は最大のレバー**: tool あたり ~500 token、30-tool server ひとつで全 skill より大きい +- **agent description は常時ロード**: 呼ばれない agent でも description は毎 Task 投入 +- **verbose は debug 用**: 普段は使わない +- **変更後は監査**: agent/skill/MCP 追加直後に走らせて creep を早期発見 + +## Misskey 固有メモ + +- Misskey は MCP server をプロジェクトで明示登録していないため (`.mcp.json` 不在)、現状 overhead の支配項は AGENTS.md と公式プラグイン群の skills / agents description である。 +- ECC プラグインがユーザースコープで `installed_plugins.json` に存在するため、プロジェクトで `enabledPlugins` に追加していなくても system reminder に 200+ skill が現れる。これらは description が短いので個別 overhead は小さいが、合計値の確認に本 skill を使う。 diff --git a/.agents/skills/create-migration/SKILL.md b/.agents/skills/create-migration/SKILL.md new file mode 100644 index 0000000000..64f76cb9d7 --- /dev/null +++ b/.agents/skills/create-migration/SKILL.md @@ -0,0 +1,156 @@ +--- +name: create-migration +description: Misskey の TypeORM マイグレーションを公式 CLI (migration:generate / migration:create) で正しく生成し、SPDX ヘッダー付与・up/down 整合・check-migrations 確認まで誘導する。エンティティのスキーマ変更を含むあらゆる DB 変更、または手書き SQL によるデータ移行が必要な時に使用する。 +--- + +# Misskey マイグレーション作成スキル + +`packages/backend/migration/` に新規 TypeORM マイグレーションを追加するためのワークフロー。 + +## 大前提 (絶対 NG) + +- **既にマージ済み (develop / master) のマイグレーションファイルを編集しない** ([AGENTS.md §3](../../../AGENTS.md#3-マージ済み-migration-を絶対に編集しない))。本番履歴の改変は深刻なデータ不整合を引き起こす。スキーマ変更は **常に新しいタイムスタンプで新規ファイル** を作る。 +- ファイル名のタイムスタンプ部分を後から書き換えない (順序が壊れる)。 + +> 作り方は AGENTS.md §3 の「`Date.now()` で UNIX ms を取得 → `{ms}-{PascalName}.js` を手書き」が最低ライン。エンティティ差分から自動生成したい (= TypeORM の `migration:generate` を使う) 場合は本 skill の手順に従う。**どちらでも構わない**が、エンティティ変更を伴う時は CLI 経由のほうが取り漏れが減るので推奨。 + +## ステップ 1: どちらの方式を使うか決める + +| 状況 | 方式 | +|---|---| +| エンティティ (`packages/backend/src/models/*.ts`) を `@Column` / `@Index` / `@Entity` 等で先に変更し、差分から自動生成したい | `typeorm migration:generate` (本 skill の手順) | +| 手書き SQL / データ移行 / `CREATE INDEX CONCURRENTLY` など、エンティティ差分では表現できない変更 | `typeorm migration:create` で空雛形を作るか、`migrate-new` command で手書き雛形を作る | +| 列追加 1 本のような小規模変更で、既存ファイルをコピーした方が速い | AGENTS.md §3 の手順 (`Date.now()` + 手書き) でよい | + +迷ったら **まずエンティティを変更 → `migration:generate`** が原則。既存 342 ファイルのほぼすべてが `queryRunner.query(\`SQL...\`)` の raw SQL なので、CLI 出力でも手書きでもスタイルは揃う。 + +## ステップ 2: CLI 実行 + +ルートディレクトリから以下を実行する。`` は変更内容を表す PascalCase (例: `AddBirthdayIndex`, `AddCategoryToAvatarDecorations`)。 + +### 2-A. エンティティ差分から生成 + +[CONTRIBUTING.md §Migration作成方法](../../../CONTRIBUTING.md#migration作成方法) に記載の基本形: + +```bash +# packages/backend ディレクトリで実行する場合 (CONTRIBUTING.md 記載形式) +pnpm dlx typeorm migration:generate -d ormconfig.js -o --esm +``` + +**リポジトリルートから実行する場合** (AI が使う推奨形式。`pnpm --filter backend exec` を使うと backend の TypeORM バージョンと一致するため確実): + +```bash +pnpm --filter backend exec typeorm migration:generate -d ormconfig.js -o --esm migration/ +``` + +> **`--esm` について**: `-o` / `--outputJs` は「TS ではなく JS を出力する」オプション、`--esm` は「ESM 形式 (`export class ...`) で出力する」オプション。Misskey の既存 migration はすべて ESM JS であるため **両方が必須**。`--esm` を省略すると CommonJS 形式の JS が生成されスタイルが揃わない。 + +事前準備: + +- `pnpm build-pre` を実行して `built/meta.json` を生成する (`loadConfig()` が `built/meta.json` を必須とするため。`pnpm build` 済みであれば不要)。 +- `.config/default.yml` が存在すること (なければ `.config/example.yml` を参考に作成する)。 +- `pnpm --filter backend compile-config` を実行して `built/.config.json` を生成する (`ormconfig.js` が `loadConfig()` 経由で必須とする。未実行だと "Compiled configuration file not found." エラーになる)。 +- `pnpm --filter backend build` でエンティティを最新ビルド (CLI は `built/` を読む)。 +- ローカル DB を起動する (`docker compose -f compose.local-db.yml up -d`)。 + +### 2-B. 空の手書きマイグレーション + +```bash +pnpm --filter backend exec typeorm migration:create -o --esm migration/ +``` + +ローカル DB の起動とビルドは不要。空の `up` / `down` だけが生成される。 + +> `-o --esm` を **必ず付ける**。これが無いと `-.ts` (CommonJS / TS 出力) が生成されるが、Misskey の `ormconfig.js` は `migration/*.js` だけを読み、既存の他 migration も全て `export class ... { async up(queryRunner) {...} }` の ESM JS 形式なので、後で手作業で `.ts → .js` リネーム + `import { MigrationInterface }` 削除 + `class ... implements MigrationInterface` 削除をしないと走らない。`-o --esm` を付ければそのまま `.js` ESM で出るので、後処理は SPDX ヘッダー付与 (ステップ 3) だけで済む。 + +## ステップ 3: SPDX ヘッダー付与 + +CLI 出力には SPDX ヘッダーが含まれない。**必ず冒頭に追加する** (CI の `spdx` ジョブが失敗するため)。 + +```js +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ +``` + +## ステップ 4: up / down の整合確認 + +- `up()` の各ステートメントに対し、`down()` で完全に巻き戻せること。 +- 列追加 (`ADD COLUMN`) ↔ 列削除 (`DROP COLUMN`)、テーブル作成 ↔ テーブル削除、 + FK 追加 ↔ FK 削除、インデックス作成 ↔ インデックス削除 を必ずペアで書く。 +- `down()` を空のまま残さない。本番ロールバック時に詰む。 + +### インデックス追加時の注意 (CREATE INDEX CONCURRENTLY) + +大規模テーブルへの `CREATE INDEX` は本番で長時間ロックする恐れがある。`CONCURRENTLY` で発行するときは **migration 側にも対応が必要**: PostgreSQL は `CREATE INDEX CONCURRENTLY` を transaction 内で実行できないため、migration class に以下を仕込んで TypeORM に「この migration は transaction を張らない」と指示する。 + +参照実装: [packages/backend/migration/1745378064470-composite-note-index.js](../../../packages/backend/migration/1745378064470-composite-note-index.js)。 + +```js +const isConcurrentIndexMigrationEnabled = process.env.MISSKEY_MIGRATION_CREATE_INDEX_CONCURRENTLY === '1'; + +export class CompositeNoteIndex1745378064470 { + name = 'CompositeNoteIndex1745378064470'; + transaction = isConcurrentIndexMigrationEnabled ? false : undefined; + + async up(queryRunner) { + const concurrently = isConcurrentIndexMigrationEnabled; + if (concurrently) { + // CREATE INDEX CONCURRENTLY ... + } else { + // CREATE INDEX ... + } + } + + async down(queryRunner) { + // 同様に環境変数で分岐 + } +} +``` + +要点: + +- **`transaction = isConcurrentIndexMigrationEnabled ? false : undefined;`** が必須。これがないと `CREATE INDEX CONCURRENTLY` が transaction 内で実行されて `ERROR: CREATE INDEX CONCURRENTLY cannot run inside a transaction block` で失敗する。 +- 環境変数 `MISSKEY_MIGRATION_CREATE_INDEX_CONCURRENTLY=1` がデフォルト OFF。OFF のときは普通の `CREATE INDEX` (transaction 内) で動く必要がある。`up`/`down` 双方を環境変数で分岐させる。 +- `ormconfig.js` の `migrationsTransactionMode` は **環境変数で切り替わる**: `MISSKEY_MIGRATION_CREATE_INDEX_CONCURRENTLY=1` のときだけ `'each'` (各 migration が個別 transaction)、未設定時は `'all'` (全 migration を 1 つの transaction でラップ) ([ormconfig.js:19](../../../packages/backend/ormconfig.js#L19))。普段は `'all'` 前提なので、CONCURRENTLY を使う migration を書く時だけこのフラグの存在を意識すれば良い。 + +### 関連エンティティとの一致 + +`migration:generate` を使った場合、エンティティ側の `@Column` / `@Entity` 修正と DB スキーマが食い違うとビルド全体がズレる。生成後に該当エンティティと SQL の対応を目視確認すること。 + +## ステップ 5: 検証 + +ルートから実行: + +```bash +# 未反映の差分が無いか (新規 migration が生成すべき DDL を取り逃していないか) +pnpm --filter backend check-migrations + +# ローカル DB に適用 +pnpm migrate + +# ロールバック (down が壊れていないか) +pnpm revert + +# 再適用 (順方向にもう一度通す) +pnpm migrate +``` + +`check-migrations` の実体は [scripts/check_migrations_clean.js](../../../packages/backend/scripts/check_migrations_clean.js)。TypeORM の `dataSource.driver.createSchemaBuilder().log()` で pending DDL を取得し、`upQueries` / `downQueries` のいずれかが残っていれば非ゼロ終了する。**順序検査ではなく**「エンティティと migration が同期しているか」の検査。 + +## ステップ 6: 既存ファイル参照テンプレ + +新規ファイルを書くときは、変更パターンが近い既存ファイルを **必ずひとつ開いて並べて書く**。スタイルが激しくズレた PR は差し戻されやすい。 + +| パターン | 参照ファイル | +|---|---| +| インデックス追加 + 関数定義 | [packages/backend/migration/1767169026317-birthday-index.js](../../../packages/backend/migration/1767169026317-birthday-index.js) | +| 列追加のみ | [packages/backend/migration/1766652173085-add-category-to-avatar-decorations.js](../../../packages/backend/migration/1766652173085-add-category-to-avatar-decorations.js) | +| テーブル新規作成 + FK | [packages/backend/migration/1761569941833-add-channel-muting.js](../../../packages/backend/migration/1761569941833-add-channel-muting.js) | + +クラス命名規則は **PascalCase 名 + 13 桁タイムスタンプ** (例: `class BirthdayIndex1767169026317`)。`name` プロパティもクラス名と同一文字列にする。 + +## ステップ 7: CHANGELOG (ユーザー影響がある場合) + +スキーマ変更がユーザーに見える挙動を生む場合のみ、`CHANGELOG.md` の `## Unreleased` → `### Server` または `### General` に 1 行追加する ([AGENTS.md §CHANGELOG](../../../AGENTS.md#changelog) 参照)。内部リファクタや純粋なインデックス追加は不要。 diff --git a/.agents/skills/source-command-harness-audit/SKILL.md b/.agents/skills/source-command-harness-audit/SKILL.md new file mode 100644 index 0000000000..d96c286ee1 --- /dev/null +++ b/.agents/skills/source-command-harness-audit/SKILL.md @@ -0,0 +1,152 @@ +--- +name: "source-command-harness-audit" +description: "Misskey の .Codex/ ハーネス (skills/agents/commands) を 7 カテゴリで採点する確定的な監査。" +--- + +# source-command-harness-audit + +Use this skill when the user asks to run the migrated source command `harness-audit`. + +## Command Template + + + +# /harness-audit — Misskey ハーネス監査 + +Misskey リポジトリの `.Codex/` 構成を 7 カテゴリで採点し、改善優先度を提示する。 + +## Usage + +`/harness-audit [scope]` + +- `scope` (任意): `repo` (default) / `skills` / `commands` / `agents` + +## 評価カテゴリ (各 0-10) + +| # | カテゴリ | 評価軸 | +| --- | --- | --- | +| 1 | Tool Coverage | skill / agent / command の数、欠けているワークフロー段、重複なし | +| 2 | Context Efficiency | frontmatter description の冗長度、SKILL.md の長さ分布、重複情報、AGENTS.md の肥大化 | +| 3 | Quality Gates | Stop / PreToolUse / PostToolUse hook の整備、`/quality-gate` 等の完了前ゲートの有無、自動 lint/typecheck | +| 4 | Memory Persistence | docs/* の同期状態を評価。プロジェクト側 `.Codex/memory/` は未採用方針 (auto-memory はユーザーホーム側で自動運用) のため、ここを採点起点にせず既定 5/10 から開始する | +| 5 | Eval Coverage | testing.md の網羅、Misskey 固有の e2e/fed/Storybook/Cypress 適用ガイド | +| 6 | Security Guardrails | SPDX 規約適用、migration 不変性ルール、ja-JP.yml 限定編集ルール、secrets 検出 | +| 7 | Cost Efficiency | enabledPlugins の重複・過剰、context-budget の整備、MCP 過剰登録なし | + +## Misskey 固有の確認項目 (採点根拠コマンド) + +採点時に以下を実コマンドで確認する。各項目の **属するカテゴリ** は項目内に明記する (#1-#3 は Security Guardrails、#4 は Tool Coverage、#5 は Quality Gates): + +```bash +# 1. [Security Guardrails] SPDX 適用率 (新規ファイル想定の汎用チェック) +# - node_modules を prune で除外 +# - packages/misskey-js は MIT サブパッケージなので AGPL ヘッダーを持たない (AGENTS.md §1) → 除外 +# - built/ なども除外 +# 候補にはなお *.config.{ts,js} / *eslint* / *.d.ts のような CI 上 SPDX 対象外 +# (.github/workflows/check-spdx-license-id.yml の exclude 参照) も混ざるため、 +# 上位に出たファイルが「新規追加した実コード」かどうかは目視判定する。 +find packages \ + \( -type d \( -name node_modules -o -name built -o -name dist -o -path 'packages/misskey-js' \) -prune \) \ + -o -type f \( -name '*.ts' -o -name '*.js' -o -name '*.vue' -o -name '*.scss' \) -print \ + | xargs -r grep -L 'SPDX-License-Identifier: AGPL-3.0-only' | head -20 +# → 上位に新規実コードが無ければ満点 + +# 2. [Security Guardrails] ja-JP.yml 以外の locales が直近で手動編集されていないか +# --pretty=format: でコミットヘッダ行を抑止し、ファイル名行のみを残してから grep する。 +# Crowdin の自動同期 commit でも他言語 yml は更新されるため、出力が 0 行になることは少ない。 +# 出力があった場合は、author / commit message を確認し Crowdin 由来か手動編集かを判定する: +# git log --since='30 days ago' --pretty=format:'%h %an %s' -- locales/.yml +git log --since='30 days ago' --pretty=format: --name-only -- 'locales/*.yml' \ + | grep -v '^$' | grep -v 'ja-JP.yml' | sort -u +# → 出力が無い、または全て Crowdin 由来 commit なら満点 + +# 3. [Security Guardrails] migration の pending DDL 検査 (TypeORM schema builder) +pnpm --filter backend check-migrations +# → 0 errors (= "All migrations are clean.") なら満点 + +# 4. [Tool Coverage] endpoint-list.ts 登録漏れ (新規 endpoint がリストにない場合) +# endpoints/ は再帰構造 (notes/create.ts, admin/announcements/create.ts 等) で 400+ ファイルあるため、 +# endpoint-list.ts も `export * as '/' from './endpoints//.js';` 形式で +# 1 ファイル 1 行登録される。両者の行数を「再帰 .ts 数」と「export * as 行数」で比較する。 +# e2e / 単体テストは endpoint ではないので *.test.ts を除外する。 +endpoint_files=$(find packages/backend/src/server/api/endpoints -type f -name '*.ts' ! -name '*.test.ts' | wc -l) +list_entries=$(grep -cE "^export \* as " packages/backend/src/server/api/endpoint-list.ts) +echo "endpoints (recursive): $endpoint_files / endpoint-list.ts entries: $list_entries" +# 差分が 0 なら満点。差分が出たら、登録漏れの具体特定: +comm -23 \ + <(find packages/backend/src/server/api/endpoints -type f -name '*.ts' ! -name '*.test.ts' \ + | sed -E 's|.*/endpoints/||;s|\.ts$||' | sort -u) \ + <(grep -oE "^export \* as '[^']+'" packages/backend/src/server/api/endpoint-list.ts \ + | sed -E "s/^export \* as '([^']+)'/\1/" | sort -u) +# 出力された行が登録漏れの endpoint。0 行なら満点。 + +# 5. [Quality Gates] console.log の混入 +grep -rn 'console\.\(log\|debug\)' packages/backend/src packages/frontend/src 2>/dev/null \ + | grep -v 'node_modules\|test\|.spec\.\|.test\.' | wc -l +# → 0 が理想 +``` + +## 出力契約 + +以下を返す: + +1. `overall_score` / `max_score` (repo は 70 点満点) +2. カテゴリごとのスコア + 具体的な根拠 +3. 失敗チェック項目と該当ファイルパス +4. Top 3 改善アクション +5. 次に適用を推奨する skill / 手順 + +## サンプル出力 + +```text +Harness Audit (repo): 55/70 + +Tool Coverage: 9/10 (skills 5, agents 2, commands 5 — 偏りなし) +Context Efficiency: 8/10 (description 平均 3-5 行、肥大なし) +Quality Gates: 5/10 (Stop hook 共有設定に未登録 / `/quality-gate` あり) +Memory Persistence: 5/10 (プロジェクト側 memory/ 未採用方針 = 既定値) +Eval Coverage: 7/10 (testing.md 網羅、Storybook 一部抜け) +Security Guardrails: 10/10 (SPDX 100%, locales OK, migrations clean) +Cost Efficiency: 8/10 (context-budget 導入済 / MCP 0) + +Failed Checks: +- packages/frontend/src/.../X.vue で SPDX 欠落 (Security Guardrails) +- console.log が backend に 3 件 (Quality Gates) +- 共有 Stop hook なし (Quality Gates) — 各 contributor が `.Codex/settings.local.json` で opt-in する方針なら減点しなくて良い + +Top 3 Actions: +1) [Security Guardrails] SPDX 欠落 1 ファイルを修正: + packages/frontend/src/.../X.vue +2) [Quality Gates] backend の console.log 3 件を logger に置換。 + git grep "console\.log" packages/backend/src +3) [Cost Efficiency] enabledPlugins から未使用のものを外す。 + .Codex/docs/plugins.md と照合。 + +Suggested next skills to apply: +- /quality-gate で完了前に lint + unit test を回す +- context-budget で plugin 由来の overhead を確認 +``` + +## 採点の信頼性 + +- 確定的: 同じ commit / 同じ `.Codex/` 構成なら同じスコア +- ヒューリスティクス: 「description の冗長度」のような主観項目は同一基準で機械的に判定 +- スクリプト不要: `pnpm` と `git`、`grep`/`find` 等の標準ツールのみ + +## 参考: ECC オリジナルとの差分 + +- ECC 版は `node scripts/harness-audit.js` を直叩きする運用で、ECC リポジトリ全体に閉じた採点だった。 +- Misskey 版は **Misskey の規約 (SPDX/migration/locales/endpoint-list)** を Security 採点に組み込み、`pnpm` ベースの実コマンドで根拠を取る方式に再設計。 +- 結果として ECC への依存はゼロ。 diff --git a/.agents/skills/source-command-quality-gate/SKILL.md b/.agents/skills/source-command-quality-gate/SKILL.md new file mode 100644 index 0000000000..577e66c1d5 --- /dev/null +++ b/.agents/skills/source-command-quality-gate/SKILL.md @@ -0,0 +1,128 @@ +--- +name: "source-command-quality-gate" +description: "Misskey の lint / typecheck / 高速テストを順に実行して品質ゲートを通すコマンド。完了前の軽量検証用。" +--- + +# source-command-quality-gate + +Use this skill when the user asks to run the migrated source command `quality-gate`. + +## Command Template + + + +# /quality-gate — Misskey 軽量品質ゲート + +`/quality-gate [scope]` + +完了前の **軽量** 品質チェック。重い E2E / 連合テスト (test:e2e / test:fed / Cypress) は CI 側で実行されるため、本コマンドには含めない。 + +## Scope + +- `repo` (default) — 全パッケージ +- `backend` — `packages/backend` のみ +- `frontend` — `packages/frontend` のみ +- `path/to/file.ts` — 単一ファイルへの ESLint --fix のみ + +## Pipeline + +### Repo scope (全部) + +各パッケージの `lint` スクリプト実体は `pnpm typecheck && pnpm eslint` ([packages/backend/package.json](../../packages/backend/package.json), [packages/frontend/package.json](../../packages/frontend/package.json)) で、ルートの `pnpm lint` は `pnpm --no-bail -r lint` (= 全パッケージで lint を `--no-bail` で実行)。**typecheck は lint に含まれている**ため、通常はこの 2 コマンドで十分: + +```bash +# 1. Lint (= typecheck + ESLint、全パッケージ。--no-bail で最初の失敗で止まらず全結果を集める) +pnpm lint + +# 2. Unit test (高速、e2e は含まない) +pnpm --filter backend test +pnpm --filter frontend test +``` + +#### 詳細を分けて見たい時のみ (optional) + +lint がまとめて失敗していて typecheck の結果だけ単独で見たい場合は、以下を個別に回す。**通常は不要** (lint の出力を読めば足りる): + +```bash +pnpm --filter backend typecheck # tsgo 単体 +pnpm --filter frontend typecheck # vue-tsc 単体 (Vue SFC の型を見るため) +``` + +### Backend scope + +`pnpm --filter backend lint` は内部で `pnpm typecheck && pnpm eslint` を実行する ([packages/backend/package.json](../../packages/backend/package.json)) ので、`lint` を回せば typecheck も終わる。軽量ゲートでは typecheck の二重実行を避けるため `lint` + `test` のみ: + +```bash +pnpm --filter backend lint +pnpm --filter backend test +``` + +`tsgo` の出力を単独で見たい時のみ optional で `pnpm --filter backend typecheck` を別途回す。 + +### Frontend scope + +`pnpm --filter frontend lint` も内部で `pnpm typecheck && pnpm eslint` を実行する ([packages/frontend/package.json](../../packages/frontend/package.json)) ため、軽量ゲートでは Backend 同様に `lint` + `test` のみ: + +```bash +pnpm --filter frontend lint +pnpm --filter frontend test +``` + +`vue-tsc` の出力を単独で見たい時のみ optional で `pnpm --filter frontend typecheck` を別途回す。 + +### Single file scope + +```bash +pnpm exec eslint --fix +``` + +## Output + +実行したフェーズの pass/fail と件数を集計する。標準パイプラインは `pnpm lint` (typecheck 内包) と unit test のみなので、デフォルトの出力は以下のようになる: + +```text +Quality Gate (repo): + +Lint: PASS (0 errors, 2 warnings) +Backend ut: PASS (412/412) +Frontend ut: PASS (87/87) + +→ 完了前の軽量チェック OK。重い e2e / 連合テストは CI 側で実行される。 +``` + +`#### 詳細を分けて見たい時のみ (optional)` で個別 typecheck (`pnpm --filter backend typecheck` / `pnpm --filter frontend typecheck`) も回した場合のみ、その結果を追加行として表示する: + +```text +Quality Gate (repo): + +Lint: PASS (0 errors, 2 warnings) +Backend tc: PASS (0 errors) # optional 実行時のみ +Frontend tc: PASS (0 errors) # optional 実行時のみ +Backend ut: PASS (412/412) +Frontend ut: PASS (87/87) +``` + +失敗時は最初に落ちたフェーズで停止して詳細を見せる。 + +## 関連 skill / コマンド + +- `/check-misskey-js` コマンド — API 変更時の misskey-js 再生成 +- [AGENTS.md §必須コマンド](../../AGENTS.md#必須コマンド) — pnpm コマンド一覧の正典 + +## 元 ECC 版との差分 + +- ジェネリックな言語自動判定を排除し、Misskey 固定 pipeline に。 +- formatter フェーズなし (Misskey は ESLint --fix のみ採用)。 +- e2e / federation / Cypress は重いため除外し CI 側に委譲。 diff --git a/.codex/agents/misskey-api-reviewer.toml b/.codex/agents/misskey-api-reviewer.toml new file mode 100644 index 0000000000..932c206440 --- /dev/null +++ b/.codex/agents/misskey-api-reviewer.toml @@ -0,0 +1,164 @@ +name = "misskey-api-reviewer" +description = "Misskey の API エンドポイント (packages/backend/src/server/api/endpoints/) の追加・変更を専門レビューする。SPDX / meta / paramDef / UUID 重複 / endpoint-list.ts 登録 / ApiError throw / misskey-js 再生成 / e2e / CHANGELOG を機械的にチェック。バックエンド API を追加・変更した PR レビューで呼び出す。" +developer_instructions = ''' +# Misskey API エンドポイントレビュアー + +Misskey バックエンド (`packages/backend`) の REST API エンドポイント追加・変更 PR を機械的にレビューする専門エージェント。規約の根拠は [.Codex/skills/add-api-endpoint/SKILL.md](../skills/add-api-endpoint/SKILL.md)。 + +## 役割 + +`packages/backend/src/server/api/endpoints/` 配下の `.ts` 変更を対象に、規約逸脱・登録漏れ・型自動生成漏れ・テスト不足を抽出する。良い点には触れず、改善が必要な箇所のみ報告する。 + +## レビュー対象の特定 + +呼び出し元から明示的にファイルが渡されたらそれを優先する。渡されなかった場合は **PR / ブランチ全体の差分** を取得する (未コミット差分のみではないことに注意)。 + +```bash +BASE=$(git merge-base origin/develop HEAD) +{ git diff --name-only "$BASE"...HEAD; git diff --name-only HEAD; git ls-files --others --exclude-standard; } \ + | sort -u \ + | grep -E '^packages/backend/src/server/api/endpoints/.*\.ts$' +``` + +`origin/develop` が無い環境では `develop` または `master` にフォールバックする。 + +加えて以下も同じ baseline で差分対象に含める: + +- `packages/backend/src/server/api/endpoint-list.ts` +- `packages/backend/test/e2e/**` (とくに `endpoints.ts` と `.ts`) +- `packages/misskey-js/src/autogen/**` +- `CHANGELOG.md` + +差分対象が空なら「レビュー対象の API エンドポイント変更なし」と短く報告して終了。 + +## チェックリスト + +### 1. SPDX ヘッダー (Critical) + +新規 `.ts` ファイル冒頭に以下があるか: + +``` +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ +``` + +欠落すると CI の `spdx` ジョブが落ちる。 + +### 2. `meta` の必須・推奨フィールド (Major) + +[endpoints.ts の型定義](../../packages/backend/src/server/api/endpoints.ts) を真とする。 + +- `tags`: OpenAPI タグ (機能領域)。 +- `requireCredential`: 明示必須 (boolean)。 +- `kind`: OAuth scope。`requireCredential: true` のとき必須 (`read:account` / `write:notes` 等)。 +- `requireModerator` / `requireAdmin`: 権限制限が要るか。 +- `prohibitMoved`: 移行済アカウントを拒否するか (write 系で要検討)。 +- `limit`: レート制限 `{ duration, max, key?, minInterval? }`。書き込み系 / コスト高い処理で未指定なら指摘。 +- `errors`: エラー定義。各要素に `message` / `code` / `id` (UUID v4) が揃っているか。 +- `res`: JSON Schema または `ref: ''`。各プロパティに `optional` / `nullable` が **明示** されているか。 +- `requireFile` / `secure` / `allowGet` / `cacheSec` / `description`: 該当するエンドポイントで使い分けているか。 + +### 3. `meta.errors` の UUID 検証 (Critical) + +各 `errors[*].id` が: + +1. UUID v4 形式 (`xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`) か +2. 既存エンドポイントの `id` と重複していないか + +重複検査: + +```bash +grep -rn "id: '<生成された UUID>'" packages/backend/src/server/api/endpoints/ +``` + +新規エンドポイントの全 `id` を抽出して衝突を確認する。 + +### 4. `paramDef` (Major) + +- JSON Schema 形式 (`type: 'object'`, `properties`, `required`) +- ID 文字列は `format: 'misskey:id'` +- `required` 配列で必須プロパティを明示 +- `as const` または `as const satisfies Schema` で型推論を効かせる (既存実装は前者多数。`as const` 自体が無く `Schema` 型注釈もない場合のみ指摘) + +### 5. エンドポイント実装本体 (Major) + +- `Endpoint` を継承しているか。 +- `@Injectable()` デコレータ + `export default class` 形式か (`// eslint-disable-line import/no-default-export` が必要)。 +- DI は `@Inject(DI.xxx)` 形式か。 +- **クライアントに返すべき API エラーは `throw new ApiError(meta.errors.)`** ([error.ts](../../packages/backend/src/server/api/error.ts) 参照)。`meta.errors` で定義したエラーケースを `throw new Error(...)` で投げているなら指摘する。 +- 防御的アサーション・「起きるはずがない」内部不整合・テスト用 ENV ガード等の **想定外フェイルファスト** は `throw new Error('...')` で構わない。既存実装でも `admin/reset-password.ts` などが採用しているパターン (例: `cannot reset password of root`)。`meta.errors` に対応がない `throw new Error` を一律で指摘しない。 +- 同期 `throw` は許容。非同期処理での例外伝搬を確認する。 + +### 6. ★ `endpoint-list.ts` への登録 (Critical) + +最も忘れやすい。**忘れると 404**。[endpoint-list.ts](../../packages/backend/src/server/api/endpoint-list.ts) に 1 行追加されているか: + +```ts +export * as '/' from './endpoints//.js'; +``` + +新規エンドポイントを抽出し、各々が `endpoint-list.ts` に存在するか grep で確認する: + +```bash +grep -F "'/'" packages/backend/src/server/api/endpoint-list.ts +``` + +**並び順の補足**: ファイル全体は厳密なアルファベット順では並んでおらず、同カテゴリ内 (`admin/queue/*` など) でも追加された経緯どおりの順になっている箇所が多い。**順序逸脱は指摘根拠にしない** (誤検知の元)。「行が存在するか」のみを Critical 観点として扱う。 + +### 7. `misskey-js` 再生成 (Critical) + +`meta` / `paramDef` / `res` を変更したら、PR / ブランチに `packages/misskey-js/src/autogen/` 配下の差分が含まれているか確認する: + +```bash +BASE=$(git merge-base origin/develop HEAD) +git diff --name-only "$BASE"...HEAD -- packages/misskey-js/src/autogen/ +``` + +差分ゼロなら `pnpm build-misskey-js-with-types` の実行漏れ。CI の `check-misskey-js-autogen` ジョブで必ず落ちるため Critical 扱い。 + +### 8. e2e テスト (Major) + +[test/e2e/endpoints.ts](../../packages/backend/test/e2e/endpoints.ts) または `test/e2e/.ts` (`note.ts`, `users.ts` 等) 配下に、対応する `api('/', ...)` 呼び出しを含む `test(...)` ケースが追加されているか確認する。複雑な分岐 (権限チェック・エラーケース) の網羅も確認する。 + +**describe ラベルの形式は問わない**: 既存テストは `describe('Note', () => { test('投稿できる', ...) })` のように人間可読ラベルで構造化されており、`/` 形式の describe は使われていない。describe 名の規約違反としては指摘しない。 + +### 9. CHANGELOG エントリ (Minor) + +ユーザー影響がある (新エンドポイント / 既存挙動変更) 場合、`CHANGELOG.md` の `## Unreleased` → `### Server` に 1 行追加されているか確認する。 + +``` +- Feat: /api// を追加 +``` + +純粋な内部リファクタなら不要。 + +## 出力形式 + +優先度別に以下のフォーマットで出力する。 + +``` +## 🔴 Critical +- packages/backend/src/server/api/endpoints/foo/bar.ts:23 + meta.errors.fooError.id が UUID v4 形式ではない (実値: 'xxx-xxx')。 + `node -e "console.log(crypto.randomUUID())"` で再生成すること。 + +## 🟡 Major +- ... + +## 🔵 Minor +- ... +``` + +問題のないチェック項目には触れない。全項目クリアなら `✅ レビュー観点上の指摘なし` と短く返す。 + +## 参照 + +- [.Codex/skills/add-api-endpoint/SKILL.md](../skills/add-api-endpoint/SKILL.md) — 実装側の規約 (本エージェントの根拠) +- [endpoints.ts (meta/paramDef 型定義)](../../packages/backend/src/server/api/endpoints.ts) +- [endpoint-list.ts (★ 登録先)](../../packages/backend/src/server/api/endpoint-list.ts) +- [endpoint-base.ts (Endpoint 基底クラス)](../../packages/backend/src/server/api/endpoint-base.ts) +- [error.ts (ApiError)](../../packages/backend/src/server/api/error.ts) +- [test/e2e/endpoints.ts](../../packages/backend/test/e2e/endpoints.ts) +- [AGENTS.md](../../AGENTS.md) — SPDX / マイグレーション履歴 / CHANGELOG 書式などの最低限ルール (Codex / Copilot と共通)''' diff --git a/.codex/agents/vue-component-reviewer.toml b/.codex/agents/vue-component-reviewer.toml new file mode 100644 index 0000000000..910226d3c2 --- /dev/null +++ b/.codex/agents/vue-component-reviewer.toml @@ -0,0 +1,173 @@ +name = "vue-component-reviewer" +description = 'Misskey フロントエンド (packages/frontend/src/components/ / pages/) の Vue 3 SFC 変更を専門レビューする。SPDX (HTML コメント) / Mk* 命名 /