Claude Code の Auto Mode や GitHub Copilot CLI の Autopilot
など、AIエージェントを自律的に動かす機能があります。
これらは非常に便利な反面、ホスト環境で直接動かすのは少し不安です。依存パッケージが汚染されたり、意図しないファイルを書き換えられたりするリスクがあります。
そこで今回は、エージェントをコンテナ内に閉じ込めつつ、ホストのプロジェクトディレクトリをシームレスに扱えるサンドボックス環境を作成しました。
https://github.com/yutakahashi114/sandbox
--------------------------------------------------------------------------------
概要
このツールは1つのシェルスクリプト sandbox を実行するだけで、Docker コンテナ上の開発環境に入ることができます。
$ sandbox # 現在のディレクトリで新しいセッションを開く
$ sandbox -- <cm最近、GitHub Copilot や Roo Code など、AI を使ってコードを書くことが増えてきました。
プロンプトでなるべく具体的な指示をすることで精度が向上します。
例えば、自分は以下のように書くことが多いです。
「ファイルAのn行目の関数Bを(使用/修正/参考に)してください」
しかし、このプロンプト文を書くのが地味に面倒でした。自分は開発に Visual Studio Code (VS Code) を使用していますが、
ファイル名をコピペ→行数を入力→関数名等をコピペ
のように書いていました。
そこで、ファイル名と行数をまとめてコピーできる拡張機能を作成しました。
作成した拡張機能
* VS Code Marketplace
https://marketplace.visualstudio.com/items?itemName=yutakahashi114.copy-with-file-path
* GitHub
https://github.com/yutakahashi114/copy-with-file-path
はじめに
organization Aのプライベートリポジトリのコードに変更があった時、organization BのプライベートリポジトリにGitHub
ActionsでPRを出したい、というケースがありました。
同じ組織の別リポジトリへPRを出す方法は、調べるといくつか記事が出てきますが、別の組織の場合は設定を変える必要があります。
少しハマったので、記録として残しておきます。
やりたいこと
org-a の repo-a から org-b の repo-b にPRを出す。
方法
以下の記事を参考にしました。
GitHub Apps / GitHub Actionsを使って別のリポジトリにファイルをコピーするPRを作成するZennHirotaka Miyagi
[https://zenn.dev/mh4gf/articles/copy-file-to-another-repository]
この記事では同じ組織の別リポジトリへのPRの場合なので、別の組織の場合に変更する部分を説明します。
GitHub Apps
GitHub Apps は org-b の組織に作成、インスはじめに
Goでecho [https://github.com/labstack/echo]を使って以下のようなサーバーを作っていました。
var db *sql.DB
func main() {
e := echo.New()
e.POST("/", func(c echo.Context) error {
ctx := c.Request().Context()
// 自身の管理するDBに保存する
db.ExecContext(ctx, "INSERT INTO ...")
// 別のサーバーで管理するDBに保存する
req, err := http.NewRequestWithContext(
ctx,
http.MethodPost,
"http://localhost:8081/",
nil,
)
if err != nil {
return err
}
res, err := http.DefaultClient.Do(req)
...
return c.NoContent(httpはじめに
関数の引数の値を書き換えた時、呼び出し元で渡した変数の値にも影響がある場合があります。どのような場合に影響があるのか、定期的に忘れるため書き残しておきました。
調査
以下の構造体で試してみます。
type TestStruct struct {
Value User
Pointer *User
ValueSlice ValueUsers
PointerSlice PointerUsers
}
type User struct {
ID int
}
type ValueUsers []User
type PointerUsers []*User
関数の引数に渡したとき、各フィールドのポイントインタがどうなるかを確認します。
package main
import (
"fmt"
"os"
"strings"
"github.com/olekukonko/tablewriter"
)
func main() {
a := TestStruct{
Value: User{
ID: 1,
},
使用しているライブラリの外部通信の中身(リクエストのヘッダやレスポンスのステータス等)をデバッグのために出力したくなったのでまとめました。
方法
以下のように外部のサーバーにリクエストするようなコードがあったとします。
package main
import (
"context"
"fmt"
"net/http"
)
func main() {
if err := run(); err != nil {
panic(err)
}
}
var c = &http.Client{}
func run() error {
ctx := context.Background()
req, err := http.NewRequestWithContext(
ctx,
http.MethodGet,
"http://localhost:8080",
nil,
)
if err != nil {
return err
}
res, err := c.Do(req)
if err != nil {
return err
}
fmtGo1.18から導入された型パラメータ comparable は、 == や != で比較可能な型のみを受け入れます。
mapのキーにジェネリックな型を指定するような関数でよく使うと思います。
func ToMap[T comparable](ss []T) map[T]struct{} {
m := make(map[T]struct{}, len(ss))
for _, s := range ss {
m[s] = struct{}{}
}
return m
}
int や string などの組み込み型はもちろん、構造体も comparable を満たします。
type User struct {
ID int
Name string
}
func main() {
us := []User{
{
ID: 1,
Name: "1",
},
{
ID: 2,
Name: "2",
},
}
m := ToMap(us)
_, ok := m[User{ID: 1, Name: "1"}]
fmはじめに
GoでRedisを使ったキャッシュを実装する際、同じような処理を何度も書くことになりそうだったので共通化しました。
実装
Redisへリクエストする処理をinterfaceで定義しておきます。
type Client interface {
Get(context.Context, string) ([]byte, bool, error)
Set(context.Context, string, []byte, time.Duration) error
}
このinterfaceを満たすclientを実装します。Redisへの接続には github.com/go-redis/redis
[https://pkg.go.dev/github.com/go-redis/redis/v8@v8.11.5] を使いました。
type client struct {
c *redis.Client
}
func NewClient(addr, pass string) cache.Client {
c := redis.NewClient(
&redis.Opはじめに
Goの標準パッケージの context.Context は context.WithValue 関数を使って任意の値を格納することができます。
context.WithValue には以下のようなコメントが書かれています。
// WithValue returns a copy of parent in which the value associated with key is
// val.
//
// Use context Values only for request-scoped data that transits processes and
// APIs, not for passing optional parameters to functions.
//
// The provided key must be comparable and should not be of type
// string or any other built-in type to avoid collisions between
// packages using csliceの構造
Goのsliceは 配列へのポインタ 長さ 容量 を持った構造体として定義されています。
go/slice.go at go1.18 · golang/goThe Go programming language. Contribute to
golang/go development by creating an account on GitHub.GitHubgolang
[https://github.com/golang/go/blob/go1.18/src/runtime/slice.go#L15-L19]type slice struct {
array unsafe.Pointer
len int
cap int
}
sliceの構造については以下の記事が分かりやすかったです。
実装して理解するスライス #golang - Qiitaはじめに この記事はGoアドベントカレンダーの1日目の記事です。 スライスの実態
runtimeのコードをみるとGoのスライスは以下のように定義されています。 type slice struct { arr
interfaceのnilチェックをしているのにメソッド呼び出し時にpanicが起きることがあり、その時に調べたことのメモです。
該当コードを簡略化して書くと以下のような感じです。
package main
import "fmt"
type Message interface {
Str() string
}
var _ Message = &Text{}
type Text struct {
text string
}
func (t *Text) Str() string {
return t.text
}
func NewText(str string) *Text {
if str == "" {
return nil
}
return &Text{
text: str,
}
}
NewText() は引数が空文字の場合にnilを返します。
これに対して、以下のような Print() 関数を用意して実行すると、
func Print(m Message) {
if m != nil {
fmt.Println(m.StrAmazon Cognito のメールアドレス変更には以下のような問題があります。
Cognitoでメールアドレス編集するとログインできなくなる問題Zennハトすけ
[https://zenn.dev/dove/articles/78ecf08b51ee0c]> *
新しいメールアドレスを申請して、メールで検証をする前にその新しいメールでログインできてしまう
* 逆に検証が成功していないのに古いメールアドレスでログインできない
* つまり間違ったメールアドレスをリクエストするとその時点で詰む(ユーザーロックというらしい)
これを回避する方法は上の記事やissue [https://github.com/aws-amplify/amplify-js/issues/987]
にいくつか書かれています。
以下では、個人的にベターと思う方法を書きました。
今回Golangのサーバーで実装していますが、同じロジックでlambdaでの実装もできると思います。
基本的な方針は以下を参考にしました。
Change email with AWS Cognito and Amplify - Jo