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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
69 changes: 57 additions & 12 deletions .agents/skills/nemoclaw-maintainer-day/scripts/bump-stragglers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,46 +37,91 @@ function main(): void {

// Create target label if needed
run("gh", [
"label", "create", to, "--repo", repo,
"--description", "Release target", "--color", "1d76db",
"label",
"create",
to,
"--repo",
repo,
"--description",
"Release target",
"--color",
"1d76db",
]);

const bumped: BumpedItem[] = [];

// Bump open PRs
const prOut = run("gh", [
"pr", "list", "--repo", repo, "--label", from,
"--state", "open", "--json", "number,title", "--limit", "100",
"pr",
"list",
"--repo",
repo,
"--label",
from,
"--state",
"open",
"--json",
"number,title",
"--limit",
"100",
]);
if (prOut) {
try {
const prs = JSON.parse(prOut) as Array<{ number: number; title: string }>;
for (const pr of prs) {
run("gh", [
"pr", "edit", String(pr.number), "--repo", repo,
"--remove-label", from, "--add-label", to,
"pr",
"edit",
String(pr.number),
"--repo",
repo,
"--remove-label",
from,
"--add-label",
to,
]);
bumped.push({ number: pr.number, title: pr.title, type: "pr" });
}
} catch { /* ignore */ }
} catch {
/* ignore */
}
}

// Bump open issues
const issueOut = run("gh", [
"issue", "list", "--repo", repo, "--label", from,
"--state", "open", "--json", "number,title", "--limit", "100",
"issue",
"list",
"--repo",
repo,
"--label",
from,
"--state",
"open",
"--json",
"number,title",
"--limit",
"100",
]);
if (issueOut) {
try {
const issues = JSON.parse(issueOut) as Array<{ number: number; title: string }>;
for (const issue of issues) {
run("gh", [
"issue", "edit", String(issue.number), "--repo", repo,
"--remove-label", from, "--add-label", to,
"issue",
"edit",
String(issue.number),
"--repo",
repo,
"--remove-label",
from,
"--add-label",
to,
]);
bumped.push({ number: issue.number, title: issue.title, type: "issue" });
}
} catch { /* ignore */ }
} catch {
/* ignore */
}
}

const output: BumpOutput = { from, to, bumped };
Expand Down
52 changes: 39 additions & 13 deletions .agents/skills/nemoclaw-maintainer-day/scripts/check-gates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,15 @@
* Usage: node --experimental-strip-types --no-warnings .agents/skills/nemoclaw-maintainer-day/scripts/check-gates.ts <pr-number> [--repo OWNER/REPO]
*/

import { isRiskyFile, isTestFile, run, ghJson, parseStringArg, REQUIRED_CHECK_NAMES, type StatusCheck } from "./shared.ts";
import {
isRiskyFile,
isTestFile,
run,
ghJson,
parseStringArg,
REQUIRED_CHECK_NAMES,
type StatusCheck,
} from "./shared.ts";

