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
30 changes: 30 additions & 0 deletions LESSONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -468,3 +468,33 @@ response = json.loads(proc.stdout.readline())
- 却下した案: skill の改名・日本語化を新規の書き起こしとして扱い、レビューは新しい文面が読みやすく自然かだけを見る。
- 決め手: test-runner の skill を日本語化したコミットで、旧版にあった注意書き5件(`no_tests` はテストが通った証拠にならない/件数には親テストとサブテストの両方が入る/error・timeout・cancelled を成功扱いしない/自動リトライしない/テスト範囲はむやみに広げない)が消えていた。さらに「全件スキップは `no_tests`」という実装と逆の記述が入った。実装(`events.go`)では件数がすべて 0 のときだけ `no_tests` で、全件スキップは `passed` になる。README は正しかったので、食い違いは skill だけで起きていた。旧版と新版を並べて差分を取り、ステータスの定義をコードの分岐と突き合わせたことで見つかった。
- 覆す条件: skill の記述を実装や README から生成・検証する仕組み(CI での照合など)が入り、手作業の書き換えで意味が変わらなくなった場合。

## 2026-09-20: `.go` の判定はパターンの綴りではなく `go list` の解決結果で行う

- 却下した案: パッケージパターン末尾の `.go` を禁止して `.go` ファイル一覧を弾く(test-runner 初版の実装)。
- 決め手: `go list -e -json` の実測で、`pkg.go` という名前のディレクトリは import path `example.com/exp/pkg.go` として解決され、ファイル一覧は `command-line-arguments`(`Module` 欠落)として解決される。綴り判定は正当なパッケージを拒否するうえ、失敗時に自分が生成した `rerun`(`packages` に解決済み import path を入れる)を再受理できず round-trip が壊れる。解決済み import path なら `go test example.com/exp/pkg.go` はファイル一覧と解釈されず正常実行される(ファイル一覧モードは実在ファイルが条件)。混在指定・不存在パスも `Error` 付きレコードとして解決結果で弾ける。
- 覆す条件: `go list` がファイル一覧を `command-line-arguments` 以外で表すようになった場合。

## 2026-09-20: 呼び出し側の不正な `-run` 式はツール実行前に入力エラーとして弾く

