refactor(cli): drop the legacy Goal Stop-hook plumbing - #11459
Conversation
The Goal v3 runtime owns every Goal surface in the cli: `/goal` dispatches against `GoalRuntime`, and the ACP/daemon paths read the canonical snapshot. Nothing in the cli writes the first-generation Stop-hook Goal store any more, so the code that still reads it can only ever see an empty store. Cut the cli off that dead path: - `MessageEmitter.emitStopHookLoop` no longer attaches a `goal` sub-object to `_meta.stopHookLoop`. The Stop-hook loop notice itself stays; only the sub-object goes, because `getActiveGoal` has no writer left. The Web Shell's reader for `stopHookLoop.goal` was already a dead branch -- every path through it returned null -- so it goes with it. `projectGoalStateToLegacy` and the `_meta.goalStatus` / `goalTerminal` / `goalState` updates are untouched: those are live compatibility surfaces the Web Shell renders. - `ui/utils/restoreGoal.ts` loses the seven exports that only ever fed the Stop-hook path and had no production caller: `findLastTerminalGoal`, `goalTerminalEventToHistoryItem`, `recordGoalStatusItem`, `installGoalTerminalObserver`, `goalRestoreBlockedBy`, `goalConditionBlockedBy` and `restoreGoalFromHistory`. `findGoalToRestore`, `collectGoalStatusItemsFromRecords`, `parseGoalStatusItem` and `isTranscriptItemRecord` stay -- `acpAgent.ts`, `session/Session.ts` and `session/recovered-goal-update.ts` import them for v3 recovery. - The matching test scaffolding goes: the legacy suites in `restoreGoal.test.ts`, the `registerGoalHook`/`getActiveGoal` mock block in `goalCommand.test.ts`, the legacy mock table in `acpAgent.test.ts`, the `setActiveGoal`/`clearActiveGoal` mocks in `use-llm-stream.test.tsx` and the `__resetActiveGoalStoreForTests` calls in `nonInteractiveCliCommands.test.ts`. Also 32 stale mocks of `Session.installGoalTerminalObserver` -- a method that does not exist on `Session` in this revision -- across `acpAgent.test.ts` and `acpAgent.worktree.test.ts`. Coverage of the retained scanners is kept rather than dropped: the condition-cap and `setAt`-run-boundary suites are retargeted from `restoreGoalFromHistory` onto `findGoalToRestore`, which is where that logic actually lives. The legacy core modules themselves are left in place; a follow-up removes them.
🖼️ web-shell visual previewRendered against a mock daemon (no real backend): the PR base vs this PR head Screenshots · before / afterFull-resolution recordings (.webm) are attached to the workflow run. — Qwen Code · web-shell visuals |
|
Thanks for the PR — this one is unusually easy to gate, because the central claim is checkable statically rather than by reproduction. Template looks good ✓ — every required heading is present and the Chinese translation is complete rather than abbreviated. Problem: Real, and I verified it rather than taking the description's word for it. At the base commit, the only production references anywhere in Direction: Aligned. Goal v3 owns these surfaces, and removing the CLI's references before deleting the core modules is the right order — it turns the core-side deletion into a mechanical follow-up instead of one large cross-package refactor. No upstream CHANGELOG signal bears on this: it is internal plumbing removal, not a capability change. Size: Cross-package ( Approach: Scope feels right, and it is genuinely minimal — deletion only, no drive-by restructuring, no formatting churn. Two honest notes, neither a blocker:
Risk: Stage 1e matched a high-risk path — Moving on to code review. 🔍 中文说明感谢贡献!这个 PR 的准入判断 unusually 容易,因为它的核心论断可以静态核对,而不需要复现。 模板完整 ✓ —— 所有必需小节都在,中文翻译也是完整的,没有省略。 问题: 真实存在,而且我自己核对过,没有只采信描述。在 base commit 上, 方向: 对齐。Goal v3 已经拥有这些界面,而且先移除 CLI 侧引用、再删除 core 模块,顺序是对的——这让 core 侧的删除变成一次机械跟进,而不是一次大范围跨包重构。上游 CHANGELOG 对此没有参考意义:这是内部管线清理,不是能力变化。 规模: 跨包( 方案: 范围合理,也确实是最小改动——只有删除,没有顺手重构,没有格式化噪音。两点如实提出,都不是阻塞项:
风险: Stage 1e 命中高风险路径—— 进入代码审查 🔍 — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
Code reviewI read the diff against an independent baseline: before opening it, I grepped the base tree for every symbol the title implies is going away, to see for myself who still calls them. The PR's claims survived that, which is not what I expected going in from a 908-deletion diff. No Critical blockers found. What I verified:
One thing worth a reviewer's eye, not a blocker: Files changed (all 10)
No sequence diagram: this is pure deletion with no new or reshaped runtime flow, so a diagram would add nothing. TestingThis is an unattended CI run, so I did not build or execute anything from this PR — the evidence below is the PR's own CI, read through the API at Zero failures at fetch time. The load-bearing check has not landed yet: Not verified: the pass counts quoted in the PR description ( Sandboxed verification would settle the remaining gap: Real-scenario tmux testing: N/A — unattended CI run (tmux is local-invocation only, and PR code is never executed here), and there is no user-visible behaviour change to drive anyway. 中文说明代码审查我没有直接看 diff,而是先独立取了一个基线:在打开 diff 之前,先在 base 树上 grep 了标题暗示要移除的每一个符号,自己确认还有谁在调用它们。这个 PR 的论断经受住了核对——对一个删了 908 行的 diff 来说,这和我进去之前的预期不一样。 未发现 Critical 阻塞项。 我核对过的内容:
有一处值得评审者过目,但不是阻塞项: 文件清单见上表(共 10 个文件)。没有画时序图:这是纯删除,没有新增或重塑的运行时流程,画了也没有信息量。 测试这是一次无人值守的 CI 运行,所以我没有构建或执行本 PR 的任何代码——下面的证据是 PR 自己的 CI,通过 API 在 抓取时刻零失败。 承重的检查还没落地: 未验证:PR 描述里引用的通过数( 沙箱验证可以补上剩下的缺口: 真实场景 tmux 测试:N/A —— 无人值守 CI 运行(tmux 仅限本地调用,且这里从不执行 PR 代码),而且本来也没有用户可见的行为变化可驱动。 Final CI results for
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。 — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
|
Confidence: 3/5 — clean review, no blocking findings; the cap is pure policy, because a fork Going in, I expected a 908-deletion diff from a fork to have a body in it somewhere. It doesn't. My independent proposal from the title alone was: grep every legacy symbol, confirm no production callers, delete the exports and their imports, delete the test mocks that stub them, and leave the core modules for a follow-up. The PR does exactly that — and beats my baseline in one respect I would not have thought to chase from the title: it also found the The scope discipline is the part I'd single out. It would have been easy to bundle the Six months from now this is a thank-you, not a curse: the follow-up core deletion becomes mechanical because the CLI side is already clean, and the misleading mocks that made a reader think a legacy path was live are gone. Two things I want on the record before a human decides, neither of them a defect in the code: Merge order matters across the Goal series. #11458 ( This is one of 13 open PRs from the same author in the last 48 hours, including a three-part Goal cluster (#11457, #11458, #11459). I evaluated this one on its own merits and it held up — but I'm flagging the volume because that is exactly the condition where a reviewer starts approving out of fatigue rather than judgement, and because the three Goal PRs interact and probably deserve to be read together rather than one at a time. Why I am not approving. Not because I found anything. The approval guardrail is deterministic: a cross-repository PR whose title is a On the evidence: Escalation. This needs a human call, and I could not resolve an accountable owner deterministically: the PR carries no labels, so the area map did not match ( ⏸️ Deferring to a maintainer — fork 中文说明Confidence: 3/5 —— 审查干净,没有阻塞项;这个上限纯粹来自政策,因为 fork 的 进去之前我以为一个来自 fork、删了 908 行的 diff 总会在某处露馅。它没有。仅凭标题我给出的独立方案是:grep 每一个 legacy 符号,确认没有生产调用者,删掉这些导出及其 import,删掉 stub 它们的测试 mock,core 模块留给后续 PR。这个 PR 做的正是这些——而且在有一点上超过了我的基线,那是我从标题不会想到去追的:它还找到了 范围克制是我最想点出来的部分。本来很容易把 六个月后回看,这是一件让人感谢而不是骂人的事:core 侧的后续删除会变成机械操作,因为 CLI 侧已经干净了;而那些让读者误以为 legacy 路径还活着的误导性 mock 也消失了。 在人做决定之前,有两点我要记录在案,都不是代码缺陷: Goal 系列的合并顺序很重要。 #11458( 这是同一作者过去 48 小时内 13 个开放 PR 之一,其中包括一个三件套的 Goal 集群(#11457、#11458、#11459)。我是按它自身的价值评估这一个的,它站得住——但我把体量点出来,因为那正是审查者会因为疲劳而不是判断力开始放行的条件,也因为这三个 Goal PR 互相影响,大概值得放在一起读,而不是一个个读。 为什么我没有批准。 不是因为我发现了什么。批准护栏是确定性的:标题为 关于证据: 升级处理。 这需要人来定,而我无法确定性地解析出一个负责人:这个 PR 没有任何 label,所以 area map 没有匹配上(需要 ⏸️ 转交维护者 —— fork — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline.
Not explored to full depth (tool budget reached): "agent 1d": none — no check was cut short..
Test Plan (not a blocker): session/Session.ts — no such file or directory; session/recovered-goal-update.ts — no such file or directory; 34 passed — this review observed 29435, 6854, 515 passed; 65 passed — this review observed 29435, 6854, 515 passed; 52 passed — this review observed 29435, 6854, 515 passed; and 4 more.
中文说明
已审查。 建议见行内评论。
未探索到全部深度(达到工具调用预算):"agent 1d":none — no check was cut short.。
Test Plan(非阻断):session/Session.ts — no such file or directory; session/recovered-goal-update.ts — no such file or directory; 34 passed — this review observed 29435, 6854, 515 passed; 65 passed — this review observed 29435, 6854, 515 passed; 52 passed — this review observed 29435, 6854, 515 passed; and 4 more。
— qwen3.8-max via Qwen Code /review (v0.23.1)
…tion Narrowing `_meta.stopHookLoop` left nothing recording that the `goal` sub-object is supposed to stay gone. `MessageEmitter.test.ts` asserts `_meta` by exact shape for `emitGoalStatus` and `emitGoalState` but had no `emitStopHookLoop` case at all, and the three assertions that observe the payload through the real emitter in `Session.test.ts` all match a subset. So a partial revert or a badly resolved merge would put a second, stale first-generation Goal projection back on the ACP wire beside `_meta.goalState`, and the whole suite would stay green. Add the exact-equality case beside its two siblings. Verified as the acceptance criterion it is meant to be: with the `goal` sub-object restored, the new case fails while the `Session.test.ts` subset assertion still passes. Also correct the comment left where the Web Shell's dead reader for that payload used to be. It ended by claiming only terminal events and the initial `set` event become transcript cards, which is narrower than `normalizeGoalStatus` right below it -- that also admits `paused`, `cleared` and `usage_limited`, and dropping `paused` regresses the bug recorded beside its own entry. The claim predates this branch, but the comment was rewritten here, so it is corrected here rather than carried forward.
|
Both taken, in f0c8e84. One correction to the second finding's premise, which I only found because I went to check it. R1-1 — the unpinned I ran the mutation you specified rather than assuming it works. With the
So the gap was real and is now closed by the assertion that bites rather than by one that would have let the projection back in. R1-2 — the enumeration in the comment. The substance is right and I fixed it: But the inaccuracy is not new in this diff. The comment I replaced said the same thing: Both halves of that claim survived my rewrite verbatim in meaning. So this is a pre-existing inaccuracy in a comment this branch happens to have rewritten, not one it introduced — worth knowing because it changes whether the finding is in scope. I fixed it anyway: the comment was rewritten here, so correcting it here is cheaper than leaving a known-wrong enumeration for the documentation slice to find. The new wording points at One thing that is not mine, flagged so it does not get attributed to this branch.
|
yiliang114
left a comment
There was a problem hiding this comment.
Approving. restoreGoal.ts drops the registerGoalHook/setGoalTerminalObserver/unregisterGoalHook plumbing and the terminal-observer scan, pairing with #11458's core-side removal of the Stop-hook routing. Net −830 lines, tests green, CI fully green (Lint, Test, web-shell E2E, no-AK integration, Real daemon E2E, Capture visuals). Bot review is Suggestion-only, no blockers.
chiga0
left a comment
There was a problem hiding this comment.
No blocking findings.
Approval blockers: none.
Scope
Source-only review. Reviewed all 11 changed files in the diff.
Not reviewed: packages/core/src/goals/ (untouched by this PR — the legacy core modules are unchanged), macOS and Windows runtime behavior (no host), execution rung (working tree unavailable).
Findings
None.
What was checked
Wire format change (_meta.stopHookLoop.goal removal)
The removed field in MessageEmitter.emitStopHookLoop (MessageEmitter.ts:95-113) was conditional on getActiveGoal(this.sessionId) returning a non-null value. The only CLI writer to the active goal store was registerGoalHook, whose only CLI caller was restoreGoalFromHistory. A search of the repository confirms restoreGoalFromHistory had no production callers in the CLI (only test files). So getActiveGoal returned undefined on every real call and the goal sub-object was never emitted.
The Web Shell reader (DaemonSessionProvider.tsx:5295–5304) checked loop['goal'] for a record and a non-empty condition, then returned null unconditionally on the last line regardless. Removing the branch is byte-identical in behavior.
The new emitStopHookLoop test in MessageEmitter.test.ts uses exact equality (not objectContaining) specifically to guard against accidental reintroduction of the goal sub-object. This addresses the CI bot's inline suggestion at MessageEmitter.ts:105, which the author confirmed was incorporated in the head commit.
Removed exports from restoreGoal.ts
Verified that all three production importers of restoreGoal.js at head — acpAgent.ts (line ~460), session/Session.ts (line ~330), and session/recovered-goal-update.ts (lines 17–20) — import only the kept symbols: findGoalToRestore and collectGoalStatusItemsFromRecords. The PR description additionally lists parseGoalStatusItem, isTranscriptItemRecord, and two types as kept, consistent with the retained module surface. None of the seven deleted exports (restoreGoalFromHistory, findLastTerminalGoal, goalTerminalEventToHistoryItem, recordGoalStatusItem, installGoalTerminalObserver, goalRestoreBlockedBy, goalConditionBlockedBy) appear in production callers outside of the files modified by this PR.
Test scaffold cleanup
installGoalTerminalObserver: vi.fn() is removed from 28 Session mock objects in acpAgent.test.ts and siblings. Confirmed the method does not appear in Session.ts at head — these were mocks of a method that does not exist, which is the kind of scaffolding that misleads readers about live paths.
The two retargeted tests (10,000-character objective pin and start-time cases) now assert directly against findGoalToRestore's return value, which is verifiable without the legacy hook machinery.
Cross-check against existing reviews
yiliang114 (APPROVED): Noted the removal pairs with core-side sibling PR #11458. Consistent with my reading — confirmed, not a miss.
qwen-code-ci-bot suggestion 1 (MessageEmitter.ts:105): "no exact-shape test for the payload." The new emitStopHookLoop describe block with exact equality addresses this directly. Author confirmed incorporation.
qwen-code-ci-bot suggestion 2 (DaemonSessionProvider.tsx:5301): Comment style. Excluded per §C.
Unreviewed dimensions
- Execution rung not run: no working tree available. The PR's own test results show all six suites passing on Linux.
packages/core/src/goals/untouched: no cross-package regression risk from this PR.- macOS and Windows: no terminal-dependent or platform-branching change introduced.
Reviewed with AI assistance.
qwen-code-dev-bot
left a comment
There was a problem hiding this comment.
Approved at head f0c8e841.
Required CI is green here — Test (ubuntu-latest, Node 22.x), Lint & Static, Integration Tests (no-AK, No Sandbox), web-shell E2E Smoke, Capture web-shell visuals, TUI parity snapshots and the OpenTUI no-flicker gate all completed successfully; only review-pr (the reviewer's own job) is still running.
What I checked, rather than trusting the description:
- Every removed export was already dead at the base commit. For each of
restoreGoalFromHistory,findLastTerminalGoal,goalTerminalEventToHistoryItem,recordGoalStatusItem,installGoalTerminalObserver,goalRestoreBlockedByandgoalConditionBlockedBy, a repo-wide grep at3a262e09finds only its own definition inrestoreGoal.tsplus this module's tests — no production caller. Since this PR changes no other production file that could have called them, nothing user-visible was cut; the three production importers (acpAgent.ts:460,session/Session.ts:330,session/recovered-goal-update.ts:20) reference onlyfindGoalToRestoreandcollectGoalStatusItemsFromRecords, both retained along with their internal helpers. - The one wire narrowing is behavior-identical. The deleted
stopHookLoopbranch innormalizeGoalStatusEventended inreturn nullon every path — theisRecord(loop)/isRecord(goal)/!conditionguards only ever short-circuited to that samenull, andcreateGoalStatusUiEventis reachable solely through thegoalStatus/goalTerminalbranches above it. So dropping it changes no card, and the replacement comment is accurate:normalizeGoalStatus(:5337-5353) does admitset,cleared,achieved,failed,aborted,usage_limitedandpaused, with the regression note recorded besidepaused. - The narrowed payload is now pinned.
MessageEmitter.test.tsasserts the_meta.stopHookLoopobject with exact equality, so re-attaching the first-generationgoalsub-object — a partial revert or a bad merge — goes red instead of riding alongside_meta.goalState. - Two retargets rather than deletions, which are the ones worth reading: the #6665 no-cap pin now asserts against the live scanner (
'x'.repeat(10_000)atrestoreGoal.test.ts:276) and the start-time cases assert the scanner'ssetAt.
No new Critical found. Order-independent with the core-side sibling: this PR leaves goalHook.ts / activeGoalStore.ts exported and untouched, and either landing first still compiles. The abortGoalForStopHookCap call that survives in session/Session.ts is already called out here as its own behaviour-bearing follow-up, which is the right split.
|
Released in v0.23.3. |


What this PR does
The CLI stops calling the first-generation Goal Stop-hook implementation.
restoreGoal.tsloses the seven exports that drove it — the history restorer that registered the hook, the terminal-event observer and recorder, the two blocked-reason gates, and the terminal-goal finder — none of which had a single production caller. What stays is the part three live call sites actually use: the transcript scanner that finds a Goal worth offering to restore, and the card parsing around it.The ACP message emitter stops attaching a
goalsub-object to_meta.stopHookLoop. That object could only ever be populated from the legacy in-memory store, which nothing writes in a real session. One consumer did read it — the Web Shell's session provider — but that branch returnednullunconditionally, deliberately suppressing per-iteration "checking" events, so it produced no UI. The branch goes with it and the explanation stays as a comment.The rest is dead test scaffolding: mocks of legacy core exports in five suites, and thirty-two mocks of a
Session.installGoalTerminalObservermethod that does not exist onSessionin this revision.The legacy core modules themselves are untouched. This PR removes the CLI's references so they can be deleted next.
Why it's needed
Goal v3 owns every surface. The legacy generation is not a second live path —
registerGoalHook's only production caller wasrestoreGoalFromHistoryin this file, and that function had no callers of its own — so what remained was cost: a 387-line module where two thirds was unreachable, a wire field nothing could populate, and test files that mocked functions the code under test never calls, which is the kind of scaffolding that makes a reader think a path is live when it is not.Together with the core-side sibling, this is what makes deleting
goalHook.ts,goalJudge.tsandactiveGoalStore.tsa mechanical follow-up rather than a large-scope refactor.Reviewer Test Plan
How to verify
The CLI's vitest config aliases
@qwen-code/qwen-code-corestraight to source, so no build is needed:Run them one file per invocation on a small host; a single combined invocation gets OOM-killed here.
The claim worth checking is that the retained half of
restoreGoal.tsis exactly what its three importers need.acpAgent.ts,session/Session.tsandsession/recovered-goal-update.tsimport onlyfindGoalToRestore,collectGoalStatusItemsFromRecords,parseGoalStatusItem,isTranscriptItemRecordand the two types — all kept.Two tests were retargeted rather than deleted, and those are the ones to read: the 10,000-character objective pin from #6665 now asserts against the live transcript scanner instead of the deleted restorer, and the start-time cases assert the
setAtthe scanner returns instead of the default clock the deleted hook registration applied.Evidence (Before & After)
Non–user-visible refactor: N/A for screenshots. The evidence is that no legacy symbol is reachable from the CLI, and every suite still passes:
prettier --checkandeslintare clean on all ten changed files.Tested on
Environment (optional)
Linux, package-local vitest for
packages/cli, one test file per invocation.Risk & Scope
_meta.stopHookLoop.goal. Its only reader was a Web Shell branch that returnednullon every path, so removing both is byte-identical in behaviour, but it is a wire surface and worth a reviewer's eye. Everything else removed was unreachable: seven exports with no production caller, and mocks of functions the code under test never calls.projectLegacyActiveGoal, the headlessactive_goalevent,_meta.goalStatus,_meta.goalTerminal,BridgeSessionGoal.activeand thegoal_statushistory card are all deliberately untouched.packages/cli/tsconfig.jsonreports type errors in seven files this PR never touches (activeWorkState,ChannelBaseOptions.locale,agentsRespawned) — those come from unbuilt sibling package dists on this host, and a worktree with a completenpm run buildtype-checks the CLI with zero errors.stopHookLoop.goalfield above. No settings, persisted records, or user-visible behaviour change.One follow-up found while doing this and left out on purpose:
session/Session.tsstill imports and calls core'sabortGoalForStopHookCap. Its own comment explains the legacy store has no writer for daemon sessions, so the call always returns false and the canonical-runtime branch always runs. Dropping that guard is behaviour-bearing enough to deserve its own commit.Linked Issues
Part of #10795. Part of #4228.
中文说明
这个 PR 做了什么
CLI 不再调用第一代 Goal Stop-hook 实现。
restoreGoal.ts去掉了驱动它的七个导出——注册该 hook 的历史恢复器、终态事件观察者与记录器、两个"被阻止原因"的判断门,以及终态 Goal 查找器——它们在生产代码里一个调用者都没有。留下的是三处存活调用点真正在用的部分:从 transcript 里找出值得提议恢复的 Goal 的扫描器,以及围绕它的卡片解析。ACP 的消息发射器不再往
_meta.stopHookLoop上挂goal子对象。那个对象只可能由内存里的 legacy store 填充,而真实会话里没有任何东西写它。确实有一个消费者读过它——Web Shell 的 session provider——但那个分支在所有路径上都无条件return null,刻意抑制逐轮的"checking"事件,所以它不产生任何 UI。该分支随之移除,解释以注释形式保留。其余是死掉的测试脚手架:五个测试套里对 legacy core 导出的 mock,以及三十二处对
Session.installGoalTerminalObserver的 mock——这个方法在当前版本的Session上并不存在。legacy core 模块本身未被触及。本 PR 移除 CLI 侧的引用,以便下一个 PR 删除它们。
为什么需要
Goal v3 拥有所有界面。legacy 那一代并不是第二条存活路径——
registerGoalHook在生产代码里唯一的调用者就是本文件里的restoreGoalFromHistory,而它自己没有任何调用者——所以剩下的只有代价:一个 387 行的模块里三分之二不可达、一个没有东西能填充的线协议字段,以及一批 mock 了被测代码从不调用的函数的测试文件——而这类脚手架正是让读者误以为某条路径还活着的原因。与 core 侧的姊妹 PR 合起来,这就是让删除
goalHook.ts、goalJudge.ts与activeGoalStore.ts变成一次机械跟进、而不是一次大范围重构的前提。评审验证方式
如何验证
CLI 的 vitest 配置把
@qwen-code/qwen-code-core直接别名到源码,所以不需要构建:在内存较小的机器上请一个文件一次调用;把它们合成一次调用会被 OOM 杀掉。
值得核对的论断是:
restoreGoal.ts保留下来的那一半,恰好就是它三个引入方需要的东西。acpAgent.ts、session/Session.ts与session/recovered-goal-update.ts只引入findGoalToRestore、collectGoalStatusItemsFromRecords、parseGoalStatusItem、isTranscriptItemRecord与两个类型——全部保留。有两个测试是被改指对象而不是删除,那两个值得读:来自 #6665 的一万字符目标长度固定断言,现在针对存活的 transcript 扫描器而不是被删掉的恢复器;起始时间那几个用例现在断言扫描器返回的
setAt,而不是被删掉的 hook 注册所应用的默认时钟。证据(前后对比)
不面向用户的重构:截图部分为 N/A。证据是 CLI 侧已无法到达任何 legacy 符号,且每个测试套仍然通过:
十个改动文件上的
prettier --check与eslint均干净。测试环境
运行环境(可选)
Linux,
packages/cli的包内 vitest,一个测试文件一次调用。风险与范围
_meta.stopHookLoop.goal。它唯一的读者是 Web Shell 里一个在所有路径上都return null的分支,所以两者一起移除在行为上逐字节相同;但它毕竟是线协议面,值得评审者过一眼。其余被移除的都是不可达的:七个没有生产调用者的导出,以及对被测代码从不调用的函数的 mock。projectLegacyActiveGoal、headless 的active_goal事件、_meta.goalStatus、_meta.goalTerminal、BridgeSessionGoal.active与goal_status历史卡片都刻意未动。packages/cli/tsconfig.json在本 PR 从未触及的七个文件里报出类型错误(activeWorkState、ChannelBaseOptions.locale、agentsRespawned)——它们来自本机上未构建的兄弟包 dist;在一个完成了npm run build的 worktree 里,CLI 的类型检查零报错。stopHookLoop.goal字段外没有。设置、持久化记录与用户可见行为均未改变。顺手发现但刻意留在范围外的一项跟进:
session/Session.ts仍然引入并调用 core 的abortGoalForStopHookCap。它自己的注释说明 legacy store 对 daemon 会话没有写入方,所以该调用永远返回 false,规范运行时那一支永远执行。移除这道保护有行为含义,值得单独一次提交。关联 Issue
Part of #10795. Part of #4228.