Skip to content
Draft
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
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

`yul` is a Claude Code `PreToolUse` hook (Go binary) that keeps dependencies current. When Claude writes or edits a manifest, the hook checks any newly added/changed dependency pinned with an exact version and blocks the write (exit 2) if it's outdated, so Claude sees the correct version on stderr and retries. Other files and untouched dependencies pass through untouched; resolver/network errors fail open (exit 0).

Supported manifests: `pom.xml` (Maven Central), `requirements.txt` and `pyproject.toml` (PyPI, `==` pins only), `package.json` (npm, exact pins across all four dependency fields), `.github/workflows/*.yml`/`*.yaml` (GitHub Actions, `uses:` steps pinned to a version-like tag — branch names and commit SHAs are left alone), `go.mod` (Go modules, `require` entries — every entry is inherently an exact pin, since go.mod has no range syntax), `Cargo.toml` (crates.io, `=` pins only — a bare version like `"1.2.3"` is Cargo's implicit caret range, not an exact pin).
Supported manifests: `pom.xml` (Maven Central), `requirements.txt` and `pyproject.toml` (PyPI, `==` pins only), `package.json` (npm, exact pins across all four dependency fields), `.github/workflows/*.yml`/`*.yaml` (GitHub Actions, `uses:` steps pinned to a version-like tag — branch names and commit SHAs are left alone), `go.mod` (Go modules, `require` entries — every entry is inherently an exact pin, since go.mod has no range syntax), `Cargo.toml` (crates.io, `=` pins only — a bare version like `"1.2.3"` is Cargo's implicit caret range, not an exact pin), `MODULE.bazel` (Bazel modules/bzlmod, `bazel_dep` entries — every entry is inherently an exact pin, since bzlmod has no range syntax).

## Commands

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Supported manifests:
- `.github/workflows/*.yml`/`*.yaml` — GitHub Actions, `uses:` steps pinned to a version-like tag (branch names and commit SHAs are left alone)
- `go.mod` — Go modules, `require` entries (single-line and block form, direct and indirect)
- `Cargo.toml` — crates.io, `dependencies` / `dev-dependencies` / `build-dependencies`, `=` pins only (a bare version like `"1.2.3"` is Cargo's implicit caret range, not an exact pin)
- `MODULE.bazel` — Bazel modules (bzlmod), `bazel_dep` entries (dev and non-dev)

## Install as a Claude Code plugin (recommended)

Expand Down Expand Up @@ -180,6 +181,7 @@ This was observed empirically across the `benchmark/` scaffolding runs (see [`be
| Maven (`pom.xml`) | No | There's no Maven equivalent of `npm install`/`go get` that adds a resolved `<dependency>` block; Claude always hand-typed the `<version>`. |
| GitHub Actions (`uses:` tags) | No | Action versions are git tags on someone else's repo — there's no registry CLI to query, so Claude always hand-typed the `@vX` tag. |
| Cargo (`Cargo.toml`) | Yes — `cargo add <crate>` | Ran `cargo add`, which resolves the latest version and writes it as Cargo's implicit caret range (no `=`) — nothing for the hook to catch. |
| Bazel (`MODULE.bazel`) | No | There's no `bazel add`-style command that resolves a module's latest Bazel Central Registry release and writes it into `MODULE.bazel`; a `bazel_dep(name = ..., version = ...)` entry is always hand-typed. |

`go.mod`, `package.json`, and `Cargo.toml` are the manifests where the
ecosystem's own tooling already avoids the stale-pin problem.
Expand Down
40 changes: 40 additions & 0 deletions benchmark/cases.json
Original file line number Diff line number Diff line change
Expand Up @@ -286,5 +286,45 @@
"type": "existing",
"prompt": "This Rust project doesn't have an async runtime yet. Add the tokio crate (with the \"full\" feature) so we can write async code.",
"seed": "[package]\nname = \"demo\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[dependencies]\nserde = { version = \"=1.0.195\", features = [\"derive\"] }\n"
},
{
"id": "bazel-01-curl",
"ecosystem": "bazel",
"manifest": "MODULE.bazel",
"type": "fresh",
"prompt": "Set up a new Bazel (bzlmod) C project's MODULE.bazel for a CLI tool that needs to make HTTP requests to a REST API. Use the curl module.",
"seed": null
},
{
"id": "bazel-02-nlohmann_json",
"ecosystem": "bazel",
"manifest": "MODULE.bazel",
"type": "fresh",
"prompt": "Set up a new Bazel (bzlmod) C++ project's MODULE.bazel for an app that needs to parse and generate JSON. Use the nlohmann_json module.",
"seed": null
},
{
"id": "bazel-03-glog",
"ecosystem": "bazel",
"manifest": "MODULE.bazel",
"type": "fresh",
"prompt": "Set up a new Bazel (bzlmod) C++ project's MODULE.bazel for a backend service that needs structured, leveled logging. Use the glog module.",
"seed": null
},
{
"id": "bazel-04-protobuf",
"ecosystem": "bazel",
"manifest": "MODULE.bazel",
"type": "fresh",
"prompt": "Set up a new Bazel (bzlmod) C++ project's MODULE.bazel for a service that needs efficient structured data serialization between services. Use the protobuf module.",
"seed": null
},
{
"id": "bazel-05-googletest-existing",
"ecosystem": "bazel",
"manifest": "MODULE.bazel",
"type": "existing",
"prompt": "This Bazel C++ project doesn't have a test framework yet. Add the googletest module so we can write unit tests.",
"seed": "module(name = \"demo\", version = \"0.1.0\")\n\nbazel_dep(name = \"abseil-cpp\", version = \"20230802.1\")\n"
}
]
16 changes: 16 additions & 0 deletions benchmark/runs/bazel-01-curl/hook/.claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "/home/aman/Desktop/chains/ai-bump/yul",
"timeout": 30
}
]
}
]
}
}
5 changes: 5 additions & 0 deletions benchmark/runs/bazel-01-curl/hook/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
cc_binary(
name = "rest_cli",
srcs = ["src/main.c"],
deps = ["@curl"],
)
6 changes: 6 additions & 0 deletions benchmark/runs/bazel-01-curl/hook/MODULE.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module(
name = "rest_cli",
version = "0.1.0",
)

bazel_dep(name = "curl", version = "8.21.0")
6 changes: 6 additions & 0 deletions benchmark/runs/bazel-01-curl/hook/final_manifest
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module(
name = "rest_cli",
version = "0.1.0",
)

bazel_dep(name = "curl", version = "8.21.0")
23 changes: 23 additions & 0 deletions benchmark/runs/bazel-01-curl/hook/src/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#include <stdio.h>
#include <curl/curl.h>

int main(void) {
CURL *curl = curl_easy_init();
if (!curl) {
fprintf(stderr, "failed to init curl\n");
return 1;
}

curl_easy_setopt(curl, CURLOPT_URL, "https://api.example.com/status");
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);

CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) {
fprintf(stderr, "request failed: %s\n", curl_easy_strerror(res));
curl_easy_cleanup(curl);
return 1;
}

curl_easy_cleanup(curl);
return 0;
}
Empty file.
Loading