Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions docs/development-history/03-2026-08-15.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
# 開発履歴と知見 (2026-08-15)

**期間**: 2026-08-14 〜 2026-08-15
**対象**: [issue #38](https://github.com/devbasex/ai-plugins/issues/38) / [PR #111](https://github.com/devbasex/ai-plugins/pull/111)

分析可能・計測可能なコードを保つための判断材料を、`refactoring` Skill へ統合した。あわせて
`safe-refactoring` を `refactoring` へ改名し、NDF を v8.0.0 へ上げた。

Skill の挙動そのものは
[`plugins/ndf-shared/skills/refactoring/`](../../plugins/ndf-shared/skills/refactoring/SKILL.md)
を正とする。本書には、そこに書かない**設計判断の理由**と**調査・実測の結果**を残す。

## 主要な開発内容

### 1. 独立 Skill を作らず、構造改善へ統合した

当初は `analyzable-coding` という独立 Skill として実装したが、**発動条件を書けなかった**。

| Skill | Use when | 発動する瞬間 |
| --- | --- | --- |
| `quality-gates` | about to report a change as done | 完了報告の直前 |
| `tdd-cycle` | adding behavior or fixing a bug | 振る舞いを足す時 |
| 旧 `analyzable-coding` | implementing or restructuring code | コードを書く時=常時 |

他の Skill が発動する瞬間を指しているのに対し、この内容は「コードを書くとき」以外に書きようが
ない。**常に該当するトリガは発動判定として働かない。** 読んだエージェントが何を出力し、何を
もって適用完了とするかも規定できていなかった。

内容の重複も統合を裏づけた。既存のコードスメル 14 件のうち 5 件(マジックナンバー・文字列 /
設定の散在 / 深いネスト / 例外の飲み込み / 条件分岐の連鎖)と重なっており、棚卸台帳の判断基準
「機能が他 Skill と重複するものは統合の対象とし、内容は統合先へ残す」に該当する。

統合により、発動点が「リファクタリングを始めるとき」に定まった。

### 2. 言語ごとの手段を 1 言語 1 ファイルに分けた

参照ファイルは読んだときだけコンテキストへ載る。4 言語を 1 ファイル(316 行)にまとめていると、
PHP の作業でも他 3 言語の内容まで読み込まれる。

| ファイル | 行数 |
| --- | ---: |
| `lang-python.md` | 85 |
| `lang-javascript.md` | 45 |
| `lang-typescript.md` | 73 |
| `lang-php.md` | 145 |

各ファイルは自己完結させ、冒頭に「判定 → その言語の手段」の対応表を置いた。言語をまたぐ対応表は
持たない(4 言語分を読ませることになるため)。JavaScript は TypeScript と手段が重なるが、参照では
なく必要な範囲を書き下ろした。

### 3. 構造改善を必須工程にした

AI エージェントによるコーディングでは、動くコードは出ても整理が済んでいない。任意工程にすると
その状態のまま次へ進む。`development-workflow` のモード表から「(必要な場合)」を外し、
`architecture` の必須工程にも追加した(`light` のみ対象外)。

対象範囲も広げた。「変更予定のない領域は手を付けない」を除外条件に置くと、読みにくい領域が
読みにくいまま残り続ける。今回変更した関数・クラスに加え、**呼び出し元・呼び出し先と、同じ
ファイル・同じモジュールの関連箇所**までを対象とし、そこから遠い領域だけを対象外とした。
広げた分は別のコミットに切る。

## 調査結果: 既存理論との対応

草稿の主張と一対一で対応する単一の理論は存在しないが、個々の主張はすべて既存理論に対応物が
ある。Claude と ChatGPT の 2 系統で独立に調査し、この結論は一致した。

| 主張 | 最も近い既存理論 | 出典 |
| --- | --- | --- |
| コードよりもデータ | Rule of Representation | Unix 哲学(Eric Raymond) |
| コードよりもデータ | Data-Oriented Programming | Yehonathan Sharvit |
| 分岐をデータで表現 | Table-Driven Methods | Code Complete 18 章 |
| 分岐をデータで表現 | Decision Table / Policy as Code | OMG DMN, Open Policy Agent |
| ループを避けベクトル化 | Array Programming | Kenneth Iverson (APL) |
| 定数を外部データへ | Magic Number 排除 / Twelve-Factor Config | 一般的リファクタリング原則 |
| 常に計測可能 | Observability 2.0 / OpenTelemetry | Charity Majors |
| ループ後にエラーを集計 | Notification パターン | Martin Fowler |

**両調査が独立に指摘した危険が 3 つあり、すべて Skill の設計へ反映した。**

| 危険 | 反映 |
| --- | --- |
| 条件分岐の置き換えの過剰適用(8th Light「条件分岐は悪ではない。重複した条件分岐が悪である」) | 「手を付けないもの」と「改善にならない置き換え」を明記 |
| 定数の外部化しすぎ(inner-platform effect) | 「外部化してよい条件」を満たせないなら外部化しない |
| 高階反復を一括処理と取り違える | 「反復の実行方式」表で、得られるものがない場合を見分ける |

`data-oriented` という名称は採用しなかった。Data-Oriented Design(Mike Acton / Unity DOTS)は
メモリ配置とキャッシュ効率の話で、別概念と混同されるためである。

## 実測で確かめた事実

いずれも一次情報または実行結果で確認した。

| 事実 | 確認方法 |
| --- | --- |
| `np.vectorize` は性能目的ではない("provided primarily for convenience, not for performance. The implementation is essentially a for loop.") | NumPy 公式ドキュメント |
| Data-Oriented Programming は 4 原則(3 個 / 5 個という記述が両調査に出たが誤り) | Sharvit 本人の記事 |
| PHP 8.3 で backed enum + `match` の未処理ケースは `\UnhandledMatchError` を投げる | 実行 |
| TypeScript 5 の `never` 代入と `satisfies` は、ケース追加漏れとキー欠落を検出する(`TS2322` / `TS1360`) | `tsc --strict --noEmit` |
| `array_column` はコールバックを取らない組み込みの列抽出であり、高階反復ではない | PHP マニュアル |

### 外部化と静的解析の関係(PHPStan 2.2 で実測)

| 書き方 | level 5 | level max |
| --- | --- | --- |
| `match`(`default` なし) | `match.unhandled` で検出 | 同左 |
| その場に書いた連想配列 | 検出なし | `offsetAccess.notFound` で検出 |
| 外部から渡した対応表(`array<string, string>`) | 検出なし | **検出なし** |

3 行目が重要である。**型情報を伴わずに実行時ロードすると、対応表は定義ごと静的解析の視界から
外れる。** ただし外部化と静的検査は排他ではなく、スキーマから型・定数を生成してビルド時に
取り込めば検査は残る。データを実行時にロードする場合は、型を生成していてもロード境界の
スキーマ検証が要る(生成した型は、ロードした値がその形である保証を与えない)。

## 知見: CI が検査していなかった箇所

`plugins/<family>-codex/.codex-plugin/plugin.json` と `.claude-plugin/marketplace.json` の版数・
Skill 数を、**同じ PR 内で 3 回落とした**。原因は構造的なものだった。

- この 2 ファイルは `build-runtime-plugins.sh` の生成対象外(`write_codex_mcp_manifest` は MCP
プラグイン専用)
- `validate-runtime-plugins.sh` は JSON の妥当性と `source` パスの実在しか見ておらず、
description 内の版数・Skill 数を誰も検査していなかった
- 結果、CI 全緑のまま古い値が残る

`validate-runtime-plugins.sh` に突き合わせ検査を追加して塞いだ(仕様は
[Runtime Plugin 配布仕様](../specifications/runtime-plugin-distribution.md))。

実装時に判明した副次的な問題として、**Skill 数の抽出が緩いと description 前半の
`8 specialized agents` の `8` を拾い、誤った理由でエラーになる**ことがあった。抽出パターンを
絞り、読み取れない場合は「読み取れない」と報告する。負例(記述の削除 / 書式変更 / 実数との相違)
で検出を確認済み。

## 知見: cross-review の収束判定を鵜呑みにしない

12 ラウンドの間に、`state.py judge` が**誤って「両方 APPROVE。収束」と判定した事例が 3 回**
あった。いずれも codex 側の異常終了を `SKIP` として扱ったことが原因である。

| 事例 | 実態 |
| --- | --- |
| ネットワーク障害(DNS 解決失敗) | 前ラウンドの `result.json` が残っており、monitor が「正常完了」と判定 |
| `Selected model is at capacity` | result 未生成で `SKIP` 扱い |
| インラインコメント投稿が HTTP 422(`Line could not be resolved`) | レビューは完了し指摘もあったが、投稿失敗で result 未生成 |

見分け方は次の 3 点である。

- 実行時間が極端に短い(`elapsed=0.0`)
- ログサイズが 0
- `result.json` の更新時刻が今回の起動より前

**ラウンド起動前に古い `result.json` を削除し、判定後は PR 上の実際のレビュー投稿と突き合わせる。**

## 関連リンク

- [refactoring Skill](../../plugins/ndf-shared/skills/refactoring/SKILL.md)
- [Runtime Plugin 配布仕様](../specifications/runtime-plugin-distribution.md)
- [NDF Skill 棚卸台帳](../specifications/ndf-skill-inventory.md)
- 草稿と外部 AI の回答: `issues/issue-38-coding-rule.md` / `issues/issue-38-chatgpt-response.md`
6 changes: 6 additions & 0 deletions docs/specifications/runtime-plugin-distribution.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -81,6 +81,11 @@ bash scripts/build-runtime-plugins.sh --check

`--check` は生成先と shared source の差分を比較し、drift がある場合に非 0 で終了する。

`.claude-plugin/marketplace.json` と `plugins/<family>-codex/.codex-plugin/plugin.json` は生成物では
なく手で更新する。この 2 つは build の対象外で drift 検査に掛からないため、版数と Skill 数は
`validate-runtime-plugins.sh` の突き合わせ検査で担保する。description から Skill 数を読み取れない
場合もエラーとして扱う(記述を消すことで検査が素通りするのを防ぐ)。

総合検証は `scripts/validate-runtime-plugins.sh` で行う。

```bash
Expand All@@ -98,6 +103,7 @@ bash scripts/validate-runtime-plugins.sh
| MCP runtime | shared MCP plugin に対応する claude / codex / kiro 配布先の存在 |
| Claude Code | `claude plugin validate` が使える環境では NDF と marketplace を検証 |
| Kiro CLI | NDF installer と MCP installer の `--dry-run` |
| 版数・Skill 数 | Claude 版 `plugin.json` の `version` を基準に、Codex 版 `version`、marketplace と両 plugin.json の description 内 `(vX.Y.Z)`、description の Skill 数と `manifests/<runtime>-skills.txt` の実数を突き合わせる |
| docs | `scripts/check-markdown-links.py` による local link 検証 |

## CI
Expand Down
Loading
Loading