Skip to content

runtime 重构:架构清晰化、去重复、AI SDK 能力收敛 #140

runtime 重构:架构清晰化、去重复、AI SDK 能力收敛

runtime 重构:架构清晰化、去重复、AI SDK 能力收敛 #140

name: Issue Governance
on:
issues:
types: [opened, edited, reopened]
permissions:
contents: read
issues: write
jobs:
triage:
runs-on: ubuntu-latest
steps:
- name: Auto-triage labels and duplicate hints
uses: actions/github-script@v7
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const issue = context.payload.issue;
const issueNumber = issue.number;
const text = `${issue.title || ""}\n${issue.body || ""}`.toLowerCase();
const labelDefs = {
"needs-triage": { color: "f9d0c4", description: "Issue needs initial triage" },
"duplicate-candidate": { color: "cfd3d7", description: "Potential duplicate, needs maintainer confirmation" },
"area:tui": { color: "1d76db", description: "Terminal UI and interaction layer" },
"area:tools": { color: "0e8a16", description: "Built-in tools and tool runtime" },
"area:security": { color: "b60205", description: "Approval, sandbox, and security policy" },
"area:core": { color: "5319e7", description: "Core runtime and session state" },
"area:docs": { color: "0075ca", description: "Documentation and docs UX" },
};
async function ensureLabel(name) {
try {
await github.rest.issues.getLabel({ owner, repo, name });
} catch (e) {
if (e.status === 404) {
const d = labelDefs[name];
if (d) {
await github.rest.issues.createLabel({ owner, repo, name, color: d.color, description: d.description });
}
} else {
throw e;
}
}
}
const toAdd = new Set(["needs-triage"]);
if (/\[bug\]|\bbug\b/.test(issue.title.toLowerCase())) toAdd.add("bug");
if (/\[feature\]|\[feat\]|\bfeature\b|\bresearch\b|\brfc\b|\bproposal\b/.test(issue.title.toLowerCase())) toAdd.add("enhancement");
if (/\bslash\b|\btui\b|\binput\b|\bprompt\b|\bresume\b/.test(text)) toAdd.add("area:tui");
if (/\btool\b|\bmcp\b|\bskill\b|\bwebfetch\b|\bgrep\b|\bwrite\b|\bedit\b/.test(text)) toAdd.add("area:tools");
if (/\bsandbox\b|\bapproval\b|\bdangerous\b|\bsecurity\b|\bexec policy\b/.test(text)) toAdd.add("area:security");
if (/\bcore\b|\bsession\b|\bruntime\b|\barchitecture\b/.test(text)) toAdd.add("area:core");
if (/\bdoc\b|\breadme\b|\bdocumentation\b/.test(text)) toAdd.add("area:docs");
for (const name of toAdd) {
await ensureLabel(name);
}
await github.rest.issues.addLabels({
owner,
repo,
issue_number: issueNumber,
labels: [...toAdd],
});
// Duplicate hint (non-destructive): token-overlap scoring against open issues.
const stopwords = new Set([
"the", "and", "for", "with", "that", "this", "from", "into", "memo", "cli", "tui",
"feature", "bug", "review", "research", "issue", "openai", "codex", "支持", "新增", "问题"
]);
function tokenize(s) {
return new Set(
s
.toLowerCase()
.replace(/[^\p{L}\p{N}\s]/gu, " ")
.split(/\s+/)
.map(t => t.trim())
.filter(t => t.length >= 2 && !stopwords.has(t))
);
}
function jaccard(a, b) {
if (!a.size || !b.size) return 0;
let inter = 0;
for (const t of a) if (b.has(t)) inter++;
const uni = new Set([...a, ...b]).size;
return inter / uni;
}
const currentTokens = tokenize(`${issue.title} ${issue.body || ""}`);
const openIssues = await github.paginate(github.rest.issues.listForRepo, {
owner,
repo,
state: "open",
per_page: 100,
});
const matches = openIssues
.filter(i => !i.pull_request && i.number !== issueNumber)
.map(i => {
const tokens = tokenize(`${i.title} ${i.body || ""}`);
return {
number: i.number,
title: i.title,
url: i.html_url,
score: jaccard(currentTokens, tokens),
};
})
.filter(i => i.score >= 0.35)
.sort((a, b) => b.score - a.score)
.slice(0, 3);
if (matches.length > 0) {
await ensureLabel("duplicate-candidate");
await github.rest.issues.addLabels({
owner,
repo,
issue_number: issueNumber,
labels: ["duplicate-candidate"],
});
const marker = "<!-- issue-governance:duplicate-check -->";
const comments = await github.paginate(github.rest.issues.listComments, {
owner,
repo,
issue_number: issueNumber,
per_page: 100,
});
const alreadyCommented = comments.some(c => (c.body || "").includes(marker));
if (!alreadyCommented) {
const lines = matches.map(m => `- #${m.number} (${m.score.toFixed(2)}): ${m.title}\n ${m.url}`);
await github.rest.issues.createComment({
owner,
repo,
issue_number: issueNumber,
body: `${marker}\nPotential duplicates found:\n${lines.join("\n")}\n\nMaintainer: if this is duplicate, close with reason \`duplicate\` and link the canonical issue.`,
});
}
}