Skip to content
Open
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## 0.4.1 - Unreleased

- Added CUDA support to the C/C++ mapper, mapping `.cu` / `.cuh` sources as standalone `main()` files and as CMake and autotools targets, including the legacy `FindCUDA` `cuda_add_executable` / `cuda_add_library` commands. Repositories containing CUDA sources are detected as `cuda` projects; CUDA targets are tagged `cuda` and carry the `concurrency` trust boundary.
- Added C/C++/CUDA source-group mapping, so source files not owned by any CMake, autotools, or `main()` target are grouped per directory into bounded review slices.
- Added conservative C/C++/CUDA validation command defaults from a root `Makefile` `check` / `test` target or a declared `CMakePresets.json` build workflow, and mapped `CMakeLists.txt`, `CMakePresets.json`, and `configure.ac` as config features.
- Made `clawpatch review` and `clawpatch fix` CUDA-aware, injecting CUDA-specific reviewer guidance (kernel races, unchecked CUDA runtime calls, host/device pointer confusion, memory-access hazards, and synchronization mistakes) for features that own `.cu` / `.cuh` sources.

## 0.4.0 - 2026-05-22

- Added `clawpatch ci` to initialize, map, review, write a report, and append a GitHub Actions step summary in one CI-friendly command.
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,10 @@ validation commands and records a patch attempt under `.clawpatch/`.
Ecto migrations, project scripts, and ExUnit suites
- Rust `src/main.rs`, `src/bin/*.rs`, `src/lib.rs`, `crates/*`, and
`tests/*.rs`
- C/C++ standalone `main()` files, CMake `add_executable` / `add_library`
targets, and autotools `bin_PROGRAMS` / `lib_LTLIBRARIES` targets
- C/C++/CUDA standalone `main()` files, CMake `add_executable` / `add_library`
targets, autotools `bin_PROGRAMS` / `lib_LTLIBRARIES` targets, and source
groups for files outside any build target, including CUDA `.cu` / `.cuh`
sources
- Python project metadata, console scripts, bounded source groups, pytest suites,
and Flask/FastAPI/Django routes
- SwiftPM `Sources/*` targets and `Tests/*` suites
Expand Down
10 changes: 10 additions & 0 deletions docs/code-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,5 +120,15 @@ Categories requested from the provider:
- `build-release`
- `maintainability`

## CUDA-aware review

When a feature owns CUDA `.cu` / `.cuh` sources, `clawpatch review` (in the
default mode) and `clawpatch fix` add CUDA-specific guidance to the provider
prompt: kernel data races and synchronization barriers, unchecked CUDA runtime
calls and missing post-launch error checks, host versus device pointer
confusion, unsafe global- and shared-memory access, stream and event
synchronization, and device-memory leaks. Findings still use the existing
categories; there is no CUDA-specific category. Deslopify mode is unaffected.

Review does not edit files. Use `clawpatch fix --finding <id>` for the explicit
patch loop.
11 changes: 9 additions & 2 deletions docs/feature-mapping.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Supported deterministic mappers today:
- Ruby project metadata, executables, source groups, RSpec/Minitest suites,
Rails configs, routes, views, assets, and database files
- Rust Cargo commands, libraries, workspace crates, and integration tests
- C/C++ standalone `main()` files, CMake targets, and autotools targets
- C/C++/CUDA standalone `main()` files, CMake targets, and autotools targets
- C#/.NET projects from `.sln`, `.slnx`, `.csproj`, `.fsproj`, and `.vbproj`,
ASP.NET Core controllers, minimal API endpoints, C#/F#/Visual Basic source
groups, and .NET test projects
Expand Down Expand Up @@ -155,7 +155,14 @@ files are skipped.
C/C++ mapping covers generic project shapes only: standalone source files with
`main()`, CMake `add_executable` / `add_library`, and autotools `bin_PROGRAMS` /
`lib_LTLIBRARIES`. It deliberately avoids project-specific C dialects such as
php-src extension metadata.
php-src extension metadata. CUDA `.cu` / `.cuh` files are mapped through the same
C/C++ shapes, including the legacy `FindCUDA` `cuda_add_executable` /
`cuda_add_library` commands; CUDA targets are tagged `cuda`, and a repository with
`.cu` / `.cuh` sources is detected as a `cuda` project. Source files not owned by
any build target are grouped per directory into bounded, low-confidence source
groups. C/C++/CUDA validation commands are emitted only when the project declares
them: a root `Makefile` `check`/`test` target, or a `CMakePresets.json` build
workflow. Otherwise they stay null.

