Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/scripts/risk-gate.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ export function createGate(rules) {
const codeExt = new RegExp(t3.codeExt)
const compile = (list) => (list || []).map((p) => [p, new RegExp(p)])
const patterns = compile(t3.diffPatterns) // every code file
// A topic is a tool name (#679): it counts when an added line registers the tool
// (`name: 'create_child_bot'`, the shape in plugins/tree/src/tools.js), not when a prompt
// merely names it. A plain substring match made any mention of the tool a T3 PR.
const topicName = (name) => new RegExp(`name\\s*:\\s*['"]${name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}['"]`)
const topicRules = (t3.topics || []).map((name) => [name, topicName(name)])
const prosePatterns = compile(t3.diffPatternsNotInTests) // code files except tests: a test may quote a URL or a fence
const tests = (t3.testPaths || []).map(globToRe)
const unknown = rules.unknownPathTier || 'T2'
Expand Down Expand Up @@ -77,7 +82,7 @@ export function createGate(rules) {
const added = (f.patch || '').split('\n').filter((l) => l.startsWith('+') && !l.startsWith('+++')).join('\n')
const active = tests.some((r) => r.test(f.filename)) ? patterns : [...patterns, ...prosePatterns]
for (const [src, re] of active) if (re.test(added)) bump('T3', `${f.filename}: /${src.slice(0, 60)}/`)
for (const w of t3.topics || []) if (added.includes(w)) bump('T3', `${f.filename}: topic ${w}`)
for (const [w, re] of topicRules) if (re.test(added)) bump('T3', `${f.filename}: topic ${w}`)
}
const real = files.filter((f) => !isGenerated(f.filename))
const lines = real.reduce((n, f) => n + (f.additions || 0) + (f.deletions || 0), 0)
Expand Down
16 changes: 15 additions & 1 deletion .github/scripts/risk-gate.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,27 @@ test('judge: content hits in added lines of code raise to T3', () => {
lint: '+// oxlint-disable-next-line',
url: "+fetch('https://evil.example.net/x')",
fence: '+const fenced = sanitizeName(x)',
topic: '+tools.create_child_bot()',
topic: "+ name: 'create_child_bot',",
}
for (const [name, line] of Object.entries(risky)) {
assert.equal(gate.judge([file('core/src/client/a.js', [line])]).tier, 'T3', name)
}
})

test('#679: a topic counts on the registration line, not on a line that names the tool', () => {
// #636's shape: prompt.js gains a sentence naming create_child_bot and tree.js changes.
const named = gate.judge([
file('plugins/tree/src/prompt.js', ["+ 'Use create_child_bot to add a child Bot under yourself.',"]),
file('plugins/tree/src/tree.js', ['+ adoptUnattached(root)']),
])
assert.equal(named.tier, 'T2', 'naming the tool in prose must not raise the tier')
assert.doesNotMatch(named.why.join('\n'), /topic create_child_bot/)
// The positive control, so the rule is narrowed rather than switched off.
const registered = gate.judge([file('plugins/tree/src/tools.js', ["+ name: 'create_child_bot',"])])
assert.equal(registered.tier, 'T3')
assert.match(registered.why.join('\n'), /plugins\/tree\/src\/tools\.js: topic create_child_bot/)
})

test('judge: removed lines and prose are not content hits', () => {
const removed = { ...file('core/src/client/a.js'), patch: '@@ -1 +1 @@\n-eval(x)\n+ok' }
assert.equal(gate.judge([removed]).tier, 'T1')
Expand Down
Loading