Skip to content

feat: Add error handling to auto-mode facade and implement followUp f…#776

Merged
gsxdsm merged 3 commits intoAutoMaker-Org:v0.15.0rcfrom
gsxdsm:fix/claude-weekly-usage
Feb 17, 2026
Merged

feat: Add error handling to auto-mode facade and implement followUp f…#776
gsxdsm merged 3 commits intoAutoMaker-Org:v0.15.0rcfrom
gsxdsm:fix/claude-weekly-usage

Conversation

@gsxdsm
Copy link
Collaborator

@gsxdsm gsxdsm commented Feb 17, 2026

…eature. Fix Claude weekly usage indicator. Fix mobile card drag

Summary by CodeRabbit

  • New Features

    • Weekly usage pace markers and pace labels added to usage bars/cards; optional Claude usage surfaced to auto-mode; facade-level runtime error reporting and structured facade errors.
  • Bug Fixes

    • Finalizes in-progress tasks during status transitions to avoid stale running tasks.
    • More robust Claude CLI output parsing and REPL handling for reliable usage detection.
  • UI/UX Improvements

    • Kanban cards use a dedicated drag handle; task progress aligns with persisted statuses.
  • Tests

    • Expanded unit tests for Claude parsing and feature state transitions.

…eature. Fix Claude weekly usage indicator. Fix mobile card drag
@coderabbitai
Copy link

coderabbitai bot commented Feb 17, 2026

No actionable comments were generated in the recent review. 🎉


📝 Walkthrough

Walkthrough

Adds Claude usage integration to auto-mode, introduces facade-level error handling and a FacadeError type, improves Claude CLI ANSI/TTY parsing and tests, finalizes in-progress tasks on feature completion, adds weekly pace indicators to usage UI and utilities, and refactors Kanban drag-handle wiring and task-status rendering.

Changes

