statuskit

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

statuskit

statuskit

The front door you open when you sit back down at a project and ask "where is this thing, and what's my single best next move?" statuskit surveys the whole project read-only — git working tree, GitHub issues, open PRs, unfiled plans — prints a one-screen status dashboard, then does the opinionated part: it ranks the possible next actions by a finish-first rule and crowns exactly one as the move to make, routing you to the kit (or plain command) that does it.
It is a read + advise tool. It never commits, pushes, closes an issue, edits a PR, merges, relabels, or writes code — every mutation happens inside the kit it hands you off to, under that kit's own guard. That zero-mutation stance is the point: statuskit is safe to run anytime, as often as you like, to re-orient.
当你回到一个项目中,想问「这个项目进展到哪了?我最佳的下一步行动是什么?」时,statuskit就是你打开的「前门」。它以只读方式扫描整个项目——包括git工作区、GitHub issues、开放PR、未归档计划——生成单屏状态仪表盘,然后执行核心的决策环节:按照「优先完成」规则对可能的下一步行动进行排序,最终确定唯一的最优行动,并引导你使用对应的kit(或普通命令)来执行。
它是一个只读+建议工具。它永远不会执行提交、推送、关闭issue、编辑PR、合并、重新打标签或编写代码等操作——所有变更都由它引导至的kit负责,且受该kit自身的安全机制管控。这种零变更原则是核心:statuskit可以随时、频繁运行,用于重新梳理项目状态,绝对安全。

When this fires

适用场景

You want to orient before acting: "what should I do next", "check project status", "where's this project at", "what's next", "project status", "orient me", "/statuskit", or a bare "what's the state of this" after stepping away.
One boundary matters:
  • Not the tracker authority — that's issuekit. statuskit reads issue counts and state to inform its recommendation and computes one cheap staleness signal. Detailed tracker health belongs to issuekit: issuekit answers "is my tracker honest?"; statuskit answers "where's this project and what do I do next?"
当你想在行动前先梳理状态时:比如「我下一步该做什么」「检查项目状态」「这个项目进展如何」「接下来做什么」「项目状态」「帮我梳理下」「/statuskit」,或者在离开项目一段时间后询问「项目当前状态如何」。
有一个重要的边界:
  • 不是追踪器权威工具——那是issuekit的职责。statuskit读取issue的数量和状态来给出建议,并计算一个简单的过时信号。详细的追踪器健康检查属于issuekit:issuekit回答「我的追踪器是否准确?」;而statuskit回答「这个项目进展到哪了,我下一步该做什么?」

The ranking principle: finish-first

排序原则:优先完成

