alishahryar1--free-claude-code
d37b34b02f
## Problem Bug-report validation rejected useful prose even when the FCC version was unambiguous, and older installations received no update guidance. Issue forms also duplicated their existing labels with forced title prefixes. ## Changes | Before | After | | --- | --- | | The FCC version field accepted only a bare version, copied command output, or `None`. | The field accepts exactly one standalone `number.number.number` value anywhere in the text, while preserving exact `None` and rejecting ambiguous input. | | Valid versions were not compared with the currently installable code. | The workflow reads the live issue and project version from the default branch, then compares numeric components safely. | | Older reports received no update guidance. | One bot-owned comment asks the reporter to update; edits update or remove that comment without labels or issue closure. | | Bug and feature forms forced `[Bug]` and `[Feature]` title prefixes. | Existing `bug` and `enhancement` labels own classification without changing the reporter's title. | | Contract coverage inspected only workflow source fragments. | Contract coverage executes the JavaScript lifecycle and protects label-only issue classification. | <!-- greptile_comment --> <details open><summary><h3>Greptile Summary</h3></summary> This PR improves issue classification and FCC version triage. The main changes are: - Removes forced title prefixes from bug and feature forms. - Accepts one unambiguous numeric FCC version within descriptive text. - Compares reported versions with the default branch project version. - Reconciles invalid-version and update-guidance comments after edits. - Adds executable workflow lifecycle and issue-form contract tests. </details> <h3>Confidence Score: 5/5</h3> This looks safe to merge. Corrected version fields now remove stale invalid-version comments. Common valid TOML quote and comment formats are handled. No blocking issues remain in the changed code. <details><summary><h3><a href="https://www.greptile.com/trex"><img alt="T-Rex" src="https://greptile-static-assets.s3.amazonaws.com/trex/trex_green.svg" height="20" align="absmiddle"></a> T-Rex Logs</h3></summary> **What T-Rex did** - I reviewed the pre-change contract-validation baseline for forms, which showed 12 failures and 18 passes under origin/main. - I executed the post-change contract-validation test run with the environment set for the project and the pytest suite targeting the contract tests, and it completed with exit code 0 and 30 passes. <a href="https://app.greptile.com/trex/runs/15163869/artifacts"><picture><source media="(prefers-color-scheme: dark)" srcset="https://greptile-static-assets.s3.amazonaws.com/badges/ViewAllArtifactsDark.svg?v=4"><source media="(prefers-color-scheme: light)" srcset="https://greptile-static-assets.s3.amazonaws.com/badges/ViewAllArtifacts.svg?v=4"><img alt="View all artifacts" src="https://greptile-static-assets.s3.amazonaws.com/badges/ViewAllArtifacts.svg?v=4"></picture></a> <sub><a href="https://www.greptile.com/trex"><img alt="T-Rex" src="https://greptile-static-assets.s3.amazonaws.com/trex/trex_green.svg" height="14" align="absmiddle"></a> Ran code and verified through T-Rex</sub> </details> <details open><summary><h3>Important Files Changed</h3></summary> | Filename | Overview | |----------|----------| | .github/workflows/validate-bug-report-version.yml | Adds live issue reconciliation, project-version parsing, numeric comparison, and managed invalid and outdated comments. | | tests/contracts/test_issue_form_version_validation.py | Adds executable coverage for version extraction, TOML parsing, numeric comparison, and comment reconciliation. | | .github/ISSUE_TEMPLATE/bug-report.yml | Removes the title prefix and allows one numeric version within descriptive text. | | .github/ISSUE_TEMPLATE/feature-request.yml | Removes the title prefix while retaining enhancement classification. | | tests/contracts/test_issue_forms.py | Checks that issue forms use labels instead of title prefixes. | </details> <sub>Reviews (3): Last reviewed commit: ["Reconcile bug version triage state"](https://github.com/alishahryar1/free-claude-code/commit/8efe7aaf95bd2a96719b1d0811afb83b06c5d79c) | [Re-trigger Greptile](https://app.greptile.com/api/retrigger?id=45775911)</sub> <!-- /greptile_comment -->
176 行
7.2 KiB
YAML
176 行
7.2 KiB
YAML
name: Validate bug report version
|
|
|
|
on:
|
|
issues:
|
|
types: [opened, edited]
|
|
|
|
concurrency:
|
|
group: bug-report-version-${{ github.event.issue.number }}
|
|
cancel-in-progress: false
|
|
|
|
permissions:
|
|
contents: read
|
|
issues: write
|
|
|
|
jobs:
|
|
validate:
|
|
if: contains(github.event.issue.labels.*.name, 'bug')
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 5
|
|
steps:
|
|
- name: Validate FCC version
|
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
with:
|
|
script: |
|
|
const repo = context.repo;
|
|
const issue_number = context.payload.issue.number;
|
|
const { data: issue } = await github.rest.issues.get({
|
|
...repo,
|
|
issue_number,
|
|
});
|
|
const fieldPattern = "^### FCC version\\r?\\n+\\s*([^\\r\\n]+)";
|
|
const versionPattern = "(?:^|[^0-9A-Za-z_.])v?([0-9]+\\.[0-9]+\\.[0-9]+)(?![0-9A-Za-z_+-]|\\.[0-9A-Za-z_])";
|
|
const projectVersionPattern = "^\\s*version\\s*=\\s*([\"'])([0-9]+\\.[0-9]+\\.[0-9]+)\\1\\s*(?:#.*)?$";
|
|
const match = (issue.body || "").match(new RegExp(fieldPattern, "m"));
|
|
const fieldValue = match?.[1]?.trim() || "";
|
|
const versionMatches = [...fieldValue.matchAll(new RegExp(versionPattern, "g"))];
|
|
const reportedVersion = versionMatches.length === 1 ? versionMatches[0][1] : undefined;
|
|
const valid = reportedVersion !== undefined || fieldValue === "None";
|
|
const labelName = "needs-fcc-version";
|
|
const invalidMarker = "<!-- fcc-version-validator -->";
|
|
const outdatedMarker = "<!-- fcc-version-outdated -->";
|
|
const labels = new Set(issue.labels.map((label) => label.name));
|
|
|
|
const comments = await github.paginate(
|
|
github.rest.issues.listComments,
|
|
{ ...repo, issue_number, per_page: 100 },
|
|
);
|
|
const managedComment = (marker) => comments.find(
|
|
(comment) => comment.user?.login === "github-actions[bot]"
|
|
&& comment.body?.includes(marker),
|
|
);
|
|
const invalidComment = managedComment(invalidMarker);
|
|
const outdatedComment = managedComment(outdatedMarker);
|
|
|
|
const deleteManagedComment = async (comment) => {
|
|
if (comment === undefined) return;
|
|
await github.rest.issues.deleteComment({
|
|
...repo,
|
|
comment_id: comment.id,
|
|
});
|
|
};
|
|
|
|
const isOlderVersion = (reported, latest) => {
|
|
const reportedParts = reported.split(".").map((part) => BigInt(part));
|
|
const latestParts = latest.split(".").map((part) => BigInt(part));
|
|
for (let index = 0; index < reportedParts.length; index += 1) {
|
|
if (reportedParts[index] < latestParts[index]) return true;
|
|
if (reportedParts[index] > latestParts[index]) return false;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
const projectVersionFromToml = (pyproject) => {
|
|
let inProjectSection = false;
|
|
for (const line of pyproject.split(/\r?\n/)) {
|
|
const stripped = line.trim();
|
|
if (stripped === "[project]") {
|
|
inProjectSection = true;
|
|
continue;
|
|
}
|
|
if (inProjectSection && stripped.startsWith("[")) return undefined;
|
|
if (!inProjectSection) continue;
|
|
const projectVersion = line.match(new RegExp(projectVersionPattern));
|
|
if (projectVersion !== null) return projectVersion[2];
|
|
}
|
|
return undefined;
|
|
};
|
|
|
|
if (!valid) {
|
|
await deleteManagedComment(outdatedComment);
|
|
if (!labels.has(labelName)) {
|
|
try {
|
|
await github.rest.issues.getLabel({ ...repo, name: labelName });
|
|
} catch (error) {
|
|
if (error.status !== 404) throw error;
|
|
try {
|
|
await github.rest.issues.createLabel({
|
|
...repo,
|
|
name: labelName,
|
|
color: "d4c5f9",
|
|
description: "FCC version must contain number.number.number or be None",
|
|
});
|
|
} catch (createError) {
|
|
if (createError.status !== 422) throw createError;
|
|
}
|
|
}
|
|
await github.rest.issues.addLabels({
|
|
...repo,
|
|
issue_number,
|
|
labels: [labelName],
|
|
});
|
|
}
|
|
if (invalidComment === undefined) {
|
|
await github.rest.issues.createComment({
|
|
...repo,
|
|
issue_number,
|
|
body: `${invalidMarker}\nPlease edit **FCC version** so it contains one \`number.number.number\` value, such as \`1.22.333\`, or is exactly \`None\`. Run \`fcc-server --version\` to find the installed version.`,
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (labels.has(labelName)) {
|
|
await github.rest.issues.removeLabel({
|
|
...repo,
|
|
issue_number,
|
|
name: labelName,
|
|
});
|
|
}
|
|
await deleteManagedComment(invalidComment);
|
|
|
|
if (reportedVersion === undefined) {
|
|
await deleteManagedComment(outdatedComment);
|
|
return;
|
|
}
|
|
|
|
const defaultBranch = context.payload.repository.default_branch;
|
|
const { data: versionFile } = await github.rest.repos.getContent({
|
|
...repo,
|
|
path: "pyproject.toml",
|
|
ref: defaultBranch,
|
|
});
|
|
if (
|
|
Array.isArray(versionFile)
|
|
|| versionFile.type !== "file"
|
|
|| versionFile.encoding !== "base64"
|
|
|| !versionFile.content
|
|
) {
|
|
throw new Error(`Could not read pyproject.toml from ${defaultBranch}`);
|
|
}
|
|
const pyproject = Buffer.from(versionFile.content, "base64").toString("utf8");
|
|
const latestVersion = projectVersionFromToml(pyproject);
|
|
if (latestVersion === undefined) {
|
|
throw new Error(`Could not read the project version from ${defaultBranch}`);
|
|
}
|
|
|
|
if (!isOlderVersion(reportedVersion, latestVersion)) {
|
|
await deleteManagedComment(outdatedComment);
|
|
return;
|
|
}
|
|
|
|
const body = `${outdatedMarker}\nThis report uses FCC \`${reportedVersion}\`, while the current version on \`${defaultBranch}\` is \`${latestVersion}\`. Please [update FCC](${context.payload.repository.html_url}#install), restart FCC, and check whether the issue still occurs. The update may already contain the fix.`;
|
|
if (outdatedComment === undefined) {
|
|
await github.rest.issues.createComment({
|
|
...repo,
|
|
issue_number,
|
|
body,
|
|
});
|
|
} else if (outdatedComment.body !== body) {
|
|
await github.rest.issues.updateComment({
|
|
...repo,
|
|
comment_id: outdatedComment.id,
|
|
body,
|
|
});
|
|
}
|