Cohort / File(s) Summary
Auto-mode: compat & facade
apps/server/src/services/auto-mode/compat.ts, apps/server/src/services/auto-mode/facade.ts, apps/server/src/services/auto-mode/types.ts, apps/server/src/services/auto-mode/index.ts
Threaded optional ClaudeUsageService into AutoModeServiceCompat/FacadeOptions; added FacadeError type and handleFacadeError in facade; wrapped key facade methods (start/stop loop, execute/stop/followUp feature) to return structured errors and emit auto_mode_error.
Claude CLI usage parsing
apps/server/src/services/claude-usage-service.ts, apps/server/tests/unit/services/claude-usage-service.test.ts
Stronger ANSI/TTY cleaning (convert ESC[nC → spaces, improved ANSI stripping), broadened usage detection (percentage patterns, extra trust prompts), refined REPL/PTY handling and timeouts; expanded unit tests for cursor-movement/ANSI scenarios.
Feature state finalization
apps/server/src/services/feature-state-manager.ts, apps/server/tests/unit/services/feature-state-manager.test.ts
Finalize in_progress tasks to completed when transitioning to waiting_approval or verified; clear currentTaskId, update tasksCompleted/justFinishedAt; added tests for finalization and persistence/emit ordering.
Task progress UI
apps/ui/src/components/ui/task-progress-panel.tsx
Derive initial task statuses from persisted planSpec.task.status; remove implicit "previous tasks completed" heuristic; add handling for auto_mode_task_status events to update per-task status and currentTaskId.
Usage visualization & utils
apps/ui/src/components/usage-popover.tsx, apps/ui/src/components/views/board-view/mobile-usage-bar.tsx, apps/ui/src/store/utils/usage-utils.ts
Added weekly pace utilities getExpectedWeeklyPacePercentage and getPaceStatusLabel; threaded pacePercentage into ProgressBar/UsageCard/mobile bar, rendering a vertical pace marker and human-readable pace label.
Agent UI (kanban card)
apps/ui/src/components/views/board-view/components/kanban-card/agent-info-panel.tsx
Added isFeatureFinished detection (waiting_approval/verified) and coerce tasks to completed for finished features in effectiveTodos derivation (defensive UI change).
Drag-and-drop refactor
apps/ui/src/components/views/board-view/components/kanban-card/card-header.tsx, apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx
Introduced dragHandleListeners and dragHandleAttributes on CardHeaderSection; moved DnD listeners/attributes and drag cursor styling to a dedicated drag handle element; removed global draggable attributes from card root and touch-none class.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Facade as AutoModeServiceFacade
  participant FeatureStore as FeatureLoader/Storage
  participant ClaudeService as ClaudeUsageService
  participant Events as EventEmitter

  Client->>Facade: followUpFeature(featureId, params)
  Facade->>FeatureStore: loadFeature(featureId)
  FeatureStore-->>Facade: feature + planSpec
  Facade->>FeatureStore: readPreviousContext(featureId)
  FeatureStore-->>Facade: previousContext
  Facade->>ClaudeService: (optional) enrich/record usage (facadeOptions.claudeUsageService)
  ClaudeService-->>Facade: usageAck (optional)
  Facade->>Facade: build continuationPrompt, store imagePaths
  Facade->>Facade: executeFeature(featureId, continuationPrompt)
  Facade->>Events: emit auto_mode_task_started / events...
  Facade-->>Client: ack / response
  Note over Facade,Events: on non-abort errors -> handleFacadeError -> emit auto_mode_error
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • #203 — Introduced ClaudeUsageService; directly related to threading Claude usage into auto-mode compat and facade.
  • #324 — Modifies kanban-card drag/handle behavior; overlaps with drag-handle props and wiring changes here.
  • #194 — Also adjusts Kanban card/header wiring and task/status UI; relevant to the drag-and-drop and task-finalization UI changes.

Suggested labels

Enhancement

Poem

🐇 I nibble ANSI and stitch the flow,

Tasks tuck in when their sunsets show,
A tiny pace flag marks the trail,
Drag handles dance where cards set sail,
Errors wrapped up — the rabbit hops slow.

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes in the PR, covering error handling in auto-mode facade, followUp feature implementation, and related fixes.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into v0.15.0rc

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @gsxdsm, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the auto-mode functionality by introducing robust error handling within the facade layer and fully implementing the agent follow-up feature. It also refines the display of Claude usage statistics with a new weekly pace indicator and corrects task status management upon feature completion. Additionally, the user interface sees an improvement in mobile drag-and-drop interactions for Kanban cards.

Highlights

  • Auto-Mode Facade Error Handling: Implemented centralized error handling for AutoModeServiceFacade methods, ensuring consistent logging and UI event emission for failures. Key methods like startAutoLoop, stopAutoLoop, executeFeature, and stopFeature now gracefully catch and report errors.
  • Follow-Up Feature Implementation: The followUpFeature in the AutoModeServiceFacade has been fully implemented. It now loads feature context, reads previous agent output, constructs a continuation prompt, handles image paths, and delegates execution, moving beyond its previous stub state.
  • Improved Claude Usage Indicator: Enhanced the Claude usage service to more accurately parse CLI output by robustly handling ANSI escape codes, particularly cursor movement sequences. The UI now displays a weekly usage pace indicator, showing if usage is ahead or behind the expected rate.
  • Refined Task Status Management: The FeatureStateManager now automatically finalizes in-progress tasks as completed when a feature transitions to waiting_approval or verified status. The UI's TaskProgressPanel has been updated to reflect these persisted statuses and respond to new real-time task status events.
  • Mobile Card Drag Fix: Modified the Kanban card component to restrict drag-and-drop functionality to a dedicated drag handle within the card header, improving the mobile user experience by preventing accidental full-card drags.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • apps/server/src/services/auto-mode/compat.ts
    • Imported ClaudeUsageService type.
    • Added claudeUsageService as an optional parameter to the constructor and passed it to FacadeOptions.
  • apps/server/src/services/auto-mode/facade.ts
    • Imported FacadeError type.
    • Added a private handleFacadeError method to centralize error logging and UI event emission.
    • Wrapped startAutoLoop, stopAutoLoop, executeFeature, and stopFeature methods in try...catch blocks to utilize handleFacadeError.
    • Fully implemented the followUpFeature method, including loading feature context, reading previous agent output, building continuation prompts, and handling image paths.
    • Removed concurrencyManager.release from the finally block in followUpFeature.
  • apps/server/src/services/auto-mode/index.ts
    • Exported the new FacadeError type.
  • apps/server/src/services/auto-mode/types.ts
    • Imported ClaudeUsageService type.
    • Added claudeUsageService to the FacadeOptions interface.
    • Defined the FacadeError interface for structured error reporting.
  • apps/server/src/services/claude-usage-service.ts
    • Improved parsing of Claude CLI output in getUsageOutput to correctly handle ANSI cursor forward escape codes (ESC[nC) when checking for usage indicators and trust prompts.
    • Updated the stripAnsiCodes method to convert ESC[nC sequences into spaces to preserve word boundaries.
    • Expanded trust prompt detection to include additional phrases like 'Quick safety check' and 'trust this folder'.
  • apps/server/src/services/feature-state-manager.ts
    • Modified updateFeatureStatus to mark in_progress tasks as completed and clear currentTaskId when a feature transitions to waiting_approval or verified status.
    • Added logic to update tasksCompleted count based on actual completed tasks.
  • apps/server/tests/unit/services/claude-usage-service.test.ts
    • Added new unit tests for stripAnsiCodes to verify correct handling of ESC[nC sequences.
    • Added a unit test for parseUsageOutput to ensure accurate parsing of Claude CLI output containing cursor movement codes.
  • apps/server/tests/unit/services/feature-state-manager.test.ts
    • Added unit tests to confirm that in_progress and pending tasks are correctly finalized when a feature moves to waiting_approval or verified status.
    • Added a test to ensure justFinishedAt is cleared when moving to verified status.
    • Added a test to ensure graceful handling of waiting_approval status without planSpec tasks.
  • apps/ui/src/components/ui/task-progress-panel.tsx
    • Updated task status initialization to directly use the persisted t.status from ParsedTask.
    • Removed the heuristic that automatically marked previous tasks as completed when a new task started.
    • Added a new auto_mode_task_status event handler to update task statuses in real-time.
  • apps/ui/src/components/usage-popover.tsx
    • Imported getExpectedWeeklyPacePercentage and getPaceStatusLabel.
    • Modified the ProgressBar component to optionally display a weekly pace indicator.
    • Updated the UsageCard component to show a pace status label and pass pace data to the progress bar.
    • Integrated weekly pace calculation into the Claude usage cards.
  • apps/ui/src/components/views/board-view/components/kanban-card/agent-info-panel.tsx
    • Added a isFeatureFinished check to ensure all tasks are displayed as completed in the UI when a feature is in waiting_approval or verified status.
    • Included isFeatureFinished in the useMemo dependencies for effectiveTodos.
  • apps/ui/src/components/views/board-view/components/kanban-card/card-header.tsx
    • Imported DraggableAttributes and DraggableSyntheticListeners types.
    • Added dragHandleListeners and dragHandleAttributes props to CardHeaderProps.
    • Applied drag handle props to the dedicated drag handle div, restricting drag functionality to this element.
    • Updated cursor styles for the drag handle.
  • apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx
    • Modified getCursorClass to remove the cursor-grab style from the entire card when draggable, as dragging is now handle-specific.
    • Removed touch-none from wrapperClasses.
    • Removed attributes and listeners from the main card div, passing them instead to CardHeaderSection.
  • apps/ui/src/components/views/board-view/mobile-usage-bar.tsx
    • Imported getExpectedWeeklyPacePercentage and getPaceStatusLabel.
    • Updated the UsageBar component to display a pace status label and include a pace indicator in the progress bar.
    • Integrated weekly pace calculation into the mobile Claude usage bar.
  • apps/ui/src/store/utils/usage-utils.ts
    • Added getExpectedWeeklyPacePercentage function to calculate the ideal weekly usage percentage based on time elapsed.
    • Added getPaceStatusLabel function to generate a human-readable string indicating if usage is ahead, behind, or on pace.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (4)
apps/ui/src/components/usage-popover.tsx (1)

415-431: ⚠️ Potential issue | 🟠 Major

Add sonnetWeeklyResetTime to ClaudeUsage type and use it for Sonnet card pace calculation.

The Sonnet card at line 422 shows sonnetResetText indicating a separate reset schedule from the general weekly counter (e.g., "Resets Dec 27" vs "Resets Dec 22"), but calculates pacePercentage using claudeUsage.weeklyResetTime. This causes the pace indicator to be inaccurate.

The ClaudeUsage type in apps/ui/src/store/types/usage-types.ts and apps/server/src/routes/claude/types.ts is missing a sonnetWeeklyResetTime field. Add this field and pass it to getExpectedWeeklyPacePercentage() for the Sonnet card instead of weeklyResetTime.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/components/usage-popover.tsx` around lines 415 - 431, The Sonnet
card uses claudeUsage.weeklyResetTime for pacePercentage but its reset text
comes from a separate sonnet schedule; add a sonnetWeeklyResetTime field to the
ClaudeUsage type (in apps/ui/src/store/types/usage-types.ts and
apps/server/src/routes/claude/types.ts) and populate it from the server, then
update the Sonnet UsageCard instantiation in usage-popover.tsx to call
getExpectedWeeklyPacePercentage(claudeUsage.sonnetWeeklyResetTime) instead of
claudeUsage.weeklyResetTime so the pace indicator matches the Sonnet reset
schedule.
apps/ui/src/components/ui/task-progress-panel.tsx (1)

134-144: ⚠️ Potential issue | 🟡 Minor

Inconsistent currentTaskId clearing between task_complete and task_status.

Line 142 unconditionally sets setCurrentTaskId(null) when a task completes, which will clobber the current ID even if a different task is already in_progress (e.g., if task_started fires for the next task before task_complete arrives for the previous one). The newer auto_mode_task_status handler at line 159 correctly uses a conditional update:

setCurrentTaskId((current) => (current === taskEvent.taskId ? null : current));

Consider applying the same conditional pattern here for consistency:

Suggested fix
-            setCurrentTaskId(null);
+            setCurrentTaskId((current) => (current === taskEvent.taskId ? null : current));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/components/ui/task-progress-panel.tsx` around lines 134 - 144,
The handler for the 'auto_mode_task_complete' case unconditionally calls
setCurrentTaskId(null) which can clobber a newly started task; change it to use
the same conditional updater used in the 'auto_mode_task_status' handler: call
setCurrentTaskId((current) => (current === taskEvent.taskId ? null : current))
so the currentTaskId is only cleared when it matches taskEvent.taskId (locate in
the case 'auto_mode_task_complete' where taskEvent is defined).
apps/server/src/services/auto-mode/facade.ts (1)

663-676: 🛠️ Refactor suggestion | 🟠 Major

followUpFeature duplicates error-handling logic instead of using handleFacadeError.

The other wrapped methods (startAutoLoop, stopAutoLoop, executeFeature, stopFeature) all delegate to handleFacadeError. This catch block manually replicates the same classifyError + emitAutoModeEvent pattern. Use the new helper for consistency.

♻️ Suggested diff
     } catch (error) {
-      const errorInfo = classifyError(error);
-      if (!errorInfo.isAbort) {
-        this.eventBus.emitAutoModeEvent('auto_mode_error', {
-          featureId,
-          featureName: undefined,
-          branchName: null,
-          error: errorInfo.message,
-          errorType: errorInfo.type,
-          projectPath: this.projectPath,
-        });
-      }
+      this.handleFacadeError(error, 'followUpFeature', featureId);
       throw error;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/services/auto-mode/facade.ts` around lines 663 - 676, The
catch in followUpFeature duplicates the classifyError + emitAutoModeEvent logic;
replace that manual handling by delegating to the existing helper: call
this.handleFacadeError(error, { featureId, featureName: undefined, branchName:
null, projectPath: this.projectPath }) (or the exact payload shape used by other
callers) instead of running classifyError and eventBus.emitAutoModeEvent
directly, then rethrow or return as handleFacadeError does to match the other
methods (startAutoLoop/stopAutoLoop/executeFeature/stopFeature).
apps/server/src/services/auto-mode/compat.ts (1)

31-42: ⚠️ Potential issue | 🟡 Minor

claudeUsageService is passed to FacadeOptions but never extracted or used in facade creation.

claudeUsageService is added to FacadeOptions in types.ts and passed via facadeOptions in compat.ts line 42, but AutoModeServiceFacade.create() (facade.ts lines 138–144) does not destructure it. The value is silently dropped and never passed to any service initialization. Either wire it into the appropriate services that need to check usage limits, or remove the parameter if it's not yet needed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/services/auto-mode/compat.ts` around lines 31 - 42, The
FacadeOptions field claudeUsageService is being set in compat.ts but never
consumed when building the facade; update AutoModeServiceFacade.create (and any
internal destructuring in facade.ts around lines where FacadeOptions is handled)
to accept and destructure claudeUsageService and then pass it into the
service(s) that enforce usage limits (or otherwise need it) during
initialization (or, if no service currently needs it, remove claudeUsageService
from FacadeOptions and compat.ts). Specifically, modify
AutoModeServiceFacade.create to include claudeUsageService in its parameter
destructuring and forward that value into the appropriate service constructors
or sharedServices wiring so it is not dropped.
🧹 Nitpick comments (8)
apps/ui/src/components/views/board-view/mobile-usage-bar.tsx (1)

206-216: getExpectedWeeklyPacePercentage is called on every render.

This is a pure computation based on new Date() and weeklyResetTime, so it's cheap. However, if the parent re-renders frequently (e.g., due to the 2-minute stale check or other store changes), you could memoize it with useMemo keyed on claudeUsage.weeklyResetTime. Not a performance concern today, just a minor note.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/components/views/board-view/mobile-usage-bar.tsx` around lines
206 - 216, Pure computation getExpectedWeeklyPacePercentage is being invoked on
every render; memoize its result with React's useMemo to avoid recomputing when
unrelated state changes. Wrap the call passed to the Weekly UsageBar's
pacePercentage prop in useMemo, e.g. const pace = useMemo(() =>
getExpectedWeeklyPacePercentage(claudeUsage.weeklyResetTime),
[claudeUsage.weeklyResetTime]); and pass pace to the UsageBar (label "Weekly")
instead of calling getExpectedWeeklyPacePercentage inline.
apps/ui/src/components/usage-popover.tsx (1)

238-257: Empty <div /> spacer when no paceLabel — consider a simpler approach.

The empty <div /> works because of justify-between, but it adds a meaningless DOM node. You could instead conditionally apply justify-end when there's no paceLabel, or just use ml-auto on the resetText element.

♻️ Optional simplification
-        <div className="mt-2 flex items-center justify-between">
-          {paceLabel ? (
-            <p
-              className={cn(
-                'text-[10px] font-medium',
-                safePercentage > (pacePercentage ?? 0) ? 'text-orange-500' : 'text-green-500'
-              )}
-            >
-              {paceLabel}
-            </p>
-          ) : (
-            <div />
-          )}
-          {resetText && (
+        <div className="mt-2 flex items-center justify-between">
+          {paceLabel && (
+            <p
+              className={cn(
+                'text-[10px] font-medium',
+                safePercentage > (pacePercentage ?? 0) ? 'text-orange-500' : 'text-green-500'
+              )}
+            >
+              {paceLabel}
+            </p>
+          )}
+          {resetText && (
-            <p className="text-xs text-muted-foreground flex items-center gap-1">
+            <p className="text-xs text-muted-foreground flex items-center gap-1 ml-auto">
               <Clock className="w-3 h-3" />
               {resetText}
             </p>
-          )}
-        </div>
+          )}
+        </div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/components/usage-popover.tsx` around lines 238 - 257, The empty
<div /> used as a spacer inside the container with class "mt-2 flex items-center
justify-between" is unnecessary; update the JSX so you don't render a
meaningless node — either conditionally set the container's justify class to
"justify-between" when paceLabel is present and "justify-end" when it's not, or
remove the spacer and add "ml-auto" to the resetText <p> (the element referenced
by resetText and the container wrapper around paceLabel) so layout stays the
same without the extra DOM node.
apps/ui/src/store/utils/usage-utils.ts (1)

53-59: "Ahead/behind pace" labels may read as inverted to users.

When actual > expected (using more budget than expected), the label says "behind pace." In common English, "behind pace" means slower than a target rate — but here the user is consuming faster. Consider whether "ahead of pace" / "behind pace" should be swapped, or use clearer phrasing like "X% over budget pace" / "X% under budget pace."

This is a UX / copy concern rather than a code bug — the code comments do explain the intent, so feel free to keep it if the team has aligned on the wording.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/store/utils/usage-utils.ts` around lines 53 - 59, The labels are
potentially inverted: when actualPercentage > expectedPercentage (diff > 0) the
UI shows "behind pace" which reads as consuming faster; change the copy mapping
in this block that computes diff (using actualPercentage and expectedPercentage)
so that diff > 0 yields a clear "over/over budget/over pace" message (e.g., "X%
over budget pace" or "X% over pace") and diff < 0 yields "under/under budget
pace" (e.g., "X% under budget pace"), updating the return strings accordingly to
avoid the ahead/behind ambiguity.
apps/ui/src/components/ui/task-progress-panel.tsx (2)

40-40: currentTaskId state is written but never read.

The state value is discarded ([, setCurrentTaskId]), meaning the component re-renders on every setCurrentTaskId call for no observable effect. If it's only bookkeeping, a useRef would avoid unnecessary re-renders.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/components/ui/task-progress-panel.tsx` at line 40, The
currentTaskId state is created but its value is never read (const [,
setCurrentTaskId] = useState...), causing unnecessary re-renders when
setCurrentTaskId is called; replace this useState with a useRef (e.g., const
currentTaskIdRef = useRef<string | null>(null)) and update all places that call
setCurrentTaskId to instead set currentTaskIdRef.current so you retain the
bookkeeping without triggering component re-renders; ensure any logic that
actually needs to read the id uses currentTaskIdRef.current or reinstates state
only where a read is required.

146-162: auto_mode_task_status event carries a full tasks array that is ignored.

Per the type definition, this event includes tasks: ParsedTask[] alongside the single taskId/status. The handler only applies the single-task update. If the server sends a reconciled task list (e.g., after a reconnect or bulk status change), the UI could drift out of sync.

Consider whether a full-state reconciliation from taskEvent.tasks would be more robust here, or document why the incremental approach is preferred.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/components/ui/task-progress-panel.tsx` around lines 146 - 162,
The handler for the 'auto_mode_task_status' event currently only applies an
incremental update via setTasks and ignores the event's tasks array; change it
to reconcile full state from taskEvent.tasks (the ParsedTask[] carried on the
event) instead of only updating one task: use setTasks(() =>
taskEvent.tasks.map(/* convert ParsedTask to TaskInfo shape */)) to replace the
task list, and update setCurrentTaskId based on the reconciled list (e.g., set
to the task with status 'in_progress' or null if none) so the UI cannot drift
out of sync; keep the existing single-task update logic only if you
intentionally want incremental updates and document that choice.
apps/server/src/services/claude-usage-service.ts (1)

297-306: Duplicated lightweight ANSI-cleaning regex in two places.

The same two-step regex (cursor-forward → spaces, then strip remaining CSI) appears in both the timeout handler (lines 299-301) and the onData callback (lines 336-338). A small private helper would eliminate the duplication and make future regex fixes single-site.

♻️ Example helper
+  /** Lightweight ANSI clean for quick checks (not full stripAnsiCodes). */
+  private quickClean(text: string): string {
+    return text
+      .replace(/\x1B\[(\d+)C/g, (_m: string, n: string) => ' '.repeat(parseInt(n, 10)))
+      .replace(/\x1B\[[0-9;?]*[A-Za-z@]/g, '');
+  }

Also applies to: 336-338

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/services/claude-usage-service.ts` around lines 297 - 306,
Introduce a small private helper (e.g., stripAnsiForCheck or
cleanOutputForCheck) that encapsulates the two-step ANSI-cleaning logic
currently duplicated: the cursor-forward replacement using /\x1B\[(\d+)C/g and
the CSI strip with /\x1B\[[0-9;?]*[A-Za-z@]/g, then replace the duplicated
inline logic in both the timeout handler (where cleanedForCheck is created) and
the onData callback with calls to this helper so both locations use the single
shared implementation.
apps/server/src/services/feature-state-manager.ts (1)

108-150: Production code is correct, but the task finalization logic is duplicated.

The waiting_approval and verified branches share nearly identical task-finalization logic (mark in_progresscompleted, recount, clear currentTaskId). Consider extracting a small helper to reduce duplication.

Note: the pipeline failures are caused by incorrect test expectations (see review on the test file), not by this code.

♻️ Example extraction
+  /**
+   * Finalize in_progress tasks to completed and update counters.
+   */
+  private finalizeInProgressTasks(planSpec: PlanSpec): number {
+    let tasksFinalized = 0;
+    if (planSpec.tasks) {
+      for (const task of planSpec.tasks) {
+        if (task.status === 'in_progress') {
+          task.status = 'completed';
+          tasksFinalized++;
+        }
+      }
+      planSpec.tasksCompleted = planSpec.tasks.filter((t) => t.status === 'completed').length;
+      planSpec.currentTaskId = undefined;
+    }
+    return tasksFinalized;
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/services/feature-state-manager.ts` around lines 108 - 150,
Extract the duplicated task-finalization logic into a small helper (e.g.,
finalizeTasksForCompletion or finalizeInProgressTasks) that accepts the feature
or its planSpec, iterates planSpec.tasks to mark any task.status ===
'in_progress' → 'completed', recalculates planSpec.tasksCompleted, and clears
planSpec.currentTaskId; then call this helper from both the status ===
'waiting_approval' and status === 'verified' branches so only justFinishedAt
handling remains in each branch (keep existing behavior of not touching pending
tasks).
apps/server/src/services/auto-mode/types.ts (1)

116-131: Prefer a top-level import for ErrorType instead of an inline import() type.

@automaker/types is already imported on line 11. Adding ErrorType to that import keeps the type surface consistent and avoids the inline import(…) noise.

♻️ Suggested diff
-import type { Feature, ModelProvider } from '@automaker/types';
+import type { Feature, ModelProvider, ErrorType } from '@automaker/types';
   /** Classified error type from the error handler */
-  errorType: import('@automaker/types').ErrorType;
+  errorType: ErrorType;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/services/auto-mode/types.ts` around lines 116 - 131, The
FacadeError interface uses an inline import() for ErrorType; change this to a
top-level import by adding ErrorType to the existing import from
'@automaker/types' (the file's imports at the top) and then replace "errorType:
import('@automaker/types').ErrorType;" inside the FacadeError interface with
"errorType: ErrorType;". This removes the inline import noise while keeping the
type reference consistent for the FacadeError definition.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/server/src/services/auto-mode/facade.ts`:
- Around line 646-657: The code currently sets feature.imagePaths using
p.split('/') which breaks on Windows and then calls
this.featureStateManager.updateFeatureStatus which re-reads the feature from
disk so the mutated in-memory imagePaths are never persisted and causes unwanted
side effects; replace p.split('/').pop() with path.basename(p) and persist the
new imagePaths explicitly instead of calling updateFeatureStatus: after setting
feature.imagePaths call the existing atomicWriteJson helper (or whichever
project-level atomic write utility is used) to write the feature JSON file for
this.projectPath/featureId so the imagePaths are saved without changing
timestamps or emitting status events, or alternatively pass imagePaths through
the executeFeature/options path (e.g., via the ExecutionService/executeFeature
options) if that flow is already supported.

In `@apps/server/tests/unit/services/feature-state-manager.test.ts`:
- Around line 154-189: The test incorrectly expects pending tasks to be marked
completed; update the test in
apps/server/tests/unit/services/feature-state-manager.test.ts so it matches
production logic: rename the test to remove "and pending tasks", assert that
only the in_progress task (task-2) is marked 'completed' while the pending task
(task-3) remains 'pending', ensure currentTaskId is cleared
(savedFeature.planSpec?.currentTaskId is undefined) and tasksCompleted reflects
only the completed tasks (e.g., 2 if two tasks are completed), referencing
manager.updateFeatureStatus and savedFeature.planSpec?.tasks to locate
assertions to change.
- Around line 191-228: The test incorrectly asserts that a 'pending' task is
marked 'completed' when updating to 'verified'—update the expectations in the
test for the updateFeatureStatus flow (the assertions reading savedFeature from
the atomicWriteJson mock) so only the 'in_progress' task(s) are expected to
become 'completed' (leave 'pending' tasks unchanged), set tasksCompleted to the
new count of completed tasks (e.g., 2) and keep assertions for clearing
currentTaskId and justFinishedAt as-is; locate assertions referencing
savedFeature.planSpec?.tasks, savedFeature.planSpec?.tasksCompleted,
savedFeature.planSpec?.currentTaskId and savedFeature.justFinishedAt and adjust
their expected values accordingly.

In `@apps/ui/src/components/ui/task-progress-panel.tsx`:
- Around line 65-74: The code is casting ParsedTask.status and taskEvent.status
unsafely which allows 'failed' to enter TaskInfo state and break rendering;
update the mapping that builds initialTasks (the map over planTasks producing
TaskInfo objects where status is currently assigned using (t.status as
TaskInfo['status']) || 'pending') and the handler that assigns taskEvent.status
(where taskEvent.status is cast directly) to explicitly normalize statuses to
the allowed TaskInfo['status'] values ('in_progress' | 'completed' | 'pending'):
implement a small helper like normalizeTaskStatus(status: ParsedTask['status'] |
string): TaskInfo['status'] and call it both in the initialTasks creation and
when consuming taskEvent.status (instead of direct casts or relying on falsy
fallbacks) so any unknown or disallowed values (including 'failed') map to
'pending' (or another defined default).

---

Outside diff comments:
In `@apps/server/src/services/auto-mode/compat.ts`:
- Around line 31-42: The FacadeOptions field claudeUsageService is being set in
compat.ts but never consumed when building the facade; update
AutoModeServiceFacade.create (and any internal destructuring in facade.ts around
lines where FacadeOptions is handled) to accept and destructure
claudeUsageService and then pass it into the service(s) that enforce usage
limits (or otherwise need it) during initialization (or, if no service currently
needs it, remove claudeUsageService from FacadeOptions and compat.ts).
Specifically, modify AutoModeServiceFacade.create to include claudeUsageService
in its parameter destructuring and forward that value into the appropriate
service constructors or sharedServices wiring so it is not dropped.

In `@apps/server/src/services/auto-mode/facade.ts`:
- Around line 663-676: The catch in followUpFeature duplicates the classifyError
+ emitAutoModeEvent logic; replace that manual handling by delegating to the
existing helper: call this.handleFacadeError(error, { featureId, featureName:
undefined, branchName: null, projectPath: this.projectPath }) (or the exact
payload shape used by other callers) instead of running classifyError and
eventBus.emitAutoModeEvent directly, then rethrow or return as handleFacadeError
does to match the other methods
(startAutoLoop/stopAutoLoop/executeFeature/stopFeature).

In `@apps/ui/src/components/ui/task-progress-panel.tsx`:
- Around line 134-144: The handler for the 'auto_mode_task_complete' case
unconditionally calls setCurrentTaskId(null) which can clobber a newly started
task; change it to use the same conditional updater used in the
'auto_mode_task_status' handler: call setCurrentTaskId((current) => (current ===
taskEvent.taskId ? null : current)) so the currentTaskId is only cleared when it
matches taskEvent.taskId (locate in the case 'auto_mode_task_complete' where
taskEvent is defined).

In `@apps/ui/src/components/usage-popover.tsx`:
- Around line 415-431: The Sonnet card uses claudeUsage.weeklyResetTime for
pacePercentage but its reset text comes from a separate sonnet schedule; add a
sonnetWeeklyResetTime field to the ClaudeUsage type (in
apps/ui/src/store/types/usage-types.ts and
apps/server/src/routes/claude/types.ts) and populate it from the server, then
update the Sonnet UsageCard instantiation in usage-popover.tsx to call
getExpectedWeeklyPacePercentage(claudeUsage.sonnetWeeklyResetTime) instead of
claudeUsage.weeklyResetTime so the pace indicator matches the Sonnet reset
schedule.

---

Nitpick comments:
In `@apps/server/src/services/auto-mode/types.ts`:
- Around line 116-131: The FacadeError interface uses an inline import() for
ErrorType; change this to a top-level import by adding ErrorType to the existing
import from '@automaker/types' (the file's imports at the top) and then replace
"errorType: import('@automaker/types').ErrorType;" inside the FacadeError
interface with "errorType: ErrorType;". This removes the inline import noise
while keeping the type reference consistent for the FacadeError definition.

In `@apps/server/src/services/claude-usage-service.ts`:
- Around line 297-306: Introduce a small private helper (e.g., stripAnsiForCheck
or cleanOutputForCheck) that encapsulates the two-step ANSI-cleaning logic
currently duplicated: the cursor-forward replacement using /\x1B\[(\d+)C/g and
the CSI strip with /\x1B\[[0-9;?]*[A-Za-z@]/g, then replace the duplicated
inline logic in both the timeout handler (where cleanedForCheck is created) and
the onData callback with calls to this helper so both locations use the single
shared implementation.

In `@apps/server/src/services/feature-state-manager.ts`:
- Around line 108-150: Extract the duplicated task-finalization logic into a
small helper (e.g., finalizeTasksForCompletion or finalizeInProgressTasks) that
accepts the feature or its planSpec, iterates planSpec.tasks to mark any
task.status === 'in_progress' → 'completed', recalculates
planSpec.tasksCompleted, and clears planSpec.currentTaskId; then call this
helper from both the status === 'waiting_approval' and status === 'verified'
branches so only justFinishedAt handling remains in each branch (keep existing
behavior of not touching pending tasks).

In `@apps/ui/src/components/ui/task-progress-panel.tsx`:
- Line 40: The currentTaskId state is created but its value is never read (const
[, setCurrentTaskId] = useState...), causing unnecessary re-renders when
setCurrentTaskId is called; replace this useState with a useRef (e.g., const
currentTaskIdRef = useRef<string | null>(null)) and update all places that call
setCurrentTaskId to instead set currentTaskIdRef.current so you retain the
bookkeeping without triggering component re-renders; ensure any logic that
actually needs to read the id uses currentTaskIdRef.current or reinstates state
only where a read is required.
- Around line 146-162: The handler for the 'auto_mode_task_status' event
currently only applies an incremental update via setTasks and ignores the
event's tasks array; change it to reconcile full state from taskEvent.tasks (the
ParsedTask[] carried on the event) instead of only updating one task: use
setTasks(() => taskEvent.tasks.map(/* convert ParsedTask to TaskInfo shape */))
to replace the task list, and update setCurrentTaskId based on the reconciled
list (e.g., set to the task with status 'in_progress' or null if none) so the UI
cannot drift out of sync; keep the existing single-task update logic only if you
intentionally want incremental updates and document that choice.

In `@apps/ui/src/components/usage-popover.tsx`:
- Around line 238-257: The empty <div /> used as a spacer inside the container
with class "mt-2 flex items-center justify-between" is unnecessary; update the
JSX so you don't render a meaningless node — either conditionally set the
container's justify class to "justify-between" when paceLabel is present and
"justify-end" when it's not, or remove the spacer and add "ml-auto" to the
resetText <p> (the element referenced by resetText and the container wrapper
around paceLabel) so layout stays the same without the extra DOM node.

In `@apps/ui/src/components/views/board-view/mobile-usage-bar.tsx`:
- Around line 206-216: Pure computation getExpectedWeeklyPacePercentage is being
invoked on every render; memoize its result with React's useMemo to avoid
recomputing when unrelated state changes. Wrap the call passed to the Weekly
UsageBar's pacePercentage prop in useMemo, e.g. const pace = useMemo(() =>
getExpectedWeeklyPacePercentage(claudeUsage.weeklyResetTime),
[claudeUsage.weeklyResetTime]); and pass pace to the UsageBar (label "Weekly")
instead of calling getExpectedWeeklyPacePercentage inline.

In `@apps/ui/src/store/utils/usage-utils.ts`:
- Around line 53-59: The labels are potentially inverted: when actualPercentage
> expectedPercentage (diff > 0) the UI shows "behind pace" which reads as
consuming faster; change the copy mapping in this block that computes diff
(using actualPercentage and expectedPercentage) so that diff > 0 yields a clear
"over/over budget/over pace" message (e.g., "X% over budget pace" or "X% over
pace") and diff < 0 yields "under/under budget pace" (e.g., "X% under budget
pace"), updating the return strings accordingly to avoid the ahead/behind
ambiguity.

Comment on lines +646 to +657
if (imagePaths && imagePaths.length > 0) {
feature.imagePaths = imagePaths.map((p) => ({
path: p,
filename: p.split('/').pop() || p,
mimeType: 'image/*',
}));
await this.featureStateManager.updateFeatureStatus(
this.projectPath,
featureId,
feature.status || 'in_progress'
);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Two concerns with imagePaths persistence.

  1. p.split('/').pop() will fail on Windows paths (backslash separators). Use path.basename(p) instead — path is already imported at line 14.

  2. Calling updateFeatureStatus with the existing status solely to persist imagePaths is a side-effect-heavy workaround: it will overwrite updatedAt, clear justFinishedAt, and emit a feature_status_changed event. The image paths are set on the in-memory feature object, but updateFeatureStatus re-reads from disk, so these imagePaths will be lost — the method loads its own copy of the feature from disk and never sees the mutation at line 647.

🐛 Fix: persist imagePaths via a dedicated write or updateFeaturePlanSpec

The current approach mutates the local feature object's imagePaths but then calls updateFeatureStatus which re-reads the feature from disk — so the in-memory imagePaths are never persisted. You need to either:

  • Write the feature directly with atomicWriteJson after updating imagePaths, or
  • Pass imagePaths through the options parameter to executeFeature so it can attach them at execution time.
-      // Store image paths on the feature so executeFeature can pick them up
-      if (imagePaths && imagePaths.length > 0) {
-        feature.imagePaths = imagePaths.map((p) => ({
-          path: p,
-          filename: p.split('/').pop() || p,
-          mimeType: 'image/*',
-        }));
-        await this.featureStateManager.updateFeatureStatus(
-          this.projectPath,
-          featureId,
-          feature.status || 'in_progress'
-        );
-      }

Depending on how executeFeature / ExecutionService consumes image paths, either pass them via options or persist with a dedicated helper.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/services/auto-mode/facade.ts` around lines 646 - 657, The
code currently sets feature.imagePaths using p.split('/') which breaks on
Windows and then calls this.featureStateManager.updateFeatureStatus which
re-reads the feature from disk so the mutated in-memory imagePaths are never
persisted and causes unwanted side effects; replace p.split('/').pop() with
path.basename(p) and persist the new imagePaths explicitly instead of calling
updateFeatureStatus: after setting feature.imagePaths call the existing
atomicWriteJson helper (or whichever project-level atomic write utility is used)
to write the feature JSON file for this.projectPath/featureId so the imagePaths
are saved without changing timestamps or emitting status events, or
alternatively pass imagePaths through the executeFeature/options path (e.g., via
the ExecutionService/executeFeature options) if that flow is already supported.

Comment on lines +65 to +74
const initialTasks: TaskInfo[] = (planTasks as ParsedTask[]).map((t: ParsedTask) => ({
id: t.id,
description: t.description,
filePath: t.filePath,
phase: t.phase,
status:
t.id === currentId
? ('in_progress' as const)
: (t.status as TaskInfo['status']) || ('pending' as const),
}));
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find where ParsedTask and TaskInfo are defined
echo "=== Searching for ParsedTask definition ==="
rg -n "type ParsedTask|interface ParsedTask" --type=ts -A 15

echo ""
echo "=== Searching for TaskInfo definition ==="
rg -n "type TaskInfo|interface TaskInfo" --type=ts -A 15

Repository: AutoMaker-Org/automaker

Length of output: 2348


🏁 Script executed:

#!/bin/bash
# Check line 152 and surrounding context
sed -n '145,162p' apps/ui/src/components/ui/task-progress-panel.tsx

Repository: AutoMaker-Org/automaker

Length of output: 792


🏁 Script executed:

#!/bin/bash
# Check the auto_mode_task_status event type definition
echo "=== Searching for auto_mode_task_status in AutoModeEvent ==="
rg -n "auto_mode_task_status" --type=ts -B2 -A5 | head -40

echo ""
echo "=== Full AutoModeEvent definition ==="
rg -n "type AutoModeEvent|interface AutoModeEvent" --type=ts -A 50 | head -80

Repository: AutoMaker-Org/automaker

Length of output: 8238


🏁 Script executed:

#!/bin/bash
# Check if there are other places where this same cast occurs
echo "=== Checking for similar unsafe casts ==="
rg -n "as TaskInfo\['status'\]" --type=ts

echo ""
echo "=== Check if 'failed' status is actually used in practice ==="
rg -n "'failed'" --type=ts | grep -i "status\|task"

Repository: AutoMaker-Org/automaker

Length of output: 3694


🏁 Script executed:

#!/bin/bash
# Check how the status field is rendered in the component
echo "=== Checking status usage in task rendering ==="
rg -n "status" apps/ui/src/components/ui/task-progress-panel.tsx | head -20

echo ""
echo "=== Check badge/icon rendering based on status ==="
sed -n '175,250p' apps/ui/src/components/ui/task-progress-panel.tsx

Repository: AutoMaker-Org/automaker

Length of output: 4182


🏁 Script executed:

#!/bin/bash
# Continue reading the task item rendering
sed -n '250,300p' apps/ui/src/components/ui/task-progress-panel.tsx

Repository: AutoMaker-Org/automaker

Length of output: 2530


Fix unsafe status casts that allow 'failed' to corrupt component state.

ParsedTask['status'] includes 'failed' but TaskInfo['status'] does not. Both casts on lines 73 and 152 are unsafe:

  • Line 73: The fallback || ('pending' as const) only catches falsy values; if t.status === 'failed', it passes through unchecked.
  • Line 152: No fallback at all—taskEvent.status is cast directly.

When 'failed' enters state, the rendering logic (lines 253–255) matches none of the three conditions (in_progress, completed, pending), so the task icon won't render and styling won't apply.

Normalize explicitly at both locations:

Suggested fix
# Line 73
-          status:
-            t.id === currentId
-              ? ('in_progress' as const)
-              : (t.status as TaskInfo['status']) || ('pending' as const),
+          status:
+            t.id === currentId
+              ? ('in_progress' as const)
+              : t.status === 'completed'
+                ? ('completed' as const)
+                : t.status === 'in_progress'
+                  ? ('in_progress' as const)
+                  : ('pending' as const),

# Line 152
-                  ? { ...t, status: taskEvent.status as TaskInfo['status'] }
+                  ? { ...t, status: taskEvent.status === 'completed' ? ('completed' as const) : taskEvent.status === 'in_progress' ? ('in_progress' as const) : ('pending' as const) }

Alternatively, extract a normalization function to avoid duplication.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/src/components/ui/task-progress-panel.tsx` around lines 65 - 74, The
code is casting ParsedTask.status and taskEvent.status unsafely which allows
'failed' to enter TaskInfo state and break rendering; update the mapping that
builds initialTasks (the map over planTasks producing TaskInfo objects where
status is currently assigned using (t.status as TaskInfo['status']) ||
'pending') and the handler that assigns taskEvent.status (where taskEvent.status
is cast directly) to explicitly normalize statuses to the allowed
TaskInfo['status'] values ('in_progress' | 'completed' | 'pending'): implement a
small helper like normalizeTaskStatus(status: ParsedTask['status'] | string):
TaskInfo['status'] and call it both in the initialTasks creation and when
consuming taskEvent.status (instead of direct casts or relying on falsy
fallbacks) so any unknown or disallowed values (including 'failed') map to
'pending' (or another defined default).

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces significant improvements across the board. It adds robust error handling to the auto-mode facade, implements the followUpFeature, and fixes issues with Claude usage parsing and task status updates on the UI. The changes are well-structured and include corresponding tests. I've identified a couple of areas for improvement: one for consistency in error handling within the facade, and another related to duplicated logic in the feature state manager. Additionally, there's a discrepancy in a new test case that should be addressed to ensure it correctly reflects the implementation's behavior. Overall, this is a solid contribution.

Comment on lines 218 to 225
Copy link
Contributor

Choose a reason for hiding this comment

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

high

Similar to the waiting_approval test, this test for the verified status seems to have incorrect assertions. The implementation does not complete pending tasks, but the test expects all tasks, including pending ones, to be marked as completed. Please update the assertions to match the actual implementation logic.

Suggested change
// All tasks should be completed
expect(savedFeature.planSpec?.tasks?.[0].status).toBe('completed');
expect(savedFeature.planSpec?.tasks?.[1].status).toBe('completed');
expect(savedFeature.planSpec?.tasks?.[2].status).toBe('completed');
// currentTaskId should be cleared
expect(savedFeature.planSpec?.currentTaskId).toBeUndefined();
// tasksCompleted should equal total tasks
expect(savedFeature.planSpec?.tasksCompleted).toBe(3);
// Only in_progress tasks should be completed
expect(savedFeature.planSpec?.tasks?.[0].status).toBe('completed');
expect(savedFeature.planSpec?.tasks?.[1].status).toBe('completed');
expect(savedFeature.planSpec?.tasks?.[2].status).toBe('pending');
// currentTaskId should be cleared
expect(savedFeature.planSpec?.currentTaskId).toBeUndefined();
// tasksCompleted should be 2, not 3
expect(savedFeature.planSpec?.tasksCompleted).toBe(2);

Comment on lines +111 to +148
// Finalize task statuses when feature is done:
// - Mark any in_progress tasks as completed (agent finished but didn't explicitly complete them)
// - Do NOT mark pending tasks as completed (they were never started)
// - Clear currentTaskId since no task is actively running
// This prevents cards in "waiting for review" from appearing to still have running tasks
if (feature.planSpec?.tasks) {
let tasksFinalized = 0;
for (const task of feature.planSpec.tasks) {
if (task.status === 'in_progress') {
task.status = 'completed';
tasksFinalized++;
}
}
if (tasksFinalized > 0) {
logger.info(
`[updateFeatureStatus] Finalized ${tasksFinalized} in_progress tasks for feature ${featureId} moving to waiting_approval`
);
}
// Update tasksCompleted count to reflect actual completed tasks
feature.planSpec.tasksCompleted = feature.planSpec.tasks.filter(
(t) => t.status === 'completed'
).length;
feature.planSpec.currentTaskId = undefined;
}
} else if (status === 'verified') {
// Also finalize in_progress tasks when moving directly to verified (skipTests=false)
// Do NOT mark pending tasks as completed - they were never started
if (feature.planSpec?.tasks) {
for (const task of feature.planSpec.tasks) {
if (task.status === 'in_progress') {
task.status = 'completed';
}
}
feature.planSpec.tasksCompleted = feature.planSpec.tasks.filter(
(t) => t.status === 'completed'
).length;
feature.planSpec.currentTaskId = undefined;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The logic for finalizing tasks when a feature's status changes to waiting_approval or verified is nearly identical. To reduce code duplication and improve maintainability, you could extract this logic into a private helper method.

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@apps/server/tests/unit/services/feature-state-manager.test.ts`:
- Line 154: Rename the test declaration whose description is 'should finalize
in_progress and pending tasks when moving to waiting_approval' to accurately
describe behavior (e.g., 'should finalize in_progress tasks when moving to
waiting_approval' or 'should finalize in_progress but keep pending tasks when
moving to waiting_approval') so the test name matches the assertions; update the
it(...) string in apps/server/tests/unit/services/feature-state-manager.test.ts
accordingly.
- Around line 191-228: The failing test assumes pending tasks are completed when
moving to 'verified', but production only auto-completes tasks in 'in_progress'
state; update the assertions in the test (the case that calls
manager.updateFeatureStatus and inspects the savedFeature from atomicWriteJson)
to expect that tasks with status 'pending' remain 'pending' (i.e., only assert
tasks[0] and tasks[1] are 'completed' and tasks[2] is still 'pending'), keep the
checks for currentTaskId cleared, tasksCompleted equal the number of completed
tasks, and justFinishedAt cleared as before.

@gsxdsm gsxdsm merged commit 381698b into AutoMaker-Org:v0.15.0rc Feb 17, 2026
6 checks passed
@gsxdsm gsxdsm deleted the fix/claude-weekly-usage branch February 17, 2026 05:33
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

Comments