@adlc/backlog-groom

中文文档
1.12.0发布于 2026/10/10MITES Module近 30 天下载 6211

Grooms a GitHub issue backlog against the code — code-grounded premise verification, clustering, relations and ranking.

原始使用文档
文档目录10 节
  1. Why it exists
  2. The three routes
  3. The verdicts
  4. Profile
  5. The gate ledger is signed
  6. Incrementality
  7. Relations
  8. Honesty rules
  9. What fixed does and does not mean
  10. The cache is not a trust boundary

@adlc/backlog-groom

Grooms a GitHub issue backlog against the code: verifies whether each issue's premise still holds at HEAD, clusters issues by the package their verified locations sit in, ranks them from what was learned, and emits a versioned set.

Read-only by default; --apply writes. Without --apply the command reads the backlog and the code and writes nothing to GitHub. With --apply --set <groomed.json> it acts on a groomed set — closes and relabels — and every action passes an adversarial reviewer, the autonomy floor and a comment-first write. Writing also needs ADLC_MANIFEST_KEY: without it every action demotes to a proposal (see The gate ledger is signed).

adlc backlog-groom                                   # report (read-only)
adlc backlog-groom --json                            # the groomed set (read-only)
adlc backlog-groom --threshold 0.4 --out set.json    # write the set to a file (read-only)
adlc backlog-groom --apply --set set.json            # gated, floored writes to GitHub

The package's own binary, backlog-groom, takes the same flags.

FlagMeaning
--profile <path>profile JSON (default .claude/backlog-groom-profile.json); refused with --apply
--cache <path> / --no-cachethe verdict cache (default .adlc/backlog-groom-cache.json), or skip it
--threshold <n>relation candidate-filter threshold, 0–1 (default 0.2)
--json / --out <path>emit the groomed set as JSON / write it to a file
--applyact on a groomed set — the only mode that writes to GitHub
--set <path>with --apply: the groomed set to act on

Exit codes: 0 the run completed (including an --apply run whose every action demoted to a proposal), 1 operational error. There is no gate-fail exit.

Why it exists

A backlog's labels are stamped once at filing and then rot. Nothing re-checks whether an issue is still true. This package answers that question mechanically where it can, says so plainly where it cannot, and never lets "we did not look" read as "we looked and it holds".

The three routes

RouteMeaning
mechanicalThe body carries a parseable code reference — a path:line and/or a fenced snippet attributed to it
modelA checkable claim about code, but nothing parseable to check against
unverifiableNo checkable claim about code at all

unverifiable is a first-class outcome. Collapsing it into "still valid" is the specific false green this package exists to avoid.

The verdicts

valid · fixed · moved · unverifiable · unverified (model route, not yet judged)

Three rules keep fixed — the verdict that leads to a close — honest:

  • The cited line is a hint, never an identity. Any unrelated edit above a citation shifts every line below it, so a line-anchored comparison reports fixed for live code.
  • An elided excerpt is still a citation. Issue bodies routinely quote non-contiguous lines. Matching is an in-order subsequence, and partial survival is unverifiable — the code changed, and changed is not fixed.
  • A path git has never tracked is not a deleted file. It is prose that looks like a path, and calling it moved floods the report with citations this repository never had.
  • A cited path must be a file. A directory at the revision is unverifiable: git would show a listing, and an excerpt never matches a listing.

Clusters and area relabels use only the citations whose own check was valid or fixed. An issue can be valid on one live citation while another names a path that never existed; that second path says nothing about where the work is.

Across several citations the precedence is moved > valid > unverifiable > fixed, so every tie-break fails towards not closing. unverifiable outranking fixed is the subtle one: an issue with one citation gone and another that could not be checked has not been shown fixed, and closing it would act on incomplete evidence.

Profile

.claude/backlog-groom-profile.json, parsed by the core and failing closed — an unrecognised key at any depth is an operational error, because a config that silently ignores what it does not understand hands you a setting you believe is in force and is not.

{
  "schemaVersion": 1,               // the only required key
  "autonomyFloor": ["close"],       // read by the write path
  "units": [{ "name": "parallax", "paths": ["packages/parallax/**"] }],
  "frozenPaths": ["packages/rails-guard/**"],
  "labels": { "priority": { "high": "P1-high" }, "areaPrefix": "area:" },
  "providers": { "decider": "anthropic", "reviewer": "openai" }
}

A missing profile is fine — the defaults are a complete, conservative profile. A malformed one is not: that is a statement you made and got wrong.

The write path treats the profile as a trust root and compares it against the merge base with the default branch: a wider autonomyFloor, fewer frozenPaths, or different providers, labels or units refuse --apply, and --apply refuses --profile because the baseline is read at the profile's own path.

That baseline is anchored to the REMOTE. git ls-remote --symref origin HEAD asks the remote for its own default branch and the commit it points at, in one exchange, and the merge base must be reachable from that commit. Local refs decide nothing: git update-ref refs/remotes/origin/main HEAD is a local write and does not move the comparison point. Every way of failing to reach the remote — no origin, an unreachable repository, a commit that cannot be fetched, a merge base outside the remote's history — refuses the run rather than falling back. Only --apply reaches the remote; the read path stays local.

What the remote anchor does not cover. origin is whatever .git/config names, and that file is local state. A caller who can rewrite it (git remote set-url) can point origin at a repository they control, holding a commit with a widened floor, and the baseline follows. The anchor stops a caller who can edit the profile or move a local ref; it does not stop one who can rewrite .git/config. Against that caller the ledger key is the boundary: without ADLC_MANIFEST_KEY nothing is written, however the policy check went.

