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
40 changes: 38 additions & 2 deletions .agents/skills/prepare-pr/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: prepare-pr
description: Prepare a NeMo Flow branch for review with the right tests, docs, and contributor hygiene
description: Prepare, open, create, publish, update, or edit a NeMo Flow pull request or PR body with the right tests, docs, contributor hygiene, and repository pull request template
author: NVIDIA Corporation and Affiliates
license: Apache-2.0
---
Expand All @@ -15,7 +15,12 @@ work. Keep changes scoped, surface assumptions, and define focused validation
before editing.

Use this skill at the end of a contributor or maintainer change before opening a
pull request.
pull request. Also use it whenever a user asks to create, open, publish, update,
or edit a NeMo Flow pull request, pull request description, or PR body.

If this repo-local guidance conflicts with generic GitHub publishing, connector,
or plugin guidance, this skill wins for PR body format, validation language, and
review handoff details.

## Checklist

Expand All @@ -32,15 +37,43 @@ pull request.
- [ ] Dependent maintainer or consumer skills updated when code changes affected
their APIs, bindings, commands, paths, packaging guidance, or best
practices
- [ ] Pull request title follows Conventional Commit style and uses the correct
type
- [ ] Pull request body follows `.github/pull_request_template.md`
- [ ] Breaking changes or renamed surfaces are called out explicitly

## Pull Request Title

Use Conventional Commit style for PR titles:

```text
<type>: <concise imperative summary>
```

Choose the type from the actual change surface, not from the impact of the
review comment or CI outcome. Use `fix` only for an actual user-facing or
runtime/product code bug fix. Never use `fix` for changes that are not related
to product code behavior, including chores, CI configuration, docs, tests,
packaging metadata, generated-output handling, or agent/skill guidance.

Common examples:

- `ci: update codecov coverage reporting`
- `docs: clarify release workflow`
- `chore: refresh generated attribution data`
- `test: add Python scope regression coverage`
- `fix: preserve scope-local middleware cleanup`

## Opening A Pull Request

Always use `.github/pull_request_template.md` as the source of truth for the PR
body. Before opening a PR, read the current template and preserve its headings,
checkboxes, comments' intent, and related-issue guidance.

This applies both when creating a new PR and when editing an existing PR
description. Do not use a generic `Summary / Why / Validation` body unless the
current repository template uses those headings.

When using GitHub CLI, prefer:

```bash
Expand All @@ -51,6 +84,9 @@ If a tool cannot consume the template directly, create the PR body from the
template content and then fill in every visible section before opening the PR.
Do not replace the template with a freeform summary.

After creating or editing a PR, fetch the rendered PR body and verify that the
template's visible headings and checklist items are still present.

The PR body must include:

- `#### Overview` with a concise summary and both contribution confirmation
Expand Down
4 changes: 4 additions & 0 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ reviews:
instructions: |
Review documentation for technical accuracy against the current API, command correctness, and consistency across language bindings.
Flag stale examples, missing SPDX headers where required, and instructions that no longer match CI or pre-commit behavior.
- path: "**/SKILL.md"
instructions: |
Do not flag SKILL.md files for missing SPDX headers. Skill entrypoints intentionally start with YAML frontmatter instead.
Verify that every SKILL.md keeps valid YAML frontmatter with at least name and description fields before the Markdown body.
- path: "{.github/**,.gitlab-ci.yml,.pre-commit-config.yaml,justfile,scripts/**}"
instructions: |
Review automation changes for reproducibility, pinned versions where appropriate, secret handling, and consistency with the documented validation matrix.
Expand Down
1 change: 1 addition & 0 deletions .github/ci-path-filters.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ ci:
- '.github/actionlint.yaml'
- '.github/ci-path-filters.yml'
- '.github/workflows/**'
- 'codecov.yml'

shared:
- 'crates/adaptive/Cargo.toml'
Expand Down
5 changes: 4 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ These conventions keep source, documentation, and binding behavior consistent ac
repository.