- 却下した案: 不正な式をそのまま `go test` に渡す(初版)か、出力の `testing: invalid regexp` 文字列を検出して事後判定する。
- 決め手: 実測で `-run '['` は全パッケージに package 単位の `fail` イベント+exit 1 を出し、失敗テスト0件のまま `status: failed`・MCP `isError: false` になる=入力エラーがプロジェクトのテスト失敗に化ける。固定 Go の `src/testing/match.go` の `splitRegexp` と同じ分割(`[]`・`()`・`\` を尊重)+空白→`_` 置換で各要素を `regexp.Compile` すると、実 `go test -run` と22ケースで完全一致した。`\` 分岐を外した版と実測比較したところ、差が出るのは呼び出し側が渡す `a\/b`・`a\|b`(エスケープされた区切り)で、分割が `a\` を作って `regexp.Compile` が落ち、go が受理する式を誤拒否する。`exactRun` の出力は `/` 区切りで連結した QuoteMeta 済み要素なので、分割が崩れて要素が結合しても必ずコンパイルでき、この分岐の有無に影響されない(当初「自分の rerun を誤拒否する」と推定したが実測で否定)。空白置換を写さないと `(?P<a b>x)` を誤って拒否する(go 側は `rewrite` で有効化する)。非印字文字のエスケープは正規表現の妥当性を変えないため写していない。
- 覆す条件: `testing` 側の分割・`rewrite` 仕様が変わり一致が崩れた場合(固定 Go の `src/testing/match.go` を再 diff して判断する)。

## 2026-09-20: `testing.rewrite` は空白置換だけでなく非印字エスケープまで写す(直前の教訓を覆す)

- 却下した案: 非印字文字のエスケープは正規表現の妥当性を変えないとして写さない(直前エントリの結論)。および、写す際に空白判定を `unicode.IsSpace` で代用する。
- 決め手: 実 `go test -run` で双方向にズレを観測した。U+200B 単体は `invalid regexp for element 0 of -test.run ("​"): invalid escape sequence` で失敗する(=旧実装が受理して入力エラーがテスト失敗に化ける、この機能が防ぐはずだった当の症状)。逆に `\`+U+200B は `ok` で通る(=旧実装が誤拒否)。妥当性を変えないという前提が誤りだった。空白判定を代用しない理由は別。全ルーン走査(U+0000–U+10FFFF)で `testing.isSpace` と `unicode.IsSpace` の相違は0件、固定 1.26.8 と 1.27.1 の `isSpace` も同一だが、`testing` 側は「not the same as Unicode Z class」と明記して手書きスイッチにしている=Unicode テーブル由来の判定は `White_Space` が追加された時点で静かにズレ、この写しが防ごうとしている乖離そのものを再発させる。期待値は手で書かず、`-run` に `[`+対象ルーンを渡すと要素が必ず不正になりエラーが書き換え後の文字列をそのまま出力する性質を使って実測した。
- 覆す条件: `testing` 側の `rewrite`/`isSpace` が変わった場合(固定 Go の `src/testing/match.go` を再 diff)。また、ミラーの手写しが破綻するなら、実 `go test -run` の出力を期待値として生成する照合を CI に入れる方向へ切り替える。

## 2026-09-20: 予約 import path の判定は綴りではなく `Module` の有無で行う

- 却下した案: import path が `command-line-arguments` なら無条件で拒否する。
- 決め手: `module command-line-arguments` を宣言したモジュールは実在でき、`go list -json ./...` が同じ import path を **`Module` 付き**で返し `go test command-line-arguments` は `ok` になる。一方 `.go` ファイル一覧指定では `Module: null` で返る。綴りだけで弾くと正当なモジュールを拒否する。直前の教訓「`.go` の判定は `go list` の解決結果で行う」と同じ誤りが、パターン側からセンチネル側へ移っていただけだった。判定は既に module framing 欠落を扱っている分岐へ移した。
- 覆す条件: `go list` がファイル一覧に `Module` を付けて返すようになった場合。

## 2026-09-20: ミラーの忠実さは「どの Go で動くか」で決まる——`strconv.IsPrint` は写さず絞る

- 却下した案: `testing.rewrite` を完全同一に写す(`!strconv.IsPrint(r)` をそのまま使う)。レビュー指摘の字面どおりの実装。
- 決め手: `rewrite` はユーザーのツールチェーン内で動き、こちらのミラーは固定 1.26.8 でビルドされたバイナリ内で動く。`strconv.IsPrint` は Unicode バージョンごとに生成されるテーブルで、全ルーン走査の実測で **1.27.1 では印字可・1.26.8 では不可が 10,615 ルーン**あった。実害も再現済みで、U+088F を `-run` に渡すと 1.27.1 の `go test` は `ok`、1.26.8 は `࢏` にエスケープして拒否する。完全同一に写すと、Go 1.27 系ユーザーに対して有効な式を——`exactRun` が報告済みテスト名から自分で組み立てた rerun ごと——誤拒否する。`unicode.IsGraphic` は同じテーブル由来で連動する(1.26.8 で該当0件/1.27.1 で全件)ため代替軸にならない。ドリフトした 10,615 ルーンは 1.26.8 では**全て `Cn`(未割当)**で、逆に U+200B(Cf)・U+0007(Cc)・U+FEFF(Cf)・U+E000(Co) は安定側。よってエスケープを Cc/Cf/Cs/Co に限定した。取りこぼしは「素通しして受理」=修正前の挙動に戻るだけで、入力エラーの誤報にはならない安全側に倒れる。空白判定側は逆に手写しが正しい(`testing.isSpace` は固定スイッチ)ため、2つの分岐で判断が分かれる。
- 覆す条件: `rewrite` の仕様自体が変わった場合。なお直前エントリの「固定 Go の `src/testing/match.go` を再 diff」は不正確で、`-run` を書き換えるのは**呼び出し側のツールチェーン**なので、固定版だけでなく対応したい最小・最大の Go 版で突き合わせる。あるいは実 `go test -run` の出力から期待値を生成する照合を CI に入れる。
6 changes: 3 additions & 3 deletions test-runner/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ agy plugin install https://github.com/kwrkb/agy-plugins/test-runner
| Argument | Meaning |
| :-- | :-- |
| `module_path` | Required directory containing `go.mod`. Prefer an absolute path: relative paths use the MCP server's working directory. |
| `packages` | Nonempty string array of Go package patterns; default `["./..."]`. Local paths and import paths are accepted only when all resolved packages belong to the selected module and are physically inside it. |
| `run` | Optional Go `-run` expression, including slash-separated subtest expressions. |
| `packages` | Nonempty string array of Go package patterns; default `["./..."]`. Local paths and import paths are accepted only when all resolved packages belong to the selected module and are physically inside it. A package directory or import path may end in `.go`; a `.go` file list is rejected by its discovery result. |
| `run` | Optional Go `-run` expression, including slash-separated subtest expressions. An expression `go test` would reject at startup is reported as an input error instead of a test failure. |
| `timeout_seconds` | Integer 1–300, default 60. Covers discovery, compilation, and tests. Process cleanup can take a few additional seconds. |

Example arguments:
Expand Down Expand Up @@ -46,7 +46,7 @@ Each MCP result contains a JSON text object:

Use a failure's `rerun` object as the next `go_test` arguments. It retains the absolute module path and timeout, selects one package, and anchors/escapes each subtest-name segment. Go still runs parent setup to discover a selected subtest. Parent failures can overlap child failures; choose the parent to rerun a group or the child to narrow the retry. There is no server-side retry state.

Malformed/oversized JSON events (over 1 MiB), over 100,000 result records, and discovery output over 8 MiB produce explicit execution errors. Output continues to drain after a parsing error to avoid blocking the child; the overall timeout still applies. Timeout/cancellation stops the Unix process group or the Windows process tree and bounds pipe shutdown. Programs that deliberately detach from the process tree are outside this mechanism.
A `run` expression over 64 KiB is rejected as an input error, because it is validated before the timeout starts. Malformed/oversized JSON events (over 1 MiB), over 100,000 result records, and discovery output over 8 MiB produce explicit execution errors. Output continues to drain after a parsing error to avoid blocking the child; the overall timeout still applies. Timeout/cancellation stops the Unix process group or the Windows process tree and bounds pipe shutdown. Programs that deliberately detach from the process tree are outside this mechanism.

## Development and validation

Expand Down
4 changes: 2 additions & 2 deletions test-runner/README_ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ agy plugin install https://github.com/kwrkb/agy-plugins/test-runner
| 引数 | 内容 |
| :-- | :-- |
| `module_path` | 必須。`go.mod` があるディレクトリ。相対パスはMCPサーバーの作業ディレクトリ基準なので、絶対パスを推奨します。 |
| `packages` | 空でない文字列配列。既定 `["./..."]`。ローカルパス・import pathを受け付け、解決した全パッケージが対象モジュールに所属し、そのディレクトリ内にあることを確認します。 |
| `run` | 任意。Goの `-run` 正規表現。`/` 区切りのサブテスト指定にも対応します。 |
| `packages` | 空でない文字列配列。既定 `["./..."]`。ローカルパス・import pathを受け付け、解決した全パッケージが対象モジュールに所属し、そのディレクトリ内にあることを確認します。`.go` で終わるパッケージディレクトリ・import pathも指定でき、`.go` ファイル一覧は解決結果で拒否します。 |
| `run` | 任意。Goの `-run` 正規表現。`/` 区切りのサブテスト指定にも対応します。`go test` が起動時に拒否する式は、テスト失敗ではなく入力エラーとして返します。 |
| `timeout_seconds` | 1~300の整数。既定60秒。探索・ビルド・テスト全体の制限です。停止処理には追加で数秒かかる場合があります。 |

引数例:
Expand Down
Binary file modified test-runner/bin/test-runner-darwin-arm64
Binary file not shown.
Binary file modified test-runner/bin/test-runner-linux-amd64
Binary file not shown.
Binary file modified test-runner/bin/test-runner.exe
Binary file not shown.
6 changes: 3 additions & 3 deletions test-runner/skills/test-runner-gemini/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ description: go_test MCP ツールを使用して Go テストを実行し、失
`go_test` を呼び出す際は、`go.mod` が存在するディレクトリの絶対パスを `module_path` に指定します。MCP サーバーのカレントワーキングディレクトリはリポジトリルートと異なる場合があるため、相対パスではなく絶対パスを推奨します。

- **`module_path`** (string, 必須): `go.mod` が置かれているディレクトリの絶対パス。マルチモジュール構成のリポジトリでは、対象モジュールごとに個別に呼び出します。
- **`packages`** (array of string, 任意): パッケージパターン。既定値は `["./..."]`。フラグや `.go` ファイル一覧の指定は不可。
- **`run`** (string, 任意): Go の `-run` 正規表現(サブテストの `/` 区切り構文に対応)。
- **`packages`** (array of string, 任意): パッケージパターン。既定値は `["./..."]`。フラグの指定は不可。`.go` ファイル一覧も不可だが、`.go` で終わるパッケージディレクトリ・import path は指定できる。
- **`run`** (string, 任意): Go の `-run` 正規表現(サブテストの `/` 区切り構文に対応)。`go test` が起動時に拒否する不正な式は、テスト失敗ではなく `status: "error"` として返ります。64 KiB を超える式も同様に入力エラーになります。
- **`timeout_seconds`** (integer, 任意): パッケージ探索・コンパイル・テスト実行全体の上限時間(1〜300秒、既定60秒)。

## 実行結果の確認ポイント
Expand All @@ -24,7 +24,7 @@ description: go_test MCP ツールを使用して Go テストを実行し、失
- `passed`: 失敗なし(全件スキップの場合も含む。下記 `tests` を参照)
- `failed`: テスト失敗またはビルド失敗(MCP ツールとしては正常終了扱い)
- `no_tests`: テストの終了イベントが 0 件(実行対象のテストが無かった)。**関連テストが通った証拠にはならない**
- `error` / `timeout` / `cancelled`: ツールエラー、タイムアウト、キャンセル中断(MCP の `isError` が true)
- `error` / `timeout` / `cancelled`: ツールエラー(不正な引数を含む)、タイムアウト、キャンセル中断(MCP の `isError` が true)
これらをテスト成功として報告してはいけません。
2. **`tests`**:
- `passed`・`failed`・`skipped` の件数。**親テストとサブテストの両方を含みます**。
Expand Down
Loading
Loading