Skip to content

feat: /goal命令能力支持,参考codex实现#1261

Open
Moyhub wants to merge 5 commits into
claude-code-best:mainfrom
Moyhub:dev-moy
Open

feat: /goal命令能力支持,参考codex实现#1261
Moyhub wants to merge 5 commits into
claude-code-best:mainfrom
Moyhub:dev-moy

Conversation

@Moyhub

@Moyhub Moyhub commented Jun 8, 2026

Copy link
Copy Markdown

从 CODEX 项目(Rust 多 crate 架构)移植 /goal 功能到 Claude Code(TypeScript 单进程),适配 React/Ink UI 框架和 JSONL 持久化方案。
从 Codex 项目移植 /goal 命令到 Claude Code,实现:

  • Goal 状态管理模块(active/paused/budget_limited/complete)
  • /goal 斜杠命令(set/clear/pause/resume/complete)
  • Goal 模型工具(get/set/complete)
  • Continuation prompt 自动注入系统提示
  • Token 用量自动追踪(保守估计,将缓存命中token一并纳入统计)

完整流程图:
image
image
image
image

Summary by CodeRabbit

  • New Features

    • Persistent /goal command to set, track, update, pause/resume, and complete session goals
    • Confirmation dialog when replacing an active goal
    • Goal pill in the status line showing objective, status, token usage, and elapsed time
    • Automatic goal continuation with turn limits and budget-aware prompts; REPL pauses goal on user cancel
    • Goal persistence across resume and transcript-aware save/clear behavior
    • New Goal tool and prompts for querying/updating goals and generating completion reports
  • Tests

    • End-to-end and unit tests covering goal lifecycle, timing, budgets, and audit rules

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 57814d65-1b5b-45fb-a94f-3e08af2e73cf

📥 Commits

Reviewing files that changed from the base of the PR and between 1d209db and 6531a30.

📒 Files selected for processing (1)
  • src/utils/sessionStorage.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/utils/sessionStorage.ts

📝 Walkthrough

Walkthrough

Adds a persistent per-session goal system: in-memory state machine, JSONL persistence and resume, LLM-facing GoalTool, /goal command and UI, REPL auto-continuation, token tracking, steering prompts, and unit/integration tests.

Changes

Persistent Goal Tracking for Thread Sessions

Layer / File(s) Summary
Goal Type System & State Machine
src/types/logs.ts, src/services/goal/goalState.ts, src/services/goal/goalAudit.ts
Goal status/enums and state shapes are defined; goalState provides the in-memory machine with transitions (active, paused, complete, blocked, budget_limited, usage_limited), token/turn counting, blocked-attempt escalation, elapsed-time computation, and formatting helpers.
Goal Persistence & Steering Prompts
src/services/goal/goalStorage.ts, src/services/goal/prompts.ts
Bridge to persist/hydrate goals into JSONL transcripts and prompt builders that emit XML/meta messages for continuation, budget-limit wrap-ups, objective updates, and compact active-goal context blocks.
Goal Tool (LLM-Facing Tool)
packages/builtin-tools/src/tools/GoalTool/GoalTool.ts, packages/builtin-tools/src/tools/GoalTool/constants.ts, packages/builtin-tools/src/tools/GoalTool/prompt.ts, packages/builtin-tools/src/index.ts
Adds a Zod-validated LLM tool exposing get/update actions, goal snapshots, completion reports, persistence calls, and mapping of tool outputs into tool_result blocks; includes tool prompt and constant export and re-exports the tool from the builtin-tools barrel.
Goal Command & User Interface
src/commands/goal/index.ts, src/commands/goal/goal.tsx, src/commands/goal/GoalReplaceConfirmDialog.tsx, src/commands.ts
Implements /goal command with subcommands (status, set, clear, pause, resume, complete), objective-length validation, replacement confirmation dialog, persistence, and meta payload emission.
Session & Transcript Integration
src/utils/sessionStorage.ts
Adds session currentSessionGoal cache, routes goal/goal-cleared JSONL entries through append/load paths, exposes saveGoal/clearGoalEntry, and surfaces goals map on transcript load for resume hydration.
REPL & Auto-Continuation Logic
src/hooks/useGoalContinuation.ts, src/screens/REPL.tsx, src/cost-tracker.ts
Hydrates goal on resume, tracks aborted turns to avoid re-enqueue loops, implements useGoalContinuation to enqueue continuation or budget prompts, and updates goal token usage from cost tracking.
UI, Tools & Feature Flags
src/components/StatusLine.tsx, src/tools.ts, scripts/defines.ts, packages/builtin-tools/src/index.ts
Adds GoalPill to status line, conditions command/tool inclusion on GOAL feature flag, and enables GOAL in default build features; exports GoalTool from builtin-tools barrel.
Goal State & Lifecycle Tests
src/services/goal/__tests__/goalState.test.ts, tests/integration/goal-lifecycle.test.ts
Unit and integration tests covering state transitions, token/turn counting, blocked-attempt rules, prompt templates, and formatting helpers.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐇 I hop through threads with purpose bright,

