fix(gates): boundary-target resolver checks only the first target per arrow clause #107
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
A boundary clause naming two or more targets in one arrow clause has only its first target resolved. The rest are neither resolved nor reported as dangling — they are invisible.
validate.shthen prints1 of 1 boundary target(s) resolveon a clause that names two, so the gate reports full coverage while checking half.A typo in a second target ships through a green gate.
Evidence
Confirmed four times independently during the #99 retrofit, across three different separators (
/,,,or). One agent extracted the resolver out ofscripts/skill-size-check.shand executed it directly againstgitea-labels-milestones' folded description:Mechanism
Three interacting rules, none wrong on its own:
BOUNDARY_MARKERmatches onlydo not|instead|rather than|not for. ADR-0020's own compressed opener —Not <thing> -> <skill-name>.— matches none of them, so_extract_sentencesetsboundary=Falseand the backtick sweep never runs.ARROW_MARKEDmatches the single arrow and stops._scan's conjunction continuation (CONT_*) is wired toROUTE_*only, never to arrows.has_boundary_clausestill returns True viaBOUNDARY_ARROW, so there is no missing-clause SUGGESTION either. The failure is completely silent.Why this matters more than it looks
ADR-0020 mandates the compressed form and its Enforcement table lists the resolvable-target check as a deterministic ERROR — one of the few rules the ADR trusts a script with. The check is real and does work; it just under-reports its own coverage, which is the one failure mode a gate must not have. ADR-0020 is explicit that a bad gate is worse than no gate "because it would be believed".
Current state of the corpus
Every
gitea-*skill retrofitted under #99 was written one arrow per target as a workaround, and now reports honestly:gitea-branches3 of 3 boundary target(s) resolve: git-branches, git-history, gitea-prsgitea-issues2 of 2 boundary target(s) resolve: gitea-labels-milestones, gitea-prsgitea-labels-milestones2 of 2 boundary target(s) resolve: gitea-issues, gitea-prsgitea-workflow2 of 2 boundary target(s) resolve: gitea-issues, gitea-prsThat convention is not written down anywhere. Skills outside this wave may still carry multi-target arrow clauses that look checked and are not.
Fix options
/,,andorwithin the same clause. Fixes existing prose; the risk is the usual false-positive exposure ADR-0020 tuned these regexes against.Recommend 2, plus a line in
skill-author/references/contract.mdstating one arrow per target — the convention exists in practice and should not stay undocumented.Files
scripts/skill-size-check.sh(the resolver; constants mirrored downstream)plugins/kyberforge/.apm/skills/skill-audit/scripts/validate.shplugins/kyberforge/.apm/skills/agent-audit/scripts/validate.shtests/test-adr0020-targets.sh— needs a case pinning that a second target in one clause is seenRelated
Found while retrofitting the
giteaplugin for #99. Reference:docs/adr/0020-skill-description-and-body-context-contract.md.Note for whoever picks this up: ADR-0020's Decision section cites
gitea-workflow->git-workflowby name as one of four cross-plugin pairs proving the walk-up resolver works. The #99 retrofit removed that route deliberately (it resolves in this monorepo but not in a gitea-only install). Three of the four cited pairs remain, so the ADR's argument stands, but it now cites one example a reader cannot verify in the tree — worth a one-line note in the ADR while touching this area.Fixed on
refactor/adr0020-skill-retrofit(not yet pushed — the PR body will carry the close)Implemented via this issue's recommended option 2: reject the shape rather than widen the arrow scan.
multi_target_arrow_clauses()walks eachARROW_MARKED/ARROW_BOUNDARYhit, appliesCONT_ANYat the match end, and returns the(first, second)pair. The finding names both targets and tells the author to split:SUGGESTION tier, as recommended — the resolver's conservative tuning is untouched, so the ~30 arrow clauses standardised by #99 carry no new false-positive exposure.
The convention is now written down.
skill-author/references/contract.md:66— "One arrow, one target. The resolver reads only the first name after an arrow, so a second is [not checked]". That closes the "not written down anywhere" half of this report.All three hosts.
scripts/skill-size-check.sh,skill-audit/scripts/validate.sh,agent-audit/scripts/validate.sh— verified the function is present in each.Tests.
tests/test-adr0020-targets.shsection 2e, three cases:/-styleorcontinuation, comma continuation, and a one-arrow-one-target control asserting silence — without the control the SUGGESTION could be satisfied by a check that fires on every arrow clause in the corpus. The section header records this issue's exact symptom (1 of 1 boundary target(s) resolveon a clause naming two) as the failure being pinned.Not addressed here: the ADR's
gitea-workflow -> git-workflowcitation noted at the end of this issue. Still an unverifiable example in the tree.Fixed by #129, squash-merged to
mainas598a7c3.Resolved via fix option 2 — a multi-target arrow clause is rejected with a SUGGESTION telling the author to split, rather than extending the arrow scan to continuation targets. That keeps the resolver's conservative tuning and makes the already-folkloric one-arrow-per-target convention explicit.
Verified against the merged tree on a fixture carrying
Not X -> \git-commits` or `no-such-skill-xyz``:Both targets are named in the finding, so the under-reporting this issue describes is gone.
Reopened automatically? No — this stayed open after the merge because Gitea's closing-keyword parser consumed only the first reference in
Closes #99, #107, …. Closing manually.