Skip to content

feat(work-items): the seam reads the tracker binding from the convention home - #11925

Merged
melodic-automation-lanes[bot] merged 4 commits into
mainfrom
work-resolve-issue/issue-11872-41293949676-1
Nov 1, 2026
Merged

melodic-automation-lanes[bot] merged 4 commits into
mainfrom
work-resolve-issue/issue-11872-41293949676-1

Conversation

@melodic-automation-lanes

Copy link
Copy Markdown
Contributor

Closes #11872

Summary

Opened by the work-resolve-issue lane. The run's own summary:

The seam's binding reader (lib/binding.sh) now reads the binding from the tracker mapping of <home>/work-items.yaml, with the personal overlay <home>/work-items.local.yaml. It resolves the home with a new generated copy of lib/resolve-convention-home.sh and parses with the existing yaml-subset.awk copy under skills/triage/scripts, so no new parser dependency. While the migration runs it still reads .work-item-tracker.json, with a one-line notice, when the YAML has no tracker key; with neither, the dispatcher exits 3 and points at /work-items:setup. Design choices and their basis: (1) the tracker mapping keeps the legacy JSON keys, so every adapter keeps reading the same effective JSON through wit_effective_binding_json and needed no change. (2) With no pointer region the home is docs/conventions, the default home ADR 0075 names and the path triage already reads. (3) Every YAML scalar stays a string: the parser drops quotes, and the jira adapter requires resolution ids, which are digit strings, to be strings. Every reader already takes numbers and booleans as text (jq -r, then a regex or string compare). (4) A rejected value now names the file (the overlay when the overlay set the value), the key and the value, for the JSON binding too.

Not verified

  • binding.test.sh and work-item-tracker.test.sh under scripts/run-test-file.sh: this lane cannot run a test runner, so the suites are left to the PR's CI. The tests were traced by hand, and the review subagent's hand-trace also found them passing.
  • convention-yaml-schema (scripts/check-convention-yaml.sh) runs only in CI, so the changed work-items.schema.json is checked there
  • tidy skipped: the change touches scripts/shared-copies.txt, a listed shared path
  • review-venue.txt was absent, so the review ran locally in a fresh-context subagent
  • Adapters' own no-binding messages (jira, linear, gitea common.sh and the onboard-adapter template) still name .work-item-tracker.json; /work-items:setup still writes the JSON file and its gitignore line. Those belong to the migrate step of this expand, migrate, contract set, not this issue's done-wh

Fix

  • plugins/work-items/tools/work-item-tracker/lib/binding.sh: home resolution, YAML tracker reader (records to JSON, rejects keys outside the schema, names parse errors by file and line), YAML overlay discovery, legacy JSON fallback with a one-line notice, invalid-value messages naming file/key/value,
  • plugins/work-items/tools/work-item-tracker/lib/binding.test.sh: cases for the YAML binding (default home and pointer-bound home, YAML wins over JSON, no notice), YAML plus overlay (merge, provider override rejected, overlay with no tracker key inert, invalid overlay value names the overlay), JSON fa
  • plugins/work-items/tools/work-item-tracker/work-item-tracker.sh: the no-binding message names both locations and points at /work-items:setup
  • plugins/work-items/tools/work-item-tracker/work-item-tracker.test.sh: asserts that the missing-binding stderr points at /work-items:setup
  • plugins/work-items/tools/work-item-tracker/CONTRACT.md: Setup (binding file) documents the new location, the YAML reading rules, the work-items.local.yaml overlay and the legacy fallback; the storage_dir rooting note covers a YAML binding
  • plugins/work-items/schemas/work-items.schema.json: declares the tracker binding keys (schema_version, provider, docs, config with lease TTLs, storage_dir, container_label, role_labels and the provider subtrees)
  • plugins/work-items/lib/resolve-convention-home.sh: new generated copy of lib/resolve-convention-home.sh (sync-shared-copies)
  • scripts/shared-copies.txt: registers the work-items copy of resolve-convention-home.sh
  • .changes/work-items/work-resolve-issue-issue-11872-8f4d2a6c.md: minor changelog fragment

Verification

The runner ran each check from the base copy against 6f1fcfd:

  • scripts/check-changelog-fragments.sh --check: passed

The run reports:

  • check-changelog-fragments: all 76 fragments valid (run after the last edit)
  • check-affected: 12 passed, 6 skipped over 9 changed files after fixing one shellcheck SC2016 info in binding.test.sh (run after the last edit)
  • sync-shared-copies: generated plugins/work-items/lib/resolve-convention-home.sh, exit 0
  • fresh-context review subagent over the whole diff: three findings, all confirmed and fixed (quoted ids turned into numbers; an invalid overlay value was named against the team file; the tab split was byte-offset sensitive on older jq)
  • The lane run: https://github.com/melodic-software/claude-code-plugins/actions/runs/41293949676