Everything statuskit crowns derives from one rule — "stop starting, start finishing" (minimize work-in-progress). The crowned move is always whatever retires the most in-flight work for the least effort, before anything new is started.
Ties within a rung break on declared priority first, then one of two signals, depending on what the rung asks you to do:
  • Resume or finish rungs — crown the most-recently-active candidate (issue/PR
    updatedAt
    , or a branch's last-commit time). Recency is a proxy for context-switch cost, and switching cost is what you're minimizing when there's a half-built thing to switch back into. The one exception is a finished-but-unreviewed PR: there's nothing to switch back into, so it breaks on leverage like a start rung does.
  • Start-something rungs — crown the highest unblock leverage (below), falling back to recency. Starting fresh means there's no context to preserve, so the cost recency measures is zero and the thing worth maximizing instead is throughput: how much work the repo can run in parallel after this one lands.
The rest become runners-up. Neither priority nor leverage promotes a candidate across rungs — finish-first is the spine, and both only order within it. One exception, and only one: a
critical
issue.
See below.
statuskit确定的所有行动都源于一个规则——「停止启动新任务,专注完成已有任务」(最小化在办工作)。最终选定的行动永远是用最少的工作量完成最多在办任务的选项,在启动任何新任务之前优先处理。
同一梯级内的平局按已声明优先级打破,若仍有平局则根据梯级类型使用以下两种信号之一:
  • 恢复或完成类梯级——选择最近活跃的候选对象(issue/PR的
    updatedAt
    时间,或分支的最后提交时间)。时效性是上下文切换成本的替代指标,当有未完成的任务需要切换回去时,我们要最小化这种切换成本。唯一例外是已完成但未审核的PR:此时没有需要切换回去的内容,因此像启动类梯级一样按影响力打破平局。
  • 启动新任务类梯级——选择解锁影响力最高的候选对象(下文详述),若仍平局则按时效性排序。启动新任务意味着没有需要保留的上下文,因此时效性衡量的成本为零,此时应最大化吞吐量:即该任务完成后,仓库可并行处理的工作量。
其余候选对象则成为备选。优先级和影响力都不会让候选对象跨梯级排序——优先完成是核心原则,这两个因素仅在同一梯级内排序。唯一例外:标记为
critical
的issue
,详情见下文。

Priority

优先级

Priority is a human's declared answer to "what matters," read off the issuekit label set
critical
,
high
,
medium
,
low
.
Everything else statuskit ranks on is inferred from the repo's mechanics; this is the only signal where somebody actually said it, and that makes it the first tiebreak everywhere rather than a competitor to the ones already here.
优先级是人类对「什么最重要」的明确回答,从issuekit标签集中读取——包括
critical
high
medium
low
statuskit排序所用的其他信号均从仓库机制中推断而来;只有优先级是由人明确设置的信号,因此它是所有平局情况下的首要打破规则,而非与其他信号竞争。

The priority scale

优先级等级

labelmeanshow it ranks
critical
drop everything — preempts work already in progresspromotes across rungs
high
do this before other workable issuesorders within a rung
medium
normal priority — the default once assessedorders within a rung
low
worth doing eventually — never preempts anythingorders within a rung
(none)unassessed — nobody has ranked itsorts below
low
Priority is one of two independent label namespaces, and statuskit reads both. Lifecycle (
ready
,
blocked
,
in-progress
, …) says whether an issue can be worked; priority says whether it should be worked next. Never derive one from the other — a
critical
issue that's
blocked
is still blocked, and crowning it would send the user at something they cannot start.
Unassessed sorts below
low
, and that isn't a judgment about the work.
It's a judgment about the tracker: an issue nobody ranked carries no claim, and statuskit's whole job is to crown a move it can defend. Ranking an unlabeled issue above a labelled one would mean inventing the claim on the user's behalf. When the unassessed pile is large, that's the finding — surface it and point at
issuekit triage
, rather than quietly sorting a backlog nobody has ordered.
标签含义排序规则
critical
放下所有工作——优先于正在进行的工作跨梯级提升优先级
high
在其他可处理的issue之前完成同一梯级内排序
medium
常规优先级——评估后的默认等级同一梯级内排序
low
最终需要完成——永远不会抢占其他任务同一梯级内排序
(无)未评估——无人排序排在
low
之后
优先级是两个独立标签命名空间之一,statuskit会读取这两个命名空间。 生命周期标签(
ready
blocked
in-progress
等)表示issue是否可处理;优先级表示是否应该优先处理。绝不能从其中一个推断另一个——标记为
critical
blocked
的issue仍然处于阻塞状态,若将其选为最优行动,会引导用户去处理无法启动的任务。
未评估的issue排在
low
之后,这不是对工作的评判。
而是对追踪器的评判:无人排序的issue没有优先级主张,而statuskit的核心职责是确定一个有依据的行动。将未标记的issue排在已标记的issue之前,相当于替用户生成了优先级主张。当未评估的issue数量较多时,这就是一个发现——应将其展示出来,并引导用户使用
issuekit triage
,而非悄悄对无人排序的待办列表进行排序。

critical
is the one thing that outranks finish-first

critical
是唯一优先于「优先完成」的规则

Every other signal here orders within a rung, and that restraint is deliberate — it's what stops a clever number from talking the user out of finishing what they started.
critical
is the exception, and the argument for it is narrow enough to state in one line: finish-first is a heuristic for what to do when nobody has said what matters, and
critical
is somebody saying it.
Minimizing work-in-progress is the right default precisely because it needs no information — it works on any repo, on any day, without asking anyone. A
critical
label is strictly better information than that default, and it is the only signal in the whole survey that a human deliberately put there. Refusing to act on it would leave statuskit ranking a half-built refactor above the thing its own user flagged as on fire, which is the one outcome that would make the dashboard untrustworthy rather than merely wrong.
Three guards keep the exception from swallowing the rule:
  • Only a workable
    critical
    promotes.
    It must be unblocked and open — a
    critical
    that's
    blocked
    has nothing to act on, so it stays in the blocked table where it belongs, and the crowned move is its blocker if that blocker is itself workable.
  • critical
    promotes; nothing else does.
    high
    is not a small
    critical
    . It orders within a rung like leverage does, and a repo that wants preemption has to say
    critical
    , which is exactly the friction that keeps the level meaningful.
  • Say what's being set down. When a
    critical
    preempts a rung that would otherwise have won, name the displaced move in the same breath — "#12 is critical, so it goes first; your red PR #34 drops to the runner-up". A preemption the user can't see is indistinguishable from a ranking bug, and this one is rare enough that it should read as an event.
When more than one
critical
is workable, that's the finding.
Two is a tiebreak (fall through to leverage, then recency), but a tracker where several issues all preempt everything has lost the level: nothing is being dropped for any of them, so
critical
has quietly become the new normal. Say so on the crowned move and point at
issuekit triage
, which flags stale
critical
labels as drift.
其他所有信号仅在同一梯级内排序,这种限制是刻意的——它避免了一个聪明的算法说服用户放弃正在完成的任务。
critical
是例外,其理由可以概括为一句话:「优先完成」是在无人明确指出重点时的启发式规则,而
critical
是有人明确指出的重点。
最小化在办任务是正确的默认规则,恰恰因为它不需要额外信息——它适用于任何仓库、任何场景,无需询问任何人。
critical
标签是比默认规则更优质的信息,也是整个扫描过程中唯一由人刻意设置的信号。若拒绝遵循该规则,statuskit可能会将一个未完成的重构任务排在用户标记为紧急的任务之前,这会让仪表盘变得不可信,而非仅仅是错误。
有三个限制条件防止该例外破坏核心规则:
  • 仅可处理的
    critical
    issue会提升优先级。
    它必须处于开放且未阻塞状态——标记为
    critical
    blocked
    的issue没有可执行的行动,因此它应留在阻塞列表中,若其阻塞因素本身可处理,则最优行动是解决该阻塞因素。
  • 只有
    critical
    会提升优先级;其他标签不会。
    high
    不是「小型的
    critical
    」。它像影响力一样在同一梯级内排序,仓库若需要抢占优先级,必须明确标记
    critical
    ,这种摩擦正是保持该等级有效性的关键。
  • 明确说明被搁置的任务。
    critical
    issue抢占了原本会胜出的梯级时,要同时指出被取代的行动——比如「#12是critical,因此优先处理;你的红色PR #34降为备选」。用户看不到的抢占操作与排序错误无法区分,而这种情况非常罕见,应将其视为一个事件。
当有多个可处理的
critical
issue时,这就是一个发现。
两个的情况下按影响力、再按时效性打破平局;但如果追踪器中有多个issue都标记为抢占所有任务的优先级,说明该等级已失去意义:没有任务会为其中任何一个被搁置,因此
critical
已悄悄成为新的常规等级。此时应在最优行动中说明这一点,并引导用户使用
issuekit triage
,该工具会将过时的
critical
标记视为追踪器漂移。

Unblock leverage

解锁影响力

unblocks(X)
is the number of open issues that become fully workable the moment X lands.
It's the answer to "which of these frees the most independent work next," and the file surfaces it as a sortable column on the two tables that already name names.
The word fully carries the rule. If #19 is blocked by both #12 and #23, closing #12 alone doesn't make #19 workable — it makes it less blocked, which is worth nothing to somebody looking for something to pick up. An issue counts toward
unblocks(#12)
only when removing #12 leaves its open-blocker set empty. Any looser definition inflates the number and points you at the wrong issue, which is worse than not ranking at all.
Leverage flows to PRs through what they close:
unblocks(PR #34)
is the leverage of the issues in its
closingIssuesReferences
. That's what turns the
Closes
column from a fact into a priority — a review that frees three issues outranks one that frees none, whatever their CI says.
Four rules keep the number honest:
  • Depth 1 only. Don't count cascades. A transitive number assumes the intermediate issue gets finished rather than merely unblocked, which is a schedule prediction statuskit has no business making — and depth-1 is naturally cycle-safe, where a transitive walk needs a guard against
    A blocked by B blocked by A
    .
  • Only still-open blockers count, the same rule the blocked set already follows.
  • A blocker may be a PR. Issue and PR numbers share one namespace on GitHub, so
    blockedBy: #34
    can mean "waiting on a merge," and it resolves against the open-PR read.
  • No declared dependencies means no column. When the repo's graph has no edges at all, every value is 0 and the column actively lies: it reads as "nothing unblocks anything" when the truth is "nobody wrote it down." Drop the column, say it once — no dependencies declared; leverage unavailable — and point at issuekit, because declaring them is tracker hygiene, not a survey's job.
Two states are surfaced but never crowned, because acting on them is a human gate, not a finish-first win statuskit should push:
  • an approved + CI-green PR ("ready to merge") — merging is your call;
  • a PR whose review someone else actually owes you — out of your hands.
Both appear in the dashboard as facts; neither becomes the #1 move.
"Out of your hands" is a claim about a person who exists. That second state holds only when somebody has genuinely been asked — a requested reviewer, or a non-author who already reviewed. Inferring it from authorship instead ("you opened it, so you must be waiting on someone") is the assumption that breaks the whole dashboard on a solo repo: every PR is yours, nobody was ever asked, and every row reads waiting on them in perpetuity while the ladder crowns something else. So read the wait off
reviewRequests
and
latestReviews
, never off
author
— and when the answer is nobody, the PR isn't out of your hands at all. It's stuck on you, and it ranks on the full ladder.
statuskit crowns the review, never the merge. That's what keeps the new rung from contradicting the rule above it: reviewing an unreviewed PR is real work with an observable finish, while pressing merge is the judgment call statuskit stays out of. The crowned move ends at reviewed and hands the merge decision back to you.
unblocks(X)
是指X完成后,变为完全可处理的开放issue数量。
它回答了「这些任务中,哪一个能释放最多独立工作」,并在两个已列出具体对象的表格中作为可排序列展示。
「完全」一词是关键规则。如果#19同时被#12和#23阻塞,仅关闭#12并不会让#19变得可处理——只是减少了阻塞因素,这对寻找可接手任务的人来说毫无价值。只有当移除#12后,其开放阻塞因素集合变为时,该issue才会计入
unblocks(#12)
。任何更宽松的定义都会夸大数值,引导用户处理错误的issue,这比不排序更糟糕。
PR的影响力通过其关闭的issue传递:
unblocks(PR #34)
等于其
closingIssuesReferences
中issue的影响力。这将
Closes
列从一个事实转化为优先级——能释放三个issue的审核比不能释放任何issue的审核更重要,无论CI状态如何。
有四条规则确保数值准确:
  • 仅深度1的依赖。 不计算级联依赖。传递性数值假设中间issue会被完成而非仅仅解锁,这是statuskit无权做出的进度预测——且深度1的依赖天然避免循环,而传递性遍历需要防范「A被B阻塞,B被A阻塞」的循环。
  • 仅计入仍开放的阻塞因素,与阻塞集合遵循相同规则。
  • 阻塞因素可以是PR。 GitHub上的issue和PR共享一个命名空间,因此
    blockedBy: #34
    可以表示「等待合并」,并会从开放PR的扫描结果中解析。
  • 无已声明依赖则不显示该列。 当仓库的依赖图没有任何边时,所有值都是0,该列会产生误导:它显示为「没有任务能解锁任何内容」,但实际情况是「无人记录依赖关系」。此时应删除该列,仅说明一次——未声明依赖关系;无法计算影响力——并引导至issuekit,因为声明依赖是追踪器的卫生工作,而非扫描工具的职责。
有两种状态会被展示但永远不会被选为最优行动,因为处理这些状态需要人工决策,而非statuskit应推动的「优先完成」目标:
  • 已批准且CI通过的PR(「准备合并」)——合并由你决定;
  • 确实需要他人审核的PR——不在你的掌控范围内。
这两种状态都会在仪表盘中作为事实展示;但都不会成为头号行动。
「不在你的掌控范围内」是针对真实存在的人的判断。 第二种状态仅当有人确实被请求审核时成立——包括被请求的审核者,或已审核的非作者。若从作者身份推断(「你发起了PR,因此你肯定在等待他人」),会在单人仓库中破坏整个仪表盘:所有PR都是你发起的,从未请求过任何人,每一行都会永久显示等待他人,而梯级会选择其他行动。因此应从
reviewRequests
latestReviews
读取等待状态,而非从
author
读取——当结果是无人时,该PR并非不在你的掌控范围内,而是卡在了你这里,它会在完整梯级中排序。
statuskit会将审核选为最优行动,而非合并。 这能确保新梯级不与上述规则冲突:审核未审核的PR是有明确完成标志的实际工作,而点击合并是statuskit应回避的判断决策。最优行动在审核完成时结束,将合并决策交还给你。

Procedure

流程

1. Preflight — degrade per source, never fail wholesale

1. 预检——按数据源降级,绝不整体失败

statuskit is git-first: git signals always drive it, and GitHub signals enrich it when available. Detect what's present and adapt, rather than bailing:
  • Not a git repo → say so; skip everything git-derived. If there's no repo yet, the move is "start with
    plankit
    ."
  • gh
    missing / unauthenticated / no remote
    → drop to the git-only ladder below. This is a first-class mode, not an error — name the actual gap once (
    gh
    is not installed, run
    gh auth login
    , or add a GitHub remote) and carry on.
  • No plan docs → skip the plans read entirely. The Plans panel is conditional even when plans do exist — see the plans survey.
  • No shell at all (e.g. a browser-based agent) → you can't run the survey; print the commands below for the user to run and reason from what they paste back.
statuskit是git优先的:git信号始终主导它,当GitHub信号可用时会作为补充。检测可用的数据源并适配,而非直接失败:
  • 不是git仓库 → 说明情况;跳过所有git相关的扫描。如果还没有仓库,行动是「使用
    plankit
    启动项目」。
  • gh
    缺失/未认证/无远程仓库
    → 切换到下文的仅Git模式梯级。这是一等模式,而非错误——仅说明一次实际缺口(
    gh
    未安装,请运行
    gh auth login
    ,或添加GitHub远程仓库),然后继续执行。
  • 无计划文档 → 完全跳过计划扫描。即使存在计划文档,Plans面板也是有条件显示的——详见计划扫描
  • 完全没有shell环境(例如基于浏览器的Agent)→ 无法运行扫描;为用户打印以下命令,让用户运行后粘贴结果进行分析。

2. Survey — collect signals read-only

2. 扫描——只读收集信号

Gather git always; gather GitHub only when
gh
is usable. All commands are read-only.
git (always):
  • working tree —
    git status --porcelain
    , current branch, upstream ahead/behind,
    git log @{u}.. --oneline
    (unpushed — skip if the branch has no upstream set, which is itself the "push/publish" signal),
    git stash list
    , and any local branches carrying unmerged commits.
  • the base branch — from gitkit, not an assumption that it's
    main
    . Every "is this a feature branch?" and "is it unmerged?" judgment below turns on it, and on a
    develop
    - or
    trunk
    -defaulted repo, assuming
    main
    misreads the whole dashboard.
  • branch → issue mapping — resolve the current branch to a tracked issue from its open PR's
    closingIssuesReferences
    (the reliable signal, and already in hand from the PR read below); fall back to a branch-name heuristic. The branch-name pattern comes from gitkit, which named the branch in the first place (
    issue-<n>-<slug>
    ) — read it there rather than keeping a second copy of the parser here, or a rename upstream leaves this one silently matching nothing. A bare
    #N
    or a slug matching an issue title are the looser fallbacks. When it stays unmappable, treat a dirty branch that isn't the base as continue, not commit.
  • worktrees — when the survey needs to know where a branch's code lives, ask gitkit rather than reading paths. statuskit never creates or removes one; it only reports.
GitHub (only when
gh
is usable):
  • issues —
    gh issue list --state open --json number,title,labels,updatedAt,blockedBy,blocking
    , bucketed by lifecycle label (
    in-progress
    /
    ready
    /
    blocked
    /
    in-review
    ) plus an unlabeled/other-status bucket for repos without that vocabulary. Counts and the actionable set only — no drift detection. Treat recent unlabeled issues as candidates for classification or planning, not as invisible work.
  • priority — read from that same
    labels
    array, so it costs no extra call: the survey is already fetching every label on every open issue, and priority is four of the names in it. Take the highest when an issue carries more than one (a tracker slip, not a state —
    issuekit triage
    repairs it), and
    none
    when it carries none. When no open issue carries any priority label, drop the column rather than printing a wall of
    , and say it once — no priorities set; ranking on leverage and recency — pointing at issuekit triage. This is the same rule the all-zero leverage column follows and for the same reason: a column whose values never vary reads as a fact that was checked and came back empty, when the truth is that nobody has filled it in yet.
  • the dependency graph
    blockedBy
    and
    blocking
    from that same call are GitHub's native issue dependencies, so the graph arrives already resolved: no body scraping, no per-issue fetch, no second round trip. Read them defensively (
    (.blockedBy // []) | length
    ) rather than assuming a field layout, and when a repo doesn't use the feature fall back to the text convention — a
    Blocked by #N
    /
    Depends on #N
    /
    Blocks #N
    line in the body, extracted in the shell with
    --jq
    so bodies never enter context. Both directions describe the same edge; normalize to one.
  • the unblocked set — every open issue that is neither blocked nor
    in-review
    , kept as number + bucket + priority +
    updatedAt
    +
    unblocks
    count
    + title rather than folded into a count. This is the pick-up-now list, and the dashboard prints it as a table so you can act on one without a second
    gh
    call. An issue is blocked when it has a still-open
    blockedBy
    entry, or carries the
    blocked
    label. It is
    in-review
    when it carries the
    in-review
    label — its code is already written and its next move is a review, which the waiting-for-review table below names in full, so repeating it here would pad the pick-up-now list with the one thing you cannot pick up. Everything else is unblocked:
    ready
    , unlabeled or needs-planning, and
    in-progress
    work you can resume. Sort by priority descending, then
    unblocks
    descending, then most-recently-updated
    — a declared priority outranks an inferred one, leverage orders what nobody ranked differently, and recency survives as its own column rather than as the sort order.
  • the blocked set — the other half of that same read, kept as number + priority + what it's waiting on + title. Priority earns its place here even though nothing in this table can be picked up, because it's what tells you whether the blocker is worth chasing: a
    critical
    sitting behind an unstarted prerequisite is the strongest argument in the whole dashboard for starting that prerequisite, and without the column it looks like any other waiting row. The blocker comes from
    blockedBy
    when it's there, a
    Blocked by #N
    /
    Depends on #N
    line when it isn't, and is unnamed when all you have is the bare
    blocked
    label. Keep all three forms; an unnamed blocker is still a fact worth printing. Reporting what an issue says it's waiting on is a fact read, not a tracker verdict — the moment you're judging whether that blocker is still real, you've crossed into issuekit and should be pointing at it.
  • open PRs —
    gh pr list --json number,title,author,statusCheckRollup,reviewDecision,reviewRequests,latestReviews,isDraft,updatedAt,closingIssuesReferences
    , classified into: your red / change-requested PR (actionable), nobody is reviewing it (actionable), approved + green (surface-only), genuinely awaiting someone else (surface-only). Cap the list on large repos to stay fast; if a JSON field is rejected, check
    gh pr list --json
    with no value, which prints the field list your
    gh
    accepts.
  • what each PR closes
    closingIssuesReferences
    from that same call, not a
    Closes #N
    scrape of the body. It's GitHub's own resolved linkage, so it covers
    Closes
    /
    Fixes
    /
    Resolves
    in any casing and issues linked by hand in the UI, and it can't be fooled by the phrase appearing in a code block or a quoted review comment.
  • the waiting-for-review set — every open non-draft PR whose review is still outstanding (
    reviewDecision
    empty or
    REVIEW_REQUIRED
    ), kept as number + what it closes + priority +
    unblocks
    count
    + CI state + author + whose move it is +
    updatedAt
    + title, in that order — the ID and the work it retires belong side by side, since together they're the whole reason to care about the row. Sort by priority descending, then
    unblocks
    descending, then most-recently-updated, the same way the unblocked set does.
  • a PR's priority is the highest priority among the issues it closes — the same way leverage flows to PRs through
    closingIssuesReferences
    , and for the same reason: a PR has no importance of its own, only the importance of the work it retires. Take the highest rather than an average, because merging the PR delivers all of those issues and the most urgent one is what's actually waiting. A PR that closes nothing has no priority — print
    , and let leverage and recency order it. The dashboard prints these as a table, because "3 awaiting review" tells you nothing about which one is yours to nudge and which is somebody else's to answer.
  • whose move it is — read it off the reviewers, never the author. Three outcomes, checked in this order: you appear in
    reviewRequests
    yours, go review it; somebody else appears in
    reviewRequests
    , or a non-author appears in
    latestReviews
    theirs, name them, you're genuinely waiting; neither → nobody is reviewing it, which is a stuck PR wearing a waiting PR's clothes. That third case is every PR on a solo repo and a routine slip on a team one (you opened it and never requested anyone), and both have the same shape — no review is coming unless you do something — so it's the only one of the three that ranks.
  • is anyone else even able to review? — asked only when that third case fires, and only once per run:
    gh api repos/{owner}/{repo}/collaborators --jq 'length'
    . Exactly one collaborator proves no other reviewer exists, so the move is self-review outright. More than one — or a 403, an error, any answer you didn't get — means you can't rule a reviewer out, so the move names both halves ("request a reviewer, or self-review it"). Never spend the call when no PR needs it, and never let its failure cost you the row: the whose-move column is already correct without it, and the probe only sharpens the wording of the recommendation.
  • stale-tracker signal — one cheap cross-check: how many merged PRs have a linked issue still open. A single count, used only to decide whether "reconcile" ranks. Never itemize which or why — that's issuekit's job.
plans (filesystem — the list always runs, the unfiled check needs a tracker):
  • list canonical
    docs/plans/plan-<slug>-YYYY-MM-DD.md
    files (or wherever the repo keeps plans — an
    rfcs/
    ,
    specs/
    , or documented location takes precedence). The list is free and always runs; it's what the ladder's plan rungs read.
  • the unfiled set — computed only when the repo actually tracks work in GitHub issues. Cross-check each plan against the issue list and keep the ones that never became an issue. Match over
    --state all
    , not the open-issues read the rest of the survey uses —
    gh issue list --state all --json number,title --limit 200
    , one call, spent only when plan docs exist. A plan that shipped months ago has a closed issue, so matching against open issues alone would report every finished plan as neglected, which is the failure mode that makes this panel worth suppressing in the first place.
  • That same call is the tracker-in-use check, so it costs nothing extra. An error (issues disabled on the repo) or an empty array means there is nothing for a plan to be unfiled against — skip the comparison, print no Plans panel, and never report a plan as unfiled by default. Same when
    gh
    is unusable at all. Plenty of projects track work in Linear, Jira, a
    TODO.md
    , or somebody's head; a survey that announces "18 unfiled" on one of them is reporting its own blind spot as a finding, and pointing the user at
    issuekit create
    for a tracker they deliberately don't use.
  • Match on the plan's slug and its title, and when the match is uncertain call it filed. An issue whose title matches the plan's title, or whose body links the plan's path, or whose slug matches — any one is enough. The asymmetry is deliberate: this panel only ever prints gaps, so a false negative costs one silent line and a false positive sends the user off to file a duplicate of work already tracked.
始终收集git信号;仅当
gh
可用时收集GitHub信号。所有命令都是只读的。
git(始终收集):
  • 工作区——
    git status --porcelain
    、当前分支、上游分支的领先/落后情况、
    git log @{u}.. --oneline
    (未推送的提交——如果分支没有设置上游则跳过,这本身就是「推送/发布」信号)、
    git stash list
    ,以及任何包含未合并提交的本地分支。
  • 基准分支——从gitkit获取,而非假设它是
    main
    。下文所有「这是特性分支吗?」和「它未合并吗?」的判断都依赖于基准分支,在默认使用
    develop
    trunk
    的仓库中,假设为
    main
    会完全误读仪表盘。
  • 分支→issue映射——从其开放PR的
    closingIssuesReferences
    将当前分支解析为追踪的issue(这是可靠信号,已从下文的PR扫描中获取);若失败则使用分支名称启发式规则。分支名称模式来自gitkit,它最初命名了该分支(
    issue-<n>-<slug>
    )——应从gitkit读取该模式,而非在此处保留解析器的副本,否则上游重命名会导致此处无法匹配。宽松的备选方案是分支名包含
    #N
    或与issue标题匹配的slug。当无法映射时,将非基准分支的脏分支视为继续处理,而非提交
  • 工作区(worktrees)——当扫描需要知道分支代码所在位置时,询问gitkit而非读取路径。statuskit永远不会创建或删除工作区;仅会报告。
GitHub(仅当
gh
可用时收集):
  • issues——
    gh issue list --state open --json number,title,labels,updatedAt,blockedBy,blocking
    ,按生命周期标签(
    in-progress
    /
    ready
    /
    blocked
    /
    in-review
    )分类,加上一个未标记/其他状态分类,适用于没有该词汇体系的仓库。仅收集数量和可行动集合——不进行漂移检测。将近期未标记的issue视为需要分类或规划的候选对象,而非不可见的工作。
  • 优先级——从同一
    labels
    数组中读取,因此无需额外调用:扫描已获取每个开放issue的所有标签,优先级是其中四个标签名称。当issue携带多个优先级标签时,取最高等级(这是追踪器失误,而非状态——
    issuekit triage
    会修复);当没有优先级标签时,取
    none
    当没有开放issue携带任何优先级标签时,删除该列,而非打印满屏的
    ,并仅说明一次——未设置优先级;按影响力和时效性排序——引导至issuekit triage。这与全零影响力列遵循相同规则,原因也相同:数值从未变化的列会被视为已检查且结果为空的事实,但实际情况是无人填写该信息。
  • 依赖图——从同一调用中获取
    blockedBy
    blocking
    ,这是GitHub的原生issue依赖关系,因此依赖图已解析完成:无需解析body、无需逐个issue获取、无需二次请求。防御式读取(
    (.blockedBy // []) | length
    ),而非假设字段结构;当仓库不使用该功能时,回退到文本约定——body中的
    Blocked by #N
    /
    Depends on #N
    /
    Blocks #N
    行,使用
    --jq
    在shell中提取,避免body进入上下文。两个方向描述的是同一条边;归一化为一种形式。
  • 未阻塞集合——所有未阻塞且非
    in-review
    的开放issue,保留编号+分类+优先级+
    updatedAt
    +
    unblocks
    计数
    +标题,而非合并为数量。这是可立即接手的列表,仪表盘会将其打印为表格,以便你无需再次调用
    gh
    即可行动。当issue有仍开放的
    blockedBy
    条目,或携带
    blocked
    标签时,视为阻塞状态。当issue携带
    in-review
    标签时,视为
    in-review
    状态——其代码已编写完成,下一步是审核,下文的等待审核表格会完整列出,因此在此处重复会让可立即接手的列表包含无法接手的任务。其余所有状态均为未阻塞:
    ready
    、未标记或需要规划、以及可恢复的
    in-progress
    工作。按优先级降序、
    unblocks
    降序、最近更新时间排序
    ——已声明的优先级优于推断的优先级,影响力对无人排序的任务进行排序,时效性作为单独列保留而非排序依据。
  • 阻塞集合——同一扫描结果的另一半,保留编号+优先级+等待对象+标题。即使该表格中的任务无法接手,优先级仍有其价值,因为它能告诉你阻塞因素是否值得处理:标记为
    critical
    的issue被未启动的前置任务阻塞,这是整个仪表盘中最有力的启动该前置任务的理由,若没有该列,它看起来就像其他等待行一样。阻塞因素优先从
    blockedBy
    获取,若没有则从
    Blocked by #N
    /
    Depends on #N
    行获取,若只有
    blocked
    标签则显示为未命名。保留这三种形式;未命名的阻塞因素仍是值得打印的事实。报告issue声称等待的对象是读取事实,而非追踪器判断——当你判断该阻塞因素是否仍然有效时,就已经进入了issuekit的职责范围,应引导至该工具。
  • 开放PRs——
    gh pr list --json number,title,author,statusCheckRollup,reviewDecision,reviewRequests,latestReviews,isDraft,updatedAt,closingIssuesReferences
    ,分类为:你的红色/需要修改的PR(可行动)、无人审核(可行动)、已批准且CI通过(仅展示)、确实等待他人(仅展示)。在大型仓库中限制列表长度以保持速度;若某个JSON字段被拒绝,检查
    gh pr list --json
    不带参数的输出,它会打印你的
    gh
    版本支持的字段列表。
  • 每个PR关闭的内容——从同一调用中获取
    closingIssuesReferences
    ,而非从body中抓取
    Closes #N
    。这是GitHub自己解析的关联关系,因此涵盖任何大小写的
    Closes
    /
    Fixes
    /
    Resolves
    ,以及在UI中手动关联的issue,且不会被代码块或引用的审核评论中的相同短语误导。
  • 等待审核集合——所有未标记为草稿且审核仍未完成的开放PR(
    reviewDecision
    为空或
    REVIEW_REQUIRED
    ),保留编号+关闭的内容+优先级+
    unblocks
    计数
    +CI状态+作者+下一步由谁处理+
    updatedAt
    +标题,按此顺序排列——ID和它完成的工作应并列,因为两者共同构成了你关心该行的全部理由。按优先级降序、
    unblocks
    降序、最近更新时间排序,与未阻塞集合的排序方式相同。
  • PR的优先级是其关闭的issue中的最高优先级——影响力通过
    closingIssuesReferences
    传递给PR的方式与此相同,原因也相同:PR本身没有重要性,仅其完成的工作有重要性。取最高优先级而非平均值,因为合并PR会完成所有相关issue,而最紧急的那个才是实际等待处理的。未关闭任何issue的PR没有优先级——打印
    ,由影响力和时效性排序。仪表盘将其打印为表格,因为「3个等待审核」无法告诉你哪个是你需要跟进的,哪个是他人需要回复的。
  • 下一步由谁处理——从审核者读取,而非作者。 三种结果,按以下顺序检查:你出现在
    reviewRequests
    中 → 你的任务,去审核;其他人出现在
    reviewRequests
    中,或非作者出现在
    latestReviews
    中 → 他们的任务,指明具体人员,你确实在等待;两者都不是 → 无人审核,这是一个伪装成等待状态的卡住的PR。第三种情况在单人仓库中很常见,在团队仓库中也是常见失误(你发起了PR但从未请求任何人审核),两种情况的本质相同——除非你采取行动,否则不会有审核——因此这是三种情况中唯一会被排序的。
  • 是否有其他人能够审核?——仅当第三种情况出现时询问,且每次运行仅询问一次:
    gh api repos/{owner}/{repo}/collaborators --jq 'length'
    。恰好一个协作者证明没有其他审核者存在,因此行动是直接自我审核。多于一个协作者——或出现403错误、其他错误、未获取到答案——意味着无法排除审核者存在的可能,因此行动会同时说明两种选择(「请求审核者,或自我审核」)。仅当有PR需要时才进行该调用,且调用失败不会影响该行:下一步由谁处理的列在没有该探测时已经正确,探测仅优化建议的措辞。
  • 追踪器过时信号——一个简单的交叉检查:有多少已合并的PR关联的issue仍处于开放状态。仅使用单个计数来决定「协调」是否应被排序。绝不列出具体是哪些或原因——这是issuekit的职责。
计划(文件系统——列表始终运行,未归档检查需要追踪器):
  • 列出标准的
    docs/plans/plan-<slug>-YYYY-MM-DD.md
    文件(或仓库存储计划的其他位置——
    rfcs/
    specs/
    或已记录的位置优先)。列表是免费的且始终运行;梯级中的计划梯级会读取该列表。
  • 未归档集合——仅当仓库实际在GitHub issues中追踪工作时计算。 将每个计划与issue列表交叉检查,保留从未转化为issue的计划。匹配时使用**
    --state all
    **,而非扫描其余部分时使用的开放issue读取——
    gh issue list --state all --json number,title --limit 200
    ,一次调用,仅当存在计划文档时执行。数月前已交付的计划会有一个已关闭的issue,因此仅匹配开放issue会将所有已完成的计划报告为未归档,这正是该面板需要默认隐藏的失败模式。
  • 同一调用也是追踪器使用情况检查,因此无需额外成本。 错误(仓库禁用issues)或空数组意味着没有计划可归档的对象——跳过比较,不打印Plans面板,且默认绝不报告计划未归档。当
    gh
    完全不可用时也是如此。许多项目在Linear、Jira、
    TODO.md
    或某个人的头脑中追踪工作;若扫描工具在这样的项目中宣布「18个未归档计划」,则是将自身的盲点作为发现,并引导用户使用
    issuekit create
    创建一个他们故意不使用的追踪器。
  • 按计划的slug和标题匹配,当匹配不确定时视为已归档。 issue标题与计划标题匹配、或body链接计划路径、或slug匹配——满足任一条件即可。这种不对称是刻意的:该面板仅打印缺口,因此假阴性只会遗漏一行,而假阳性会引导用户去归档已追踪工作的副本。

3. Rank — crown one finish-first move

3. 排序——确定一个优先完成的行动

Map the signals onto candidate actions, each tagged with its owning kit/command, then crown the highest applicable rung — ties inside it break as the rung's own row says, defaulting to most-recently-active, and everything else becomes a runner-up. Pick the ladder by whether GitHub signals are available.
Git-only ladder (no
gh
):
#StateMove →
1uncommitted work on a feature branchcontinue /
commitkit
2unpushed commits
git push
3a stashrestore or drop it
4an unmerged local feature branchfinish it, or clean it up —
gitkit
5a plan doc on disk — filed or not is unknowable with no tracker to checkimplement the newest —
implementkit
6clean on the base branch, nothing pendingstart something (newest plan) /
plankit
Full ladder (
gh
available) — every git-only state has an explicit home below. (Surfaced, never crowned: an approved+green PR; a PR someone else genuinely owes you.)
#StateMove →
0a workable
critical
issue — open, unblocked, and not already the crowned move
drop what you're on:
issuekit start
if it's
ready
, resume it if it's
in-progress
1your PR is red or change-requestedfix CI / address review —
mergekit fix
2your PR that nobody is reviewing (highest priority, then
unblocks
, then most-recently-updated)
self-review it —
mergekit <N>
, or request a reviewer
3in-progress issue whose branch you're on (uncommitted work folds in here as "continue")resume /
implementkit
4orphaned work — uncommitted on the base branch or an untracked branch, or unpushed commits
commitkit
/ push
5a stashrestore it to finish the work, or drop it if obsolete
6an unmerged local feature branchfinish it, or clean it and its worktree up —
gitkit
7stale-tracker signal firedreconcile —
issuekit sync
8a
ready
issue to start (highest priority, then
unblocks
, then most-recently-updated)
issuekit start
(worktree via
gitkit
), then
implementkit
9an unlabeled/other-status issue needing classificationclassify it —
issuekit triage
10an unassessed backlog — open issues with no priority labelrank them —
issuekit triage
11an unfiled plan (only when the tracker is in use), or no plans at all
issuekit create
/
plankit
Rung 0 is numbered zero because it isn't really a rung — it's the one documented override of the finish-first spine, and numbering it inside the sequence would make it look like an ordinary state that merely happens to sort first. It fires rarely, it must name what it displaced, and everything below it is the actual ladder. If rung 0 is firing on most runs,
critical
has stopped meaning anything and the real move is
issuekit triage
.
Rung 10 ranks below every actionable rung and above "go plan something." An unranked backlog is a genuine gap — nothing above it can order itself properly — but it is still tracker hygiene rather than work, so it never outranks a thing the user could actually finish. It earns a rung at all because without one, a repo where nobody has set a single priority would silently rank on leverage forever and never be told why.
Rung 11's first half only exists when the unfiled set was computed. A repo that doesn't track work in GitHub issues gives statuskit no way to tell a filed plan from an unfiled one, so it never asserts one is unfiled — the rung reduces to its second half, no plans at all →
plankit
. Ranking "file your plans" at a project that files its work somewhere else is worse than staying quiet: it's a confident recommendation built on a read that never happened.
Rungs 1 and 2 are the same thought twice: your own PR is stuck on you. A red PR is stuck loudly and an unreviewed one silently, and the silent kind is the one that sits for weeks, which is why it outranks resuming a half-built issue rather than trailing it — the code is already written and green, so it retires the most work for the least effort, which is the whole of finish-first. It's the one rung that breaks ties on leverage while asking you to finish rather than start, because there's no context to switch back into: reviewing a finished PR is the same work whichever one you pick, so the tiebreak may as well go to the one that frees the most.
When the owning kit isn't installed, name the plain action instead ("commit your changes" rather than "run commitkit") — statuskit routes, it doesn't require the ecosystem.
将信号映射到候选行动,每个行动标记其所属的kit/命令,然后选择最高适用的梯级——梯级内的平局按梯级自身规则打破,默认按最近活跃时间排序,其余行动成为备选。根据GitHub信号是否可用选择梯级。
仅Git模式梯级(无
gh
):
#状态行动 →
1特性分支上有未提交的工作继续处理 /
commitkit
2有未推送的提交
git push
3有stash恢复或丢弃
4有未合并的本地特性分支完成它,或清理——
gitkit
5磁盘上有计划文档——无追踪器无法判断是否已归档实现最新的计划——
implementkit
6基准分支干净,无待处理任务启动新任务(最新计划)/
plankit
完整模式梯级
gh
可用)——所有仅Git模式的状态在下文都有明确位置。(仅展示,不会被选为最优行动:已批准且CI通过的PR;确实需要他人审核的PR。)
#状态行动 →
0存在**可处理的
critical
**issue——开放、未阻塞,且未被选为最优行动
放下当前工作:若为
ready
状态则使用
issuekit start
,若为
in-progress
状态则恢复处理
1你的PR是红色或需要修改修复CI / 处理审核意见——
mergekit fix
2你的PR无人审核(优先级最高,其次是
unblocks
,然后是最近活跃时间)
自我审核——
mergekit <N>
,或请求审核者
3你所在分支对应的in-progress issue(未提交工作归入此处的「继续处理」)恢复处理 /
implementkit
4孤立工作——基准分支或未追踪分支上的未提交工作,或未推送的提交
commitkit
/ 推送
5有stash恢复以完成工作,或若过时则丢弃
6有未合并的本地特性分支完成它,或清理它及其工作区——
gitkit
7追踪器过时信号触发协调——
issuekit sync
8可启动的
ready
issue(优先级最高,其次是
unblocks
,然后是最近活跃时间)
issuekit start
(通过
gitkit
创建工作区),然后
implementkit
9需要分类的未标记/其他状态issue分类——
issuekit triage
10未评估的待办列表——无优先级标签的开放issue排序——
issuekit triage
11未归档计划(仅当使用追踪器时),或无任何计划
issuekit create
/
plankit
梯级0编号为0,因为它并非真正的梯级——它是唯一有文档记录的覆盖规则,若将其编号到序列中,会让它看起来像一个普通的、只是排序靠前的状态。它很少触发,必须指明它取代的行动,其下方才是实际的梯级。如果梯级0在大多数运行中都触发,说明
critical
已失去意义,实际行动是
issuekit triage
梯级10排在所有可行动梯级之后,「去规划新任务」之前。 未排序的待办列表是一个真实的缺口——其上方的所有任务无法正确排序——但它仍是追踪器卫生工作而非实际工作,因此永远不会优先于用户实际可以完成的任务。它能成为一个梯级,是因为如果没有它,无人设置优先级的仓库会永远按影响力排序,且永远不会被告知原因。
梯级11的第一部分仅当计算了未归档集合时存在。 不在GitHub issues中追踪工作的仓库无法让statuskit区分已归档和未归档计划,因此它绝不会断言某个计划未归档——该梯级简化为第二部分,无任何计划 →
plankit
。在其他地方归档工作的项目中,排序「归档你的计划」比保持沉默更糟糕:这是基于未执行的读取做出的自信建议。
梯级1和2是同一思路的两种情况:你自己的PR卡在了你这里。 红色PR是明显卡住,而未审核的PR是隐性卡住,隐性卡住的PR会搁置数周,这就是为什么它优先于恢复未完成的issue而非排在后面——代码已编写完成且CI通过,因此它用最少的工作量完成最多的工作,这正是「优先完成」的核心。这是唯一在要求你完成而非启动任务时,按影响力打破平局的梯级,因为没有需要切换回去的上下文:审核已完成的PR的工作量是相同的,因此平局时应选择能释放最多工作的那个。
当所属kit未安装时,改为命名普通行动(例如「提交你的更改」而非「运行commitkit」)——statuskit仅负责引导,不依赖生态系统。

4. Output — dashboard, then one crowned move

4. 输出——仪表盘,然后是一个最优行动

Print a compact panel (one line per signal source, empty panels suppressed — and Plans suppressed unless it has a finding, below), then the ranked next-actions list with the #1 move bolded and its exact kit/command. Three tables carry the detail a bare count can't — the unblocked issue IDs, the PRs waiting for review, and the blocked issues with their blocker — because those are the three places a number sends you straight back to
gh
to find out which. They print in that order, and the last one sits below the Pull requests panel rather than under the Issues count line. Keep it to one screen:
undefined
打印紧凑的面板(每个信号源一行,隐藏空面板——Plans面板仅当有发现时显示,下文详述),然后是排序后的下一步行动列表,头号行动加粗并显示其确切的kit/命令。三个表格承载了单纯数字无法传达的细节——未阻塞issue的ID、等待审核的PR、带有阻塞因素的阻塞issue——因为这三个地方的数字会让你直接回到
gh
去查找具体是哪个。表格按此顺序打印,最后一个表格位于Pull requests面板下方而非Issues计数行下方。保持内容在一屏内:
undefined

Project status — <repo> · <branch> · YYYY-MM-DD

Project status — <repo> · <branch> · YYYY-MM-DD

Working tree <clean | N uncommitted · M unpushed · stash K>

Working tree <clean | N uncommitted · M unpushed · stash K>

Issues in-progress N · ready N · in-review N · blocked N (omit without gh)

Issues in-progress N · ready N · in-review N · blocked N (omit without gh)

Unblocked (N) — highest priority first
IssuePriorityUnblocksStatusLast activeTitle
#12critical3ready2d<title>
#31high1in-progress4h<title>
#470unlabeled3w<title>
Unblocked (N) — highest priority first
IssuePriorityUnblocksStatusLast activeTitle
#12critical3ready2d<title>
#31high1in-progress4h<title>
#470unlabeled3w<title>

Pull requests <open N — X awaiting review, Y CI-red, Z ready to merge> (omit without gh)

Pull requests <open N — X awaiting review, Y CI-red, Z ready to merge> (omit without gh)

Waiting for review (X) — highest priority first
PRClosesPriorityUnblocksCIAuthorNext moveLast activeTitle
#34#12critical3younobody reviewing → yours1d<title>
#29#19, #23high0@someoneyours6h<title>
#380youtheirs — @reviewer2w<title>
Blocked issues (N)
IssuePriorityWaiting onTitle
#19high#12<title>
#23low
blocked
label, no blocker named
<title>
Waiting for review (X) — highest priority first
PRClosesPriorityUnblocksCIAuthorNext moveLast activeTitle
#34#12critical3younobody reviewing → yours1d<title>
#29#19, #23high0@someoneyours6h<title>
#380youtheirs — @reviewer2w<title>
Blocked issues (N)
IssuePriorityWaiting onTitle
#19high#12<title>
#23low
blocked
label, no blocker named
<title>

Plans <M unfiled — plan-debugkit, plan-testkit> (omit entirely unless M ≥ 1)

Plans <M unfiled — plan-debugkit, plan-testkit> (omit entirely unless M ≥ 1)

Next move

Next move

→ <the #1 action> — run
<kit / command>
.
Then:
  • <runner-up>
    <kit / command>
  • <runner-up>
    <kit / command>
  • <runner-up>
    <kit / command>

**Every move line is written in the procedural register.** The crowned move, the runner-ups, and the snapshot's checkbox list are read at a glance by someone deciding what to touch next, so use ASD-STE100 Simplified Technical English: one instruction per line, active voice, present tense, name the actor, no metaphor and no word carrying a second meaning. Say "merge #34" and "file the backlog", not "get #34 over the line". Keep one term per thing across the whole dashboard — a move that calls it the *plan doc* and a panel that calls it the *unfiled plan* read as two different objects. This applies to the printed dashboard and the snapshot file alike, and it is what lets the block be scanned rather than read.

**A signal panel is one line.** Working tree, Issues, Pull requests, Plans — heading and counts on the same line, nothing following but a table. No paragraph, no parenthetical tracing a plan to the commit that shipped it, no clause explaining why a count matters: that reasoning is an argument for a move, so it belongs in the move, where the user can act on it. The entire value of the block is that four lines tell you where the project stands before you've started reading, and a panel that grows a second sentence has quietly become a report. `Next move` is the exception and the only one — it's the block everything above exists to produce.

**The three tables run in the order you can act on them, and blocked work goes last.** Unblocked leads, because it is the pick-up-now list. Waiting for review follows: that code is written and one review retires it. Blocked issues close the block, and they are the one table that leaves its panel — they sit under the Pull requests panel instead of under the Issues count line that counts them. The reason is that a reader scans from the top and stops when they find their next move, so every row they can act on must come before the first row they cannot. Call the table `Blocked issues (N)` in full: away from the Issues panel, a bare `Blocked (N)` reads as blocked PRs. This is the only table that detaches from its panel, and the only ordering exception in the block — nothing else moves.

**An `in-review` issue appears once, on the Pull requests panel, and never in the Unblocked table.** Its next move is a review of a PR, so the row that names a person and a CI state says strictly more than a second row in a table of work you can start. Keep the `in-review` count on the Issues line, because the count is still a fact about the tracker. The work never falls off the dashboard when its PR isn't waiting for review either: a red PR ranks as the crowned move, and an approved one prints under `Surfaced, not queued`. An `in-review` issue with **no open PR at all** is the one case with nothing to point at, and that is tracker drift rather than a gap in the table — say it in one line on the Issues panel (`in-review 7 — 1 with no open PR`) and route it to `issuekit triage`. Never repair it by putting the issue back in the Unblocked table, which would assert it is startable when somebody has already labelled it as being reviewed.

**Plans is the one conditional panel — it prints only when a plan doc never became an issue.** The other four report state that always exists: a tree is always in some condition, a repo always has some number of issues and PRs, and zero is a real reading of each. A plan count isn't like that. `21 filed · 0 unfiled` is a fact about a directory rather than a call to action, and it spends a line of the dashboard every single run to say nothing is wrong. So Plans is a **finding**, and a finding with nothing in it doesn't print: no plan docs, no tracker to compare them against, or every plan already filed all resolve to the same output — no Plans line at all, and no mention of why. When it does print, every name on it is something to act on, which is what earns it the space.

**Don't generalize that into "suppress the quiet panels."** Plans is conditional because its empty state is *unactionable*, not because it's boring — a clean working tree and an empty PR list are both things you actively want to see confirmed, and a dashboard whose panel set changes with the mood of the repo stops being comparable day to day. Plans is the exception, it stays the only one, and the same rule governs the snapshot file: no unfiled plan means no `## Plans` section in it either.

**The panel set is closed.** Working tree, Issues, Pull requests, Plans *(when it fires)*, Next move — that is the dashboard, plus **at most one** repo-specific panel when the repo keeps a first-class queue the standard five genuinely can't see (an `IDEAS.md` backlog, an RFC index). It takes the same shape as the rest: a name, one line, sourced from a file the survey read. Anything you'd have to *run* to fill a panel is out of bounds — statuskit surveys read-only, so a build, test, or lint result is not a signal it has, and inventing a `Health` panel from one is both a mutation risk and a claim the survey can't back. Without this rule every run improvises a different set and no two days' files compare.

**The `Closes` column carries two signals.** Filled, it tells you what merging that PR actually retires — read against the `Blocked issues (N)` table it says which review is holding up which issue, which is the difference between "3 PRs awaiting review" and "reviewing #34 frees #19." Empty (`—`) is the more valuable reading: that PR will merge and leave its issue open, which is precisely the condition the stale-tracker signal counts after the fact. Seeing it *before* the merge costs nothing and is far cheaper than reconciling afterwards. Print `—`, never omit the cell — a blank reads as "not checked."

**`Next move` names a person, or admits there isn't one.** Three values, and the third is the one that earns the column: `yours` when you're the requested reviewer, `theirs — @name` when somebody specific owes you the review, and `nobody reviewing → yours` when no one was ever asked. Naming the reviewer in the middle case is what makes the claim checkable — an unattributed *theirs* is indistinguishable from the bug it replaces, where every PR you opened asserted a reviewer who didn't exist. **Drop the `Author` column entirely when every row shares one author**, and say it once on the count line instead (`Waiting for review (3) — all yours, highest leverage first`). It's the same rule the all-zero `Unblocks` column follows: a column whose values never vary spends width to report nothing, and on a solo repo a wall of `you` is worse than nothing because it looks like a fact that was checked.

**`Priority` and `Unblocks` sort, `Last active` informs.** The two actionable tables lead with priority, then leverage, because a column you have to scan is not a priority list — the row you should pick up next belongs on the first line, not somewhere in the middle where a big number happens to sit. Recency doesn't disappear, it moves into its own `Last active` column as a compact relative stamp (`4h`, `2d`, `3w`), so "what did I touch last" is still answerable at a glance without being the thing that decides the order. Say `— highest priority first` on the count line so the ordering is declared rather than inferred; a table that silently changed its sort is a table you'll misread once and distrust after. When no row carries a priority the column drops and the declaration reverts to `— highest leverage first`, which keeps the two honest together: the sort you announce is always the sort a reader can verify from the columns in front of them. The `Blocked issues (N)` table keeps its recency sort and gains no leverage column — nothing in it can be picked up, so ranking it by what it would free is a number with nowhere to go — but it *does* carry priority, because that's the column that says whether the blocker is worth chasing.

**The sort keys sit adjacent, and `Closes` never leaves the PR's side.** On the issue table that puts `Priority` and `Unblocks` immediately after the ID; on the PR table they go *after* `Closes`, because `PR | Closes` is the pairing that makes the row legible at all and inserting a sort key between them would cost more than the tidier grouping is worth. Everything after the sort keys is context, in decreasing order of how often you act on it.

**These tables are at their column budget, so lean on the drop rules.** Three columns disappear on their own — `Author` when every row shares one, `Unblocks` when every value is zero, `Priority` when nothing is ranked — and on a solo repo that's exactly how the nine-column PR table stays inside one screen. The rules aren't cleanup, they're what makes the full set affordable; skip them and the table wraps, at which point it communicates less than the bare count it replaced. If a table still doesn't fit after every drop rule has fired, cut `Title` to its first few words rather than dropping a sort key — a truncated title is still a hint, where a hidden sort key is a lie.

All three tables list **every** row that qualifies — the whole point is completeness, so don't trim to the interesting ones. On a repo big enough to blow the one-screen budget, cap at 10 rows and close with a `+N more` line naming the `gh` command that shows the rest; never truncate silently. An empty set drops the table but keeps its count line, so "0 waiting for review" still reads as a surveyed fact rather than a missing panel.

Runner-ups get **one line each, naming exactly one issue or PR** — never "start #12, #19 and #23" on a single line. This is the same rule the snapshot's checkboxes follow (see [Write the status snapshot](#5-write-the-status-snapshot--the-default-not-an-offer)), and it holds here so the printed list and the file agree item for item.

Drop any panel with nothing to show (no PRs → no PR line; no `gh` → omit Issues + PRs and say so once).
→ <the #1 action> — run
<kit / command>
.
Then:
  • <runner-up>
    <kit / command>
  • <runner-up>
    <kit / command>
  • <runner-up>
    <kit / command>

**每个行动行都使用过程式表述。** 最优行动、备选行动和快照的复选框列表都是供用户快速浏览以决定下一步操作的,因此使用ASD-STE100简化技术英语:每行一个指令,主动语态,现在时态,指明执行者,无隐喻和歧义词汇。例如说「merge #34」和「file the backlog」,而非「get #34 over the line」。整个仪表盘保持术语一致——若某个行动称其为*计划文档*,而面板称其为*未归档计划*,会让人误以为是两个不同的对象。这适用于打印的仪表盘和快照文件,也是让内容可快速扫描而非逐字阅读的关键。

**信号面板为一行。** Working tree、Issues、Pull requests、Plans——标题和计数在同一行,后面仅跟表格。无段落、无追溯计划到提交的括号说明、无解释计数重要性的从句:这些推理是行动的论据,应放在行动部分,让用户可以据此行动。该面板的全部价值在于,四行内容就能让你在开始阅读前了解项目状态,若面板扩展为第二句话,就悄悄变成了报告。`Next move`是唯一的例外——它是上方所有内容要生成的结果。

**三个表格按可行动顺序排列,阻塞工作排在最后。** 未阻塞列表在前,因为它是可立即接手的列表。等待审核列表紧随其后:代码已编写完成,一次审核即可完成任务。阻塞issue排在最后,且该表格会脱离其所属面板——它位于Pull requests面板下方而非计数它的Issues计数行下方。原因是读者从上到下扫描,找到下一步行动后就会停止,因此所有可行动的行必须排在不可行动的行之前。表格全名应为`Blocked issues (N)`:离开Issues面板后,单纯的`Blocked (N)`会被误认为是阻塞的PR。这是唯一脱离所属面板的表格,也是面板中唯一的排序例外——其他内容都不会移动。

**`in-review`的issue仅在Pull requests面板中出现一次,绝不会出现在Unblocked表格中。** 它的下一步是审核PR,因此指明人员和CI状态的行比可启动任务表格中的第二行提供的信息更多。Issues行保留`in-review`计数,因为该计数仍是追踪器的事实。当PR不等待审核时,工作也不会从仪表盘中消失:红色PR会被选为最优行动,已批准的PR会打印在`Surfaced, not queued`下。**没有开放PR的`in-review`issue**是唯一没有指向对象的情况,这是追踪器漂移而非表格缺口——在Issues面板中用一行说明(`in-review 7 — 1 with no open PR`)并引导至`issuekit triage`。绝不能将该issue放回Unblocked表格来修复,因为这会断言它可启动,但有人已将其标记为正在审核。

**Plans是唯一的条件面板——仅当计划文档从未转化为issue时打印。** 其他四个面板报告的状态始终存在:工作区始终处于某种状态,仓库始终有一定数量的issues和PRs,零是真实的读取结果。计划计数并非如此。`21 filed · 0 unfiled`是关于目录的事实而非行动号召,它每次运行都会占用仪表盘一行来说明没有问题。因此Plans是一个**发现**,无内容的发现不会打印:无计划文档、无追踪器可比较、或所有计划已归档,这些情况的输出都是相同的——无Plans行,也不说明原因。当它打印时,列出的每个名称都是可行动的,这正是它占用空间的理由。

**不要将此推广为「隐藏安静的面板」。** Plans面板是条件性的,因为其空状态是*不可行动的*,而非因为它无聊——干净的工作区和空PR列表都是你希望确认的状态,若仪表盘的面板集随仓库状态变化,就无法在不同日期进行比较。Plans是唯一的例外,且同样规则适用于快照文件:无未归档计划意味着文件中也无`## Plans`部分。

**面板集是固定的。** Working tree、Issues、Pull requests、Plans(触发时)、Next move——这就是仪表盘,加上**最多一个**仓库特定面板,当标准五个面板无法看到仓库的一等队列时(例如`IDEAS.md`待办列表、RFC索引)。它的形状与其他面板相同:一个名称、一行内容、来自扫描读取的文件。任何需要*运行*才能填充的面板都超出范围——statuskit是只读扫描,因此构建、测试或 lint 结果不是它能获取的信号,基于这些结果创建`Health`面板既有变更风险,也无法支撑扫描的主张。没有此规则,每次运行都会生成不同的面板集,不同日期的文件无法比较。

**`Closes`列承载两个信号。** 填充时,它告诉你合并该PR实际完成的工作——与`Blocked issues (N)`表格一起读取,它能说明哪个审核在阻塞哪个issue,这就是「3个PR等待审核」和「审核#34会释放#19」的区别。空值(`—`)是更有价值的读取结果:该PR合并后会让其issue保持开放,这正是追踪器过时信号事后计数的情况。在合并前看到这一点无需成本,且比事后协调便宜得多。打印`—`,绝不省略单元格——空白会被误认为「未检查」。

**`Next move`指明人员,或承认没有具体人员。** 三个值,第三个值是该列存在的理由:当你是被请求的审核者时为`yours`,当特定人员欠你审核时为`theirs — @name`,当从未请求任何人时为`nobody reviewing → yours`。在中间情况中指明审核者能让主张可验证——未指明的*theirs*与它替换的错误无法区分,即你发起的每个PR都断言存在一个不存在的审核者。**当所有行共享同一个作者时,完全删除`Author`列**,并在计数行中说明一次(`Waiting for review (3) — all yours, highest leverage first`)。这与全零`Unblocks`列遵循相同规则:数值从未变化的列占用宽度却报告无价值信息,在单人仓库中满屏的`you`比无内容更糟糕,因为它看起来像已检查的事实。

**`Priority`和`Unblocks`用于排序,`Last active`用于参考。** 两个可行动表格先按优先级、再按影响力排序,因为需要扫描的列不是优先级列表——你应接手的下一行应在第一行,而非中间某个位置有大数字的地方。时效性不会消失,它会移动到自己的`Last active`列,显示为紧凑的相对时间戳(`4h`、`2d`、`3w`),因此「我最后处理的是什么」仍可快速找到答案,而不会成为排序依据。在计数行中说明`— highest priority first`,以便排序是明确声明的而非推断的——悄悄改变排序的表格会让你误读一次,之后就不再信任。当没有行携带优先级时,该列会删除,声明会恢复为`— highest leverage first`,这能保持两者的一致性:你宣布的排序始终是读者可从面前的列验证的排序。`Blocked issues (N)`表格保持时效性排序,且无影响力列——其中的任务无法接手,因此按它能释放的工作排序是无意义的数字——但它*确实*携带优先级,因为该列能说明阻塞因素是否值得处理。

**排序键相邻,`Closes`永远不与PR分离。** 在issue表格中,`Priority`和`Unblocks`紧跟在ID之后;在PR表格中,它们位于`Closes`之后,因为`PR | Closes`是让该行清晰可读的配对,在两者之间插入排序键的成本高于更整洁分组的价值。排序键之后的所有内容都是上下文,按行动频率降序排列。

**这些表格已达列数上限,因此要遵循删除规则。** 三个列会自动消失——所有行共享同一作者时删除`Author`,所有值为零时删除`Unblocks`,无排序时删除`Priority`——在单人仓库中,这正是九列的PR表格保持在一屏内的方式。这些规则不是清理,而是让完整列集可承受的关键;跳过这些规则会让表格换行,此时它传达的信息比它取代的单纯数字更少。若应用所有删除规则后表格仍不适合,将`Title`截断为前几个词而非删除排序键——截断的标题仍是提示,而隐藏的排序键是谎言。

三个表格列出**所有**符合条件的行——完整性是关键,因此不要只保留有趣的行。在大到超出一屏预算的仓库中,限制为10行并以`+N more`行结尾,指明显示其余内容的`gh`命令;绝不静默截断。空集会删除表格但保留计数行,因此「0 waiting for review」仍会被视为已扫描的事实而非缺失的面板。

备选行动**每行一个,仅指明一个issue或PR**——绝不在一行中写「start #12, #19 and #23」。这与快照的复选框遵循相同规则(详见[写入状态快照](#5-写入状态快照——默认操作,而非可选操作)),且在此处保持一致,以便打印列表和文件逐项对应。

删除任何无内容可显示的面板(无PR → 无PR行;无`gh` → 省略Issues + PRs并说明一次)。

5. Write the status snapshot — the default, not an offer

5. 写入状态快照——默认操作,而非可选操作

Write the file every run. A terminal dashboard scrolls away and its ranked moves can't be ticked off; the same content on disk reads better and doubles as the run's to-do list. So don't ask permission — write it, then say where it went in one line:
Saved to
docs/status/status-<repo-slug>-YYYY-MM-DD.md
— scratch file, gitignored, not committed.
Skip only when asked. "Just print it", "no file", "don't write anything", "screen only", "/statuskit --no-file" — honor that for the run and print the dashboard alone. A skip applies to that run only; it isn't a standing preference unless the user says so or the repo's agent-guide file (
CLAUDE.md
or an equivalent) does. Skip silently too when there's no writable filesystem (below).
Where it goes.
docs/status/status-<repo-slug>-YYYY-MM-DD.md
— a short lowercase kebab-case slug (normally the repo name; use a narrower one such as the branch or issue when the snapshot covers a slice of the project) and the ISO creation date. Create
docs/status/
if it doesn't exist.
One file per day — always update, never add. Before writing, list
docs/status/
and look for a snapshot already carrying today's date. If one exists, that's the file: update it in place, keeping its existing name even if this run would have picked a different slug. Only when the directory has nothing dated today do you create a new file. A status file is a point-in-time read, and three of them from one afternoon is how a scratch directory becomes archaeology — worse, it splits the user's ticked boxes across files that all look current. If today's snapshot genuinely covers a different project in a monorepo, make the slug specific to that project and match on slug + date instead; there is no case where the same project gets two files on the same day, so never fall back to a sequence suffix.
Updating means merging, not overwriting. Re-derive the whole survey from git and GitHub — never trust what the file says — then carry over the checked state of every move that's still open, matching on its key (below) and nothing else. Rewrite every other word from the fresh survey: a move whose wording changed completely is the same move if its key matches, and a move that kept its wording by coincidence is a different one if its key doesn't. A ticked move that no longer applies goes to
Done today
; an unticked one that no longer applies just drops.
What it contains. The dashboard as printed, with two additions the file earns:
  • a provenance line recording when the snapshot was taken, against which commit, and how many times it's been rewritten today (
    Snapshot: 2026-07-23 14:20 · <branch> @ <short-sha> · run 3 today (first 09:05)
    ) — without it a stale file reads as current, and without the run count an afternoon rewrite is indistinguishable from the morning's original;
  • the ranked moves as a checkbox list so the file works as a to-do, crowned move first and each carrying its kit/command:
markdown
undefined
每次运行都写入文件。 终端仪表盘会滚动消失,其排序的行动无法勾选;磁盘上的相同内容更易阅读,且可作为本次运行的待办列表。因此无需询问许可——直接写入,然后用一行说明位置:
Saved to
docs/status/status-<repo-slug>-YYYY-MM-DD.md
— scratch file, gitignored, not committed.
仅当被要求时跳过。 当用户说「Just print it」「no file」「don't write anything」「screen only」「/statuskit --no-file」时——在本次运行中遵守该要求,仅打印仪表盘。跳过仅适用于本次运行;除非用户明确说明或仓库的agent-guide文件(
CLAUDE.md
或等效文件)设置,否则不是长期偏好。当无可写文件系统时也静默跳过(下文详述)。
存储位置。
docs/status/status-<repo-slug>-YYYY-MM-DD.md
——短小写连字符分隔的slug(通常是仓库名称;当快照覆盖项目的一部分时,使用更具体的slug,例如分支或issue)和ISO创建日期。若
docs/status/
不存在则创建。
每天一个文件——始终更新,绝不新增。 写入前,列出
docs/status/
并查找已带有今日日期的快照。若存在,就使用该文件:就地更新,即使本次运行会选择不同的slug,仍保留其现有名称。仅当目录中无今日日期的文件时才创建新文件。状态文件是某个时间点的读取结果,一个下午生成三个文件会让临时目录变成历史记录——更糟的是,用户勾选的框会分散在所有看起来都是当前的文件中。若今日的快照确实涵盖了单体仓库中的不同项目,让slug特定于该项目,并按slug+日期匹配;同一项目在同一天绝不会有两个文件,因此绝不回退到序列后缀。
更新意味着合并,而非覆盖。 从git和GitHub重新推导整个扫描——绝不信任文件中的内容——然后保留每个仍开放行动的勾选状态,仅按其键(下文详述)匹配,不考虑其他内容。用新扫描的结果重写所有其他内容:若行动的措辞完全改变,但键匹配,则仍是同一个行动;若措辞巧合相同,但键不匹配,则是不同的行动。已勾选但不再适用的行动会移至
Done today
;未勾选但不再适用的行动直接删除。
文件内容。 与打印的仪表盘相同,加上两个文件特有的内容:
  • 来源行记录快照的拍摄时间、对应的提交、以及今日已重写的次数(
    Snapshot: 2026-07-23 14:20 · <branch> @ <short-sha> · run 3 today (first 09:05)
    )——没有它,过时的文件会被视为当前内容;没有运行次数,下午的重写会与早上的原始版本无法区分;
  • 排序后的行动作为复选框列表,以便文件可作为待办事项,最优行动在前,每个行动携带其kit/命令:
markdown
undefined

Next moves

Next moves

  • <the #1 move> — critical, unblocks 3
    <kit / command>
    <!-- k: issue-12 -->
  • <runner-up> — high, unblocks 1 —
    <kit / command>
    <!-- k: pr-34 -->
  • <runner-up>
    <kit / command>
    <!-- k: plan-debugkit -->
  • <the #1 move> — critical, unblocks 3
    <kit / command>
    <!-- k: issue-12 -->
  • <runner-up> — high, unblocks 1 —
    <kit / command>
    <!-- k: pr-34 -->
  • <runner-up>
    <kit / command>
    <!-- k: plan-debugkit -->

Done today

Done today

  • <move, as it read when it was ticked> <!-- k: issue-9 -->
  • <move, as it read when it was ticked> <!-- k: issue-9 -->

Surfaced, not queued

Surfaced, not queued

  • #34 approved + CI-green — merge when you're ready (
    mergekit
    )
  • #29 awaiting @someone's review

**Every move carries a key.** The trailing `<!-- k: … -->` comment is what the merge matches on, and it exists because the visible text can't be matched on: the wording is regenerated every run, so a move that survives the survey comes back phrased differently and its tick is silently lost. Moves with an issue or PR number are the easy half; the ones without — provision the labels, file the backlog — are exactly where text matching fails and where a user's tick most needs to survive. The key is invisible when rendered because the file is read by a human and the key means nothing outside it.

Draw keys from a fixed vocabulary, never an improvised slug, or the key drifts run to run the same way the prose does:

| Move's subject | Key |
|---|---|
| an issue | `issue-12` |
| a PR | `pr-34` |
| a plan doc | `plan-<slug>` — the plan's own slug |
| a local branch | `branch-issue-12-retry-budget` |
| a stash entry | `stash-0` |
| a ladder rung with no subject | one fixed slug per rung — `push`, `reconcile`, `triage`, `prioritize`, `repo-labels` |

**A move that frees work says so, and a move somebody ranked says that first.** When a queued move carries a priority above `medium` or an `unblocks` count above zero, put both into its line — `**Start #12 — critical, unblocks 3** — \`issuekit start 12\``. The checkbox list is where the user actually chooses, often hours after the tables scrolled past, and those two clauses are the whole argument for why this item outranks the one below it. Omit each clause when it says nothing: no `unblocks 0`, and no `medium` or `—`, since the default and the absence are both what the reader already assumes. Priority leads the pair when both are present, matching the sort.

The key never leaves the file. Don't put it in a commit message, a branch name, an issue body, or anywhere else: it's a join key between two versions of one gitignored scratch file, and exporting it into permanent history would make durable artifacts reference a throwaway one. The linkage that *does* belong in git already exists — `Closes #12` on the PR, which the survey reads anyway.

**Every move must have a signal that retires it.** A queued move is something the next survey can observe as finished — the PR merged, the issue closed, the labels now exist, the tree went clean. Completion is detected that way, not from the ticks; the tick is only a human's own mid-day annotation, which is why the merge has to preserve it and why it is never evidence. A move with no observable signal ("decide whether this repo dogfoods its own workflow") can never drop off on its own, so it re-ranks every run forever and the only thing that ever silences it is a tick that today's file takes to the grave. Those aren't next actions, they're decisions — route them to `plankit` or file them with `issuekit create`, and let the resulting issue be what appears here. If you can't name what would make a move disappear, it doesn't belong on the list.

**Ticked moves go to `Done today`, not the bin.** When the fresh survey no longer supports a move the user had ticked, that's the move getting *finished* — record it under `## Done today` rather than deleting it with the rest of the stale ladder. One file per day only pays off if the day accumulates in it; a file that shows nothing but what's left reads identically at 6pm and 9am, which is the one impression a status file must never give. Drop the section entirely on a day with nothing done.

**One task per checkbox — never bundle.** Every item is a single thing the user can finish and tick off on its own, so it names **exactly one** issue or PR. "Start #12, #19, and #23 — `issuekit start`" is three items, not one; so is "triage the 4 unlabeled issues." When a rung of the ladder applies to several issues at once, split it into one item per issue, each carrying that issue's own number, title, and command, and keep them in the rung's order. The whole reason the snapshot is a checkbox list is that a half-done item is invisible — a box covering three issues can't be ticked until all three are done, and until then it reads exactly like nothing has happened. The same rule governs the `Surfaced, not queued` list: one line per PR, never a summary line. If the split makes the list long, that's the true length of the work; cap it the way the tables do — most-recently-updated first, then a `+N more` line — rather than by merging items back together.

All three tables — unblocked, waiting for review, blocked issues — go into the file as printed, in that order. They're the part of the snapshot that ages into a worklist, and a file that kept only the counts would be strictly worse than the terminal it replaced. Beyond the file's own additions, don't inflate it into a report the dashboard didn't contain — same survey, same closed panel set, durable form.

**It's disposable.** This file is scratch, not a tracked artifact: add `docs/status/` to `.gitignore` before writing the first one (say so in the same line), and leave it uncommitted. Commit it only if the user explicitly asks — then it's their call, and honor it without arguing. Skip the `.gitignore` edit if the path is already ignored or the repo has no `.gitignore` you should be touching.

**No filesystem?** Print the snapshot as a codeblock with the canonical `docs/status/status-<repo-slug>-YYYY-MM-DD.md` path so the user can save it themselves.
  • #34 approved + CI-green — merge when you're ready (
    mergekit
    )
  • #29 awaiting @someone's review

**每个行动都带有一个键。** 末尾的`<!-- k: … -->`注释是合并时匹配的依据,它存在是因为可见文本无法用于匹配:每次运行都会重新生成措辞,因此存活到下一次扫描的行动会以不同的措辞返回,其勾选状态会静默丢失。带有issue或PR编号的行动是简单的部分;没有编号的行动——设置标签、归档待办列表——正是文本匹配失败的地方,也是用户的勾选最需要保留的地方。键在渲染时不可见,因为文件是供人类阅读的,键在文件外无意义。

从固定词汇表中提取键,绝不使用即兴slug,否则键会像 prose 一样在每次运行中漂移:

| 行动主题 | 键 |
|---|---|
| issue | `issue-12` |
| PR | `pr-34` |
| 计划文档 | `plan-<slug>` — 计划自身的slug |
| 本地分支 | `branch-issue-12-retry-budget` |
| stash条目 | `stash-0` |
| 无主题的梯级 | 每个梯级一个固定slug — `push`, `reconcile`, `triage`, `prioritize`, `repo-labels` |

**释放工作的行动要说明,有人排序的行动要先说明排序。** 当排队的行动优先级高于`medium`或`unblocks`计数大于0时,将两者都加入行中——`**Start #12 — critical, unblocks 3** — \`issuekit start 12\``。复选框列表是用户实际选择的地方,通常是在表格滚动过去数小时后,这两个分句正是该项优于下方项的全部论据。当分句无意义时省略:不写`unblocks 0`,不写`medium`或`—`,因为默认值和缺失值都是读者已假设的。当两者都存在时,优先级在前,与排序一致。

键永远不会离开文件。不要将其放入提交消息、分支名称、issue body或其他任何地方:它是同一个git忽略的临时文件两个版本之间的连接键,将其导出到永久历史中会让持久工件引用临时文件。*确实*属于git的连接已经存在——PR上的`Closes #12`,扫描会读取该内容。

**每个行动必须有一个表明其完成的信号。** 排队的行动是下一次扫描可观察到完成的任务——PR已合并、issue已关闭、标签已存在、工作区已干净。完成状态通过这种方式检测,而非从勾选状态;勾选只是人类中午的注释,这就是为什么合并必须保留它,且它永远不是证据。没有可观察信号的行动(「决定该仓库是否自用其工作流」)永远不会自行消失,因此每次运行都会重新排序,唯一能让它静默的是勾选后被今日文件带入「坟墓」。这些不是下一步行动,而是决策——引导至`plankit`或用`issuekit create`归档,让生成的issue出现在此处。若你无法说出什么会让行动消失,它就不属于该列表。

**已勾选的行动移至`Done today`,而非删除。** 当新扫描不再支持用户已勾选的行动时,说明该行动已*完成*——将其记录在`## Done today`下,而非与其他过时梯级一起删除。每天一个文件的价值在于它能积累当日的工作;仅显示剩余任务的文件在下午6点和上午9点看起来完全相同,这是状态文件绝不能给人的印象。当日无完成任务时删除该部分。

**每个复选框一个任务——绝不捆绑。** 每个条目都是用户可单独完成并勾选的单一任务,因此仅指明**一个**issue或PR。「Start #12, #19, and #23 — `issuekit start`」是三个条目,而非一个;「triage the 4 unlabeled issues」也是如此。当梯级同时适用于多个issue时,将其拆分为每个issue一个条目,每个条目携带该issue自己的编号、标题和命令,并保持梯级的顺序。快照是复选框列表的全部原因是,未完成的条目是不可见的——覆盖三个issue的框要等到所有三个都完成才能勾选,在此之前它看起来像什么都没发生。相同规则适用于`Surfaced, not queued`列表:每个PR一行,绝不使用汇总行。若拆分后列表过长,这就是工作的真实长度;像表格一样限制——按最近活跃时间排序,然后是`+N more`行——而非重新合并条目。

三个表格——未阻塞、等待审核、阻塞issue——按打印顺序放入文件中。它们是快照中会演变为工作列表的部分,仅保留计数的文件严格劣于终端。除了文件自身的添加内容,不要将其扩展为仪表盘未包含的报告——相同的扫描、相同的固定面板集、持久化形式。

**它是一次性的。** 该文件是临时文件,而非追踪的工件:在写入第一个文件前,将`docs/status/`添加到`.gitignore`中(在同一行说明),并保持未提交状态。仅当用户明确要求时才提交——然后由他们决定,无需争论。若路径已被忽略或仓库没有可修改的`.gitignore`,则跳过`.gitignore`编辑。

**无文件系统?** 将快照打印为代码块,并显示标准路径`docs/status/status-<repo-slug>-YYYY-MM-DD.md`,以便用户自行保存。

Notes

注意事项

  • Zero mutation, always. statuskit surveys and advises; it never changes git or GitHub state. If a recommendation needs a mutation, it routes to the kit that owns it — that kit previews and gets approval on its own. The one thing it writes is the status snapshot — a gitignored scratch file that touches no git or tracker state, which is why writing it by default is still zero mutation.
  • Route, don't launch. Routing means naming the kit and its one-line command — statuskit never invokes the kit for you; the user launches it. Naming "run
    issuekit sync
    " and then calling the kit yourself would restart mutation in the same breath as "orient me," breaking the read-only stance.
  • Route, don't require. Every recommendation degrades to a plain command when its kit isn't installed. statuskit is useful in a bare repo with only git.
  • Hold the issuekit line. Display issue counts, the unblocked set by ID, the blocked set with what each says it's waiting on, the ready/in-progress set, and each issue's declared priority; compute the one staleness boolean to rank "reconcile." Listing IDs is not crossing the line — it's the same read, printed usefully, and it saves a round trip to
    gh
    before acting on the crowned move. What stays on issuekit's side is judgment about the tracker: never render an itemized health verdict, and the moment you're explaining which issues are stale and why, that's issuekit
    triage
    /
    sync
    and statuskit should be pointing at it, not doing it.
  • statuskit reads priority; it never assigns one. Printing the label an issue carries is a fact read like any other, and sorting on it is what the label is for. Inferring a priority for an unranked issue is not — it's the tracker judgment that belongs to issuekit
    triage
    , and it's the one place this survey could quietly manufacture the very signal it claims to be reporting. An unassessed issue stays unassessed in the dashboard, sorts below
    low
    , and gets routed rather than guessed at.
  • gitkit owns the git facts. The base branch, the branch-name convention, and where a worktree lives all come from gitkit — statuskit reads them and reports. Keeping a second copy of any of them here is how a dashboard starts confidently describing a repo that no longer matches it.
  • On-demand, no state. Every run is a fresh read — statuskit keeps no
    STATUS.md
    at the repo root and no last-run cache, and it never reads a snapshot back to shortcut the survey. The one thing it takes from an existing file is which boxes were already ticked; the survey itself is always re-derived from git and GitHub. A
    docs/status/
    file is output for a human (or the next agent), not memory statuskit trusts.
  • 始终零变更。 statuskit仅扫描和建议;永远不会更改git或GitHub状态。若建议需要变更,它会引导至负责的kit——该kit会自行预览并获取批准。它唯一写入的是状态快照——一个git忽略的临时文件,不涉及git或追踪器状态,这就是为什么默认写入仍属于零变更。
  • 引导,而非启动。 引导意味着命名kit及其单行命令——statuskit永远不会为你调用kit;由用户启动。若命名「run
    issuekit sync
    」然后自行调用kit,会在「帮我梳理下」的同时重启变更操作,打破只读原则。
  • 引导,而非强制依赖。 当所属kit未安装时,每个建议都会降级为普通命令。statuskit在仅有git的裸仓库中也有用。
  • 坚守issuekit的边界。 显示issue数量、未阻塞集合的ID、带有等待对象的阻塞集合、ready/in-progress集合、以及每个issue的已声明优先级;计算一个过时布尔值来排序「协调」。列出ID并未越界——这是相同的读取,只是打印得更有用,且能在执行最优行动前省去一次
    gh
    调用。属于issuekit职责的是对追踪器的判断:绝不列出逐项的健康 verdict,当你开始解释哪些issue过时及原因时,这就是issuekit的
    triage
    /
    sync
    功能,statuskit应引导至该工具,而非自行处理。
  • statuskit读取优先级;绝不分配优先级。 打印issue携带的标签是像其他任何内容一样的事实读取,按标签排序正是标签的用途。为未排序的issue推断优先级则不是——这属于issuekit
    triage
    的追踪器判断,也是该扫描可能悄悄生成它声称要报告的信号的唯一地方。未评估的issue在仪表盘中保持未评估状态,排在
    low
    之后,并被引导至对应工具而非猜测。
  • gitkit拥有git事实。 基准分支、分支名称约定、工作区位置都来自gitkit——statuskit读取并报告。在此处保留任何内容的副本会导致仪表盘开始自信地描述不再匹配的仓库。
  • 按需运行,无状态。 每次运行都是全新的读取——statuskit不在仓库根目录保留
    STATUS.md
    或上次运行的缓存,也永远不会读取快照来 shortcut 扫描。它从现有文件中获取的唯一内容是哪些框已被勾选;扫描本身始终从git和GitHub重新推导。
    docs/status/
    文件是供人类(或下一个Agent)使用的输出,而非statuskit信任的内存。