- Keep SPDX headers on source, docs, scripts, and configuration files. The project is Apache-2.0.
- `SKILL.md` files are skill entrypoints and do not need SPDX headers, but they must always start with YAML frontmatter containing at least `name` and `description`.
- Follow binding naming conventions: Rust and Python `snake_case`, C FFI exports prefixed `nemo_flow_`, Go `PascalCase` for public APIs, Node.js `camelCase`.
- Preserve the shared runtime model across bindings. Do not add behavior to one primary binding without considering Rust, Python, and Node.js parity.
- Prefer documented public APIs and stable wrapper commands. Do not rely on internal helpers in examples or user-facing docs.
Expand Down Expand Up @@ -236,4 +237,6 @@ with repository expectations.
- Keep stable public wrappers at the `scripts/` root in docs and examples. Reference namespaced helper paths only when documenting internal maintenance work.
- Use branch prefixes from the contributor docs: `feat/`, `fix/`, `docs/`, `test/`, or `refactor/`.
- Use signed-off commits for PR work: `git commit -s`.
- PR descriptions should include what changed, why, how it was tested, and any breaking changes.
- Before creating, opening, publishing, or editing a pull request, read `.github/pull_request_template.md` and use it as the PR body skeleton. Preserve its visible headings, checklist items, and related-issue guidance; fill the sections instead of replacing them with a generic summary.
- If repo-local PR guidance such as the `prepare-pr` skill conflicts with generic GitHub connector or plugin guidance, follow the repo-local PR guidance for PR body format and review handoff details.
- PR descriptions should include what changed, why, how it was tested, and any breaking changes within the repository template format.
21 changes: 15 additions & 6 deletions codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,16 @@
codecov:
require_ci_to_pass: true
notify:
# CI uploads coverage from 5 test jobs, each on a 3-way platform matrix.
after_n_builds: 15
# Codecov counts one build per upload step in a full CI run:
# | Binding | Platforms that upload coverage | Uploads |
# |-------------|----------------------------------------------------------------------|---------|
# | Rust | linux-amd64, linux-arm64, macos-arm64, windows-amd64 | 4 |
# | Python | linux-amd64, linux-arm64, macos-arm64, windows-amd64, windows-arm64 | 5 |
# | Go | linux-amd64, linux-arm64, macos-arm64, windows-amd64, windows-arm64 | 5 |
# | Node.js | linux-amd64, linux-arm64, macos-arm64 | 3 |
# | WebAssembly | linux-amd64, linux-arm64, macos-arm64, windows-amd64, windows-arm64 | 5 |
# | Total | | 22 |
after_n_builds: 22
wait_for_ci: true

coverage:
Expand Down Expand Up @@ -81,7 +89,7 @@ component_management:
- component_id: wasm_binding
name: WebAssembly Binding
paths:
- "NeMo-Flow/crates/wasm/pkg/*.js"
- "NeMo-Flow/crates/wasm/wrappers/nodejs/*.js"
statuses:
- type: project
target: 84% # cannot be any higher due to internals
Expand All @@ -90,7 +98,7 @@ component_management:
if_ci_failed: error