The gate ledger is signed

Each entry in .adlc/backlog-groom-ledger.json carries an HMAC over its whole content, keyed by ADLC_MANIFEST_KEY and domain-separated from every other signed artifact (#1035). ledgerApproves refuses an entry whose signature is missing, wrong, or no longer matches what it covers — including applied, which decides whether a write is skipped and reported as already done.

Writing is therefore a key-holder act. With no key nothing can be sealed, so every action demotes to a proposal, the run still reports what it would have done, and it exits 0. An unattended agent proposes; closing an issue takes the key. The signature is the one field a caller cannot compute — every other one, artifactDigest included, is derivable from the action itself.

The ledger is per-checkout state. .adlc/backlog-groom-ledger.json is gitignored, and an absent file is a first run. The one-shot rule — one review per (issue, contentHash) — therefore holds within one checkout: removing the file, or running from a fresh checkout or a new worktree, forgets every spent review, and the same revision can be reviewed again. The signature stops a forged approval, not a forgotten refusal. The key is the boundary: whoever holds it can re-run from a fresh checkout and get a fresh review.

Incrementality

A gitignored cache at .adlc/backlog-groom-cache.json, keyed per issue on (updatedAt, contentHash). An issue with no contentHash is only ever cached as unverifiable or unverified. That covers an issue with no referenced paths and one citing any path unreadable at the revision: its key has no code component, so no code change can invalidate it, and a cached valid or fixed would outlive the fix — or the revert.

Relations

Similarity is a candidate filter, never evidence. Judgment decides each candidate, and an emitted relation cites reasoning rather than a score. The filter's recall is the ceiling on what can be found, so each run reports how many pairs it excluded — those were never judged, and they bound what the run could have found.

A true miss rate would need ground truth this tool does not have; reporting one would be false precision. The excluded count is what is actually knowable.

Choose --threshold from those reported numbers. Because judgment is per pair, the only useful threshold is one whose surfaced count will actually be judged, and that count collapses steeply: measured on this repo at 381 open issues (72,390 pairs), the 0.2 default surfaces 23,327 pairs, 0.3 surfaces 360, 0.35 surfaces 47, 0.4 surfaces 13, 0.5 surfaces 1, and 0.6 and above surface none. 0.4 is a reasonable starting point. The figures scale with backlog size and title similarity, so re-measure rather than reusing them — every run prints relationFilter.

Honesty rules

Every run leads with its route distribution, and a truncated fetch says so loudly. A sweep that mechanically verified 4% is still useful — but that number sits next to the conclusions, because an incomplete examination must never present as a complete one.

What fixed does and does not mean

fixed means every line this issue cited is gone from the file it cited. It does not mean the defect was fixed, and the difference matters:

  • A refactor can preserve the same wrong behaviour while rewriting every quoted line. This tool would verdict fixed; the bug would still be there.
  • Conversely, code can survive verbatim while the surrounding logic stops reaching it.

That is why fixed produces a close proposal rather than a close, why the evidence carries the revision it was computed against and the paths it read, and why the write path puts every proposal through a fresh-context reviewer and the autonomy floor before anything is applied. The verdict is a strong signal for triage, not a proof, and no part of this design treats it as one.

The evidence field is named lastCommitTouchingPath for the same reason: it is the last commit to touch the file, not necessarily the commit that removed the cited lines. Finding that would need a pickaxe search per citation, and calling it the removing commit would be a claim the tool never checked.

The cache is not a trust boundary

The cache is a local, gitignored performance artifact. Anyone able to write it can inject a verdict — but anyone able to write it can also edit the code that produces verdicts, so it adds no privilege. It follows that a cached verdict is not evidence: the write path must treat proposals as things to be reviewed on their own merits, and --no-cache forces full re-verification when a run needs to stand on its own.

版本与发布记录

@adlc/backlog-groom 共发布 2 个版本,首次发布于 2026/10/10;过去 12 个月发布了 2 个版本。

版本发布日期距今
1.12.0latest2026/10/1027 天前
0.0.0-stage2026/10/1027 天前

关于 @adlc/backlog-groom 的常见问题

@adlc/backlog-groom 是什么?

Grooms a GitHub issue backlog against the code — code-grounded premise verification, clustering, relations and ranking.

如何安装 @adlc/backlog-groom?

在项目目录运行 npm install @adlc/backlog-groom,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 backlog-groom,可以用 npx backlog-groom 直接运行。

@adlc/backlog-groom 支持 TypeScript 吗?

该包的 package.json 没有声明类型入口。需要类型提示时,可以在 npm 上查找社区维护的 @types/adlc__backlog-groom 是否存在。

@adlc/backlog-groom 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@adlc/backlog-groom 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@adlc/backlog-groom 可以商用吗?

@adlc/backlog-groom 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@adlc/backlog-groom 的下载量有多少?

根据 npm 官方下载统计,@adlc/backlog-groom 在 2026/10/05 – 2026/11/03 这 30 天内被下载 6,211 次,日均约 207 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@adlc/backlog-groom 还在维护吗?

最新版本 1.12.0 发布于 2026/10/10(27 天前);至今共发布 2 个版本,其中过去 12 个月发布了 2 个;首次发布于 2026/10/10。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。