@adlc/ticket-prune

中文文档
1.12.0发布于 2026/10/10MITES Module构建来源已验证近 30 天下载 8195

Reports and archives stale/shipped tickets out of .adlc/tickets.json (dry-run by default).

原始使用文档
文档目录10 节
  1. Usage
  2. Tombstoning (legacy store), not removal (#104)
  3. Completing rail-freezing tickets ( --ceremony is deprecated…
  4. How "stale" is decided
  5. Flags
  6. Exit codes
  7. Examples
  8. Locking and atomicity
  9. Relationship to sibling tools
  10. Compatibility

ticket-prune

ADLC phase: C12 — maintenance (ticket lifecycle hygiene)

The active ADLC ticket store is a mutable working set. Nothing else prunes it after a ticket's work ships, so completed tickets accumulate and masquerade as an open backlog — you can't tell live work from leftovers without cross-checking deliverables and merged PRs by hand. ticket-prune reports (and, with --write, tombstones) tickets it can determine are stale.

Addresses issue #39.

Usage

ticket-prune [--tickets path] [--base-ref ref] [--infer-scope] [--write] [--allow-unsigned] [--json]

Dry-run by default, consistent with every other ADLC writer (skill-rot, rejection-mining, model-ratchet): it reports what it would prune without mutating the store until you pass --write. On a canonical sharded store, --write archives stale tickets through the active→archive transaction; on a legacy .adlc/tickets.json store it tombstones them in place, as described next.

Tombstoning (legacy store), not removal (#104)

On a legacy .adlc/tickets.json store, --write does not delete a stale ticket or move it to a side file. It adds completed: true to the ticket in place and changes nothing else. This is deliberate: .adlc/tickets.json is the rails-guard trust root, and its CI gate (scripts/rails-guard-ci.mjs) hard-denies any PR that removes or otherwise mutates a base ticket. The one exception the gate carves out is adding exactly completed: true to a ticket that declares no rails — that annotation grants zero unfreeze privilege, so it is safe in an ordinary PR. Tombstoning is built to produce precisely that diff, so a routine prune merges without an admin override.

A stale ticket that still freezes rails is not auto-tombstoned by --write: completing it would expire its rails from the union (a privileged unfreeze), which the gate reserves for the protected-base admin ceremony. Those tickets are reported — in dry-run as well as write (#198) — under needsCeremony with blocker: "rails-freeze", so the drift is visible before it blocks a PR rather than only when you try to write.

Completing rail-freezing tickets (--ceremony is deprecated, #208)

Rail-freezing tickets in needsCeremony are the one completion an ordinary PR cannot make (the diff expires rails, which rails-guard-ci denies for a base ticket). Complete them on a protected-base checkout of main, one id at a time, with the canonical command:

adlc ticket complete <id> --write --authorize --json

Per-ticket by design: it names one id (no bulk recompute, no cross-ticket blast radius — though the id’s own version is still resolved at run time, so verify it is still genuinely done before completing; a revision-bound --expect is a planned follow-up), goes through the ticket-store transaction (worktree lock + expected-snapshot CAS), records completion evidence to .adlc/manifest.jsonl, and works on both the legacy and directory stores. A preexisting-completed-field ticket carries a completed value someone set on purpose — it stays reported, not overwritten.

ticket-prune --ceremony used to do this as a bulk pass, but it took no ids and recomputed its target set at run time (a TOCTOU window and no per-ticket filter), wrote without recording manifest evidence, and had no directory-store support. It is deprecated (#208): invoking it now fails closed and redirects to the command above.

How "stale" is decided

This was the open question in issue #39: an explicit status field on the ticket vs. inferring from merged deliverables/PRs. Decision, in order:

  1. Prefer an explicit status field when present. The @adlc/core ticket schema (packages/core/lib/tickets.mjs) doesn't declare a status field, but validateTicket() doesn't reject unknown properties either, so a ticket can already carry one. done / closed / complete / completed / archived / shipped (case-insensitive) are stale; any other string value is treated as an explicit "not done" and wins over the inference below even if the ticket's scope looks fully shipped.

  2. Otherwise, and only with --infer-scope, infer from scope existing on a base ref. A ticket with no explicit status is stale only if it declares at least one scope glob and every declared glob resolves to at least one file tracked at --base-ref (default HEAD, via git ls-tree). A ticket with no declared scope is never inferred stale — it's reported active until an explicit status settles it.

The inference is OFF by default (#779). Scope existence is not evidence that a ticket's work landed: it asks "does packages/core/** match a tracked file?", which on any repo older than its ticket backlog is true the moment the ticket is authored — the directory was already there. Nothing checks that the scope files changed, that this ticket created them, or that the ticket ever entered a build. Left on by default it classified 179 of this repo's 206 tickets as stale, 23 of them rails-less and therefore archived by --write — all 23 open work that had never been completed — while the run printed a success line and exited 0. So the default answers from an author-asserted status alone, and a caller that wants the weaker signal asks for it by name. Both the text report and --json say which classifier produced the counts.

Why not "scope/rails + a closing PR reference"? The issue floated PR references as a corroborating signal. This repo's ticket schema has no field that records a PR number, and commit-message conventions aren't reliable enough to infer one from git history: a git log --oneline survey on this repo shows tickets referenced inconsistently — some commits name the ticket id directly (e.g. (T13/T14)), but the OpenCode T1-T5 tickets' shipping PRs (#27–#31) reference "Phase A/B/C/E" and never the literal ticket id at all. Grepping history for ticket ids would therefore miss exactly the stale tickets the issue's own worked example was written to catch. Scope-existence against a tracked-files snapshot is deterministic, needs no fuzzy text matching, and is exactly the check the issue's worked example did by hand.

Flags

FlagDescription
--tickets <path>Ticket-store override. The default auto-detects sharded .adlc/tickets/ or legacy .adlc/tickets.json.
--base-ref <ref>Git ref to check declared scope globs against (default HEAD). Point at origin/main to audit a feature branch's tickets against what's already shipped on trunk.
--infer-scopeEnable the scope-existence staleness inference (default off, #779). Without it, only an explicit done-shaped status makes a ticket stale.
--writeTombstone rails-less stale tickets: add completed: true in place on the legacy flat-file store (never remove, never mutate any other field); move them to .adlc/ticket-archive/ on the sharded store. Rails-freezing stale tickets are left untouched and reported under needsCeremony.
--allow-unsignedLet a --write against a frozen trust root record its audit entry unsigned when no ADLC_MANIFEST_KEY is set (warns first; the unsigned entry is permanent). Same contract as adlc ticket --allow-unsigned.
--ceremonyDeprecated (#208). Fails closed and redirects to adlc ticket complete <id> --write --authorize --json. Rail-freezing tickets are completed per-ticket via that command, not in bulk here.
--jsonMachine-readable { baseRef, write, ceremony, inferScope, stale[], active[], tombstoned[], archived[], ceremonyCompleted[], needsCeremony[], blocked[] }.

--json fields: archived[] lists the tickets --write moved out of the active directory store (.adlc/tickets/), so on that store it, not tombstoned[], records what changed; tombstoned[] lists the in-place completed: true tombstones --write adds on the legacy flat-file store (.adlc/tickets.json); blocked[] lists stale tickets left unarchived because a ticket outside the batch still references them; ceremony is always false and ceremonyCompleted[] always empty since --ceremony was deprecated (#208), both retained for compatibility.

Exit codes

CodeMeaning
0Report or tombstone succeeded — regardless of how many stale tickets were found. This is advisory (like model-ratchet), not a pass/fail gate: stale tickets are clutter, not a merge blocker.
1Operational error — bad/missing ticket file, invalid JSON, unresolvable --base-ref, the write lock could not be acquired, or the deprecated --ceremony was invoked (it redirects).

Examples

# Report stale tickets on the currently checked-out ref (dry-run, default).
# Stale here means an explicit done-shaped `status` — nothing is inferred.
ticket-prune

# Tombstone the rails-less stale tickets found above (completed:true in place).
# Same classifier as the dry run above, so what you reviewed is what it writes.
ticket-prune --write

# Audit against main from a feature branch. --infer-scope is what makes this an
# audit rather than a status report: it also treats a ticket whose declared scope
# fully resolves as shipped. That signal is WEAK — it is true the moment a ticket
# is authored on a repo older than its backlog — so this is a review aid. Confirm
# each id by hand and complete it per-ticket; do not pair --infer-scope with
# --write to archive in bulk on the strength of it (#779).
ticket-prune --base-ref origin/main --infer-scope --json

# Admin, on a protected-base checkout of main: complete a rail-freezing shipped
# ticket, expiring its rails (T36). Per-ticket, records manifest evidence, both
# backends. (ticket-prune --ceremony is deprecated — see #208.)
adlc ticket complete <id> --write --authorize --json

Locking and atomicity

On sharded stores, --write uses the canonical ticket service's worktree lock, hash CAS, journal, evidence, and active→archive transaction. The legacy bridge retains the shared .adlc/tickets.lock plus archive-first atomic replacement so sync and prune do not race and a ticket never disappears from both stores. Under the lock the tool re-reads and re-classifies before writing, so a ticket another writer un-staled in the race window is never pruned.

Relationship to sibling tools

  • @adlc/ticket-sync — writes/reads the active ticket store from an external tracker; shares the same lock path so a sync and a prune never interleave.
  • model-ratchet / skill-rot — the other decay-driven, dry-run-by-default maintenance checks wired into /adlc-maintain; ticket-prune follows the same reporting contract.

Compatibility

New repositories archive to immutable shards through @adlc/tickets. The old flat active/archive pair remains writable only as the 1.x compatibility path; use adlc ticket store migrate to preview the representation-only migration.

版本与发布记录

@adlc/ticket-prune 共发布 15 个版本,首次发布于 2026/07/11;过去 12 个月发布了 15 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
1.12.0latest2026/10/1027 天前
1.11.12026/09/091 个月前
1.11.02026/08/252 个月前
1.10.02026/08/112 个月前
1.9.02026/08/092 个月前
1.8.02026/08/082 个月前

关于 @adlc/ticket-prune 的常见问题

@adlc/ticket-prune 是什么?

Reports and archives stale/shipped tickets out of .adlc/tickets.json (dry-run by default).

如何安装 @adlc/ticket-prune?

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

@adlc/ticket-prune 支持 TypeScript 吗?

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

@adlc/ticket-prune 支持 ESM 还是 CommonJS?

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

@adlc/ticket-prune 需要什么版本的 Node.js?

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

@adlc/ticket-prune 可以商用吗?

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

@adlc/ticket-prune 的下载量有多少?

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

@adlc/ticket-prune 还在维护吗?

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