// ---------------------------------------------------------------------------
// Types
Expand All @@ -34,7 +42,11 @@ interface GateOutput {
title: string;
allPass: boolean;
gates: {
ci: GateResult & { failingChecks?: string[]; pendingChecks?: string[]; missingChecks?: string[] };
ci: GateResult & {
failingChecks?: string[];
pendingChecks?: string[];
missingChecks?: string[];
};
conflicts: GateResult & { mergeStateStatus?: string };
coderabbit: GateResult & { unresolvedThreads?: CodeRabbitThread[] };
riskyCodeTested: GateResult & { riskyFiles?: string[]; hasTests?: boolean };
Expand Down Expand Up @@ -97,7 +109,12 @@ function checkCi(
}

if (failing.length > 0) {
return { pass: false, details: `${failing.length} failing check(s)`, failingChecks: failing, pendingChecks: pending };
return {
pass: false,
details: `${failing.length} failing check(s)`,
failingChecks: failing,
pendingChecks: pending,
};
}
if (pending.length > 0) {
return { pass: false, details: `${pending.length} pending check(s)`, pendingChecks: pending };
Expand Down Expand Up @@ -170,11 +187,16 @@ function checkCodeRabbit(

const [owner, repoName] = repo.split("/");
const out = run("gh", [
"api", "graphql",
"-F", `owner=${owner}`,
"-F", `repo=${repoName}`,
"-F", `number=${number}`,
"-f", `query=${query}`,
"api",
"graphql",
"-F",
`owner=${owner}`,
"-F",
`repo=${repoName}`,
"-F",
`number=${number}`,
"-f",
`query=${query}`,
]);

// Fail-closed: if we cannot reach the API, do not assume clean
Expand Down Expand Up @@ -209,8 +231,8 @@ function checkCodeRabbit(
if (thread.isResolved) continue;

const comments = thread.comments.nodes;
const coderabbitComments = comments.filter(
(c) => CODERABBIT_LOGINS.has(c.author?.login?.toLowerCase()),
const coderabbitComments = comments.filter((c) =>
CODERABBIT_LOGINS.has(c.author?.login?.toLowerCase()),
);

for (const comment of coderabbitComments) {
Expand Down Expand Up @@ -282,9 +304,13 @@ function main(): void {
const repo = parseStringArg(args, "--repo", "NVIDIA/NemoClaw");

const prData = ghJson([
"pr", "view", String(prNumber),
"--repo", repo,
"--json", "number,title,url,files,statusCheckRollup,mergeStateStatus",
"pr",
"view",
String(prNumber),
"--repo",
repo,
"--json",
"number,title,url,files,statusCheckRollup,mergeStateStatus",
]) as {
number: number;
title: string;
Expand Down
47 changes: 32 additions & 15 deletions .agents/skills/nemoclaw-maintainer-day/scripts/handoff-summary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,19 @@ interface HandoffOutput {
}

const AREA_LABELS: Record<string, RegExp[]> = {
"Installer / bootstrap": [/^install\.sh$/, /^setup\.sh$/, /^brev-setup\.sh$/, /^scripts\/.*\.sh$/],
"Installer / bootstrap": [
/^install\.sh$/,
/^setup\.sh$/,
/^brev-setup\.sh$/,
/^scripts\/.*\.sh$/,
],
"Onboarding / host glue": [/^bin\/lib\/onboard\.js$/, /^bin\/.*\.js$/],
"Sandbox / policy / SSRF": [/^nemoclaw\/src\/blueprint\//, /^nemoclaw-blueprint\//, /policy/i, /ssrf/i],
"Sandbox / policy / SSRF": [
/^nemoclaw\/src\/blueprint\//,
/^nemoclaw-blueprint\//,
/policy/i,
/ssrf/i,
],
"Workflow / enforcement": [/^\.github\/workflows\//, /\.prek\./],
"Credentials / inference": [/credential/i, /inference/i],
};
Expand All @@ -57,9 +67,7 @@ function main(): void {
const targetVersion = bumpPatch(previousTag);

// Commits since last tag
const logOut = run("git", [
"log", "--oneline", "--format=%h %s", `${previousTag}..origin/main`,
]);
const logOut = run("git", ["log", "--oneline", "--format=%h %s", `${previousTag}..origin/main`]);
const commits: CommitInfo[] = [];
if (logOut) {
for (const line of logOut.split("\n")) {
Expand All @@ -74,10 +82,13 @@ function main(): void {
}

// Files changed since last tag
const diffOut = run("git", [
"diff", "--name-only", `${previousTag}..origin/main`,
]);
const changedFiles = diffOut ? diffOut.split("\n").map((f) => f.trim()).filter(Boolean) : [];
const diffOut = run("git", ["diff", "--name-only", `${previousTag}..origin/main`]);
const changedFiles = diffOut
? diffOut
.split("\n")
.map((f) => f.trim())
.filter(Boolean)
: [];
const riskyFilesTouched = changedFiles.filter(isRiskyFile);

// Map risky files to area labels
Expand All @@ -93,12 +104,18 @@ function main(): void {

// Suggest test focus based on areas
const suggestedTestFocus: string[] = [];
if (areasHit.has("Installer / bootstrap")) suggestedTestFocus.push("Fresh install and upgrade paths");
if (areasHit.has("Onboarding / host glue")) suggestedTestFocus.push("Onboarding wizard, sandbox creation");
if (areasHit.has("Sandbox / policy / SSRF")) suggestedTestFocus.push("Policy enforcement, network egress, SSRF protections");
if (areasHit.has("Workflow / enforcement")) suggestedTestFocus.push("CI checks, pre-commit hooks, DCO signing");
if (areasHit.has("Credentials / inference")) suggestedTestFocus.push("Credential storage, inference provider routing");
if (suggestedTestFocus.length === 0 && commits.length > 0) suggestedTestFocus.push("General smoke test — no risky areas touched");
if (areasHit.has("Installer / bootstrap"))
suggestedTestFocus.push("Fresh install and upgrade paths");
if (areasHit.has("Onboarding / host glue"))
suggestedTestFocus.push("Onboarding wizard, sandbox creation");
if (areasHit.has("Sandbox / policy / SSRF"))
suggestedTestFocus.push("Policy enforcement, network egress, SSRF protections");
if (areasHit.has("Workflow / enforcement"))
suggestedTestFocus.push("CI checks, pre-commit hooks, DCO signing");
if (areasHit.has("Credentials / inference"))
suggestedTestFocus.push("Credential storage, inference provider routing");
if (suggestedTestFocus.length === 0 && commits.length > 0)
suggestedTestFocus.push("General smoke test — no risky areas touched");

const output: HandoffOutput = {
previousTag,
Expand Down
25 changes: 17 additions & 8 deletions .agents/skills/nemoclaw-maintainer-day/scripts/hotspots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,16 @@ function gitChurn(days: number): Map<string, number> {

function openPrFileOverlap(repo: string): Map<string, number> {
const prListOut = run("gh", [
"pr", "list",
"--repo", repo,
"--state", "open",
"--limit", "200",
"--json", "number",
"pr",
"list",
"--repo",
repo,
"--state",
"open",
"--limit",
"200",
"--json",
"number",
]);

const counts = new Map<string, number>();
Expand All @@ -78,9 +83,13 @@ function openPrFileOverlap(repo: string): Map<string, number> {
const sample = prs.slice(0, 50);
for (const pr of sample) {
const filesOut = run("gh", [
"pr", "view", String(pr.number),
"--repo", repo,
"--json", "files",
"pr",
"view",
String(pr.number),
"--repo",
repo,
"--json",
"files",
]);
if (!filesOut) continue;

Expand Down
26 changes: 9 additions & 17 deletions .agents/skills/nemoclaw-maintainer-day/scripts/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,7 @@ export const RISKY_PATTERNS: RegExp[] = [
/inference/i,
];

export const TEST_PATTERNS: RegExp[] = [
/\.test\.[jt]sx?$/,
/\.spec\.[jt]sx?$/,
/^test\//,
];
export const TEST_PATTERNS: RegExp[] = [/\.test\.[jt]sx?$/, /\.spec\.[jt]sx?$/, /^test\//];

export function isRiskyFile(path: string): boolean {
return RISKY_PATTERNS.some((re) => re.test(path));
Expand All @@ -53,11 +49,7 @@ export function isTestFile(path: string): boolean {
* Run a command and return its stdout. On failure, logs the error to stderr
* and returns an empty string so callers can handle the absence of data.
*/
export function run(
cmd: string,
args: string[],
timeoutMs = 120_000,
): string {
export function run(cmd: string, args: string[], timeoutMs = 120_000): string {
try {
return execFileSync(cmd, args, {
encoding: "utf-8",
Expand Down Expand Up @@ -94,9 +86,9 @@ export function ghJson(args: string[]): unknown {
// need a maintainer to click "Approve and run" before these execute.
// If any are missing from statusCheckRollup, CI cannot be considered green.
export const REQUIRED_CHECK_NAMES: string[] = [
"checks", // pr.yaml — lint, typecheck, test
"commit-lint", // commit-lint.yaml
"dco-check", // dco-check.yaml
"checks", // pr.yaml — lint, typecheck, test
"commit-lint", // commit-lint.yaml
"dco-check", // dco-check.yaml
];

// ---------------------------------------------------------------------------
Expand All @@ -106,11 +98,11 @@ export const REQUIRED_CHECK_NAMES: string[] = [
/** Union of CheckRun and StatusContext fields from GitHub's statusCheckRollup. */
export interface StatusCheck {
__typename?: string;
name?: string; // CheckRun field
context?: string; // StatusContext field
status?: string; // CheckRun: COMPLETED, IN_PROGRESS, QUEUED, etc.
name?: string; // CheckRun field
context?: string; // StatusContext field
status?: string; // CheckRun: COMPLETED, IN_PROGRESS, QUEUED, etc.
conclusion?: string; // CheckRun: SUCCESS, FAILURE, NEUTRAL, SKIPPED, etc.
state?: string; // StatusContext: SUCCESS, FAILURE, PENDING, ERROR
state?: string; // StatusContext: SUCCESS, FAILURE, PENDING, ERROR
}

// ---------------------------------------------------------------------------
Expand Down
Loading
Loading