Files
siriusscan--sirius/.github/workflows/issue-triage.yml
T
wehub-resource-sync 161ef94b4f
Check engine pin consistency / Dockerfile / CI pin consistency (push) Successful in 8s
Sirius CI/CD Pipeline / Detect Changes (push) Successful in 23s
Validate Docker Configuration / Validate Docker Compose Configuration (push) Successful in 47s
Sirius CI/CD Pipeline / Build API (${{ matrix.platform }}) (push) Has been cancelled
Sirius CI/CD Pipeline / Build UI (${{ matrix.platform }}) (push) Has been cancelled
Sirius CI/CD Pipeline / Merge Engine Manifest (push) Has been cancelled
Sirius CI/CD Pipeline / Merge API Manifest (push) Has been cancelled
Sirius CI/CD Pipeline / Merge UI Manifest (push) Has been cancelled
Sirius CI/CD Pipeline / Build Engine (${{ matrix.platform }}) (push) Has been cancelled
Sirius CI/CD Pipeline / Build Infra (${{ matrix.service }}, ${{ matrix.platform }}) (push) Has been cancelled
Sirius CI/CD Pipeline / Merge Infra Manifest (sirius-postgres) (push) Has been cancelled
Sirius CI/CD Pipeline / Merge Infra Manifest (sirius-rabbitmq) (push) Has been cancelled
Sirius CI/CD Pipeline / Merge Infra Manifest (sirius-valkey) (push) Has been cancelled
Sirius CI/CD Pipeline / Integration Test (push) Has been cancelled
Sirius CI/CD Pipeline / Public Stack Contract (push) Has been cancelled
Sirius CI/CD Pipeline / Dispatch Demo Deployment (sirius-demo branch) (push) Has been cancelled
Sirius CI/CD Pipeline / Dispatch Demo Canary (main branch) (push) Has been cancelled
Sirius CI/CD Pipeline / Guard Registry Namespace (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:32:25 +08:00

198 lines
8.0 KiB
YAML

name: Issue Triage
on:
issues:
types: [opened, edited]
permissions:
contents: read
issues: write
jobs:
triage:
name: Deterministic issue triage
runs-on: ubuntu-latest
steps:
- name: Apply labels and publish triage card
uses: actions/github-script@v7
with:
script: |
const issue = context.payload.issue;
const issueNumber = issue.number;
const owner = context.repo.owner;
const repo = context.repo.repo;
const text = `${issue.title}\n${issue.body || ""}`.toLowerCase();
const statusLabels = new Set([
"status:needs-triage",
"status:needs-info",
"status:repro-ready",
"status:confirmed",
"status:in-progress",
"status:blocked",
"status:ready-to-merge",
"status:done",
]);
const nextLabels = new Set();
const reasons = [];
// Type classification
if (/(security|vulnerability|cve|hardening|auth bypass|cors)/.test(text)) {
nextLabels.add("type:security");
reasons.push("Detected security-oriented keywords.");
} else if (/(question|how do i|help|clarify)/.test(text)) {
nextLabels.add("type:question");
reasons.push("Detected question/support language.");
} else if (/(docs|documentation|readme|guide)/.test(text)) {
nextLabels.add("type:docs");
reasons.push("Detected documentation-related language.");
} else if (/(feature|enhancement|improve|request)/.test(text)) {
nextLabels.add("type:enhancement");
reasons.push("Detected enhancement/request language.");
} else {
nextLabels.add("type:bug");
reasons.push("Defaulted to bug classification.");
}
// Area classification
if (/(sirius_api_key|x-api-key|401|auth|nextauth|jwt|session)/.test(text)) {
nextLabels.add("area:auth");
reasons.push("Matched auth/API-key indicators.");
}
if (/(installer|startup|secret|nextauth_secret|initial_admin_password|postgres_password)/.test(text)) {
nextLabels.add("area:installer-secrets");
reasons.push("Matched installer/startup secret indicators.");
}
if (/(docker-compose\.dev|dev overlay|volume mount|mount shadow|prisma)/.test(text)) {
nextLabels.add("area:compose-dev");
reasons.push("Matched dev-overlay/compose indicators.");
}
if (/(docker-compose\.prod|production overlay|hardened production)/.test(text)) {
nextLabels.add("area:compose-prod");
reasons.push("Matched production overlay indicators.");
}
if (/(ui|frontend|next\.js|trpc|browser)/.test(text)) nextLabels.add("area:ui");
if (/(api|fiber|endpoint|rest)/.test(text)) nextLabels.add("area:api");
if (/(engine|scanner|grpc|agent)/.test(text)) nextLabels.add("area:engine");
if (/(rabbitmq|queue|amqp)/.test(text)) nextLabels.add("area:rabbitmq");
if (/(postgres|database|migration|prisma schema)/.test(text)) nextLabels.add("area:postgres");
if (/(valkey|redis|cache)/.test(text)) nextLabels.add("area:valkey");
if (/(windows|wsl|crlf)/.test(text)) nextLabels.add("platform:windows");
// Severity hint
if (/(critical|auth bypass|remote code execution|data loss|privilege escalation)/.test(text)) {
nextLabels.add("sev:critical");
} else if (/(stack won't boot|cannot start|service down|outage|unusable|401 everywhere)/.test(text)) {
nextLabels.add("sev:high");
} else if (/(intermittent|major|fails often)/.test(text)) {
nextLabels.add("sev:medium");
} else {
nextLabels.add("sev:low");
}
// Missing info checks for fast mobile triage
const missing = [];
if (!/(repro|steps to reproduce|reproduction)/.test(text)) {
missing.push("Reproduction steps");
}
if (!/(docker compose logs|logs --no-color|stack trace|error:)/.test(text)) {
missing.push("Relevant service logs");
}
if (!/(docker compose config --quiet|installer|sirius-installer)/.test(text)) {
missing.push("Installer/config-render validation evidence");
}
if (missing.length > 0) {
nextLabels.add("status:needs-info");
reasons.push("Missing diagnostics were detected.");
} else {
nextLabels.add("status:repro-ready");
reasons.push("Sufficient diagnostics detected for reproduction.");
}
// Remove existing status labels to keep lifecycle single-valued
for (const label of issue.labels.map((l) => l.name)) {
if (statusLabels.has(label) && !nextLabels.has(label)) {
try {
await github.rest.issues.removeLabel({
owner,
repo,
issue_number: issueNumber,
name: label,
});
} catch (error) {
core.warning(`Could not remove label ${label}: ${error.message}`);
}
}
}
// Add computed labels
const labelsToAdd = [...nextLabels];
if (labelsToAdd.length > 0) {
await github.rest.issues.addLabels({
owner,
repo,
issue_number: issueNumber,
labels: labelsToAdd,
});
}
const triageMarker = "<!-- sirius-triage-card -->";
const missingBlock = missing.length
? missing.map((m) => `- [ ] ${m}`).join("\n")
: "- [x] Issue includes baseline diagnostics.";
const body = `${triageMarker}
## Triage Card
**Summary**
- Type: ${[...nextLabels].find((l) => l.startsWith("type:")) || "not-set"}
- Areas: ${[...nextLabels].filter((l) => l.startsWith("area:") || l.startsWith("platform:")).join(", ") || "not-set"}
- Severity hint: ${[...nextLabels].find((l) => l.startsWith("sev:")) || "not-set"}
- Status: ${[...nextLabels].find((l) => l.startsWith("status:")) || "not-set"}
**Why this classification**
${reasons.map((r) => `- ${r}`).join("\n")}
**Missing info checklist**
${missingBlock}
**Relevant runbooks**
- API Key Operations: \`documentation/dev/operations/README.api-key-operations.md\`
- Startup/Secrets ADR: \`documentation/dev/architecture/ADR.startup-secrets-model.md\`
- Auth Surface Matrix: \`documentation/dev/architecture/README.auth-surface-matrix.md\`
**Maintainer mobile commands**
- \`/triage needs-info\`
- \`/triage repro-ready\`
- \`/triage confirmed\`
- \`/test health\`
- \`/test integration\`
- \`/test security auth-surface\`
`;
const comments = await github.paginate(github.rest.issues.listComments, {
owner,
repo,
issue_number: issueNumber,
per_page: 100,
});
const existing = comments.find((c) => c.body && c.body.includes(triageMarker));
if (existing) {
await github.rest.issues.updateComment({
owner,
repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner,
repo,
issue_number: issueNumber,
body,
});
}