Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ See `templates/report.md` for the format.

## Composition with other skills

The pr-comparator (`nemoclaw-maintainer-pr-comparator`) calls this skill as a sub-step when comparing competing PRs. Adjacent-fix counts feed Tier 3 tiebreakers; contradicting hits factor into Tier 2 quality scoring.
This skill is a separate, optional follow-up to `nemoclaw-maintainer-pr-comparator`. The comparator does not call it or include its findings in the deterministic score. Run the sweep explicitly when a maintainer wants adjacent-fix or contradiction evidence alongside the comparator verdict, and report that evidence separately.

## What this skill does NOT do (deferred)

Expand Down
13 changes: 7 additions & 6 deletions .agents/skills/nemoclaw-maintainer-cut-release-tag/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ user_invocable: true

Use the release scripts for normal release operations. Do not run raw `git tag`, `git push`, `gh api`, or version-bump commands by hand for the normal release flow.

The release is one annotated semver tag on an already-merged `origin/main` commit. The GitHub workflow moves `latest`; release admins promote `lkg` manually after validation. After the tag is cut, finish version-label housekeeping for remaining open issues/PRs and draft release notes for the maintainer to post.
The release is one annotated semver tag on an already-merged `origin/main` commit. The GitHub workflow moves `latest`; release admins promote `lkg` manually after validation. After the tag and `latest` are verified, automatically move remaining open issues/PRs from the released version label to the next patch label, then draft release notes for the maintainer to post.

## Hard Rules

Expand All @@ -33,7 +33,7 @@ Release Progress:
- [ ] Step 2: Show plan and exact confirmation phrase
- [ ] Step 3: Cut the semver tag from the confirmed plan
- [ ] Step 4: Wait for workflow-managed latest
- [ ] Step 5: Housekeep remaining open issues/PRs
- [ ] Step 5: Bump remaining open issues/PRs
- [ ] Step 6: Generate release-note data and draft Markdown
- [ ] Step 7: Hand off announcement steps
```
Expand Down Expand Up @@ -108,14 +108,16 @@ The script waits until `vX.Y.Z^{}` and `latest^{}` both peel to the planned comm

If it fails, report the failed workflow/status. Do not manually move `latest`.

### Step 5: Housekeep Remaining Open Issues/PRs
### Step 5: Bump Remaining Open Issues/PRs

Move any remaining open issues or PRs labeled with the released version to the next patch label:
Move every remaining open issue or PR carrying the released version to the next patch label:

```bash
node --experimental-strip-types --no-warnings .agents/skills/nemoclaw-maintainer-day/scripts/bump-stragglers.ts <released-version> <next-version>
```

This is automatic post-tag housekeeping covered by the release plan and exact confirmation in Step 2. The script creates the next patch label when needed, removes the released-version label, and adds the next-version label to every open straggler. Do not run it before Step 4 verifies both the semver tag and workflow-managed `latest`.

Then verify the released version has no open stragglers:

```bash
Expand All @@ -125,7 +127,6 @@ gh pr list --repo NVIDIA/NemoClaw --state open --label <released-version> --limi

Summarize:

- shipped/closed items that remain associated with `<released-version>`;
- open issues/PRs bumped to `<next-version>`;
- any items that need manual maintainer attention.

Expand Down Expand Up @@ -174,4 +175,4 @@ Return:
- `latest` workflow fails or times out: report the workflow/status; do not move `latest` manually.
- `latest` workflow rejects a rollback: keep `latest` unchanged, inspect the plan target commit, and regenerate the plan for the current `origin/main` tip if appropriate.
- `lkg` changed: stop and escalate to a release admin.
- Housekeeping finds open items that should still ship in the released version: stop and ask the maintainer whether to leave the label or bump them.
- Post-tag housekeeping fails: report the error and list items still carrying the released label. After the failure is fixed, rerun the same bump command; already-moved items no longer match the source label.
15 changes: 10 additions & 5 deletions .agents/skills/nemoclaw-maintainer-day/MERGE-GATE.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
<!-- SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -->
<!-- SPDX-License-Identifier: Apache-2.0 -->

# Merge Gate Workflow

Run the last maintainer check before approval. Never merge automatically.
Expand All @@ -6,11 +9,12 @@ Run the last maintainer check before approval. Never merge automatically.

For the full priority list see [PR-REVIEW-PRIORITIES.md](PR-REVIEW-PRIORITIES.md). A PR is approval-ready only when **all** hard gates pass:

