fix: derive auto-repair inScope from requiredFix references, not finding.file alone - #155
Conversation
…ing.file alone
A review finding's `file` records where the problem was OBSERVED, but the
fix often targets a different file named in `requiredFix` (docs vs sample
data, config vs code). planQualityFollowUp derived the generated repair
round's inScope from `finding.file` alone, so the repair's acceptance
("edit README.md") could name a path its own scope forbade. evaluateQualityCompletion
then rejects every honest completion ("X is undeclared"), the repair
fails, and the downstream review-round stays permanently blocked on a
failed dependency — the whole quality loop dead-locks.
Reproduced end-to-end with a real multi-agent session (worker implemented
a task; reviewer filed needs_revision with file=<observed data file> and
requiredFix="edit README.md"; the auto repair could not be completed
honestly by any path).
Fix: new pure helper repairScopeFromFindings(findings, fallback) derives
the repair inScope from the observed files PLUS workspace-relative paths
referenced in the requiredFix text (slash paths and common code/doc
filenames, `:line` suffixes stripped, absolute/illegal paths dropped,
deduped, falling back to the source task's inScope when nothing legal
remains). planQualityFollowUp now uses it. Over-inclusion is accepted by
design: inScope is an audit upper bound, and requiredFix still tells the
implementer what to touch.
Tests: scripts/quality-gates-repair-scope.test.mjs (node --test), covering
the requiredFix-target inclusion, absolute-path dropping, fallback, :line
stripping, and an end-to-end planQualityFollowUp check on the incident
shape. All pass against the patched build.
… in previous commit)
|
Unrelated to this PR's correctness — the derivation fix (extract paths from When a contract is wrong, the workers are not allowed to correct it, so the loop can only dead-lock or cheat:
I hit the same shape with a human-authored contract: the So this PR removes one way to generate a self-contradictory contract. A controlled contract amendment for unfinished tasks — captain-only, recorded as a revision/event, refused once a review has passed — would remove the class, including contradictions that originate from the human rather than the generator. |
…ask) When a quality contract is wrong - a verify command that cannot pass, or an inScope that forbids the file the objective names - the worker has no honest completion: it dead-locks or games the gate. Captains can now amend objective/acceptance/verify/inScope/outOfScope mid-flight. Every amendment appends a TaskRevision (previous values + reason + fields) to the task's revisions ledger; terminal tasks and tasks that already passed review/requirements judgment are frozen; kind=work has no contract to amend. Completion gates evaluate the amended contract naturally because they read current task fields. - amendTaskContract: pure rule in quality-gates.ts with ordered validation
|
Implemented — please see Your three locks, addressed:
Recording: every amendment appends a Captain-only: the tool is registered in Also in this commit: |
|
Cross-referencing #173 / #177, because this PR touches the same function for an adjacent reason.
That is the same dead-lock shape this PR set out to remove, reached by a different route. Worth flagging that widening the generated #177 drops the intersecting inherited patterns (keeping non-colliding ones verbatim) and names both lists in the rejection message instead of reporting a bare |
Brings the branch up to main @ 3b95edb. Conflict resolution in src/index.ts keeps v0.1.18's rule 7 scheduling guidance (correct never-started pending tasks through edit_plan update_task; a captain may cancel a pending task directly) and appends this branch's amend_task paragraph. Verified on the merged tree: tsc --noEmit for the host and client programs, pnpm build, and the repository verify chain (verify, fallback-tdd, member-failure-tdd, quality-gates-tdd, lifecycle, stress, web-routes, harness-contract, stability, compatibility, doctor) all pass.
… covers repairScopeFromFindings now skips a candidate that the source task's inherited outOfScope already matches. classifyChangedPath consults outOfScope before inScope (tdd.scope.out-of-scope-wins), so declaring such a path widened the generated repair contract with an entry the member can never register: the dead-lock from issue NanmiCoder#173, reached by a different route than the finding.file/requiredFix mismatch this branch already fixes. Widening the scope must not manufacture that contradiction. Resolving the inherited patterns themselves stays with NanmiCoder#177's generator-level conflict fix (drop the intersecting inherited patterns, name both lists in the rejection), so the two changes remain complementary and this filter is a no-op once NanmiCoder#177 lands. The fallback path is unchanged. Tests: scripts/quality-gates-repair-scope.test.mjs gains the covered-candidate case, the all-candidates-covered fallback, and an end-to-end planQualityFollowUp case asserting no declared path is covered by the inherited outOfScope.
|
@Tonited — your analysis was right, and it changed what this branch does. I merged Kept deliberately complementary to #177: I did not touch On landing order, your prediction holds: the Also on the merged tree (Windows, Node 24): |
|
@licat2023 — following up on your comment: the amendment channel you proposed is implemented, and the branch has moved since my earlier reply.
What is new since that reply: the branch is now merged with If you have a moment, an informal check of the amend path against your fake-green incident (the implementer pointing |
NanmiCoder#155 added the captain-only `agent_teams_amend_task`, so the captain-visible team tool surface is 14 rather than 13. scripts/capabilities.test.mjs was updated with that commit, but the Real Harness fixture scripts/fixtures/harness-runtime-entry.mjs kept the old expectation in four places. On main (fa5803d) the Verify workflow's Real Harness jobs then failed on every supported host (0.1.2-rc.1, 0.1.2-alpha.5, 0.1.5-rc.1, 0.1.2-alpha.2) in the progressive-entry scenario with `AssertionError: 14 !== 13`, while both static check jobs passed. The pull request itself produced no run: its three pull_request runs carried zero jobs (workflow file issue), so the mismatch only surfaced after the merge. Update the four expectations to 14. No product code changes.
…nt tool `progressive-entry` asserts the captain's fixed team-tool exposure, and that set grew from 13 to 14 when `agent_teams_amend_task` landed (e651d50, via NanmiCoder#155). The fixture kept the old count, so every Real Harness job went red on main immediately after that merge: AssertionError: 14 !== 13 at scripts/fixtures/harness-runtime-entry.mjs:43 The tool itself is wired correctly: captain-only (src/tool-names.ts derives CAPTAIN_TOOL_NAMES), hidden from members through both capabilities.ts and memberToolFilter, registered under the exact name the set declares, and visible from the captain's first request, which is what the fixed-exposure contract requires. Only the fixture's mirror of the count was stale. Also update the prose that still stated the old count, and add the missing `agent_teams_amend_task` row to the usage tool table. Verified locally with the same harness CI runs, on all four supported hosts (0.1.5-rc.1, 0.1.2-rc.1, 0.1.2-alpha.5, 0.1.2-alpha.2): all ten scenarios pass, including the previously failing progressive-entry.
Keep requiredFix targets before removing conflicting previous-round exclusions. Preserve handwritten contract precedence and unrelated exclusions. Cover the real F_DOC failure, directory collisions, fallback deduplication and completion. Adapt the generated-contract helper and regression from PR #177 (ebb3641). Refs #173, #155. Co-authored-by: Ashley Lin <44889191+Tonited@users.noreply.github.com>
Brings in v0.1.18 plus NanmiCoder#155, NanmiCoder#167, NanmiCoder#180 and NanmiCoder#172. Conflict resolution: - package.json: both sides appended to the `verify` chain, so both steps are kept — `dispatch-visibility-tdd.mjs` (this branch) and `verify:member-spawn-recovery` (NanmiCoder#172). - src/tools.ts `dispatchMember`: NanmiCoder#172 and this branch record a rejected spawn on two different surfaces, and both are kept — the member carries `spawnError` (with the failing stack, rendered as `start failed:` under the member) and the team carries `lastDispatchError` (rendered above the roster as `Last dispatch rejection:`). The successful-spawn path now clears both (`delete member.spawnError` + `clearDispatchFailure`). Guard rejections (`halted`, `staged`, member stopping, stale attempt, …) remain unique to this branch: NanmiCoder#172 only covers the thrown-spawn path.
Problem
A review finding's
filerecords where the problem was observed, but the fix often targets a different file named inrequiredFix(docs vs sample data, config vs code).planQualityFollowUpderived the auto-generated repair round'sinScopefromfinding.filealone:When
file ≠ fix target, the generated repair contract is self-contradictory and unsatisfiable:requiredFix) instruct editing file B.inScopeonly declares file A.evaluateQualityCompletionrejects every honest completion ("README.md is undeclared"), becausechangedPaths ⊆ inScopecannot hold for the mandated edit.The repair then fails, and the downstream
review-round-Nstays permanently blocked on a failed dependency (a failed dependency cannot be taken over or cancelled) — the whole quality loop dead-locks and the team can never declare delivery.Real-world reproduction
Reproduced end-to-end in a production multi-agent session (5 tasks): implementation completed → review returned
needs_revisionwithfile=<observed data file>andrequiredFix="edit README.md …"→ the auto repair round could not be completed honestly by any path (declaring the actual edit was rejected by the scope gate; declaring the declared file would be a false report) → downstream review wedged → team had to be abandoned. Full incident notes available on request.Fix 1 — derive auto-repair inScope from requiredFix (211e729, 4c80f50)
New pure helper:
Derives the repair
inScopefrom the observed files plus workspace-relative paths referenced in therequiredFixtext:src/parser.ts) and bare filenames with a code/doc extension allowlist (README.md,wc.js) — the allowlist keeps version tokens (v0.1.17), hashes, and prose out of the scope;:linesuffixes are tolerated and stripped;pathMatchesScoperejects drive-letter patterns);outOfScopealready covers is skipped (see Fix 3);inScopewhen nothing legal remains.planQualityFollowUpnow uses it. Over-inclusion is accepted by design:inScopeis an audit upper bound, and therequiredFixtext still tells the implementer what to touch.Fix 2 — captain-only controlled contract amendment (e651d50)
Following the analysis in this comment: when a quality contract is wrong (a verify command that cannot pass, an inScope that forbids the file the objective names), the worker has no honest completion — it dead-locks or games the gate (e.g. pointing
package.json.mainat the test file to make a broken command exit 0). This adds the missing correction channel:agent_teams_amend_task(captain-only): replacesobjective/acceptance/verify/inScope/outOfScope(full lists, not deltas) on one non-terminal quality task. Registered viaTEAM_TOOL_NAMESbut notMEMBER_TOOL_NAMES, soCAPTAIN_TOOL_NAMESfiltering keeps it out of member tool schemas entirely — and currentmainderivesMEMBER_DENIED_TOOLS = CAPTAIN_TOOL_NAMES, so the runtime denial list covers it without another hand-written entry.TaskRevision(at/by/reason/fields/previous) to the task'srevisionsledger, validated at the durable JSON boundary (isTaskRevision), plus anagent-teams/task-amendedsession event. Rulings travel in the task record, not the mailbox.kind=work(no contract), and once areview/requirementstask withverdict=passhas passed judgment on the task. Mid-flight amendments are allowed; completion gates evaluate the amended contract naturally (they read current task fields).Fix 3 — the derived scope yields to the inherited outOfScope (a8f4176)
Raised by @Tonited in this comment, cross-referencing #173 / #177.
planQualityFollowUpcopiesoutOfScope: source?.outOfScopeverbatim, andclassifyChangedPathconsultsoutOfScopebeforeinScope(tdd.scope.out-of-scope-wins). A derived candidate covered by an inherited directory pattern is therefore unregistrable — so Fix 1, which widens the derived scope, would have added more such entries rather than fewer.repairScopeFromFindingsnow takes the inherited list and skips any candidate it covers, so the widening in Fix 1 cannot manufacture an entry the gate always rejects. Deliberately complementary to #177: resolving the inherited patterns themselves (dropping the intersecting ones, and naming both lists in the rejection message) stays in #177's generator-level change; this PR only stops its own widening from adding colliding entries, and the fallback path is unchanged. Once #177 lands, the filter is a no-op.History / rebase status
Merged
main@3b95edbe(v0.1.18) into this branch (merge-forward;mergeable_stateis clean). The only conflict wassrc/index.tsrule 7: the resolution keeps v0.1.18's new scheduling guidance (correct never-started pending tasks throughedit_plan update_task; a captain may cancel a pending task directly) and appends this branch'samend_taskparagraph. No upstream change is reverted.Earlier commits had two defects that are fixed here:
repairScopeFromFindingsreturnedreadonly string[](a TS2322 againstPlannedFollowUpTask.inScope?: string[]), andcapabilities.test.mjstool-count expectations still said 13.Tests
scripts/quality-gates-repair-scope.test.mjs— requiredFix fix-target inclusion, absolute-path dropping + fallback,:linestripping, end-to-end incident shape, plus (Fix 3) covered-candidate skipping, all-candidates-covered fallback, and an end-to-end case asserting no declared path is matched by the inheritedoutOfScope.scripts/quality-gates-amend.test.mjs— amendment recording (previous values/fields/reason), gate evaluation of the amended contract, mid-flight vs frozen (review/requirements pass), terminal + work rejection, reason/author/field presence, malformed replacements (blank strings, empty lists, absolute paths,..), revision accumulation, durable-state validation.scripts/capabilities.test.mjs— tool surface assertions updated to the 14-operation SDK.Verified on the merged tree (Windows, Node v24.12.0) against upstream CI steps:
tsc -p tsconfig.json --noEmit/tsconfig.client.json --noEmitbuild(clean-build+tscemit ×2 +tsdown)verify.mjs,fallback-tdd,member-failure-tdd,quality-gates-tdd,lifecycle-verify,stress-verify,web-routes-verify,harness-compat-tdd,stability-tdd,compatibility.mjs,doctor.mjsnode --test(repair-scope, amend, capabilities, release-metadata, http-body)One gap in this environment, reported for honesty:
scripts/compatibility.test.mjssymlinks its bin into%TEMP%, and this Windows host denies symlink creation (EPERM), so that one suite could not run here; its underlying scripts (compatibility.mjs,doctor.mjs) pass when invoked directly. CI is the authority for it.Notes for reviewers
3b95edbe); the derivation helper and the amendment path are merged with the current scheduling, mailbox, and member-setup code rather than sitting on older file versions.file≠ fix target are easy to produce (the reviewer naturally points at the file with wrong content). A docs-side mitigation is to document that reviewers should setfileto the fix target — happy to add that to the docs in a follow-up.中文摘要:①review finding 的
file记录的是"问题所在的文件",而requiredFix指向的才是"要改的文件";自动 repair 轮的 inScope 只按前者派生,导致修复契约自相矛盾,worker 无法诚实完成。②契约本身写错时 worker 无纠正通道(update_task 无契约字段 / edit_plan 仅 staged / 终态不可变),只能死锁或作弊;本 PR 增加受控契约修订agent_teams_amend_task:captain-only、修订入 revisions 台账 + 事件、review pass 后冻结、成员 schema 不可见。③(Tonited 指出,#173/#177)继承的outOfScope优先于inScope,而 Fix 1 拓宽 inScope 反而更容易撞上继承的排除模式;现在派生阶段会跳过被继承outOfScope覆盖的候选,与 #177 的生成器级修复互补(#177 落地后该过滤自动失效)。④分支已 merge 到 main v0.1.18(3b95edbe),冲突仅src/index.ts规则 7,已保留上游新调度指引并追加本 PR 段落;本地按上游 CI 步骤验证(typecheck / build / 全部 verify 脚本 / node --test 43 项全过)。