diff --git a/.changes/pr-pipeline/fix-admit-knowable-plugin-arm-scope-0ea8d215.md b/.changes/pr-pipeline/fix-admit-knowable-plugin-arm-scope-0ea8d215.md new file mode 100644 index 0000000000..eed9123b51 --- /dev/null +++ b/.changes/pr-pipeline/fix-admit-knowable-plugin-arm-scope-0ea8d215.md @@ -0,0 +1,11 @@ +--- +bump: minor +--- + +### Changed + +- Intake admission also takes issues whose Uncertainty is Knowable (`admission-rules.json`), which the run lane already runs research-first (owner decision 2026-10-24, amending Q51 criterion 3). + +### Fixed + +- The address-feedback scope rule (`thread-scope.mjs`) no longer lets a fix edit a plugin's control or instruction files through the plugin-wide arm; it excludes the same control paths the pull-request-files arm does. diff --git a/.changes/source-control/fix-admit-knowable-plugin-arm-scope-df25da1a.md b/.changes/source-control/fix-admit-knowable-plugin-arm-scope-df25da1a.md new file mode 100644 index 0000000000..0913a80e82 --- /dev/null +++ b/.changes/source-control/fix-admit-knowable-plugin-arm-scope-df25da1a.md @@ -0,0 +1,7 @@ +--- +bump: patch +--- + +### Fixed + +- `feedback-thread.sh check-paths` and `record` no longer let a lane review-fix edit a plugin's control or instruction files (`.claude-plugin/`, `hooks/`, `monitors/`, `bin/`, `.mcp.json`, `.lsp.json`, `AGENTS.md`, `CLAUDE.md`, `.claude/`) through the plugin-wide scope of a thread on another file in that plugin. The thread's own path and named paths are unchanged. diff --git a/.github/actions/resolve-config/admission-rules.json b/.github/actions/resolve-config/admission-rules.json index 4dbb318ee9..f0c14a8ac3 100644 --- a/.github/actions/resolve-config/admission-rules.json +++ b/.github/actions/resolve-config/admission-rules.json @@ -4,7 +4,7 @@ "types-under-parent": ["Feature"], "classes": ["work-class: read-only", "work-class: mechanical", "work-class: scoped"], "sizes": ["small", "medium"], - "uncertainty": ["known"], + "uncertainty": ["known", "knowable"], "refuse-labels": ["duplicate", "needs-owner-decision", "do-not-merge", "wontfix"], "task-list": "- [ ] " } diff --git a/.github/scripts/intake-triage/admit.test.mjs b/.github/scripts/intake-triage/admit.test.mjs index 8156cba456..eda589abd6 100644 --- a/.github/scripts/intake-triage/admit.test.mjs +++ b/.github/scripts/intake-triage/admit.test.mjs @@ -60,6 +60,11 @@ describe("decide", () => { assert.deepEqual(decide(facts()), { verdict: "admit", reasons: [] }); }); + test("admits Uncertainty Knowable, which the lane researches first", () => { + const verdict = { ...VERDICT, dimensions: { ...VERDICT.dimensions, uncertainty: "knowable" } }; + assert.deepEqual(decide(facts({ verdict })), { verdict: "admit", reasons: [] }); + }); + const refusals = [ ["criterion 2: a type the rules do not list", { issue: issueOf({ type: { name: "Epic" } }) }, /type Epic/], ["criterion 2: an issue with no type", { issue: issueOf({ type: null }) }, /type none/], @@ -85,14 +90,14 @@ describe("decide", () => { /parent/, ], [ - "criterion 3: Uncertainty Knowable", + "criterion 3: Uncertainty Unknown", { verdict: { ...VERDICT, - dimensions: { ...VERDICT.dimensions, uncertainty: "knowable" }, + dimensions: { ...VERDICT.dimensions, uncertainty: "unknown" }, }, }, - /Uncertainty knowable/, + /Uncertainty unknown/, ], [ "criterion 3: Size Large", diff --git a/.github/scripts/pr-unblock/thread-scope.mjs b/.github/scripts/pr-unblock/thread-scope.mjs index c0d3d8fc10..a27a752087 100644 --- a/.github/scripts/pr-unblock/thread-scope.mjs +++ b/.github/scripts/pr-unblock/thread-scope.mjs @@ -73,8 +73,8 @@ export function namedInScope(path, threadPath) { } // A kept thread's own path, a file in the plugins/
/ directory a kept -// thread's path sits in that is no instruction path and no script or code -// file, a test file in the same directory as a kept thread's path, +// thread's path sits in that is no instruction path, no control-plane or +// agent-instruction surface and no script or code file, a test file in the same directory as a kept thread's path, // a path in a kept thread's named_paths (in `threads`, each { path, // named_paths }) that namedInScope allows for that thread, or a path the pull // request already changes (`prPaths`, prPathsOf's) that the fix does not add @@ -96,7 +96,13 @@ export function inThreadScope(path, threadPaths, threads = [], { prPaths = new S } const kept = [...threadPaths]; const plugin = pluginOf(path); - if (plugin !== undefined && !CODE_FILE.test(path) && !isInstructionPath(path) && kept.some((p) => p.startsWith(plugin))) { + if ( + plugin !== undefined && + !CODE_FILE.test(path) && + !isInstructionPath(path) && + !CONTROL_PATH.test(path) && + kept.some((p) => p.startsWith(plugin)) + ) { return true; } const dir = dirOf(path); diff --git a/.github/scripts/pr-unblock/thread-scope.test.mjs b/.github/scripts/pr-unblock/thread-scope.test.mjs index 01c0eecbd2..fd7d73efb0 100644 --- a/.github/scripts/pr-unblock/thread-scope.test.mjs +++ b/.github/scripts/pr-unblock/thread-scope.test.mjs @@ -60,7 +60,7 @@ test("permission config and non-test code in the same plugin are refused", () => } }); -test("hooks, manifests, agent rules, monitors, bin and MCP config in the same plugin are in scope", () => { +test("hooks, manifests, agent rules, monitors, bin and MCP config in the same plugin are out of scope", () => { const named = new Set(["plugins/p/skills/s/SKILL.md"]); for (const path of [ "plugins/p/hooks/hooks.json", @@ -73,7 +73,7 @@ test("hooks, manifests, agent rules, monitors, bin and MCP config in the same pl "plugins/p/.mcp.json", "plugins/p/.lsp.json", ]) { - assert.equal(inThreadScope(path, named), true, path); + assert.equal(inThreadScope(path, named), false, path); } }); diff --git a/plugins/source-control/skills/pull-request/scripts/feedback-thread.sh b/plugins/source-control/skills/pull-request/scripts/feedback-thread.sh index 2d220c10bf..71584f2b3c 100755 --- a/plugins/source-control/skills/pull-request/scripts/feedback-thread.sh +++ b/plugins/source-control/skills/pull-request/scripts/feedback-thread.sh @@ -47,8 +47,9 @@ scratch_dir=${FEEDBACK_SCRATCH:-} workspace=${FEEDBACK_WORKSPACE:-} # Not under .github/workflows/ in any letter case, with no empty, . or .. segment, of # plain ASCII segments, and in a kept thread's scope: its path, a file in the # plugins/
/ directory it sits in that is no instruction path (.gitmodules, -# CODEOWNERS, a settings*.json, a package manifest or lockfile) and no script -# or code file, a test file in its directory, or a path in its named_paths +# CODEOWNERS, a settings*.json, a package manifest or lockfile), no control +# surface (CLAUDE.md, AGENTS.md, .mcp.json, .lsp.json, .claude-plugin/, hooks/, +# monitors/, bin/) and no script or code file, a test file in its directory, or a path in its named_paths # (the files a write-access fix request named) in that same plugins/
/
# directory that is no instruction path; or, when the gated head holds it as a
# regular file, a file in the thread file's pr_paths (the files the pull request already
@@ -67,7 +68,8 @@ allowed() {
def plugin: first(capture("^(?