From a0dc2b4585e3e8fb0f8ba8da96124b89faa92fe0 Mon Sep 17 00:00:00 2001 From: spencatro-pub Date: Wed, 4 Feb 2026 14:43:58 -0800 Subject: [PATCH] Copilot v3 implementation w/ snapshots This change gets basic hooks working in copilot. There are still issues: * No model attribution (hook output lacks breadcrumbs) * Only works in copilot-cli --- CONTRIBUTING.md | 3 + package-lock.json | 822 ++++++++++++++++++++++++++++++++++ packages/cli/src/capture.ts | 194 +++++++- packages/cli/src/index.ts | 4 + packages/cli/src/lib/hooks.ts | 124 ++++- packages/cli/src/lib/types.ts | 2 +- 6 files changed, 1139 insertions(+), 10 deletions(-) create mode 100644 package-lock.json diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f169655..fd84a37 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,6 +22,9 @@ bun install # Build all packages bun run build + +# Install the local version of agentblame globally (so `which agentblame` has your changes) +cd packages/cli && npm install -g . ``` ### Project Structure diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..e3cdc12 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,822 @@ +{ + "name": "agentblame", + "version": "3.1.9", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "agentblame", + "version": "3.1.9", + "license": "Apache-2.0", + "workspaces": [ + "packages/cli", + "packages/extension", + "packages/chrome", + "packages/firefox" + ], + "dependencies": { + "diff": "^8.0.2" + }, + "devDependencies": { + "@biomejs/biome": "^2.3.10", + "@types/chrome": "^0.0.287", + "@types/diff": "^8.0.0", + "@types/node": "^20.11.30", + "esbuild": "^0.24.0", + "typescript": "^5.5.4" + } + }, + "node_modules/@agentblame/chrome": { + "resolved": "packages/chrome", + "link": true + }, + "node_modules/@agentblame/extension": { + "resolved": "packages/extension", + "link": true + }, + "node_modules/@agentblame/firefox": { + "resolved": "packages/firefox", + "link": true + }, + "node_modules/@biomejs/biome": { + "version": "2.3.14", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.3.14.tgz", + "integrity": "sha512-QMT6QviX0WqXJCaiqVMiBUCr5WRQ1iFSjvOLoTk6auKukJMvnMzWucXpwZB0e8F00/1/BsS9DzcKgWH+CLqVuA==", + "dev": true, + "license": "MIT OR Apache-2.0", + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "2.3.14", + "@biomejs/cli-darwin-x64": "2.3.14", + "@biomejs/cli-linux-arm64": "2.3.14", + "@biomejs/cli-linux-arm64-musl": "2.3.14", + "@biomejs/cli-linux-x64": "2.3.14", + "@biomejs/cli-linux-x64-musl": "2.3.14", + "@biomejs/cli-win32-arm64": "2.3.14", + "@biomejs/cli-win32-x64": "2.3.14" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "2.3.14", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.3.14.tgz", + "integrity": "sha512-UJGPpvWJMkLxSRtpCAKfKh41Q4JJXisvxZL8ChN1eNW3m/WlPFJ6EFDCE7YfUb4XS8ZFi3C1dFpxUJ0Ety5n+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "2.3.14", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.3.14.tgz", + "integrity": "sha512-PNkLNQG6RLo8lG7QoWe/hhnMxJIt1tEimoXpGQjwS/dkdNiKBLPv4RpeQl8o3s1OKI3ZOR5XPiYtmbGGHAOnLA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "2.3.14", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.3.14.tgz", + "integrity": "sha512-KT67FKfzIw6DNnUNdYlBg+eU24Go3n75GWK6NwU4+yJmDYFe9i/MjiI+U/iEzKvo0g7G7MZqoyrhIYuND2w8QQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "2.3.14", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.3.14.tgz", + "integrity": "sha512-LInRbXhYujtL3sH2TMCH/UBwJZsoGwfQjBrMfl84CD4hL/41C/EU5mldqf1yoFpsI0iPWuU83U+nB2TUUypWeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "2.3.14", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.3.14.tgz", + "integrity": "sha512-ZsZzQsl9U+wxFrGGS4f6UxREUlgHwmEfu1IrXlgNFrNnd5Th6lIJr8KmSzu/+meSa9f4rzFrbEW9LBBA6ScoMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "2.3.14", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.3.14.tgz", + "integrity": "sha512-KQU7EkbBBuHPW3/rAcoiVmhlPtDSGOGRPv9js7qJVpYTzjQmVR+C9Rfcz+ti8YCH+zT1J52tuBybtP4IodjxZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "2.3.14", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.3.14.tgz", + "integrity": "sha512-+IKYkj/pUBbnRf1G1+RlyA3LWiDgra1xpS7H2g4BuOzzRbRB+hmlw0yFsLprHhbbt7jUzbzAbAjK/Pn0FDnh1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "2.3.14", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.3.14.tgz", + "integrity": "sha512-oizCjdyQ3WJEswpb3Chdngeat56rIdSYK12JI3iI11Mt5T5EXcZ7WLuowzEaFPNJ3zmOQFliMN8QY1Pi+qsfdQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", + "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", + "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", + "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", + "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", + "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", + "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", + "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", + "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", + "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", + "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", + "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", + "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", + "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", + "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", + "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", + "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", + "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", + "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", + "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", + "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", + "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", + "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", + "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", + "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", + "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@mesadev/agentblame": { + "resolved": "packages/cli", + "link": true + }, + "node_modules/@types/chrome": { + "version": "0.0.287", + "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.287.tgz", + "integrity": "sha512-wWhBNPNXZHwycHKNYnexUcpSbrihVZu++0rdp6GEk5ZgAglenLx+RwdEouh6FrHS0XQiOxSd62yaujM1OoQlZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/filesystem": "*", + "@types/har-format": "*" + } + }, + "node_modules/@types/diff": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@types/diff/-/diff-8.0.0.tgz", + "integrity": "sha512-o7jqJM04gfaYrdCecCVMbZhNdG6T1MHg/oQoRFdERLV+4d+V7FijhiEAbFu0Usww84Yijk9yH58U4Jk4HbtzZw==", + "deprecated": "This is a stub types definition. diff provides its own type definitions, so you do not need this installed.", + "dev": true, + "license": "MIT", + "dependencies": { + "diff": "*" + } + }, + "node_modules/@types/filesystem": { + "version": "0.0.36", + "resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.36.tgz", + "integrity": "sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/filewriter": "*" + } + }, + "node_modules/@types/filewriter": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.33.tgz", + "integrity": "sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/har-format": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.16.tgz", + "integrity": "sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", + "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/bun-types": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.3.9.tgz", + "integrity": "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/diff": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", + "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/esbuild": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", + "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.24.2", + "@esbuild/android-arm": "0.24.2", + "@esbuild/android-arm64": "0.24.2", + "@esbuild/android-x64": "0.24.2", + "@esbuild/darwin-arm64": "0.24.2", + "@esbuild/darwin-x64": "0.24.2", + "@esbuild/freebsd-arm64": "0.24.2", + "@esbuild/freebsd-x64": "0.24.2", + "@esbuild/linux-arm": "0.24.2", + "@esbuild/linux-arm64": "0.24.2", + "@esbuild/linux-ia32": "0.24.2", + "@esbuild/linux-loong64": "0.24.2", + "@esbuild/linux-mips64el": "0.24.2", + "@esbuild/linux-ppc64": "0.24.2", + "@esbuild/linux-riscv64": "0.24.2", + "@esbuild/linux-s390x": "0.24.2", + "@esbuild/linux-x64": "0.24.2", + "@esbuild/netbsd-arm64": "0.24.2", + "@esbuild/netbsd-x64": "0.24.2", + "@esbuild/openbsd-arm64": "0.24.2", + "@esbuild/openbsd-x64": "0.24.2", + "@esbuild/sunos-x64": "0.24.2", + "@esbuild/win32-arm64": "0.24.2", + "@esbuild/win32-ia32": "0.24.2", + "@esbuild/win32-x64": "0.24.2" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "packages/chrome": { + "name": "@agentblame/chrome", + "version": "3.1.9", + "devDependencies": { + "@types/chrome": "^0.0.287", + "@types/node": "^20.11.30", + "esbuild": "^0.24.0", + "typescript": "^5.5.4" + } + }, + "packages/cli": { + "name": "@mesadev/agentblame", + "version": "3.1.12", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "diff": "^8.0.2" + }, + "bin": { + "agentblame": "dist/index.js" + }, + "devDependencies": { + "@types/diff": "^8.0.0", + "@types/node": "^20.11.30", + "bun-types": "^1.3.6", + "typescript": "^5.5.4" + }, + "engines": { + "bun": ">=1.0.0" + } + }, + "packages/extension": { + "name": "@agentblame/extension", + "version": "3.1.9", + "devDependencies": { + "@types/chrome": "^0.0.287", + "@types/node": "^20.11.30", + "esbuild": "^0.24.0", + "typescript": "^5.5.4" + } + }, + "packages/firefox": { + "name": "@agentblame/firefox", + "version": "3.1.9", + "devDependencies": { + "@types/chrome": "^0.0.287", + "@types/node": "^20.11.30", + "esbuild": "^0.24.0", + "typescript": "^5.5.4" + } + } + } +} diff --git a/packages/cli/src/capture.ts b/packages/cli/src/capture.ts index 0b8b9ce..40ab0e2 100644 --- a/packages/cli/src/capture.ts +++ b/packages/cli/src/capture.ts @@ -105,6 +105,29 @@ interface OpenCodePayload { hook_event?: "before" | "after"; } +interface CopilotToolArgs { + path?: string; // File path for edit/create + content?: string; // Content for create + old_str?: string; // For edit operations (if provided) + new_str?: string; // For edit operations (if provided) + command?: string; // For bash tool + description?: string; // Tool description +} + +interface CopilotPayload { + timestamp: number; + cwd: string; + source: string; + initialPrompt?: string; + prompt?: string; + toolName?: string; // "edit" | "create" | "bash" | "view" + toolArgs?: CopilotToolArgs; // JSON string -> CopilotToolArgs + toolResult?: { + resultType: "success" | "failure" | "denied"; + textResultForLlm: string; + }; +} + // ============================================================================= // Utilities // ============================================================================= @@ -454,17 +477,14 @@ async function getBeforeContent( // Payload Processing // ============================================================================= -function parseArgs(): { - provider: "cursor" | "claude" | "opencode"; - event?: string; -} { +function parseArgs(): { provider: "cursor" | "claude" | "opencode" | "copilot"; event?: string } { const args = process.argv.slice(2); - let provider: "cursor" | "claude" | "opencode" = "cursor"; + let provider: "cursor" | "claude" | "opencode" | "copilot" = "cursor"; let event: string | undefined; for (let i = 0; i < args.length; i++) { if (args[i] === "--provider" && args[i + 1]) { - provider = args[i + 1] as "cursor" | "claude" | "opencode"; + provider = args[i + 1] as "cursor" | "claude" | "opencode" | "copilot"; i++; } else if (args[i] === "--event" && args[i + 1]) { event = args[i + 1]; @@ -802,6 +822,166 @@ async function processOpenCodePayload(payload: OpenCodePayload): Promise { } } +/** + * Process Copilot payload. + * Copilot provides toolArgs as a JSON string containing the file path. + * Only process edit and create tools on success. + */ +async function processCopilotPayload(payload: CopilotPayload, event?: string): Promise { + + const toolName = payload.toolName?.toLowerCase() || ""; + const eventType = event || ""; + const pathForRepo = payload.cwd; + const copilotModel = "copilot"; + + // Parse toolArgs JSON string + const toolArgsRaw = payload?.toolArgs; + let toolArgs: CopilotToolArgs = toolArgsRaw || {}; + if (toolArgsRaw && typeof toolArgsRaw === "string") { + toolArgs = JSON.parse(toolArgsRaw); + } + + if (eventType === "sessionStart") { + const conversationId = `copilot-sessionstart-${Date.now()}`; + const ctx = await setupCaptureContext(pathForRepo, copilotModel, conversationId, copilotModel); + if (!ctx) return; + + upsertSession({ + id: ctx.sessionId, + agent: copilotModel, + repo: ctx.repoId, + model: ctx.model, + conversationId, + }); + + if (payload.initialPrompt) { + const contentHash = hashPromptContent(payload.initialPrompt); + if (!promptExists(ctx.sessionId, contentHash)) { + insertPrompt({ + sessionId: ctx.sessionId, + content: ctx.storePromptContent ? payload.initialPrompt : null, + contentHash, + }); + if (process.env.AGENTBLAME_DEBUG) { + console.error(`[agentblame] copilot sessionStart success`); + } + } else { + if (process.env.AGENTBLAME_DEBUG) { + console.error(`[agentblame] copilot sessionStart prompt already exists`); + } + } + } + // we are done with this event, don't process anything else from it + return; + } + + if (eventType === "preToolUse") { + if (toolName === "edit") { + // Parse toolArgs JSON string + const filePath = toolArgs?.path; + if (filePath) { + // TODO: get a real conversation ID ? + const conversationId = `copilot-pretooluse-${Date.now()}`; + const ctx = await setupCaptureContext(pathForRepo, copilotModel, conversationId, copilotModel); + if (!ctx) return; + + await detectAndRecordHumanEdits(ctx, conversationId, filePath); + await captureFileCheckpoint(ctx.repoRoot, conversationId, filePath); + if (process.env.AGENTBLAME_DEBUG) { + console.error(`[agentblame] copilot preToolUse success: ${filePath}`); + } + } + } + // TODO: support multi-file edits? (Will copilot send a multi-edit, or multiple edit events?) + + // we are done with this event, don't process anything else from it + return; + } + + if (eventType === "userPromptSubmitted") { + const conversationId = `copilot-userpromptsubmitted-${Date.now()}`; + const ctx = await setupCaptureContext(pathForRepo, copilotModel, conversationId, copilotModel); + if (!ctx) return; + + const prompt = payload.prompt || ""; + + const contentHash = hashPromptContent(prompt); + if (!promptExists(ctx.sessionId, contentHash)) { + insertPrompt({ + sessionId: ctx.sessionId, + content: ctx.storePromptContent ? prompt : null, + contentHash, + }); + if (process.env.AGENTBLAME_DEBUG) { + console.error(`[agentblame] copilot userPromptSubmitted success: ${prompt}`); + } + } else { + if (process.env.AGENTBLAME_DEBUG) { + console.error(`[agentblame] copilot userPromptSubmitted already exists: ${prompt}`); + } + } + } + + // assume this is now a PostToolUse event + // Only process successful operations + if (payload.toolResult?.resultType !== "success") { + console.warn(`[agentblame] copilot payload was not successful, skipping`); + return; + } + + // Only process edit and create tools + if (toolName !== "edit" && toolName !== "create") { + console.warn(`[agentblame] copilot tool (${toolName}) not edit / create, skipping`); + return; + } + + const filePath = toolArgs.path; + if (!filePath) { + console.warn(`[agentblame] copilot payload toolargs missing filepath`); + return; + } + + // TODO: get a real conversation ID + const timestamp = new Date(payload.timestamp).toISOString(); + const ctx = await setupCaptureContext(filePath, "copilot", `copilot-${timestamp}`, "copilot"); + if (!ctx) return; + + const absolutePath = path.isAbsolute(filePath) + ? filePath + : path.join(ctx.repoRoot, filePath); + + // Handle create tool (new file creation) + if (toolName === "create") { + // For create, we need to read the file to get its content + const afterCreateContent = readFileContent(absolutePath); + if (afterCreateContent) { + await recordAIDelta(ctx, filePath, "", afterCreateContent); + } + } + + // Handle edit tool (editing a file) + if (toolName === "edit") { + const conversationId = `copilot-posttooluse-${Date.now()}`; + const beforeContent = await getBeforeContent(ctx, conversationId, filePath); + const afterContent = readFileContent(absolutePath); + + if (process.env.AGENTBLAME_DEBUG) { + console.error(`[agentblame] PostToolUse ${toolName}: ${filePath}`); + console.error(`[agentblame] beforeContent: ${beforeContent ? beforeContent.length + ' chars' : 'null'}`); + console.error(`[agentblame] afterContent: ${afterContent ? afterContent.length + ' chars' : 'null'}`); + } + + if (afterContent) { + await recordAIDelta(ctx, filePath, beforeContent, afterContent); + // Update checkpoint to current state so next PreToolUse doesn't + // incorrectly detect this AI edit as a "human edit" + await captureFileCheckpoint(ctx.repoRoot, conversationId, filePath); + } else if (process.env.AGENTBLAME_DEBUG) { + console.error(`[agentblame] SKIPPED: no afterContent for ${filePath}`); + } + } +} + // ============================================================================= // Main // ============================================================================= @@ -839,6 +1019,8 @@ export async function runCapture(): Promise { await processClaudePayload(payload as ClaudePayload); } else if (provider === "opencode") { await processOpenCodePayload(payload as OpenCodePayload); + } else if (provider === "copilot") { + await processCopilotPayload(payload as CopilotPayload, event); } process.exit(0); diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 43f5aa6..4049749 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -24,6 +24,7 @@ import { installCursorHooks, installClaudeHooks, installOpenCodeHooks, + installCopilotHooks, installGitHubAction, installGitHookSmart, detectHooksSetup, @@ -267,6 +268,9 @@ async function runInit(initArgs: string[] = []): Promise { const opencodeSuccess = await installOpenCodeHooks(repoRoot); results.push({ name: "OpenCode hooks (.opencode/)", success: opencodeSuccess }); + const copilotSuccess = await installCopilotHooks(repoRoot); + results.push({ name: "Copilot hooks", success: copilotSuccess }); + // Install GitHub Actions workflow const githubActionSuccess = await installGitHubAction(repoRoot); results.push({ name: "GitHub Actions workflow", success: githubActionSuccess }); diff --git a/packages/cli/src/lib/hooks.ts b/packages/cli/src/lib/hooks.ts index 790fec9..7b4b7a4 100644 --- a/packages/cli/src/lib/hooks.ts +++ b/packages/cli/src/lib/hooks.ts @@ -29,6 +29,13 @@ export function getOpenCodePluginDir(repoRoot: string): string { return path.join(repoRoot, ".opencode", "plugin"); } +/** + * Get the Copilot hooks.json path for a repo. + */ +export function getCopilotHooksPath(repoRoot: string): string { + return path.join(repoRoot, ".github", "hooks", "hooks.json"); +} + /** * Get the OpenCode agentblame plugin file path for a repo. */ @@ -384,6 +391,116 @@ export async function areOpenCodeHooksInstalled(repoRoot: string): Promise { + if (process.platform === "win32") { + console.error("Windows is not supported yet"); + return false; + } + + const hooksPath = getCopilotHooksPath(repoRoot); + + try { + // Create .github/hooks directory if it doesn't exist + await fs.promises.mkdir(path.dirname(hooksPath), { recursive: true }); + + let config: any = {}; + try { + const existing = await fs.promises.readFile(hooksPath, "utf8"); + config = JSON.parse(existing || "{}"); + } catch { + // File doesn't exist or invalid JSON + } + + config.version = config.version ?? 1; + config.hooks = config.hooks ?? {}; + + // iterate over the following hooktypes installing hooks: preToolUse, postToolUse, sessionStart, userPromptSubmitted + const hookTypeList = ["preToolUse", "postToolUse", "sessionStart", "userPromptSubmitted"]; + for (const hookType of hookTypeList) { + // create the hook object + config.hooks[hookType] = config.hooks[hookType] ?? []; + if (!Array.isArray(config.hooks[hookType])) { + config.hooks[hookType] = []; + } + + // Add the hook + config.hooks[hookType].push({ + type: "command", + bash: `agentblame capture --provider copilot --event ${hookType}`, + cwd: ".", + timeoutSec: 10, + }); + } + + await fs.promises.writeFile( + hooksPath, + JSON.stringify(config, null, 2), + "utf8" + ); + + return true; + } catch (err) { + console.error("Failed to install Copilot hooks:", err); + return false; + } +} + +/** + * Check if Copilot hooks are installed for a repo. + */ +export async function areCopilotHooksInstalled(repoRoot: string): Promise { + try { + const hooksPath = getCopilotHooksPath(repoRoot); + const config = JSON.parse( + await fs.promises.readFile(hooksPath, "utf8") + ); + + const hasHook = config.hooks?.postToolUse?.some( + (h: any) => h?.bash?.includes("agentblame") + ); + return hasHook === true; + } catch { + return false; + } +} + +/** + * Uninstall Copilot hooks from a repo + */ +export async function uninstallCopilotHooks(repoRoot: string): Promise { + try { + const hooksPath = getCopilotHooksPath(repoRoot); + if (fs.existsSync(hooksPath)) { + const config = JSON.parse( + await fs.promises.readFile(hooksPath, "utf8") + ); + + const hookTypeList = ["preToolUse", "postToolUse", "sessionStart", "userPromptSubmitted"]; + for (const hookType of hookTypeList) { + // remove the hook if it is from agentblame + if (config.hooks?.[hookType]) { + config.hooks[hookType] = config.hooks[hookType].filter( + (h: any) => !h?.bash?.includes("agentblame") + ); + } + } + + await fs.promises.writeFile( + hooksPath, + JSON.stringify(config, null, 2), + "utf8" + ); + } + return true; + } catch (err) { + console.error("Failed to uninstall Copilot hooks:", err); + return false; + } +} + /** * Uninstall OpenCode hooks from a repo */ @@ -448,15 +565,16 @@ export async function areClaudeHooksInstalled(repoRoot: string): Promise { +): Promise<{ cursor: boolean; claude: boolean; opencode: boolean; copilot: boolean }> { const cursor = await installCursorHooks(repoRoot); const claude = await installClaudeHooks(repoRoot); const opencode = await installOpenCodeHooks(repoRoot); - return { cursor, claude, opencode }; + const copilot = await installCopilotHooks(repoRoot); + return { cursor, claude, opencode, copilot }; } /** diff --git a/packages/cli/src/lib/types.ts b/packages/cli/src/lib/types.ts index c7ae4da..bf7c358 100644 --- a/packages/cli/src/lib/types.ts +++ b/packages/cli/src/lib/types.ts @@ -12,7 +12,7 @@ /** * AI agent that generated the code */ -export type AiAgent = "cursor" | "claude" | "opencode"; +export type AiAgent = "cursor" | "claude" | "opencode" | "copilot"; // ============================================================================= // Session Types (SQLite)