Left out: the items under Not verified.

Related

#11872

Lane-Activity: work-resolve-issue/resolve-issue
@github-actions

github-actions Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

claude-security-review reviewed 681ce3a32159a4ed20cf6050a7b275b66b303fad: read 3 of 3 changed file(s); 0 finding(s), 0 as inline comments.

Security review (incremental, b9fbd07 → 681ce3a)

critical-findings: 0

I found no security issues in this change.

Scope: I read the whole incremental diff for all three in-scope files: CONTRACT.md, lib/binding.sh and lib/binding.test.sh. I used the PR context to check the change against its stated purpose.

What changed: binding.sh gains a pre-pass, wit_yaml_mark_empty_seqs. It swaps an empty flow sequence (key: []) in a value position for a marker scalar that starts with a control character. The parser then reads that marker, and wit_yaml_binding_json turns it back into a JSON [].

Paths checked:

  • Where the input comes from: the input is the repository's own work-items.yaml / work-items.local.yaml binding, read locally by the seam. The change adds no new input source, no network access and no credentials.
  • Command or code injection:
    • The marker is a fixed readonly constant passed to awk -v and jq --arg. No file content is ever interpreted as awk or jq code.
    • The awk sub/match/substr calls only rewrite the line text. The rewritten text then goes through the existing yaml-subset.awk parser.
  • A forged marker: a file that somehow produced the marker string could at most turn one scalar into []. That is a value the legacy JSON binding could already set directly, and the existing schema and adapter checks still judge it. No trust boundary is crossed.
  • Error reporting: parse errors still name the file and line on stderr. The pre-pass keeps the line count, so no extra content is exposed.

Trusted paths: none of the changed files match the TRUSTED PATHS globs. No trust control, merge hold, token scope or plugin manifest is touched.

Instruction surface: the CONTRACT.md edit adds documentation and removes no guardrail.

Spend: none.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

claude-review: 1 inline finding(s) on 6f1fcfd446ea9957ec930b2ea192fa3583ee71d9; the summary is in this lane's pull request comment.

claude-lane: claude-review

@github-actions

github-actions Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

claude-review reviewed 681ce3a32159a4ed20cf6050a7b275b66b303fad: read 3 of 3 changed file(s); 0 finding(s), 0 as inline comments.

Code review (incremental since b9fbd07): no findings

Scope: .claude-lane/incremental.diff, covering CONTRACT.md, lib/binding.sh and lib/binding.test.sh. I read every hunk of all three files. To check the new pre-pass against the parser it feeds, I also read plugins/work-items/skills/triage/scripts/yaml-subset.awk and the surrounding code in binding.sh.

What changed: before the subset parser runs, wit_yaml_mark_empty_seqs replaces an empty flow sequence in a value position (key: [], - key: [], - []) with a double-quoted marker scalar (\002[]). wit_yaml_binding_json then turns that marker back into []. This way a YAML done_category_keys: [] reads the same as the legacy JSON's empty array, where before it read as the key being absent.