1. **CI green** — all required checks in `statusCheckRollup`.
2. **No conflicts** — `mergeStateStatus` clean.
3. **No major CodeRabbit** — ignore style nits; block on correctness/security bugs.
4. **PR Review Advisor: merge_as_is** — `check-gates.ts` checks this automatically. The gate passes only when the latest advisor comment has `recommendation: merge_as_is`. All other recommendation values — including `blocked`, `needs_rework`, `merge_after_fixes`, `superseded`, `info_only`, and any unknown value — fail the gate. The referenced Actions run is validated (name, event, head SHA, run attempt, timestamp) before the recommendation is trusted. Correctness, security, acceptance, and test-depth findings block until addressed or explicitly judged false-positive by a maintainer.
5. **Risky code tested** — see [RISKY-AREAS.md](RISKY-AREAS.md). Confirm tests exist (added or pre-existing).
1. **Contributor compliance** — the PR body contains the contributor's `Signed-off-by:` declaration and every PR commit appears as `Verified` in GitHub. Reject noncompliant PRs; maintainers do not repair contributor history.
2. **CI green** — all required checks in `statusCheckRollup`.
3. **No conflicts** — `mergeStateStatus` clean.
4. **No major CodeRabbit** — ignore style nits; block on correctness/security bugs.
5. **PR Review Advisor: merge_as_is** — `check-gates.ts` checks this automatically. The gate passes only when the latest advisor comment has `recommendation: merge_as_is`. All other recommendation values — including `blocked`, `needs_rework`, `merge_after_fixes`, `superseded`, `info_only`, and any unknown value — fail the gate. The referenced Actions run is validated (name, event, head SHA, run attempt, timestamp) before the recommendation is trusted. Correctness, security, acceptance, and test-depth findings block until addressed or explicitly judged false-positive by a maintainer.
6. **Risky code tested** — see [RISKY-AREAS.md](RISKY-AREAS.md). Confirm tests exist (added or pre-existing).
Comment thread
cv marked this conversation as resolved.

## Step 1: Run the Gate Checker

