mesh-issues: check every new issue for a duplicate; claude-code: one judgement with no tools (hq ADR 0278)
mesh/merge-gate pass: builds claude-code, mesh-issues → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 o…
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery superseded: a newer head of the same pull request

The operator asked for duplicate issues to be consolidated as they are opened.
The tracker consumes its own opened event and asks the agent on its node, through
claude-code's new claude_code_judge, whether an older issue not resolved asks the
same question; it closes only the newer issue of a certain duplicate, keeping its
report on the older one, and fails loudly when it cannot judge. groom runs the
same judgement over the backlog as a dry run, applied on confirm.
This commit is contained in:
jochen
2026-10-10 17:11:54 +02:00
parent fb77be42ca
commit f58840b8a6
13 changed files with 1750 additions and 16 deletions
+31
View File
@@ -23,6 +23,7 @@ what it refuses.
| `note` | `number`, `text` | appends a note; a note is never edited |
| `link` | `number`, `kind`, `target`, optionally `remove` | adds or removes a typed link |
| `import` | optionally `dry-run`, `commit` | hq's issue folders, read through the records module, each written as an issue under its own number; see below |
| `groom` | optionally `pass`, `confirm` | the duplicate check over the backlog: without arguments a dry run in the background, answering its pass id; with `pass` its progress and findings; with `confirm` and `pass` exactly that pass's findings applied, each checked again; see below |
**The cycle's rules, at the write** (hq playbook 03, which `00-META/checks/cycle.py` enforced on the
folders until the import): a status is one of `open`,
@@ -35,6 +36,33 @@ needs a `reason`. A refused change changes nothing and says which rule refused i
tracker like any other: the folder holds its narrative, the tracker its status. Until the import of
slice 3 ran, such an issue kept its status in the folder and `move` refused it; the import filled it.
## The duplicate check (hq ADR 0278)
Every issue opened is checked by the module itself: it consumes its own `mesh-issues.opened` event, and for an
issue still at `open` asks the agent on its node — `claude-code.claude_code_judge@<node>`, which runs the agent
with no tools under the node's licence — two questions: a shortlist of at most five older issues by their titles,
then a verdict on the reports (`duplicate`, `related` or `none`, whether it is certain, and why). Only issues at
`open`, `diagnosing` or `located` that are older than the new one are offered; a resolved or wontfix issue is
never offered, linked or noted.
| verdict | done, through the module's own `note`, `link` and `move`, each recorded as by `duplicate-check` |
|---|---|
| `duplicate`, certain | the verdict noted on the new issue; linked `duplicates` to the older; its title and report noted on the older; moved to `wontfix`, reason "duplicate of N" |
| `related`, or `duplicate` not certain | the verdict noted; linked `relates-to`; noted on the older; nothing moves |
| `none` | one note: checked against so many issues |
The older issue never moves. The verdict note is written first, its first line in a fixed form (`Duplicate
check: duplicate of N.`), so a second delivery of the event, or the backlog's pass, does only the steps not yet
done and never judges again. A judgement that fails — no answer, a late one, an answer outside the shape, an
issue not offered — leaves one note saying why, and the event is not taken: the bus offers it again, and after
its fifth delivery the controller raises `max-deliveries` on the module's consumer and keeps the event for a
person to deliver again.
`groom` runs the same judgement over every issue not resolved and not yet checked, oldest first, against the
older ones not resolved and not found a duplicate in the same pass. It writes only the pass, in the state
`grooming` (the last pass, one key), and applies that pass's findings on `confirm` — never a fresh judgement.
A finding whose issues have moved on is skipped and said. It adds no "none" note to the backlog.
## The import of hq's issues (slice 3)
`issue-tracker.import` reads every `04-ISSUES/NNN-slug/00-report.md` of hq **through the records module
@@ -127,6 +155,9 @@ verb takes an argument naming a caller, and none can be given one.
## Settings
`duplicate-check` (`on`; `off` stops the check on new issues) and `judge-model` (`sonnet`; `haiku` or `opus`)
steer the duplicate check.
```
{
"first-number": 365,