How I checked it against yaml-subset.awk:

  • Line numbers: the pre-pass prints exactly one line for each input line, so a parse error still names the file's own line. The dup.yaml test covers this.
  • Which lines it rewrites: the lead regex accepts the same things the parser does: a plain key, a quoted key, or - followed by a key or a value. Separators the parser rejects are left alone, so the parser still reports them as before:
    • key:[] (no space after the colon)
    • -\t[] (a tab after the dash)
    • a tab in the indent
  • Comments: the trailing-comment forms ([] # c, []#c) match the parser's own check for text after a flow sequence.
  • Quoted values and comments containing []: they are not rewritten, and the new test covers this.
  • The marker itself: inside double quotes, qscan copies the raw \002 byte as it is. Through awk -v, the marker has no backslash, so no escape processing touches it. A real file can't produce the same value: written raw it is outside YAML's character set, and written as an escape the subset rejects it.
  • CRLF and the BOM: the pre-pass strips them the same way the parser does, which makes the parser's own strip a no-op. The CRLF test covers this.
  • Overlay: an empty sequence in an overlay (e.g. docs: []) now becomes an array. The overlay allowlist check only allows scalars and object prefixes, so it rejects the array, which matches what an overlay JSON file with [] would get.

Observation (not counted, and the incremental diff didn't cause it): wit_yaml_has_binding (binding.sh:97-101) still calls wit_yaml_records without the marker. So a YAML file whose tracker mapping holds nothing but empty sequences is still treated as "no binding" and falls back to the legacy JSON. That was already the behavior before this change, and it's an unlikely edge case.

Closing issue #11872: the incremental change keeps the contract text and the tests in line with the reader's behavior. The criterion that the tests pass under scripts/run-test-file.sh is author-claimed and unverified. I couldn't run the tests, so CI will have to check it.

Spend / hold: none.

@melodic-automation-lanes

Copy link
Copy Markdown
Contributor Author

The unattended merge path armed native auto-merge at head 6f1fcfd446ea9957ec930b2ea192fa3583ee71d9.

Lane-Activity: pr-unblock/resolve-conflicts

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

claude-review: 1 inline finding(s) on 757ede64539354552667e527d5ef9c483f370237; the summary is in this lane's pull request comment.

claude-lane: claude-review

Lane-Activity: pr-unblock/address-feedback
Lane-Thread: PRRT_kwDOTCGFQM6yZ_i8
@melodic-automation-lanes

Copy link
Copy Markdown
Contributor Author

pr-unblock / address-feedback finished round 1 of max 4 on b9fbd07.

@melodic-automation-lanes melodic-automation-lanes Bot added the needs-session A pr-unblock stop a session builder takes: a change the lane cannot make. Not an owner stop. label Nov 1, 2026
@melodic-automation-lanes

Copy link
Copy Markdown
Contributor Author

The pr-unblock lane stopped address-feedback on this pull request: the pull request has an unresolved review thread the lane does not take (a reviewer it does not act on, a review of an older head, or a thread it already answered), so no lane resolves it and a session builder takes it. The lane labels it needs-session for a session builder, who makes the change this lane cannot, and the run ends in a failing pr-unblock/address-feedback status on the head the lane gated.

When a retry on this head can change the outcome, the lane runs one on its own, up to its per-head cap, and edits this comment if it stops again; a run that does not stop clears the status. When no retry can (a review thread the lane does not take or settle, or a failing check after the retries are spent), a person resolves the threads or gets the checks green, then dispatches the workflow below: that run finds the cause gone and clears the status. On a pull request the lanes App opened, the dispatchers start that run once per stop; on any other, a person does. A new commit to the branch starts clean.

To run it again on this head now, dispatch the pr-unblock workflow:

gh workflow run pr-unblock.yml -R melodic-software/claude-code-plugins -f pr-number=11925 -f activity=address-feedback

@melodic-automation-lanes

melodic-automation-lanes Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor Author

Unattended merge: native auto-merge leaves this pull request unarmed at head 681ce3a32159a4ed20cf6050a7b275b66b303fad. The lanes clear this reason on their own; the next scheduled run judges it again.

pr-merge's gate refuses its verifier verdict (verify:missing)

@kyle-sexton kyle-sexton removed the needs-session A pr-unblock stop a session builder takes: a change the lane cannot make. Not an owner stop. label Nov 1, 2026
Lane-Activity: pr-unblock/address-feedback
Lane-Thread: PRRT_kwDOTCGFQM6yZWq6
@melodic-automation-lanes

Copy link
Copy Markdown
Contributor Author

pr-unblock / address-feedback finished round 2 of max 4 on 681ce3a.

@melodic-automation-lanes

Copy link
Copy Markdown
Contributor Author

The unattended merge path armed native auto-merge at head 681ce3a32159a4ed20cf6050a7b275b66b303fad.

@melodic-automation-lanes
melodic-automation-lanes Bot added this pull request to the merge queue Nov 1, 2026
Merged via the queue into main with commit be80854 Nov 1, 2026
50 checks passed
@melodic-automation-lanes
melodic-automation-lanes Bot deleted the work-resolve-issue/issue-11872-41293949676-1 branch November 1, 2026 13:35
@github-actions

github-actions Bot commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

Post-merge mutation testing

Killed 3, survived 3, timeout 0, no coverage 0, invalid 0, not run 1.
Covered-code score 50%; mutation score 50%.

File Killed Survived Timeout No coverage Invalid Not run
plugins/work-items/lib/resolve-convention-home.sh 0 0 0 0 0 1
plugins/work-items/tools/work-item-tracker/lib/binding.sh 3 2 0 0 0 0
plugins/work-items/tools/work-item-tracker/work-item-tracker.sh 0 1 0 0 0 0

Surviving mutants

Location Operator Disposition Cause
plugins/work-items/tools/work-item-tracker/lib/binding.sh:99 other productive input-gap
plugins/work-items/tools/work-item-tracker/lib/binding.sh:243 LCR productive input-gap
plugins/work-items/tools/work-item-tracker/work-item-tracker.sh:120 SBR productive no-assertion

The run's summary has the full report.

@melodic-automation-lanes

Copy link
Copy Markdown
Contributor Author

The post-merge check verified merge commit be80854d74c7469fda2d0f36b2b0f847bc2141f6; later checks count it verified from this comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(work-items): the seam reads the tracker binding from the convention home

1 participant