Expand All @@ -25,6 +29,7 @@ This checks all gates programmatically and returns structured JSON with `allPass
The script handles the deterministic checks. You handle judgment calls:

- **Missing required checks:** The script verifies that `checks`, `commit-lint`, and `dco-check` are present in the status rollup. If any are missing, **workflows have not been triggered** — this happens on fork PRs from first-time contributors that need "Approve and run" clicked in the Actions tab. Go to the PR's Checks tab, approve the workflows, wait for all checks to complete, then re-run the gate checker. **Never approve a PR with missing checks.**
- **Contributor compliance failed:** Reject the PR and ask the contributor to provide the PR-body DCO declaration or replace unverified commits with a clean verified history. Do not approve, merge, amend, sign, or force-push on the contributor's behalf.
- **Conflicts (DIRTY):** Do NOT approve — GitHub invalidates approvals when new commits are pushed. Salvage first (rebase), wait for CI, then re-run the gate checker. Follow [SALVAGE-PR.md](SALVAGE-PR.md).
- **CI failing but narrow:** Follow the salvage workflow in [SALVAGE-PR.md](SALVAGE-PR.md).
- **CI pending:** Wait and re-check. Do not approve while checks are still running.
Expand Down
20 changes: 12 additions & 8 deletions .agents/skills/nemoclaw-maintainer-day/PR-REVIEW-PRIORITIES.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
<!-- SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -->
<!-- SPDX-License-Identifier: Apache-2.0 -->

# PR Review Priorities

Ordered list of what NemoClaw maintainers look for in a pull request. Higher items block approval; lower items inform queue ranking.

## Hard gates (all must pass to approve)

1. **Security correctness** — no sandbox escape, SSRF, credential exposure, policy bypass, or installer trust violation. PRs touching risky areas (see [RISKY-AREAS.md](RISKY-AREAS.md)) get a deep security pass before anything else.
2. **CI green** — all required checks in `statusCheckRollup` must pass.
3. **No merge conflicts** — `mergeStateStatus` must be clean.
4. **No unresolved major/critical CodeRabbit findings** — correctness and safety findings block; style nits do not. Use judgment on borderline cases.
5. **No unresolved actionable PR Review Advisor findings** — correctness, security, acceptance-coverage, and test-depth findings block unless explicitly judged false-positive. Ask the user before acting on ambiguous or design-changing advice.
6. **Tests for touched risky code** — risky areas must have test coverage, either added in the PR or pre-existing. No exceptions.
1. **Contributor compliance** — the PR body has the contributor's DCO declaration and every commit appears as `Verified` in GitHub. Maintainers reject noncompliant PRs and do not repair contributor history.
2. **Security correctness** — no sandbox escape, SSRF, credential exposure, policy bypass, or installer trust violation. PRs touching risky areas (see [RISKY-AREAS.md](RISKY-AREAS.md)) get a deep security pass before anything else.
3. **CI green** — all required checks in `statusCheckRollup` must pass.
4. **No merge conflicts** — `mergeStateStatus` must be clean.
5. **No unresolved major/critical CodeRabbit findings** — correctness and safety findings block; style nits do not. Use judgment on borderline cases.
6. **No unresolved actionable PR Review Advisor findings** — correctness, security, acceptance-coverage, and test-depth findings block unless explicitly judged false-positive. Ask the user before acting on ambiguous or design-changing advice.
7. **Tests for touched risky code** — risky areas must have test coverage, either added in the PR or pre-existing. No exceptions.
Comment thread
cv marked this conversation as resolved.

## Quality expectations (block if violated, but fixable via salvage)

Expand All @@ -30,10 +34,10 @@ The team follows a daily ship cycle. All maintainer skills operate within this r

1. **Morning** (`/nemoclaw-maintainer-morning`) — triage the backlog, pick items for the day, label them with the target version (e.g., `v0.0.8`).
2. **During the day** (`/nemoclaw-maintainer-day`) — land PRs using the maintainer loop. Version labels make progress visible on dashboards.
3. **Evening** (`/nemoclaw-maintainer-evening`) — check what shipped, bump open items to the next version (`v0.0.9`), generate a QA-focused summary, cut the tag, and prepare release notes for posting.
3. **Evening** (`/nemoclaw-maintainer-evening`) — check what shipped, identify open stragglers, generate a QA-focused summary, cut the tag, automatically bump stragglers to the next patch, and prepare release notes for posting.
4. **Overnight** — QA team (different timezone) tests the tag. Any issues they file enter the next morning's triage like any other issue.

Version labels are living markers: they always mean "ship in this version." If an item doesn't make the cut, the label moves to the next patch version.
Version labels activate release work; they are not readiness claims. If an open item misses the tag, its label moves to the next patch during post-tag housekeeping.

## Explicitly not priorities

Expand Down
148 changes: 133 additions & 15 deletions .agents/skills/nemoclaw-maintainer-day/scripts/check-gates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,30 @@
/**
* Deterministic merge-gate checker for a single NemoClaw PR.
*
* Checks all 5 required gates and outputs structured JSON.
* Checks all required gates and outputs structured JSON.
* Claude uses the output to decide: approve, route to salvage, or report blockers.
*
* Usage: node --experimental-strip-types --no-warnings .agents/skills/nemoclaw-maintainer-day/scripts/check-gates.ts <pr-number> [--repo OWNER/REPO]
*/

import {
evalPraComment,
type PrAdvisorGateResult,
type PraRun,
parsePraCommentNdjson,
parsePraMeta,
selectLatestTrustedPraComment,
validateAdvisorRun,
} from "./pra-gate.ts";
import {
ghJson,
isRiskyFile,
isTestFile,
run,
ghJson,
parseStringArg,
REQUIRED_CHECK_NAMES,
run,
type StatusCheck,
} from "./shared.ts";
import {
parsePraCommentNdjson,
parsePraMeta,
selectLatestTrustedPraComment,
evalPraComment,
validateAdvisorRun,
type PraRun,
type PrAdvisorGateResult,
} from "./pra-gate.ts";

// ---------------------------------------------------------------------------
// Types
Expand Down Expand Up @@ -60,6 +60,10 @@ interface GateOutput {
coderabbit: GateResult & { unresolvedThreads?: CodeRabbitThread[] };
riskyCodeTested: GateResult & { riskyFiles?: string[]; hasTests?: boolean };
prAdvisor: PrAdvisorGateResult;
contributorCompliance: GateResult & {
dcoDeclarationPresent?: boolean;
unverifiedCommits?: Array<{ sha: string; reason: string }>;
};
};
}

Expand Down Expand Up @@ -351,6 +355,112 @@ function checkRiskyCodeTested(
};
}

// ---------------------------------------------------------------------------
// Gate 6: Contributor compliance
// ---------------------------------------------------------------------------

const DCO_DECLARATION = /^Signed-off-by:\s+.+\s+<[^<>\s]+@[^<>\s]+>\s*$/mu;

interface CommitVerificationRecord {
sha: string;
verified: boolean;
reason: string;
}

function normalizeCommitVerification(value: unknown): CommitVerificationRecord {
if (typeof value !== "object" || value === null || Array.isArray(value)) {
return { sha: "(unknown)", verified: false, reason: "malformed_commit_verification_data" };
}

const record = value as Record<string, unknown>;
if (
typeof record.sha !== "string" ||
typeof record.verified !== "boolean" ||
typeof record.reason !== "string"
) {
return {
sha: typeof record.sha === "string" ? record.sha : "(unknown)",
verified: false,
reason: "malformed_commit_verification_data",
};
}

return { sha: record.sha, verified: record.verified, reason: record.reason };
}

function checkContributorCompliance(
repo: string,
number: number,
body: string,
): GateResult & {
dcoDeclarationPresent?: boolean;
unverifiedCommits?: Array<{ sha: string; reason: string }>;
} {
const dcoDeclarationPresent = DCO_DECLARATION.test(body ?? "");
const raw = run("gh", [
"api",
`repos/${repo}/pulls/${number}/commits`,
"--paginate",
"--jq",
'.[] | {sha, verified: (.commit.verification.verified // false), reason: (.commit.verification.reason // "unknown")}',
]);

if (!raw) {
return {
pass: false,
details: "Could not verify PR commit signatures (API error — fail-closed)",
dcoDeclarationPresent,
};
}

const commits: CommitVerificationRecord[] = [];
try {
for (const line of raw.split("\n")) {
const trimmed = line.trim();
if (trimmed) commits.push(normalizeCommitVerification(JSON.parse(trimmed) as unknown));
}
} catch {
return {
pass: false,
details: "Could not parse PR commit signature data — fail-closed",
dcoDeclarationPresent,
};
}

if (commits.length === 0) {
return {
pass: false,
details: "No PR commits returned while checking contributor compliance — fail-closed",
dcoDeclarationPresent,
};
}

const unverifiedCommits = commits
.filter((commit) => commit.verified !== true)
.map(({ sha, reason }) => ({ sha, reason }));
if (!dcoDeclarationPresent || unverifiedCommits.length > 0) {
const failures = [
...(dcoDeclarationPresent ? [] : ["PR body lacks a valid Signed-off-by declaration"]),
...(unverifiedCommits.length > 0
? [`${unverifiedCommits.length} commit(s) are not GitHub Verified`]
: []),
];
return {
pass: false,
details: failures.join("; "),
dcoDeclarationPresent,
unverifiedCommits,
};
}

return {
pass: true,
details: `DCO declaration present; all ${commits.length} commit(s) are GitHub Verified`,
dcoDeclarationPresent,
unverifiedCommits: [],
};
}

// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------
Expand All @@ -372,11 +482,12 @@ function main(): void {
"--repo",
repo,
"--json",
"number,title,url,files,statusCheckRollup,mergeStateStatus,headRefOid",
"number,title,url,body,files,statusCheckRollup,mergeStateStatus,headRefOid",
]) as {
number: number;
title: string;
url: string;
body: string;
files: Array<{ path: string; status: string }>;
statusCheckRollup: StatusCheck[];
mergeStateStatus: string;
Expand All @@ -393,13 +504,20 @@ function main(): void {
const coderabbit = checkCodeRabbit(repo, prNumber);
const riskyCodeTested = checkRiskyCodeTested(prData.files ?? []);
const prAdvisor = checkPrAdvisor(repo, prNumber, prData.headRefOid ?? "");
const contributorCompliance = checkContributorCompliance(repo, prNumber, prData.body ?? "");

const output: GateOutput = {
pr: prNumber,
url: prData.url,
title: prData.title,
allPass: ci.pass && conflicts.pass && coderabbit.pass && riskyCodeTested.pass && prAdvisor.pass,
gates: { ci, conflicts, coderabbit, riskyCodeTested, prAdvisor },
allPass:
ci.pass &&
conflicts.pass &&
coderabbit.pass &&
riskyCodeTested.pass &&
prAdvisor.pass &&
contributorCompliance.pass,
gates: { ci, conflicts, coderabbit, riskyCodeTested, prAdvisor, contributorCompliance },
};

console.log(JSON.stringify(output, null, 2));
Expand Down
6 changes: 4 additions & 2 deletions .agents/skills/nemoclaw-maintainer-day/scripts/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,10 @@ export const SCORE_NEAR_MISS = 30;
export const SCORE_SECURITY_ACTIONABLE = 20;
/** PR carries the "security" GitHub label */
export const SCORE_LABEL_SECURITY = 15;
/** PR carries a "priority: high" GitHub label */
export const SCORE_LABEL_PRIORITY_HIGH = 10;
/** PR has Urgent Project Priority */
export const SCORE_PROJECT_PRIORITY_URGENT = 15;
/** PR has High Project Priority */
export const SCORE_PROJECT_PRIORITY_HIGH = 10;
/** PR has been stale > 7 days — mild priority bump to prevent rot */
export const SCORE_STALE_AGE = 5;

Expand Down
Loading
Loading