mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 10:35:51 +00:00
Clarify continuous maintenance provenance
This commit is contained in:
@@ -9,6 +9,8 @@ const BUG_LABEL = "bug";
|
||||
const DOCS_LABEL = "documentation";
|
||||
const ENHANCEMENT_LABEL = "enhancement";
|
||||
const MAINTAINER_AUTHOR_ASSOCIATIONS = new Set(["OWNER", "MEMBER", "COLLABORATOR"]);
|
||||
const RETEST_COMMENT_GRACE_MS = 5 * 60 * 1000;
|
||||
const RETEST_COMMENT_LOOKBACK_MS = 24 * 60 * 60 * 1000;
|
||||
|
||||
function escapeRegExp(value) {
|
||||
return String(value || "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
@@ -297,6 +299,45 @@ async function syncLabels({ github, context, core }) {
|
||||
});
|
||||
}
|
||||
|
||||
async function postRetestCommentForIssue({
|
||||
github,
|
||||
context,
|
||||
core,
|
||||
issue,
|
||||
latestVersion,
|
||||
}) {
|
||||
const { reportedVersion, isBugLike, comparison } = buildTriageState(
|
||||
issue,
|
||||
core,
|
||||
latestVersion
|
||||
);
|
||||
|
||||
if (!isBugLike) {
|
||||
core.info("Issue is not bug-like after classification. Skipping public retest guidance.");
|
||||
return false;
|
||||
}
|
||||
if (!reportedVersion) {
|
||||
core.info("Issue is missing Pulse version metadata. Skipping public retest guidance.");
|
||||
return false;
|
||||
}
|
||||
if (comparison === null || comparison >= 0) {
|
||||
core.info("Issue is already on the latest stable core or newer. Skipping public retest guidance.");
|
||||
return false;
|
||||
}
|
||||
if (await hasRetestComment(github, context, issue.number)) {
|
||||
core.info("Retest guidance comment already exists.");
|
||||
return false;
|
||||
}
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body: buildRetestCommentBody(reportedVersion, latestVersion),
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
async function postRetestComment({ github, context, core }) {
|
||||
const issue = context.payload.issue;
|
||||
const action = context.payload.action || "";
|
||||
@@ -306,38 +347,67 @@ async function postRetestComment({ github, context, core }) {
|
||||
}
|
||||
|
||||
const latestVersion = await getLatestStableVersion(github, context, core);
|
||||
const { reportedVersion, isBugLike, comparison } = buildTriageState(
|
||||
issue,
|
||||
await postRetestCommentForIssue({
|
||||
github,
|
||||
context,
|
||||
core,
|
||||
latestVersion
|
||||
);
|
||||
|
||||
if (!isBugLike) {
|
||||
core.info("Issue is not bug-like after classification. Skipping public retest guidance.");
|
||||
return;
|
||||
}
|
||||
if (!reportedVersion) {
|
||||
core.info("Issue is missing Pulse version metadata. Skipping public retest guidance.");
|
||||
return;
|
||||
}
|
||||
if (comparison === null || comparison >= 0) {
|
||||
core.info("Issue is already on the latest stable core or newer. Skipping public retest guidance.");
|
||||
return;
|
||||
}
|
||||
if (await hasRetestComment(github, context, issue.number)) {
|
||||
core.info("Retest guidance comment already exists.");
|
||||
return;
|
||||
}
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body: buildRetestCommentBody(reportedVersion, latestVersion),
|
||||
issue,
|
||||
latestVersion,
|
||||
});
|
||||
}
|
||||
|
||||
async function postEligibleRetestComments({
|
||||
github,
|
||||
context,
|
||||
core,
|
||||
nowMs = Date.now(),
|
||||
graceMs = RETEST_COMMENT_GRACE_MS,
|
||||
lookbackMs = RETEST_COMMENT_LOOKBACK_MS,
|
||||
}) {
|
||||
const issues = await github.paginate(github.rest.issues.listForRepo, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: "open",
|
||||
sort: "created",
|
||||
direction: "desc",
|
||||
since: new Date(nowMs - lookbackMs).toISOString(),
|
||||
per_page: 100,
|
||||
});
|
||||
const latestVersion = await getLatestStableVersion(github, context, core);
|
||||
let eligibleCount = 0;
|
||||
let postedCount = 0;
|
||||
|
||||
for (const issue of issues) {
|
||||
if (issue.pull_request || !canPostRetestComment(issue, "opened")) continue;
|
||||
|
||||
const createdMs = new Date(issue.created_at || "").getTime();
|
||||
if (!Number.isFinite(createdMs)) {
|
||||
core.warning(`Issue #${issue.number} has an invalid created_at value.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const ageMs = nowMs - createdMs;
|
||||
if (ageMs < graceMs || ageMs > lookbackMs) continue;
|
||||
eligibleCount += 1;
|
||||
|
||||
const posted = await postRetestCommentForIssue({
|
||||
github,
|
||||
context,
|
||||
core,
|
||||
issue,
|
||||
latestVersion,
|
||||
});
|
||||
if (posted) postedCount += 1;
|
||||
}
|
||||
|
||||
core.info(
|
||||
`Retest guidance sweep complete: eligible=${eligibleCount}, posted=${postedCount}.`
|
||||
);
|
||||
return { eligibleCount, postedCount };
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
postEligibleRetestComments,
|
||||
syncLabels,
|
||||
postRetestComment,
|
||||
internals: {
|
||||
@@ -347,6 +417,8 @@ module.exports = {
|
||||
ENHANCEMENT_LABEL,
|
||||
NEEDS_VERSION_LABEL,
|
||||
RETEST_COMMENT_MARKER,
|
||||
RETEST_COMMENT_GRACE_MS,
|
||||
RETEST_COMMENT_LOOKBACK_MS,
|
||||
RETEST_LABEL,
|
||||
TRIAGE_FOOTER,
|
||||
VERSION_LABEL_PREFIX,
|
||||
|
||||
@@ -7,6 +7,7 @@ function createGithub({
|
||||
latestVersion = "6.0.1",
|
||||
existingLabels = new Set(),
|
||||
existingComments = [],
|
||||
issues = [],
|
||||
} = {}) {
|
||||
const calls = {
|
||||
createComment: [],
|
||||
@@ -42,6 +43,7 @@ function createGithub({
|
||||
calls.createComment.push(payload);
|
||||
return { data: payload };
|
||||
},
|
||||
listForRepo: Symbol("listForRepo"),
|
||||
listComments: Symbol("listComments"),
|
||||
},
|
||||
repos: {
|
||||
@@ -51,9 +53,9 @@ function createGithub({
|
||||
},
|
||||
},
|
||||
},
|
||||
async paginate() {
|
||||
calls.paginate.push(true);
|
||||
return existingComments;
|
||||
async paginate(endpoint) {
|
||||
calls.paginate.push(endpoint);
|
||||
return endpoint === github.rest.issues.listForRepo ? issues : existingComments;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -152,6 +154,69 @@ test("postRetestComment comments once for older non-maintainer bug reports", asy
|
||||
);
|
||||
});
|
||||
|
||||
test("scheduled retest guidance waits five minutes and reads the current issue", async () => {
|
||||
const nowMs = Date.parse("2026-08-26T09:06:00Z");
|
||||
const issues = [
|
||||
{
|
||||
number: 1780,
|
||||
title: "Agent token scope",
|
||||
body: "## Feedback type\nBug / regression\n\n## Pulse version\n6.3.2\n",
|
||||
labels: [{ name: "bug" }],
|
||||
author_association: "NONE",
|
||||
created_at: "2026-08-26T09:01:51Z",
|
||||
},
|
||||
];
|
||||
const { github, calls } = createGithub({ latestVersion: "6.3.2", issues });
|
||||
|
||||
const result = await triage.postEligibleRetestComments({
|
||||
github,
|
||||
context: createContext({ issue: null }),
|
||||
core: createCore(),
|
||||
nowMs,
|
||||
});
|
||||
|
||||
assert.deepEqual(result, { eligibleCount: 0, postedCount: 0 });
|
||||
assert.equal(calls.createComment.length, 0);
|
||||
|
||||
const laterResult = await triage.postEligibleRetestComments({
|
||||
github,
|
||||
context: createContext({ issue: null }),
|
||||
core: createCore(),
|
||||
nowMs: Date.parse("2026-08-26T09:07:00Z"),
|
||||
});
|
||||
|
||||
assert.deepEqual(laterResult, { eligibleCount: 1, postedCount: 0 });
|
||||
assert.equal(calls.createComment.length, 0);
|
||||
});
|
||||
|
||||
test("scheduled retest guidance posts once after the grace window", async () => {
|
||||
const issues = [
|
||||
{
|
||||
number: 1200,
|
||||
title: "Upgrade regression",
|
||||
body: "## Feedback type\nRegression\n\n## Pulse version\n5.1.9\n",
|
||||
labels: [{ name: "bug" }],
|
||||
author_association: "NONE",
|
||||
created_at: "2026-08-26T08:55:00Z",
|
||||
},
|
||||
];
|
||||
const { github, calls } = createGithub({ latestVersion: "6.3.2", issues });
|
||||
|
||||
const result = await triage.postEligibleRetestComments({
|
||||
github,
|
||||
context: createContext({ issue: null }),
|
||||
core: createCore(),
|
||||
nowMs: Date.parse("2026-08-26T09:01:00Z"),
|
||||
});
|
||||
|
||||
assert.deepEqual(result, { eligibleCount: 1, postedCount: 1 });
|
||||
assert.equal(calls.createComment.length, 1);
|
||||
assert.equal(calls.createComment[0].issue_number, 1200);
|
||||
assert.ok(
|
||||
calls.createComment[0].body.endsWith(`\n\n${triage.internals.TRIAGE_FOOTER}`)
|
||||
);
|
||||
});
|
||||
|
||||
test("timeout close comments use the canonical triage footer", () => {
|
||||
const { buildTimeoutCloseCommentBody, CLOSE_COMMENT_MARKER, TRIAGE_FOOTER } =
|
||||
triage.internals;
|
||||
|
||||
@@ -4,20 +4,21 @@ const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
const workflowDir = path.resolve(__dirname, "../workflows");
|
||||
const commentPublisherPatterns = [
|
||||
const issueMutationPatterns = [
|
||||
/github\.rest\.issues\.createComment/,
|
||||
/github\.rest\.pulls\.createReviewComment/,
|
||||
/addDiscussionComment/,
|
||||
/postRetestComment/,
|
||||
/post(?:Eligible)?RetestComments?/,
|
||||
/syncLabels/,
|
||||
];
|
||||
|
||||
test("automated workflow comments use the dedicated triage identity", () => {
|
||||
test("automated workflow issue mutations use the dedicated triage identity", () => {
|
||||
const publishers = [];
|
||||
|
||||
for (const name of fs.readdirSync(workflowDir)) {
|
||||
if (!name.endsWith(".yml") && !name.endsWith(".yaml")) continue;
|
||||
const workflow = fs.readFileSync(path.join(workflowDir, name), "utf8");
|
||||
if (!commentPublisherPatterns.some((pattern) => pattern.test(workflow))) continue;
|
||||
if (!issueMutationPatterns.some((pattern) => pattern.test(workflow))) continue;
|
||||
|
||||
publishers.push(name);
|
||||
assert.match(
|
||||
@@ -39,6 +40,7 @@ test("automated workflow comments use the dedicated triage identity", () => {
|
||||
|
||||
assert.deepEqual(publishers.sort(), [
|
||||
"close-needs-retest-timeout.yml",
|
||||
"issue-version-label-sync.yml",
|
||||
"issue-version-retest-comment.yml",
|
||||
]);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user