Files
2026-07-13 12:55:23 +08:00

179 lines
4.7 KiB
JavaScript

const LABELS = {
"new-cli": {
color: "0E8A16",
description: "Adds a new CLI or generated harness",
},
"existing-cli-fix": {
color: "1D76DB",
description: "Fixes or improves an existing CLI harness",
},
"cli-anything-skill": {
color: "5319E7",
description: "Changes CLI-Anything plugin or skill files",
},
"cli-anything-hub": {
color: "FBCA04",
description: "Changes CLI-Hub, registries, or hub docs",
},
"documentation": {
color: "0075CA",
description: "Documentation issue or improvement",
},
"github-actions": {
color: "6F42C1",
description: "Changes GitHub Actions or automation",
},
};
const SCRIPT_MANAGED_LABELS = ["new-cli", "existing-cli-fix", "documentation"];
const REGISTRY_FILES = new Set([
"registry.json",
"public_registry.json",
"matrix_registry.json",
]);
function isHarnessFile(path) {
return /^[^/]+\/agent-harness\//.test(path);
}
function isNewHarnessManifest(file) {
return (
file.status === "added" &&
/^[^/]+\/agent-harness\/(setup\.py|pyproject\.toml)$/.test(file.filename)
);
}
function isDocumentationFile(path) {
if (/(^|\/)SKILL\.md$/i.test(path)) {
return false;
}
return (
/^README(?:_[A-Z]+)?\.md$/.test(path) ||
/^(CONTRIBUTING|SECURITY)\.md$/.test(path) ||
/^docs\//.test(path) ||
/^[^/]+\/agent-harness\/.*\.md$/i.test(path) ||
/^[^/]+\.md$/i.test(path)
);
}
function isHarnessImplementationFile(path) {
return isHarnessFile(path) && !isDocumentationFile(path);
}
function titleLooksLikeRegistryCli(title) {
return /\b(add|introduce|new)\b/i.test(title) && /\b(cli|harness|registry)\b/i.test(title);
}
function computeScriptLabels(files, title) {
const paths = files.map((file) => file.filename);
const labelsToApply = new Set();
const hasHarnessImplementationChange = paths.some(isHarnessImplementationFile);
const hasNewHarness = files.some(isNewHarnessManifest);
const registryOnly = paths.length > 0 && paths.every((path) => REGISTRY_FILES.has(path));
const registryNewCli = registryOnly && titleLooksLikeRegistryCli(title || "");
const documentationOnly = paths.length > 0 && paths.every(isDocumentationFile);
if (hasNewHarness || registryNewCli) {
labelsToApply.add("new-cli");
} else if (hasHarnessImplementationChange) {
labelsToApply.add("existing-cli-fix");
}
if (documentationOnly) {
labelsToApply.add("documentation");
}
return labelsToApply;
}
async function ensureLabels(github, owner, repo, core) {
const existing = await github.paginate(github.rest.issues.listLabelsForRepo, {
owner,
repo,
per_page: 100,
});
const existingNames = new Set(existing.map((label) => label.name));
for (const [name, definition] of Object.entries(LABELS)) {
if (existingNames.has(name)) {
continue;
}
core.info(`Creating missing label: ${name}`);
await github.rest.issues.createLabel({
owner,
repo,
name,
color: definition.color,
description: definition.description,
});
}
}
async function syncScriptLabels(github, owner, repo, pullNumber, currentLabels, labelsToApply, core) {
for (const label of labelsToApply) {
if (currentLabels.has(label)) {
continue;
}
core.info(`Adding label: ${label}`);
await github.rest.issues.addLabels({
owner,
repo,
issue_number: pullNumber,
labels: [label],
});
}
for (const label of SCRIPT_MANAGED_LABELS) {
if (!currentLabels.has(label) || labelsToApply.has(label)) {
continue;
}
core.info(`Removing label: ${label}`);
try {
await github.rest.issues.removeLabel({
owner,
repo,
issue_number: pullNumber,
name: label,
});
} catch (error) {
if (error.status !== 404) {
throw error;
}
}
}
}
module.exports = async ({ github, context, core }) => {
const pullRequest = context.payload.pull_request;
if (!pullRequest) {
core.info("No pull_request payload found; skipping PR labeling.");
return;
}
const { owner, repo } = context.repo;
const pullNumber = pullRequest.number;
await ensureLabels(github, owner, repo, core);
const files = await github.paginate(github.rest.pulls.listFiles, {
owner,
repo,
pull_number: pullNumber,
per_page: 100,
});
const labelsToApply = computeScriptLabels(files, pullRequest.title);
const currentLabels = new Set((pullRequest.labels || []).map((label) => label.name));
await syncScriptLabels(github, owner, repo, pullNumber, currentLabels, labelsToApply, core);
};
module.exports.computeScriptLabels = computeScriptLabels;
module.exports.LABELS = LABELS;
module.exports.SCRIPT_MANAGED_LABELS = SCRIPT_MANAGED_LABELS;