Python mapping covers `pyproject.toml`, `setup.cfg`, `setup.py`, and
`requirements.txt` metadata; `[project.scripts]`, `[tool.poetry.scripts]`,
Expand Down
126 changes: 126 additions & 0 deletions src/detect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,9 @@ async function languageDefaultCommands(
if (languages.includes("ruby")) {
return rubyDefaultCommands(root);
}
if (languages.includes("c") || languages.includes("cpp") || languages.includes("cuda")) {
return cOrCppDefaultCommands(root);
}

return {
typecheck: null,
Expand Down Expand Up @@ -691,6 +694,119 @@ async function rubyDefaultCommands(root: string): Promise<ProjectCommands> {
};
}

async function cOrCppDefaultCommands(root: string): Promise<ProjectCommands> {
const makefileCommands = await makefileDefaultCommands(root);
if (makefileCommands !== null) {
return makefileCommands;
}
const presetCommands = await cmakePresetDefaultCommands(root);
if (presetCommands !== null) {
return presetCommands;
}
return { typecheck: null, lint: null, format: null, test: null };
}

async function makefileDefaultCommands(root: string): Promise<ProjectCommands | null> {
if (!(await pathExists(join(root, "Makefile")))) {
return null;
}
const source = await readFile(join(root, "Makefile"), "utf8").catch(() => "");
const test = makefileHasTarget(source, "check")
? "make check"
: makefileHasTarget(source, "test")
? "make test"
: null;
if (test === null) {
return null;
}
return { typecheck: null, lint: null, format: null, test };
}

function makefileHasTarget(source: string, target: string): boolean {
return new RegExp(`^${target}\\s*:(?!=)`, "mu").test(source);
}

type CMakePresetSets = {
workflowPresets: string[];
configurePresets: string[];
buildPresets: string[];
testPresets: string[];
};

async function cmakePresetDefaultCommands(root: string): Promise<ProjectCommands | null> {
if (!(await pathExists(join(root, "CMakePresets.json")))) {
return null;
}
const presets = await readCMakePresets(root);
if (presets === null) {
return null;
}
const testPreset = singlePresetName(presets.testPresets);
return {
typecheck: cmakeBuildCommand(presets),
lint: null,
format: null,
test: testPreset === null ? null : `ctest --preset ${testPreset}`,
};
}

function cmakeBuildCommand(presets: CMakePresetSets): string | null {
const workflow = singlePresetName(presets.workflowPresets);
if (workflow !== null) {
return `cmake --workflow --preset ${workflow}`;
}
const configure = singlePresetName(presets.configurePresets);
const build = singlePresetName(presets.buildPresets);
if (configure !== null && build !== null) {
return `cmake --preset ${configure} && cmake --build --preset ${build}`;
}
return null;
}

function singlePresetName(names: string[]): string | null {
return names.length === 1 ? (names[0] ?? null) : null;
}

async function readCMakePresets(root: string): Promise<CMakePresetSets | null> {
let parsed: unknown;
try {
parsed = JSON.parse(await readFile(join(root, "CMakePresets.json"), "utf8"));
} catch {
return null;
}
if (typeof parsed !== "object" || parsed === null) {
return null;
}
const record = parsed as Record<string, unknown>;
return {
workflowPresets: cmakePresetNames(record["workflowPresets"]),
configurePresets: cmakePresetNames(record["configurePresets"]),
buildPresets: cmakePresetNames(record["buildPresets"]),
testPresets: cmakePresetNames(record["testPresets"]),
};
}

function cmakePresetNames(value: unknown): string[] {
if (!Array.isArray(value)) {
return [];
}
const names: string[] = [];
for (const entry of value) {
if (typeof entry !== "object" || entry === null) {
continue;
}
const preset = entry as { name?: unknown; hidden?: unknown };
if (
typeof preset.name === "string" &&
preset.hidden !== true &&
/^[A-Za-z0-9._-]+$/u.test(preset.name)
) {
names.push(preset.name);
}
}
return names;
}

async function mixProjectInfo(root: string): Promise<MixProjectInfo> {
if (!(await pathExists(join(root, "mix.exs")))) {
return { dependencies: new Set() };
Expand Down Expand Up @@ -1285,6 +1401,9 @@ async function detectLanguages(root: string): Promise<string[]> {
if (!languages.includes("cpp") && (await containsCppFile(root))) {
languages.push("cpp");
}
if (!languages.includes("cuda") && (await containsCudaFile(root))) {
languages.push("cuda");
}
if (!languages.includes("php") && (await containsReviewablePhpFile(root))) {
languages.push("php");
}
Expand Down Expand Up @@ -1339,6 +1458,13 @@ async function containsCFile(root: string): Promise<boolean> {
return containsFileWithExtension(root, ".c", 5, shouldSkipCOrCppSearchEntry);
}

async function containsCudaFile(root: string): Promise<boolean> {
return (
(await containsFileWithExtensionIgnoringCase(root, ".cu", 5, shouldSkipCOrCppSearchEntry)) ||
(await containsFileWithExtensionIgnoringCase(root, ".cuh", 5, shouldSkipCOrCppSearchEntry))
);
}

async function containsCppFile(root: string): Promise<boolean> {
return (
(await containsFileWithExtension(root, ".C", 5, shouldSkipCOrCppSearchEntry)) ||
Expand Down
Loading