comment:
after_n_builds: 15
after_n_builds: 22
layout: "header, diff, components, files, footer"
behavior: default
require_changes: false
Expand All @@ -113,5 +121,6 @@ ignore:
- "**/*.d.ts"
- "**/*.pyi"
- "**/python/nemo_flow/lib_native*.dylib.dSYM/**"
# impossible to cover due to wasm-bindgen internals
- "**/crates/wasm/src/api/mod.rs"
# WebAssembly Rust wrappers are covered through wasm-pack execution and
# reported through generated package JavaScript coverage.
- "**/crates/wasm/src/**/*.rs"
2 changes: 1 addition & 1 deletion crates/wasm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"pretest:pkg": "npm run build:pkg",
"test:pkg": "node --test --test-force-exit tests-js/*.mjs",
"precoverage:pkg": "npm run build:pkg",
"coverage:pkg": "c8 --include=\"pkg/**/*.js\" --exclude=\"tests-js/**\" --reporter=text --reporter=json-summary --reporter=cobertura --reports-dir coverage node --test --test-force-exit --test-reporter=spec --test-reporter-destination=stdout --test-reporter=junit --test-reporter-destination=junit.xml tests-js/*.mjs"
"coverage:pkg": "c8 --include=\"pkg/**/*.js\" --exclude=\"tests-js/**\" --reporter=text --reporter=json-summary --reporter=cobertura --reports-dir coverage node --test --test-force-exit --test-reporter=spec --test-reporter-destination=stdout --test-reporter=junit --test-reporter-destination=junit.xml tests-js/*.mjs && node scripts/normalize_coverage.mjs coverage/cobertura-coverage.xml"
},
"devDependencies": {
"c8": "^11.0.0"
Expand Down
130 changes: 130 additions & 0 deletions crates/wasm/scripts/normalize_coverage.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import fs from 'node:fs';

const [inputPath, outputPath = inputPath] = process.argv.slice(2);

if (!inputPath) {
console.error('Usage: node scripts/normalize_coverage.mjs <input-cobertura.xml> [output-cobertura.xml]');
process.exit(1);
}

const filenameMap = new Map([
['pkg/adaptive.js', 'wrappers/nodejs/adaptive.js'],
['pkg/index.js', 'wrappers/nodejs/index.js'],
['pkg/plugin.js', 'wrappers/nodejs/plugin.js'],
['pkg/typed.js', 'wrappers/nodejs/typed.js'],
['wrappers/nodejs/adaptive.js', 'wrappers/nodejs/adaptive.js'],
['wrappers/nodejs/index.js', 'wrappers/nodejs/index.js'],
['wrappers/nodejs/plugin.js', 'wrappers/nodejs/plugin.js'],
['wrappers/nodejs/typed.js', 'wrappers/nodejs/typed.js'],
]);

function getAttribute(xml, name) {
const match = xml.match(new RegExp(`${name}="([^"]*)"`));
return match?.[1] ?? '';
}

function setAttribute(xml, name, value) {
const escaped = String(value).replaceAll('&', '&amp;').replaceAll('"', '&quot;');
const pattern = new RegExp(`${name}="[^"]*"`);
return xml.replace(pattern, `${name}="${escaped}"`);
}

function rate(covered, valid) {
return valid === 0 ? '1' : String(covered / valid);
}

function normalizeCoverageFilename(filename) {
return filename.replaceAll('\\', '/').replace(/^(?:\.\/)+/, '');
}

function summarizeClass(xml) {
let linesValid = 0;
let linesCovered = 0;
let branchesValid = 0;
let branchesCovered = 0;

for (const lineMatch of xml.matchAll(/<line\b[^>]*>/g)) {
const line = lineMatch[0];
linesValid += 1;
if (Number(getAttribute(line, 'hits')) > 0) {
linesCovered += 1;
}

const branchMatch = getAttribute(line, 'condition-coverage').match(/\((\d+)\/(\d+)\)/);
if (branchMatch) {
branchesCovered += Number(branchMatch[1]);
branchesValid += Number(branchMatch[2]);
}
}

return {
linesValid,
linesCovered,
branchesValid,
branchesCovered,
};
}

function sumCoverage(items) {
return items.reduce(
(total, item) => ({
linesValid: total.linesValid + item.linesValid,
linesCovered: total.linesCovered + item.linesCovered,
branchesValid: total.branchesValid + item.branchesValid,
branchesCovered: total.branchesCovered + item.branchesCovered,
}),
{ linesValid: 0, linesCovered: 0, branchesValid: 0, branchesCovered: 0 },
);
}

const input = fs.readFileSync(inputPath, 'utf8');
const classes = [];

for (const classMatch of input.matchAll(/<class\b[\s\S]*?<\/class>/g)) {
let classXml = classMatch[0];
const filename = normalizeCoverageFilename(getAttribute(classXml, 'filename'));
const normalizedFilename = filenameMap.get(filename);

if (!normalizedFilename) {
continue;
}

const summary = summarizeClass(classXml);
classXml = setAttribute(classXml, 'filename', normalizedFilename);
classXml = setAttribute(classXml, 'line-rate', rate(summary.linesCovered, summary.linesValid));
classXml = setAttribute(classXml, 'branch-rate', rate(summary.branchesCovered, summary.branchesValid));
classes.push({ xml: classXml, ...summary });
}

if (classes.length === 0) {
throw new Error(`no checked-in WebAssembly wrapper coverage classes found in ${inputPath}`);
}

const total = sumCoverage(classes);
let output = input;
output = output.replace(/<coverage\b[^>]*>/, (match) => {
let updated = match;
updated = setAttribute(updated, 'lines-valid', total.linesValid);
updated = setAttribute(updated, 'lines-covered', total.linesCovered);
updated = setAttribute(updated, 'line-rate', rate(total.linesCovered, total.linesValid));
updated = setAttribute(updated, 'branches-valid', total.branchesValid);
updated = setAttribute(updated, 'branches-covered', total.branchesCovered);
updated = setAttribute(updated, 'branch-rate', rate(total.branchesCovered, total.branchesValid));
return updated;
});
output = output.replace(/<package\b[^>]*>/, (match) => {
let updated = match;
updated = setAttribute(updated, 'name', 'wasm_wrappers');
updated = setAttribute(updated, 'line-rate', rate(total.linesCovered, total.linesValid));
updated = setAttribute(updated, 'branch-rate', rate(total.branchesCovered, total.branchesValid));
return updated;
});
output = output.replace(
/<classes>[\s\S]*?<\/classes>/,
`<classes>\n${classes.map((item) => item.xml).join('\n')}\n </classes>`,
);

fs.writeFileSync(outputPath, output);
Loading