Counting tokens, turns, and time by night.
When budgets bite or three strikes come near,
I pause, report, and carry the cheer.
A saved objective — rabbit's delight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: /goal命令能力支持,参考codex实现' clearly describes the main feature addition: implementing /goal command support based on CODEX design. It aligns with the PR's primary objective of porting the /goal feature.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/utils/sessionStorage.ts (1)

3199-3210: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pre-boundary metadata scan omits goal / goal-cleared markers.

loadTranscriptFile() now parses goal metadata in the pre-boundary pass (Line 3694+), but METADATA_TYPE_MARKERS never includes those entry types. When pre-compact skip truncates earlier bytes, goal state can be silently lost on resume.

Suggested fix
 const METADATA_TYPE_MARKERS = [
   '"type":"summary"',
   '"type":"custom-title"',
   '"type":"tag"',
   '"type":"agent-name"',
   '"type":"agent-color"',
   '"type":"agent-setting"',
   '"type":"mode"',
   '"type":"worktree-state"',
+  '"type":"goal"',
+  '"type":"goal-cleared"',
   '"type":"pr-link"',
 ]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils/sessionStorage.ts` around lines 3199 - 3210, METADATA_TYPE_MARKERS
used by the pre-boundary scan omits goal metadata, causing loadTranscriptFile to
skip '"type":"goal"' and '"type":"goal-cleared"' entries when pre-compact
truncation occurs; update the METADATA_TYPE_MARKERS array (and thereby
METADATA_MARKER_BUFS) to include '"type":"goal"' and '"type":"goal-cleared"' so
the pre-boundary pass will detect and preserve goal state, then run tests that
exercise loadTranscriptFile to verify goals survive resume.
🧹 Nitpick comments (3)
src/services/goal/goalAudit.ts (1)

5-6: ⚡ Quick win

Switch these imports to src/* aliases.

Use alias-based imports here for consistency with the source-tree import policy.

As per coding guidelines, "Import paths must use the src/* alias (e.g., import { ... } from 'src/utils/...')."

Suggested diff
-import { BLOCKED_CONSECUTIVE_THRESHOLD, MAX_GOAL_TURNS } from './goalState.js'
-import type { GoalStatus } from '../../types/logs.js'
+import { BLOCKED_CONSECUTIVE_THRESHOLD, MAX_GOAL_TURNS } from 'src/services/goal/goalState.js'
+import type { GoalStatus } from 'src/types/logs.js'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/goal/goalAudit.ts` around lines 5 - 6, Replace the relative
imports in goalAudit.ts with src/* aliases: change the import of
BLOCKED_CONSECUTIVE_THRESHOLD and MAX_GOAL_TURNS from './goalState.js' to the
aliased path (e.g., 'src/services/goal/goalState') and change the import of the
GoalStatus type from '../../types/logs.js' to the aliased path (e.g.,
'src/types/logs'); ensure the imported symbols BLOCKED_CONSECUTIVE_THRESHOLD,
MAX_GOAL_TURNS, and GoalStatus are used unchanged.

Source: Coding guidelines

src/services/goal/goalState.ts (1)

8-10: ⚡ Quick win

Use src/* alias imports in src/ files.

These relative imports should use the src/... alias to match the repo import-path contract.

As per coding guidelines, "Import paths must use the src/* alias (e.g., import { ... } from 'src/utils/...')."

Suggested diff
-import type { GoalState, GoalStatus } from '../../types/logs.js'
-import { getSessionId } from '../../bootstrap/state.js'
-import { logForDebugging } from '../../utils/debug.js'
+import type { GoalState, GoalStatus } from 'src/types/logs.js'
+import { getSessionId } from 'src/bootstrap/state.js'
+import { logForDebugging } from 'src/utils/debug.js'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/goal/goalState.ts` around lines 8 - 10, Replace the relative
imports in src/services/goal/goalState.ts with the repository alias form: change
the import of types GoalState and GoalStatus, getSessionId, and logForDebugging
to use 'src/...' paths (e.g., import { GoalState, GoalStatus } from
'src/types/logs' and import { getSessionId } from 'src/bootstrap/state' and
import { logForDebugging } from 'src/utils/debug') so the file follows the
repo's import-path contract.

Source: Coding guidelines

src/services/goal/prompts.ts (1)

44-49: ⚡ Quick win

Escape objective text before embedding into XML-wrapped prompts.

Raw goal.objective/newObjective/previousObjective can break wrapper tags (or inject competing tags), which makes steering prompts brittle.

Suggested fix
+function escapeXmlText(value: string): string {
+  return value
+    .replaceAll('&', '&')
+    .replaceAll('<', '&lt;')
+    .replaceAll('>', '&gt;')
+    .replaceAll('"', '&quot;')
+    .replaceAll("'", '&apos;')
+}

 export function buildContinuationPrompt(goal: GoalState): string {
+  const safeObjective = escapeXmlText(goal.objective)
   const elapsed = formatGoalElapsed(goal)
   const tokenInfo = formatTokenUsage(goal)
   const turnInfo = `Continuation turns executed: ${goal.turnsExecuted}`
@@
-${goal.objective}
+${safeObjective}
@@
 export function buildBudgetLimitPrompt(goal: GoalState): string {
+  const safeObjective = escapeXmlText(goal.objective)
   return `<goal-steering type="budget_limit">
@@
-- Goal: ${goal.objective}
+- Goal: ${safeObjective}
@@
 export function buildObjectiveUpdatedPrompt(
   newObjective: string,
   previousObjective?: string,
 ): string {
+  const safeNewObjective = escapeXmlText(newObjective)
   const previousSection = previousObjective
-    ? `\nPrevious objective: ${previousObjective}\n`
+    ? `\nPrevious objective: ${escapeXmlText(previousObjective)}\n`
     : ''
@@
-New objective: ${newObjective}
+New objective: ${safeNewObjective}
@@
 export function buildGoalContextBlock(goal: GoalState): string {
+  const safeObjective = escapeXmlText(goal.objective)
@@
-    goal.objective,
+    safeObjective,

Also applies to: 93-95, 120-127, 146-150

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/goal/prompts.ts` around lines 44 - 49, The XML-wrapped prompt
templates in src/services/goal/prompts.ts embed raw text (e.g., goal.objective,
newObjective, previousObjective) which can break or inject tags; fix by
HTML/XML-escaping special characters (&, <, >, ", ') before interpolation. Add
or reuse a small helper like escapeXml(str) and call it wherever those template
strings are constructed (the places that return `<goal-steering ...>` and the
other template blocks referenced around lines 93-95, 120-127, 146-150),
replacing direct uses of goal.objective/newObjective/previousObjective with
escapeXml(value).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/builtin-tools/src/tools/GoalTool/prompt.ts`:
- Around line 34-37: The prompt's contract is incorrect: it claims the `update`
operation is a no-op when no goal exists, but the implementation (GoalTool.ts
`update` method) throws/returns an error; update the prompt in
GoalTool/prompt.ts to state that `update` returns an error if no active goal
exists (or alternately change the implementation to be a no-op), and ensure the
wording matches the `update` method behavior and error semantics used in
GoalTool.ts so planners use and handle the tool consistently.

In `@src/commands/goal/goal.tsx`:
- Around line 18-31: The file uses relative imports; update all imports to use
the src/* path alias instead (e.g. change imports that reference
'../../commands.js', '../../types/command.js',
'../../services/goal/goalState.js', '../../services/goal/goalStorage.js', and
'./GoalReplaceConfirmDialog.js' to their corresponding src/... paths).
Specifically replace the imports that provide LocalJSXCommandContext,
LocalJSXCommandOnDone, the functions clearGoal, completeGoal, formatGoalElapsed,
formatGoalStatusLabel, getGoal, pauseGoal, resumeGoal, setGoal, the
persistCurrentGoal/persistGoalClear functions, and the GoalReplaceConfirmDialog
component to import from src/commands.js, src/types/command.js,
src/services/goal/goalState.js, src/services/goal/goalStorage.js, and
src/commands/goal/GoalReplaceConfirmDialog.js (or the correct src-relative
locations) so the module uses the src alias consistently.

In `@src/commands/goal/GoalReplaceConfirmDialog.tsx`:
- Around line 9-13: The imports at the top of GoalReplaceConfirmDialog.tsx use
relative paths; change them to the project alias form (src/...) to comply with
the src/* import rule. Replace the relative import for GoalState, Select
(CustomSelect), PermissionDialog, and the goal state helpers (formatGoalElapsed,
formatGoalStatusLabel) with their equivalent src/ alias module paths so lines
importing GoalState, Select, PermissionDialog, formatGoalElapsed, and
formatGoalStatusLabel use src/... instead of ../../ or ../../components/... etc.

In `@src/commands/goal/index.ts`:
- Line 1: The import currently pulls the Command type using a relative path for
the symbol "Command" in the module's top import; replace that relative import
with the repo alias style (use the src/* alias) so the type import uses the
aliased module path instead of '../../commands.js' (e.g., import type { Command
} from 'src/commands'); update any extension as needed to match TS build config.
- Around line 9-10: The command is marked bridgeSafe: true but its interactive
path can open a local JSX dialog (GoalReplaceConfirmDialog), which makes
bridge/mobile invocations unsafe; remove or set bridgeSafe to false on the
command export (the object containing bridgeSafe and load in
src/commands/goal/index.ts) or guard the interactive branch so it never triggers
under bridge-safe invocation (e.g., check bridge-safe context before showing
GoalReplaceConfirmDialog and fall back to a non-interactive flow); update the
exported command object to reflect the safe setting and ensure
GoalReplaceConfirmDialog is not invoked when bridgeSafe is true.

In `@src/components/StatusLine.tsx`:
- Line 48: Replace the relative import in StatusLine.tsx that pulls in
formatTokens from '../utils/format.js' with the project alias path
'src/utils/format.js' so it conforms to the src/* import rule; update the import
statement referencing the formatTokens symbol to use 'src/utils/format.js' and
ensure any IDE/type imports still resolve after the change.

In `@src/hooks/useGoalContinuation.ts`:
- Around line 54-55: The budgetLimitFiredRef (declared in useGoalContinuation)
is never reset, so once a budget-limit wrap-up is injected it blocks future
goals; update useGoalContinuation to reset budgetLimitFiredRef.current = false
whenever a new goal starts (e.g., inside an effect that watches the current goal
id or goal index) and apply the same reset logic to the similar refs/handlers
referenced in the 85-101 region so each new goal can emit its own budget-limit
prompt; locate budgetLimitFiredRef and the other refs in useGoalContinuation and
add a useEffect or reset point at goal transition to clear them.
- Line 97: In useGoalContinuation, the enqueue calls are forcing origin to a
string which breaks the expected MessageOrigin shape; remove the "as unknown as
string" casts and pass the origin as an object with the correct shape (e.g., {
kind: 'goal-budget-limit' } and { kind: 'goal-continuation' }) so enqueue (and
the QueuedCommand origin) retains type safety and downstream code can read
origin.kind; update any local typings only if necessary to accept MessageOrigin
in the enqueue call within the useGoalContinuation hook.

In `@src/screens/REPL.tsx`:
- Around line 2545-2559: The shared cancel handler in REPL.tsx currently always
pauses active goals and sets wasAborted; change it so only
keyboard/interrupt-caused cancels pause goals: add an explicit abort-source flag
(e.g., userInterrupt or abortCause) to the onCancel path and update callers
(restore/edit flows) to pass false; then wrap the existing feature('GOAL') block
(the calls to getGoal, pauseGoal, persistCurrentGoal) and the
setWasAborted(true) so they run only when that flag indicates a real user
interrupt (not a restore/dialog dismissal). Ensure references to getGoal,
pauseGoal, persistCurrentGoal and setWasAborted are guarded by the new
condition.
- Around line 2233-2240: REPL currently only hydrates goal state inside
REPL.resume() via hydrateGoalFromTranscript(log.goal), but resumes using
initialMessages (ResumeConversation -> <REPL
initialMessages={resumeData.messages} />) don't call resume(), so the in-memory
goalState map isn't populated; update the initialMessages mount effect (the
useEffect that restores read-file state/remote tasks) to also detect a resumed
goal (e.g., resumeData.currentSessionGoal or resumeData.goal) and call
hydrateGoalFromTranscript with a Map keyed by sessionId (same shape used in
REPL.resume), or alternatively call the same helper used in REPL.resume to
populate goalState; ensure you reference hydrateGoalFromTranscript, REPL.resume,
ResumeConversation, initialMessages, restoreSessionMetadata/currentSessionGoal
and populate the goalState map during the mount flow so goal continuation works
for initialMessages-based resumes.

In `@src/services/goal/goalState.ts`:
- Around line 94-100: The resumeGoal logic currently permits resuming goals in
'blocked', 'budget_limited', and 'usage_limited' states; update the check in
resumeGoal so only goals with status 'paused' may be resumed (do not allow
resume for terminal states), or alternatively call and respect
isGoalTerminal(goal) to prevent resuming any terminal/limit states (including
'blocked', 'budget_limited', 'usage_limited'); ensure the function returns null
and the UX message remains "No paused goal to resume" when the goal is not
resumable.

In `@src/services/goal/prompts.ts`:
- Around line 141-147: The opening tag string in buildGoalContextBlock is
constructing invalid XML/HTML by injecting " | budget: ..." into the tag body;
change this to produce a proper attribute instead—compute a budgetAttr (e.g.,
budgetAttr = goal.tokenBudget !== null ? `
budget="${goal.tokensUsed}/${goal.tokenBudget}"` : '') and interpolate that into
the tag `<active-goal ...${budgetAttr} turns="...">`, ensuring spacing and no
pipe character so the tag remains well-formed; update the code that builds the
`budget` variable (or replace it) and adjust the return line that currently
references `budget` to use the new attribute string.

---

Outside diff comments:
In `@src/utils/sessionStorage.ts`:
- Around line 3199-3210: METADATA_TYPE_MARKERS used by the pre-boundary scan
omits goal metadata, causing loadTranscriptFile to skip '"type":"goal"' and
'"type":"goal-cleared"' entries when pre-compact truncation occurs; update the
METADATA_TYPE_MARKERS array (and thereby METADATA_MARKER_BUFS) to include
'"type":"goal"' and '"type":"goal-cleared"' so the pre-boundary pass will detect
and preserve goal state, then run tests that exercise loadTranscriptFile to
verify goals survive resume.

---

Nitpick comments:
In `@src/services/goal/goalAudit.ts`:
- Around line 5-6: Replace the relative imports in goalAudit.ts with src/*
aliases: change the import of BLOCKED_CONSECUTIVE_THRESHOLD and MAX_GOAL_TURNS
from './goalState.js' to the aliased path (e.g., 'src/services/goal/goalState')
and change the import of the GoalStatus type from '../../types/logs.js' to the
aliased path (e.g., 'src/types/logs'); ensure the imported symbols
BLOCKED_CONSECUTIVE_THRESHOLD, MAX_GOAL_TURNS, and GoalStatus are used
unchanged.

In `@src/services/goal/goalState.ts`:
- Around line 8-10: Replace the relative imports in
src/services/goal/goalState.ts with the repository alias form: change the import
of types GoalState and GoalStatus, getSessionId, and logForDebugging to use
'src/...' paths (e.g., import { GoalState, GoalStatus } from 'src/types/logs'
and import { getSessionId } from 'src/bootstrap/state' and import {
logForDebugging } from 'src/utils/debug') so the file follows the repo's
import-path contract.

In `@src/services/goal/prompts.ts`:
- Around line 44-49: The XML-wrapped prompt templates in
src/services/goal/prompts.ts embed raw text (e.g., goal.objective, newObjective,
previousObjective) which can break or inject tags; fix by HTML/XML-escaping
special characters (&, <, >, ", ') before interpolation. Add or reuse a small
helper like escapeXml(str) and call it wherever those template strings are
constructed (the places that return `<goal-steering ...>` and the other template
blocks referenced around lines 93-95, 120-127, 146-150), replacing direct uses
of goal.objective/newObjective/previousObjective with escapeXml(value).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2d824707-13ff-45aa-bb4d-b3cf9f599ff0

📥 Commits

Reviewing files that changed from the base of the PR and between fac16da and cf56226.

📒 Files selected for processing (22)
  • packages/builtin-tools/src/index.ts
  • packages/builtin-tools/src/tools/GoalTool/GoalTool.ts
  • packages/builtin-tools/src/tools/GoalTool/constants.ts
  • packages/builtin-tools/src/tools/GoalTool/prompt.ts
  • scripts/defines.ts
  • src/commands.ts
  • src/commands/goal/GoalReplaceConfirmDialog.tsx
  • src/commands/goal/goal.tsx
  • src/commands/goal/index.ts
  • src/components/StatusLine.tsx
  • src/cost-tracker.ts
  • src/hooks/useGoalContinuation.ts
  • src/screens/REPL.tsx
  • src/services/goal/__tests__/goalState.test.ts
  • src/services/goal/goalAudit.ts
  • src/services/goal/goalState.ts
  • src/services/goal/goalStorage.ts
  • src/services/goal/prompts.ts
  • src/tools.ts
  • src/types/logs.ts
  • src/utils/sessionStorage.ts
  • tests/integration/goal-lifecycle.test.ts

Comment thread packages/builtin-tools/src/tools/GoalTool/prompt.ts
Comment thread src/commands/goal/goal.tsx Outdated
Comment thread src/commands/goal/GoalReplaceConfirmDialog.tsx Outdated
Comment thread src/commands/goal/index.ts Outdated
Comment thread src/commands/goal/index.ts Outdated
Comment thread src/hooks/useGoalContinuation.ts Outdated
Comment thread src/screens/REPL.tsx
Comment thread src/screens/REPL.tsx
Comment thread src/services/goal/goalState.ts Outdated
Comment thread src/services/goal/prompts.ts
moyu added 2 commits June 8, 2026 17:47
- prompt 中 update 行为描述与运行时不一致(no-op → error)
- src/commands/goal/ 使用相对路径导入,改为 src/* 别名
- /goal 命令标记 bridgeSafe 但含交互式对话框,改为 false
- useGoalContinuation 中 origin 使用 as unknown as string 强转,改为直接传字符串
- ResumeConversation 路径缺少 goal hydration,补齐恢复逻辑
- onCancel 在非查询状态下误暂停 goal,加 queryGuard 守卫
- resumeGoal 允许从终态恢复,收紧为仅允许 paused 状态
- buildGoalContextBlock 生成畸形 XML 属性,改为合法 budget 属性

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
src/services/goal/goalState.ts (2)

67-72: ⚡ Quick win

Avoid redundant resolveSessionId calls.

resolveSessionId(sessionId) is called twice in this function. Cache the result to avoid repeated computation.

♻️ Suggested fix
 export function clearGoal(sessionId?: string): boolean {
-  const had = goals.has(resolveSessionId(sessionId))
-  const result = goals.delete(resolveSessionId(sessionId))
+  const id = resolveSessionId(sessionId)
+  const had = goals.has(id)
+  const result = goals.delete(id)
   if (had) goalLog('CLEAR', 'goal removed')
   return result
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/goal/goalState.ts` around lines 67 - 72, The function clearGoal
calls resolveSessionId(sessionId) twice causing redundant work; cache the
resolved id in a local variable (e.g., const id = resolveSessionId(sessionId))
and use that variable for goals.has(id), goals.delete(id), and the
goalLog('CLEAR', ...) call so resolveSessionId is invoked only once and behavior
of clearGoal, goals.has, goals.delete, and goalLog remains unchanged.

8-8: 💤 Low value

Use src/* import alias instead of relative path.

The import uses a relative path ../../types/logs.js. As per coding guidelines, import paths should use the src/* alias.

♻️ Suggested fix
-import type { GoalState, GoalStatus } from '../../types/logs.js'
+import type { GoalState, GoalStatus } from 'src/types/logs.js'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/goal/goalState.ts` at line 8, Replace the relative import for
GoalState and GoalStatus with the project alias: change the import in
goalState.ts that currently references '../../types/logs.js' to use the 'src/*'
alias (importing GoalState and GoalStatus from 'src/types/logs' or
'src/types/logs.js' depending on resolver) so the symbols GoalState and
GoalStatus are imported via the alias rather than a relative path.

Source: Coding guidelines

src/screens/REPL.tsx (1)

2547-2550: ⚡ Quick win

Keep feature() as the direct branch condition.

Line 2550 uses feature('GOAL') inside an && chain. Split this into nested conditionals so the Bun feature flag stays in the direct if position required by the repo rule.

As per coding guidelines, feature flags "can only be used directly in if statements or ternary expressions, not assigned to variables or used in && chains."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/screens/REPL.tsx` around lines 2547 - 2550, The branch currently uses
feature('GOAL') in an && chain; change it so feature('GOAL') is the direct if
condition by nesting: first check if (feature('GOAL')) and then inside that
block check queryGuard.getSnapshot() (the existing queryGuard.getSnapshot()
condition and its body). Ensure the symbol feature('GOAL') remains the direct
branch and that queryGuard.getSnapshot() is evaluated only within the nested
block.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/screens/REPL.tsx`:
- Around line 2547-2550: The branch currently uses feature('GOAL') in an &&
chain; change it so feature('GOAL') is the direct if condition by nesting: first
check if (feature('GOAL')) and then inside that block check
queryGuard.getSnapshot() (the existing queryGuard.getSnapshot() condition and
its body). Ensure the symbol feature('GOAL') remains the direct branch and that
queryGuard.getSnapshot() is evaluated only within the nested block.

In `@src/services/goal/goalState.ts`:
- Around line 67-72: The function clearGoal calls resolveSessionId(sessionId)
twice causing redundant work; cache the resolved id in a local variable (e.g.,
const id = resolveSessionId(sessionId)) and use that variable for goals.has(id),
goals.delete(id), and the goalLog('CLEAR', ...) call so resolveSessionId is
invoked only once and behavior of clearGoal, goals.has, goals.delete, and
goalLog remains unchanged.
- Line 8: Replace the relative import for GoalState and GoalStatus with the
project alias: change the import in goalState.ts that currently references
'../../types/logs.js' to use the 'src/*' alias (importing GoalState and
GoalStatus from 'src/types/logs' or 'src/types/logs.js' depending on resolver)
so the symbols GoalState and GoalStatus are imported via the alias rather than a
relative path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 73c7157c-353c-48f6-8aa0-7c6aa6297905

📥 Commits

Reviewing files that changed from the base of the PR and between beb01cb and c551f5a.

📒 Files selected for processing (8)
  • src/commands/goal/GoalReplaceConfirmDialog.tsx
  • src/commands/goal/goal.tsx
  • src/commands/goal/index.ts
  • src/hooks/useGoalContinuation.ts
  • src/screens/REPL.tsx
  • src/screens/ResumeConversation.tsx
  • src/services/goal/goalState.ts
  • src/services/goal/prompts.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/commands/goal/index.ts
  • src/commands/goal/goal.tsx
  • src/commands/goal/GoalReplaceConfirmDialog.tsx
  • src/services/goal/prompts.ts
  • src/hooks/useGoalContinuation.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/hooks/useGoalContinuation.ts (1)

50-51: ⚡ Quick win

optsRef is assigned but never read.

optsRef.current is set but never accessed in the effect body. The effect reads opts directly (lines 57, 62, 69, 73, 77), making this ref unused.

♻️ Remove unused ref
 export function useGoalContinuation(opts: UseGoalContinuationOpts): void {
-  const optsRef = useRef(opts)
-  optsRef.current = opts
-
   const enqueuedRef = useRef(false)
   const budgetLimitFiredRef = useRef(false)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/useGoalContinuation.ts` around lines 50 - 51, The optsRef variable
in useGoalContinuation is assigned (optsRef.current = opts) but never read;
remove the unused ref to simplify the hook: delete the optsRef declaration and
the assignment, and update the effect to keep using the existing opts references
(used in the effect body where opts is read on lines referencing opts) so
behavior is unchanged; ensure no other code in useGoalContinuation (the effect
callbacks) tries to access optsRef after removal.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/hooks/useGoalContinuation.ts`:
- Around line 50-51: The optsRef variable in useGoalContinuation is assigned
(optsRef.current = opts) but never read; remove the unused ref to simplify the
hook: delete the optsRef declaration and the assignment, and update the effect
to keep using the existing opts references (used in the effect body where opts
is read on lines referencing opts) so behavior is unchanged; ensure no other
code in useGoalContinuation (the effect callbacks) tries to access optsRef after
removal.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3ac63775-567a-4254-8fd2-0a5233c52daf

📥 Commits

Reviewing files that changed from the base of the PR and between c551f5a and 1d209db.

📒 Files selected for processing (2)
  • src/components/StatusLine.tsx
  • src/hooks/useGoalContinuation.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/StatusLine.tsx

@Moyhub

Moyhub commented Jun 8, 2026

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Moyhub

Moyhub commented Jun 9, 2026

Copy link
Copy Markdown
Author

本地测试流程:
开启bun run dev -- --debug 开启debug模式便于观察是否启用goal以及内部的变化。
测试模型:
qwen3.6-plus
测试Prompt:
由于模型能力较强,且可以在一个turn调用大量tools完成任务,因此简单任务往往一个turn就完成了无法验证多轮,因此可以采用两种方案,1使用复杂任务 2让模型输出足够长超出最大限制,强行触发多轮。我采用了第二种,如下:

/goal 为以下每个模块创建完整的单元测试文件,每个文件至少20个测试用例,覆盖所有导出函数的正常路径、边界条件和错误处理。每个测试文件写完后立即运行验证。模块列表:1) src/services/goal/goalState.ts 2) src/services/goal/goalStorage.ts 3) src/services/goal/goalAudit.ts 4) src/services/goal/prompts.ts 5) src/commands/goal/goal.tsx 6) src/hooks/useGoalContinuation.ts 7) packages/builtin-tools/src/tools/GoalTool/GoalTool.ts。全部完成后运行bun test确认零失败。

debug日志:
image

image image

@claude-code-best 可以看看是否有BUG

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant