diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 9681ee11..f328a74c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -27,7 +27,7 @@ jobs:
# Python Backend Tests - All Platforms
# --------------------------------------------------------------------------
test-python:
- name: Python ${{ matrix.python-version }} on ${{ matrix.os }}
+ name: test-python (${{ matrix.python-version }}, ${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
@@ -98,7 +98,7 @@ jobs:
# Frontend Tests - All Platforms
# --------------------------------------------------------------------------
test-frontend:
- name: Frontend on ${{ matrix.os }}
+ name: test-frontend (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
@@ -151,7 +151,7 @@ jobs:
# Platform-Specific Integration Tests
# --------------------------------------------------------------------------
test-platform-integration:
- name: Platform Integration Tests on ${{ matrix.os }}
+ name: test-integration (${{ matrix.os }})
runs-on: ${{ matrix.os }}
# Only run integration tests after basic tests pass
diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml
index 989eaec5..1b09de7b 100644
--- a/.github/workflows/pr-labeler.yml
+++ b/.github/workflows/pr-labeler.yml
@@ -64,9 +64,7 @@ jobs:
// Label definitions
LABELS: Object.freeze({
SIZE: ['size/XS', 'size/S', 'size/M', 'size/L', 'size/XL'],
- AREA: ['area/frontend', 'area/backend', 'area/fullstack', 'area/ci'],
- STATUS: ['đ Checking', 'â
Ready for Review', 'â Checks Failed'],
- REVIEW: ['Missing AC Approval', 'AC: Approved', 'AC: Changes Requested', 'AC: Needs Re-review']
+ AREA: ['area/frontend', 'area/backend', 'area/fullstack', 'area/ci']
}),
// Pagination
@@ -230,7 +228,6 @@ jobs:
const pr = context.payload.pull_request;
const prNumber = pr.number;
const title = pr.title || '';
- const isNewPR = context.payload.action === 'opened' || context.payload.action === 'reopened';
console.log(`::group::PR #${prNumber} - Auto-labeling`);
console.log(`Title: ${title.slice(0, 100)}${title.length > 100 ? '...' : ''}`);
@@ -271,25 +268,9 @@ jobs:
CONFIG.LABELS.SIZE.filter(l => l !== sizeLabel).forEach(l => labelsToRemove.add(l));
console.log(` đ Size: ${sizeLabel} (${totalLines} lines)`);
- // 4. Set status label (only on new PRs - let pr-status-gate handle updates on pushes)
- // Note: On synchronize events, CI workflows will trigger pr-status-gate when they complete
- if (isNewPR) {
- labelsToAdd.add('đ Checking');
- CONFIG.LABELS.STATUS.filter(l => l !== 'đ Checking').forEach(l => labelsToRemove.add(l));
- console.log(` đ Status: Checking`);
- } else {
- console.log(` âšī¸ Status: Unchanged (will be updated by pr-status-gate)`);
- }
-
- // 5. Add review label for new PRs only
- if (isNewPR) {
- labelsToAdd.add('Missing AC Approval');
- console.log(` âŗ Review: Missing AC Approval`);
- }
-
console.log('::endgroup::');
- // 6. Apply label changes
+ // 4. Apply label changes
console.log(`::group::Applying labels`);
// Remove labels that should be replaced (exclude ones we're adding)
@@ -302,7 +283,7 @@ jobs:
console.log('::endgroup::');
console.log(`â
PR #${prNumber} labeled successfully`);
- // 7. Write job summary
+ // 5. Write job summary
const summaryType = type ? CONFIG.TYPE_MAP[type] || 'unknown' : 'none';
const summaryArea = areaLabel ? areaLabel.replace('area/', '') : 'other';
@@ -312,9 +293,7 @@ jobs:
[{ data: 'Category', header: true }, { data: 'Label', header: true }],
['Type', summaryType],
['Area', summaryArea],
- ['Size', sizeLabel],
- ['Status', isNewPR ? 'đ Checking' : '(unchanged)'],
- ['Review', isNewPR ? 'Missing AC Approval' : '(unchanged)']
+ ['Size', sizeLabel]
])
.addRaw(`\n**Files:** ${files.length} | **Lines:** +${pr.additions || 0} / -${pr.deletions || 0}\n`)
.write();
diff --git a/.github/workflows/pr-status-gate.yml b/.github/workflows/pr-status-gate.yml
deleted file mode 100644
index 69cb9bd5..00000000
--- a/.github/workflows/pr-status-gate.yml
+++ /dev/null
@@ -1,585 +0,0 @@
-name: PR Status Gate
-
-on:
- workflow_run:
- workflows: [CI, Lint, Quality Security]
- types: [completed]
-
- issue_comment:
- types: [created, edited]
-
- pull_request:
- types: [synchronize]
-
-concurrency:
- group: pr-status-gate-${{ github.event.workflow_run.pull_requests[0].number || github.event.issue.number || github.event.pull_request.number || github.run_id }}
- cancel-in-progress: true
-
-permissions:
- pull-requests: write
- checks: read
-
-env:
- # Shared configuration - single source of truth
- REQUIRED_CHECKS: |
- CI / test-frontend
- CI / test-python (3.12)
- CI / test-python (3.13)
- Lint / python
- Quality Security / CodeQL (javascript-typescript)
- Quality Security / CodeQL (python)
- Quality Security / Python Security (Bandit)
- Quality Security / Security Summary
-
-jobs:
- # âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
- # JOB 1: CI STATUS (triggered by workflow_run)
- # Updates CI status labels when monitored workflows complete
- # âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
- update-ci-status:
- name: Update CI Status
- runs-on: ubuntu-latest
- if: github.event_name == 'workflow_run' && github.event.workflow_run.pull_requests[0] != null
- timeout-minutes: 5
-
- steps:
- - name: Check all required checks and update label
- uses: actions/github-script@v7
- env:
- REQUIRED_CHECKS: ${{ env.REQUIRED_CHECKS }}
- with:
- retries: 3
- retry-exempt-status-codes: 400,401,403,404,422
- script: |
- // NOTE: STATUS_LABELS is intentionally duplicated across jobs.
- // GitHub Actions jobs run in isolated contexts and cannot share runtime constants.
- // If label values change, update ALL occurrences: update-ci-status, check-status-command
- const STATUS_LABELS = Object.freeze({
- CHECKING: 'đ Checking',
- PASSED: 'â
Ready for Review',
- FAILED: 'â Checks Failed'
- });
-
- const REQUIRED_CHECKS = process.env.REQUIRED_CHECKS
- .split('\n')
- .map(s => s.trim())
- .filter(Boolean);
-
- async function fetchCheckRuns(sha) {
- const { owner, repo } = context.repo;
- // Let the configured retries (retries: 3) handle transient failures
- // Don't catch errors - allow them to propagate for retry logic
- const checkRuns = await github.paginate(
- github.rest.checks.listForRef,
- { owner, repo, ref: sha, per_page: 100 },
- (response) => response.data
- );
- return checkRuns;
- }
-
- function analyzeChecks(checkRuns) {
- const results = [];
- let allComplete = true;
- let anyFailed = false;
-
- for (const checkName of REQUIRED_CHECKS) {
- const check = checkRuns.find(c => c.name === checkName);
-
- if (!check) {
- results.push({ name: checkName, status: 'âŗ Pending', complete: false });
- allComplete = false;
- } else if (check.status !== 'completed') {
- results.push({ name: checkName, status: 'đ Running', complete: false });
- allComplete = false;
- } else if (check.conclusion === 'success') {
- results.push({ name: checkName, status: 'â
Passed', complete: true });
- } else if (check.conclusion === 'skipped') {
- results.push({ name: checkName, status: 'âī¸ Skipped', complete: true, skipped: true });
- } else {
- results.push({ name: checkName, status: 'â Failed', complete: true, failed: true });
- anyFailed = true;
- }
- }
- return { allComplete, anyFailed, results };
- }
-
- async function updateStatusLabels(prNumber, newLabel) {
- const { owner, repo } = context.repo;
- const allLabels = Object.values(STATUS_LABELS);
-
- // Remove all status labels first - throw on non-404 errors to prevent conflicting labels
- for (const label of allLabels) {
- try {
- await github.rest.issues.removeLabel({ owner, repo, issue_number: prNumber, name: label });
- } catch (e) {
- if (e && e.status !== 404) {
- // Throw to prevent adding new label if removal failed (could cause conflicting labels)
- throw new Error(`Failed to remove label '${label}': ${e.message}`);
- }
- }
- }
-
- try {
- await github.rest.issues.addLabels({ owner, repo, issue_number: prNumber, labels: [newLabel] });
- } catch (e) {
- if (e && e.status === 404) {
- core.warning(`Label '${newLabel}' does not exist`);
- } else {
- throw e;
- }
- }
- }
-
- // Main logic
- const prNumber = context.payload.workflow_run.pull_requests[0].number;
- const headSha = context.payload.workflow_run.head_sha;
- const triggerWorkflow = context.payload.workflow_run.name;
-
- console.log(`PR #${prNumber} - Triggered by: ${triggerWorkflow}, SHA: ${headSha.slice(0, 8)}`);
-
- const checkRuns = await fetchCheckRuns(headSha);
- console.log(`Found ${checkRuns.length} check runs`);
- const { allComplete, anyFailed, results } = analyzeChecks(checkRuns);
-
- for (const r of results) {
- console.log(` ${r.status} ${r.name}`);
- }
-
- if (!allComplete) {
- const pending = results.filter(r => !r.complete).length;
- console.log(`âŗ ${pending}/${REQUIRED_CHECKS.length} checks pending`);
- // Update to CHECKING status if checks are still running (prevents stale Ready/Failed status)
- await updateStatusLabels(prNumber, STATUS_LABELS.CHECKING);
- return;
- }
-
- const newLabel = anyFailed ? STATUS_LABELS.FAILED : STATUS_LABELS.PASSED;
- await updateStatusLabels(prNumber, newLabel);
-
- const passedCount = results.filter(r => r.status === 'â
Passed').length;
- const failedCount = results.filter(r => r.failed).length;
-
- if (anyFailed) {
- console.log(`â PR #${prNumber}: ${failedCount} check(s) failed`);
- } else {
- console.log(`â
PR #${prNumber}: Ready for review (${passedCount}/${REQUIRED_CHECKS.length} passed)`);
- }
-
- # âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
- # JOB 2: /check-status COMMAND
- # Manual status check - anyone can trigger by commenting /check-status
- # âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
- check-status-command:
- name: Check Status Command
- runs-on: ubuntu-latest
- if: |
- github.event_name == 'issue_comment' &&
- github.event.issue.pull_request &&
- contains(github.event.comment.body, '/check-status')
- timeout-minutes: 5
-
- steps:
- - name: Run status check and post report
- uses: actions/github-script@v7
- env:
- REQUIRED_CHECKS: ${{ env.REQUIRED_CHECKS }}
- with:
- retries: 3
- retry-exempt-status-codes: 400,401,403,404,422
- script: |
- // NOTE: STATUS_LABELS is intentionally duplicated across jobs.
- // GitHub Actions jobs run in isolated contexts and cannot share runtime constants.
- // If label values change, update ALL occurrences: update-ci-status, check-status-command
- const STATUS_LABELS = Object.freeze({
- CHECKING: 'đ Checking',
- PASSED: 'â
Ready for Review',
- FAILED: 'â Checks Failed'
- });
-
- // NOTE: REVIEW_LABELS is intentionally duplicated across jobs.
- // If label values change, update ALL occurrences: check-status-command, update-review-status
- const REVIEW_LABELS = Object.freeze([
- 'Missing AC Approval',
- 'AC: Approved',
- 'AC: Changes Requested',
- 'AC: Blocked',
- 'AC: Needs Re-review',
- 'AC: Reviewed'
- ]);
-
- const REQUIRED_CHECKS = process.env.REQUIRED_CHECKS
- .split('\n')
- .map(s => s.trim())
- .filter(Boolean);
-
- const { owner, repo } = context.repo;
- const prNumber = context.payload.issue.number;
- const requestedBy = context.payload.comment.user.login;
-
- // Get PR details
- const { data: pr } = await github.rest.pulls.get({
- owner, repo, pull_number: prNumber
- });
- const headSha = pr.head.sha;
-
- console.log(`PR #${prNumber} - /check-status by @${requestedBy}, SHA: ${headSha.slice(0, 8)}`);
-
- // Fetch check runs with pagination to handle >100 checks
- const checkRuns = await github.paginate(
- github.rest.checks.listForRef,
- { owner, repo, ref: headSha, per_page: 100 },
- (response) => response.data
- );
- console.log(`Found ${checkRuns.length} check runs`);
-
- // Analyze results
- const results = [];
- let allComplete = true;
- let anyFailed = false;
-
- for (const checkName of REQUIRED_CHECKS) {
- const check = checkRuns.find(c => c.name === checkName);
-
- if (!check) {
- results.push({ name: checkName, emoji: 'âŗ', complete: false });
- allComplete = false;
- } else if (check.status !== 'completed') {
- results.push({ name: checkName, emoji: 'đ', complete: false });
- allComplete = false;
- } else if (check.conclusion === 'success') {
- results.push({ name: checkName, emoji: 'â
', complete: true });
- } else if (check.conclusion === 'skipped') {
- results.push({ name: checkName, emoji: 'âī¸', complete: true, skipped: true });
- } else {
- results.push({ name: checkName, emoji: 'â', complete: true, failed: true });
- anyFailed = true;
- }
- }
-
- // Get current labels
- const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({
- owner, repo, issue_number: prNumber
- });
- const labelNames = currentLabels.map(l => l.name);
- const currentStatusLabel = Object.values(STATUS_LABELS).find(l => labelNames.includes(l)) || 'None';
- const currentReviewLabel = REVIEW_LABELS.find(l => labelNames.includes(l)) || 'None';
-
- // Update label if all checks complete
- let newStatusLabel = STATUS_LABELS.CHECKING;
- let statusChanged = false;
-
- if (allComplete) {
- newStatusLabel = anyFailed ? STATUS_LABELS.FAILED : STATUS_LABELS.PASSED;
-
- if (newStatusLabel !== currentStatusLabel) {
- statusChanged = true;
- // Remove all status labels first - throw on non-404 errors to prevent conflicting labels
- for (const label of Object.values(STATUS_LABELS)) {
- try {
- await github.rest.issues.removeLabel({ owner, repo, issue_number: prNumber, name: label });
- } catch (e) {
- if (e && e.status !== 404) {
- throw new Error(`Failed to remove label '${label}': ${e.message}`);
- }
- }
- }
- await github.rest.issues.addLabels({ owner, repo, issue_number: prNumber, labels: [newStatusLabel] });
- }
- }
-
- // Build status report
- const passedCount = results.filter(r => r.emoji === 'â
').length;
- let statusEmoji = 'đ';
- if (allComplete && !anyFailed) statusEmoji = 'â
';
- else if (allComplete && anyFailed) statusEmoji = 'â';
-
- const checksTable = results.map(r => `| ${r.emoji} | ${r.name} |`).join('\n');
-
- const lines = [
- `## ${statusEmoji} PR Status Report`,
- '',
- `| Label | Value |`,
- `|-------|-------|`,
- `| CI Status | ${newStatusLabel} |`,
- `| AC Review | ${currentReviewLabel} |`,
- ''
- ];
-
- if (statusChanged) {
- lines.push(`> Status updated: \`${currentStatusLabel}\` â \`${newStatusLabel}\``);
- lines.push('');
- }
-
- lines.push(`### CI Checks (${passedCount}/${REQUIRED_CHECKS.length} passed)`);
- lines.push('');
- lines.push('| Status | Check |');
- lines.push('|--------|-------|');
- lines.push(checksTable);
- lines.push('');
- lines.push('---');
- lines.push(`Triggered by \`/check-status\` from @${requestedBy}`);
-
- await github.rest.issues.createComment({
- owner, repo, issue_number: prNumber, body: lines.join('\n')
- });
-
- console.log(`â
Posted status report to PR #${prNumber}`);
-
- # âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
- # JOB 3: AUTO-CLAUDE REVIEW
- # Processes Auto-Claude review comments from trusted sources
- # Security: Only bots and collaborators can update labels
- # âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
- update-review-status:
- name: Update Review Status
- runs-on: ubuntu-latest
- if: |
- github.event_name == 'issue_comment' &&
- github.event.issue.pull_request &&
- !contains(github.event.comment.body, '/check-status')
- timeout-minutes: 5
-
- steps:
- - name: Check for Auto-Claude review
- uses: actions/github-script@v7
- with:
- retries: 3
- retry-exempt-status-codes: 400,401,403,404,422
- script: |
- // Security configuration
- // SECURITY: Only [bot] suffixed accounts are protected by GitHub.
- // Regular usernames can be registered by anyone and are NOT trusted.
- const TRUSTED_BOT_ACCOUNTS = Object.freeze([
- 'github-actions[bot]',
- 'auto-claude[bot]'
- ]);
-
- const TRUSTED_AUTHOR_ASSOCIATIONS = Object.freeze([
- 'COLLABORATOR',
- 'MEMBER',
- 'OWNER'
- ]);
-
- const IDENTIFIER_PATTERNS = Object.freeze([
- 'đ¤ Auto Claude PR Review',
- 'Auto Claude Review',
- 'Auto-Claude Review'
- ]);
-
- // SECURITY: Regex patterns are tightened to prevent false matches
- // Using \s* instead of .* and requiring specific emoji + verdict format
- const VERDICTS = Object.freeze({
- APPROVED: {
- patterns: ['Auto Claude Review - APPROVED', 'â
Auto Claude Review - APPROVED'],
- // Match: "Merge Verdict:" followed by whitespace/emoji, then â
, then APPROVED/READY TO MERGE
- regex: /Merge Verdict:\s*â
\s*(?:APPROVED|READY TO MERGE)/i,
- label: 'AC: Approved'
- },
- CHANGES_REQUESTED: {
- patterns: ['NEEDS REVISION', 'Needs Revision'],
- // Match: "Merge Verdict:" followed by whitespace/emoji, then đ
- regex: /Merge Verdict:\s*đ /,
- label: 'AC: Changes Requested'
- },
- BLOCKED: {
- patterns: ['BLOCKED'],
- // Match: "Merge Verdict:" followed by whitespace/emoji, then đ´
- regex: /Merge Verdict:\s*đ´/,
- label: 'AC: Blocked'
- }
- });
-
- // NOTE: REVIEW_LABELS is intentionally duplicated across jobs.
- // GitHub Actions jobs run in isolated contexts and cannot share runtime constants.
- // If label values change, update ALL occurrences: check-status-command, update-review-status
- const REVIEW_LABELS = Object.freeze([
- 'Missing AC Approval',
- 'AC: Approved',
- 'AC: Changes Requested',
- 'AC: Blocked',
- 'AC: Needs Re-review',
- 'AC: Reviewed'
- ]);
-
- // Helper functions
- // SECURITY: Verify both username AND account type to prevent spoofing
- function isTrustedBot(username, userType) {
- const isKnownBot = TRUSTED_BOT_ACCOUNTS.some(t => username.toLowerCase() === t.toLowerCase());
- // Only trust if it's a known bot account AND GitHub confirms it's a Bot type
- return isKnownBot && userType === 'Bot';
- }
-
- function isTrustedAssociation(assoc) {
- return TRUSTED_AUTHOR_ASSOCIATIONS.includes(assoc);
- }
-
- function isAutoClaudeComment(body) {
- return IDENTIFIER_PATTERNS.some(p => body.includes(p));
- }
-
- function parseVerdict(body) {
- const safeBody = body.slice(0, 5000);
- for (const [key, config] of Object.entries(VERDICTS)) {
- const patternMatch = config.patterns.some(p => safeBody.includes(p));
- const regexMatch = config.regex && config.regex.test(safeBody);
- if (patternMatch || regexMatch) {
- return { verdict: key, label: config.label };
- }
- }
- return null;
- }
-
- async function updateReviewLabels(prNumber, newLabel) {
- const { owner, repo } = context.repo;
-
- // Remove all review labels first - throw on non-404 errors to prevent conflicting labels
- for (const label of REVIEW_LABELS) {
- try {
- await github.rest.issues.removeLabel({ owner, repo, issue_number: prNumber, name: label });
- console.log(` Removed: ${label}`);
- } catch (e) {
- if (e && e.status !== 404) {
- // Throw to prevent adding new label if removal failed (could cause conflicting labels)
- throw new Error(`Failed to remove label '${label}': ${e.message}`);
- }
- }
- }
-
- try {
- await github.rest.issues.addLabels({ owner, repo, issue_number: prNumber, labels: [newLabel] });
- console.log(` Added: ${newLabel}`);
- } catch (e) {
- if (e && e.status === 404) {
- core.warning(`Label '${newLabel}' does not exist`);
- } else {
- throw e;
- }
- }
- }
-
- // Main logic
- const prNumber = context.payload.issue.number;
- const comment = context.payload.comment;
- const commenter = comment.user.login;
- const commenterType = comment.user.type;
- const authorAssociation = comment.author_association;
- const body = comment.body || '';
-
- console.log(`PR #${prNumber} - Comment by: ${commenter} (type: ${commenterType}, assoc: ${authorAssociation})`);
-
- // Security checks
- // SECURITY: Bot status requires BOTH username match AND verified Bot type
- const isBot = isTrustedBot(commenter, commenterType);
- const isCollaborator = isTrustedAssociation(authorAssociation);
- const isACComment = isAutoClaudeComment(body);
-
- console.log(` Trusted bot: ${isBot}, Collaborator: ${isCollaborator}, AC comment: ${isACComment}`);
-
- if (!isBot && !isCollaborator) {
- console.log('Skipping: Not a trusted bot or collaborator');
- return;
- }
-
- if (!isACComment) {
- console.log('Skipping: Not an Auto-Claude comment');
- return;
- }
-
- const verdictResult = parseVerdict(body);
- if (!verdictResult) {
- console.log('Skipping: Could not parse verdict');
- return;
- }
-
- console.log(`Verdict: ${verdictResult.verdict} â ${verdictResult.label}`);
- await updateReviewLabels(prNumber, verdictResult.label);
- console.log(`â
PR #${prNumber} review status updated`);
-
- # âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
- # JOB 4: RE-REVIEW ON PUSH
- # When new commits pushed after AC approval, require re-review
- # âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
- require-re-review:
- name: Require Re-review on Push
- runs-on: ubuntu-latest
- if: github.event_name == 'pull_request' && github.event.action == 'synchronize'
- timeout-minutes: 5
-
- steps:
- - name: Check and reset AC approval if needed
- uses: actions/github-script@v7
- with:
- retries: 3
- retry-exempt-status-codes: 400,401,403,404,422
- script: |
- const { owner, repo } = context.repo;
- const prNumber = context.payload.pull_request.number;
- const pusher = context.payload.sender.login;
-
- console.log(`PR #${prNumber} - New commits by: ${pusher}`);
-
- // Get current labels
- const { data: labels } = await github.rest.issues.listLabelsOnIssue({
- owner, repo, issue_number: prNumber
- });
- const labelNames = labels.map(l => l.name);
-
- // Check if PR was approved
- const wasApproved = labelNames.includes('AC: Approved');
-
- if (!wasApproved) {
- console.log('PR was not AC-approved, no action needed');
- return;
- }
-
- console.log('PR was AC-approved, resetting to require re-review');
-
- // Remove AC: Approved - throw on non-404 errors to prevent conflicting labels
- try {
- await github.rest.issues.removeLabel({
- owner, repo, issue_number: prNumber, name: 'AC: Approved'
- });
- console.log(' Removed: AC: Approved');
- } catch (e) {
- if (e && e.status !== 404) {
- // Throw to prevent adding 'AC: Needs Re-review' if removal failed (could cause conflicting labels)
- core.error(`Failed to remove 'AC: Approved' label: ${e.message}`);
- throw e;
- }
- }
-
- // Add AC: Needs Re-review
- try {
- await github.rest.issues.addLabels({
- owner, repo, issue_number: prNumber, labels: ['AC: Needs Re-review']
- });
- console.log(' Added: AC: Needs Re-review');
- } catch (e) {
- if (e && e.status === 404) {
- core.warning("Label 'AC: Needs Re-review' does not exist");
- } else {
- throw e;
- }
- }
-
- // Post notification comment
- const commentLines = [
- '## đ Re-review Required',
- '',
- 'New commits were pushed after Auto-Claude approval.',
- '',
- '| Previous | Current |',
- '|----------|---------|',
- '| `AC: Approved` | `AC: Needs Re-review` |',
- '',
- 'Please run Auto-Claude review again or request a manual review.',
- '',
- '---',
- `Triggered by push from @${pusher}`
- ];
-
- await github.rest.issues.createComment({
- owner, repo, issue_number: prNumber, body: commentLines.join('\n')
- });
-
- console.log(`â
Posted re-review notification to PR #${prNumber}`);
diff --git a/.github/workflows/test-azure-auth.yml b/.github/workflows/test-azure-auth.yml
deleted file mode 100644
index f3fb73c2..00000000
--- a/.github/workflows/test-azure-auth.yml
+++ /dev/null
@@ -1,21 +0,0 @@
-name: Test Azure Auth
-
-on:
- workflow_dispatch:
-
-jobs:
- test-auth:
- runs-on: windows-latest
- permissions:
- id-token: write
- contents: read
- steps:
- - name: Azure Login (OIDC)
- uses: azure/login@v2
- with:
- client-id: ${{ secrets.AZURE_CLIENT_ID }}
- tenant-id: ${{ secrets.AZURE_TENANT_ID }}
- subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
-
- - name: Success
- run: echo "Azure authentication successful!"
diff --git a/.github/workflows/test-on-tag.yml b/.github/workflows/test-on-tag.yml
deleted file mode 100644
index f633c868..00000000
--- a/.github/workflows/test-on-tag.yml
+++ /dev/null
@@ -1,63 +0,0 @@
-name: Test on Tag
-
-on:
- push:
- tags:
- - 'v*'
-
-jobs:
- # Python tests
- test-python:
- runs-on: ubuntu-latest
- strategy:
- matrix:
- python-version: ['3.12', '3.13']
-
- steps:
- - name: Checkout
- uses: actions/checkout@v4
-
- - name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v5
- with:
- python-version: ${{ matrix.python-version }}
-
- - name: Install uv
- uses: astral-sh/setup-uv@v4
- with:
- version: "latest"
-
- - name: Install dependencies
- working-directory: apps/backend
- run: |
- uv venv
- uv pip install -r requirements.txt
- uv pip install -r ../../tests/requirements-test.txt
-
- - name: Run tests
- working-directory: apps/backend
- env:
- PYTHONPATH: ${{ github.workspace }}/apps/backend
- run: |
- source .venv/bin/activate
- pytest ../../tests/ -v --tb=short
-
- # Frontend tests
- test-frontend:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v4
-
- - name: Setup Node.js
- uses: actions/setup-node@v4
- with:
- node-version: '24'
-
- - name: Install dependencies
- working-directory: apps/frontend
- run: npm ci --ignore-scripts
-
- - name: Run tests
- working-directory: apps/frontend
- run: npm run test
diff --git a/.github/workflows/validate-version.yml b/.github/workflows/validate-version.yml
deleted file mode 100644
index a076114d..00000000
--- a/.github/workflows/validate-version.yml
+++ /dev/null
@@ -1,71 +0,0 @@
-name: Validate Version
-
-on:
- push:
- tags:
- - 'v*'
-
-jobs:
- validate-version:
- name: Validate package.json version matches tag
- runs-on: ubuntu-latest
-
- steps:
- - name: Checkout
- uses: actions/checkout@v4
-
- - name: Extract version from tag
- id: tag_version
- run: |
- # Extract version from tag (e.g., v2.5.5 -> 2.5.5)
- TAG_VERSION=${GITHUB_REF#refs/tags/v}
- echo "version=$TAG_VERSION" >> $GITHUB_OUTPUT
- echo "Tag version: $TAG_VERSION"
-
- - name: Extract version from package.json
- id: package_version
- run: |
- # Read version from package.json
- PACKAGE_VERSION=$(node -p "require('./apps/frontend/package.json').version")
- echo "version=$PACKAGE_VERSION" >> $GITHUB_OUTPUT
- echo "Package.json version: $PACKAGE_VERSION"
-
- - name: Compare versions
- run: |
- TAG_VERSION="${{ steps.tag_version.outputs.version }}"
- PACKAGE_VERSION="${{ steps.package_version.outputs.version }}"
-
- echo "=========================================="
- echo "Version Validation"
- echo "=========================================="
- echo "Git tag version: v$TAG_VERSION"
- echo "package.json version: $PACKAGE_VERSION"
- echo "=========================================="
-
- if [ "$TAG_VERSION" != "$PACKAGE_VERSION" ]; then
- echo ""
- echo "â ERROR: Version mismatch detected!"
- echo ""
- echo "The version in package.json ($PACKAGE_VERSION) does not match"
- echo "the git tag version ($TAG_VERSION)."
- echo ""
- echo "To fix this:"
- echo " 1. Delete this tag: git tag -d v$TAG_VERSION"
- echo " 2. Update package.json version to $TAG_VERSION"
- echo " 3. Commit the change"
- echo " 4. Recreate the tag: git tag -a v$TAG_VERSION -m 'Release v$TAG_VERSION'"
- echo ""
- echo "Or use the automated script:"
- echo " node scripts/bump-version.js $TAG_VERSION"
- echo ""
- exit 1
- fi
-
- echo ""
- echo "â
SUCCESS: Versions match!"
- echo ""
-
- - name: Version validation result
- if: success()
- run: |
- echo "::notice::Version validation passed - package.json version matches tag v${{ steps.tag_version.outputs.version }}"
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index bded7f5c..376eeb67 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -429,7 +429,6 @@ All pull requests and pushes to `main` trigger automated CI checks via GitHub Ac
|----------|---------|----------------|
| **CI** | Push to `main`, PRs | Python tests (3.11 & 3.12), Frontend tests |
| **Lint** | Push to `main`, PRs | Ruff (Python), ESLint + TypeScript (Frontend) |
-| **Test on Tag** | Version tags (`v*`) | Full test suite before release |
### PR Requirements
diff --git a/RELEASE.md b/RELEASE.md
index 21d0e6b5..4eb9ff02 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -186,7 +186,6 @@ The release workflow **validates** that `CHANGELOG.md` has an entry for the vers
|----------|---------|---------|
| `prepare-release.yml` | Push to `main` | Detects version bump, **validates CHANGELOG.md**, creates tag |
| `release.yml` | Tag `v*` pushed | Builds binaries, extracts changelog, creates release |
-| `validate-version.yml` | Tag `v*` pushed | Validates tag matches package.json |
| `update-readme` (in release.yml) | After release | Updates README with new version |
## Troubleshooting