feat: Add Fedora/RHEL RPM package support#511
feat: Add Fedora/RHEL RPM package support#511DhanushSantosh merged 11 commits intoAutoMaker-Org:v0.12.0rcfrom
Conversation
…ation Add native RPM package building for Fedora-based distributions: - Extend electron-builder configuration to include RPM target - Add rpm-build installation to GitHub Actions CI/CD workflow - Update artifact upload patterns to include .rpm files - Declare proper RPM dependencies (gtk3, libnotify, nss, etc.) - Use xz compression for optimal package size Documentation: - Update README.md with Fedora/RHEL installation instructions - Create comprehensive docs/install-fedora.md guide covering: - Installation methods (dnf/yum, direct URL) - System requirements and capabilities - Configuration and troubleshooting - SELinux handling and firewall rules - Performance tips and security considerations - Building from source - Support for Fedora 39+, RHEL 9+, Rocky Linux, AlmaLinux End-to-end support enables Fedora users to install Automaker via: sudo dnf install ./Automaker-<version>-x86_64.rpm Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. 📝 WalkthroughWalkthroughThis PR adds persistent backlog-plan storage and UI, centralizes customizable AI prompts across server and UI, introduces an EventHookService for shell/HTTP hooks, enables runtime server logging controls, adds RPM packaging support, and expands many UI features (dependencies, selection targets, prompt customization, provider toggles, and graph layout persistence). Changes
Sequence Diagram(s)sequenceDiagram
participant UI as UI (Graph/Backlog Dialog)
participant API as Server API (/api/backlog-plan/*)
participant Server as Server (backlog-plan module)
participant FS as FileSystem (persist saved plan)
participant Auto as AutoMode/EventEmitter
participant Hook as EventHookService
participant Model as AI Model
UI->>API: POST /api/backlog-plan/generate (projectPath, prompt, model?)
API->>Server: forward request
Server->>Model: stream generate plan (uses prompts from settings)
Model-->>Server: streaming plan/result
Server->>FS: saveBacklogPlan(projectPath, savedPlan)
Server->>Auto: emit backlog_plan_started / backlog_plan_complete
Auto->>Hook: event handler invoked (feature_success/complete)
Hook->>Hook: run matching hooks (shell / http requests, with variable substitution)
Hook->>UI: (via events) notify status updates (progress, complete, error)
UI->>API: POST /api/backlog-plan/clear (projectPath) to clear saved plan
API->>Server: clearBacklogPlan -> FS delete saved file
Notes: colored rectangles are not used; diagram focuses on primary interactions. Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. 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. Comment |
Update: TypeScript Build Fix AppliedFixed TypeScript compilation error in Fix: Cast The build is now proceeding. Local RPM build will verify:
Once complete, will test RPM installation and basic functionality. |
Summary of ChangesHello @DhanushSantosh, 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 broadens the application's compatibility and extensibility. It introduces native RPM package support for a wider range of Linux distributions, making the application more accessible. A new event hook system provides powerful customization, allowing users to automate workflows based on application events. Furthermore, the changes centralize and expand AI prompt customization, offering greater control over the AI's behavior across various features, alongside several UI/UX improvements for better project and feature management. Highlights
🧠 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. Ignored Files
Using Gemini Code AssistThe 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
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 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
|
There was a problem hiding this comment.
Code Review
This pull request is a major update that goes far beyond its title of adding RPM package support. It introduces several significant new features and refactorings. Key additions include:
- RPM Package Support: Configuration for building and documenting
.rpmpackages for Fedora/RHEL systems. - Centralized Prompt Management: A large-scale refactoring to move hardcoded AI prompts into a customizable, centralized system.
- Event Hooks: A new service to trigger custom shell commands or webhooks on system events like feature completion or errors.
- Backlog Plan Persistence: The ability to save, load, and clear backlog plans, making them persistent across sessions.
- Runtime Logging Configuration: The server's log level and HTTP request logging can now be changed at runtime via settings.
- UI Enhancements: Numerous UI improvements, including a new dependency selector for features, better context menu handling, a new Wiki button, and a more capable selection action bar.
My review focuses on improving maintainability by reducing code duplication, enhancing the robustness of UI rendering logic, and suggesting performance optimizations for batch operations. Overall, this is a substantial and impressive set of changes that significantly expands the application's capabilities.
| const LOG_LEVEL_MAP: Record<string, LogLevel> = { | ||
| error: LogLevel.ERROR, | ||
| warn: LogLevel.WARN, | ||
| info: LogLevel.INFO, | ||
| debug: LogLevel.DEBUG, | ||
| }; |
There was a problem hiding this comment.
| // Add this feature as a dependency to new child features | ||
| for (const childId of toAdd) { | ||
| const childFeature = features.find((f) => f.id === childId); | ||
| if (childFeature) { | ||
| const childDeps = childFeature.dependencies || []; | ||
| if (!childDeps.includes(featureId)) { | ||
| const newDeps = [...childDeps, featureId]; | ||
| updateFeature(childId, { dependencies: newDeps }); | ||
| persistFeatureUpdate(childId, { dependencies: newDeps }); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Remove this feature as a dependency from removed child features | ||
| for (const childId of toRemove) { | ||
| const childFeature = features.find((f) => f.id === childId); | ||
| if (childFeature) { | ||
| const childDeps = childFeature.dependencies || []; | ||
| const newDeps = childDeps.filter((depId) => depId !== featureId); | ||
| updateFeature(childId, { dependencies: newDeps }); | ||
| persistFeatureUpdate(childId, { dependencies: newDeps }); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
The logic to update child dependencies makes separate updateFeature and persistFeatureUpdate calls for each child that is added or removed. This can lead to multiple individual API calls to the backend. Since this PR introduces a bulkUpdate endpoint on the server, it would be more efficient to batch these updates into a single API call. You could collect all the updates for child dependencies and then send them as one request to improve performance, especially when modifying several dependencies at once.
| useEffect(() => { | ||
| // Only run once: when we have nodes and edges but haven't done a layout with edges yet | ||
| if (!hasLayoutWithEdges.current && layoutedNodes.length > 0 && layoutedEdges.length > 0) { | ||
| hasLayoutWithEdges.current = true; | ||
| // Small delay to ensure React Flow is mounted and ready | ||
| const timeoutId = setTimeout(() => { | ||
| const { nodes: relayoutedNodes, edges: relayoutedEdges } = runLayout('LR'); | ||
| setNodes(relayoutedNodes); | ||
| setEdges(relayoutedEdges); | ||
| fitView({ padding: 0.2, duration: 300 }); | ||
| }, 100); | ||
| return () => clearTimeout(timeoutId); | ||
| } |
There was a problem hiding this comment.
Using setTimeout to delay the layout calculation is a common workaround for timing issues in complex rendering scenarios, but it can be fragile. It might fail on slower devices or if the component tree renders slower than expected for any reason. A more robust approach would be to leverage React Flow's onInit prop. The onInit callback provides the React Flow instance, and you could trigger the layout from there, ensuring the graph is fully initialized.
const handleInit = (reactFlowInstance) => {
// You can now safely use the instance
const { nodes: relayoutedNodes, edges: relayoutedEdges } = runLayout('LR');
setNodes(relayoutedNodes);
setEdges(relayoutedEdges);
reactFlowInstance.fitView({ padding: 0.2, duration: 300 });
};
return <ReactFlow onInit={handleInit} ... />;This would eliminate the race condition more reliably than a fixed timeout.
Build Status: Ready for Merge ✅Successfully VerifiedLocal Build Results:
Build Process Validated:
Commits
CI/CD ReadyGitHub Actions will now:
Documentation Complete
Ready to merge! 🚀 |
There was a problem hiding this comment.
Actionable comments posted: 13
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (6)
apps/ui/src/components/views/ideation-view/components/prompt-category-grid.tsx (1)
70-88: Add keyboard navigation support to the interactive Card.The
group+group-hover:*pattern is well applied for synchronized visual feedback across the card, icon badge, and title. Transitions are consistent atduration-300.However, the
Cardis interactive viaonClickbut lacks keyboard accessibility—it has notabIndexoronKeyDownhandler. AddtabIndex={0}and anonKeyDownhandler to support keyboard navigation (Enter/Space to select):Suggested fix
<Card key={category.id} className="group cursor-pointer transition-all duration-300 hover:border-primary hover:shadow-lg hover:-translate-y-1" onClick={() => onSelect(category.id)} tabIndex={0} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { onSelect(category.id); } }} > {/* ... */} </Card>apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx (1)
68-132: DefaultselectionTargetto preserve prior selection behavior.
WithselectionTarget = null,isSelectableis always false unless callers pass a target, which can silently disable selection for existing usages. Consider defaulting to'backlog'while still allowing explicitnullto disable.🔧 Suggested fix
- selectionTarget = null, + selectionTarget = 'backlog', ... - const isSelectable = isSelectionMode && feature.status === selectionTarget; + const isSelectable = + isSelectionMode && selectionTarget !== null && feature.status === selectionTarget;apps/ui/src/hooks/use-settings-sync.ts (1)
476-486: AddeventHookstorefreshSettingsFromServer()to restore server settings symmetrically.eventHooksis included inSETTINGS_FIELDS_TO_SYNC(line 63) but missing from theuseAppStore.setStatecall inrefreshSettingsFromServer()(around line 502), causing event hooks to be pushed to the server but not restored when refreshing settings.Suggested fix
Add
eventHooksto theuseAppStore.setStatecall afterpromptCustomization:promptCustomization: serverSettings.promptCustomization ?? {}, + eventHooks: serverSettings.eventHooks ?? [], projects: serverSettings.projects,apps/server/src/routes/backlog-plan/routes/generate.ts (1)
64-66: Running details not cleared on completion.When generation completes (in the
finallyblock),setRunningState(false, null)is called butsetRunningDetails(null)is not. This could leave stale running details in memory after the backlog plan generation finishes.Proposed fix
.finally(() => { setRunningState(false, null); + setRunningDetails(null); });apps/server/src/routes/backlog-plan/routes/apply.ts (1)
150-155: Error after response sent could cause issues.
clearBacklogPlanis called afterres.json()has already sent the response. IfclearBacklogPlanthrows, the error will be caught by the outer catch block which attempts to callres.status(500).json(...), causing an "headers already sent" error.Consider either:
- Moving
clearBacklogPlanbeforeres.json(), or- Wrapping it in its own try-catch to handle errors independently
Proposed fix (option 2 - fire-and-forget cleanup)
res.json({ success: true, appliedChanges, }); - await clearBacklogPlan(projectPath); + // Fire-and-forget cleanup - errors are logged but don't affect the response + clearBacklogPlan(projectPath).catch((err) => { + logger.warn(`[BacklogPlan] Failed to clear plan after apply: ${getErrorMessage(err)}`); + }); } catch (error) {apps/ui/src/components/views/graph-view-page.tsx (1)
1-1: Remove@ts-nocheckand address the underlying TypeScript type mismatch.This directive suppresses all TypeScript errors in the file. Based on the commit history, the proper fix is to cast the value to string where
secureFs.readFilereturnsstring | Buffer, rather than suppressing all type checking.
🤖 Fix all issues with AI agents
In `@apps/server/src/routes/backlog-plan/generate-plan.ts`:
- Around line 203-208: Wrap the await saveBacklogPlan(...) call in a try/catch
so persistence failures are best‑effort and do not abort the plan flow: catch
any thrown error from saveBacklogPlan, log the error (e.g., processLogger.error
or console.error) with context including projectPath and savedAt, and do not
rethrow; ensure the function continues to return the valid result object to the
caller even if saving fails.
In `@apps/server/src/services/event-hook-service.ts`:
- Around line 196-225: Lower the sensitivity of logged command/URL by changing
the info-level log in executeShellHook (the logger.info call that prints the
substituted command from substituteVariables) to logger.debug so secrets aren't
exposed, and similarly change any equivalent info logging for HTTP hook URLs to
debug; additionally, in the HTTP hook implementation (the function that performs
fetch and sets a timeoutId), ensure the timeout cleanup clearTimeout(timeoutId)
is executed inside a finally block so the timeout is always cleared even if
fetch throws — update the fetch call to use try { await fetch(...) } finally {
clearTimeout(timeoutId) } while preserving existing error handling.
In `@apps/ui/package.json`:
- Around line 222-236: Remove the unsupported "license" property from the rpm
build configuration in package.json: locate the "rpm" object (the property named
"rpm") and delete the "license": "SEE LICENSE IN LICENSE" entry so the rpm
config only contains valid keys like "depends", "compression", and "vendor";
rely on the top-level package.json "license" field instead.
In `@apps/ui/src/components/ui/dependency-selector.tsx`:
- Around line 221-238: The remove button inside the Badge (in
dependency-selector.tsx) is icon-only and needs an accessible name; update the
button element that calls handleRemove(feature.id) to include an aria-label (and
optional title) that uses getFeatureLabel(feature) (e.g., "Remove {feature
label}") so screen readers announce which feature will be removed; keep the
existing onClick, disabled, and className behavior.
In `@apps/ui/src/components/views/board-view/board-header.tsx`:
- Around line 199-206: The icon-only pending-plan button rendered when
hasPendingPlan is true lacks an accessible name; update the <button>
(data-testid "plan-review-button") used with onOpenPendingPlan ||
onOpenPlanDialog to provide an accessible label—either add an aria-label (e.g.,
aria-label="Review pending plan") or include visually hidden text alongside the
ClipboardCheck icon so screen readers announce the control; ensure the label is
clear and matches the action triggered by the onClick handler.
In `@apps/ui/src/components/views/board-view/hooks/use-board-actions.ts`:
- Around line 356-378: The persistFeatureUpdate calls inside the child
dependency sync loops are not awaited, which can lead to race conditions and
inconsistent state; update the two loops that iterate over toAdd and toRemove so
that you await persistFeatureUpdate(childId, { dependencies: newDeps }) after
calling updateFeature, ensuring the async persistence completes (refer to the
for (const childId of toAdd) and for (const childId of toRemove) blocks and the
functions updateFeature and persistFeatureUpdate).
- Around line 193-206: The loop that updates child feature dependencies can
silently fail because persistFeatureUpdate is not awaited; update the code in
use-board-actions.ts so that each persistFeatureUpdate(childId, { dependencies:
newDeps }) is awaited (and await updateFeature(childId, { dependencies: newDeps
}) as well if updateFeature returns a Promise) to ensure errors are propagated
and state stays consistent; alternatively collect the persistFeatureUpdate
Promises for the childIds into a Promise.all and await that before continuing,
referencing the symbols featureData.childDependencies, features,
createdFeature.id, updateFeature, and persistFeatureUpdate.
In `@apps/ui/src/components/views/board-view/shared/model-selector.tsx`:
- Around line 92-103: When the currently selected provider (selectedProvider)
becomes disabled via disabledProviders, auto-switch it to the first entry in
availableProviders (computed from
isClaudeDisabled/isCursorDisabled/isCodexDisabled) and update the component
state or prop callback (where selection is set) so the UI still renders; ensure
this logic runs inside the same effect or render path that computes
availableProviders. Also replace the hardcoded model alias string 'sonnet' with
resolveModelString(...) from `@automaker/model-resolver` (import
resolveModelString) to derive the full model name before use.
In `@apps/ui/src/components/views/graph-view-page.tsx`:
- Around line 136-173: Update the backlog-plan event handling to only act on
events for the active project and to re-subscribe when the project changes: have
the server include projectPath in the emitted BacklogPlanEvent (so
BacklogPlanResult events carry projectPath), then in the useEffect that calls
getElectronAPI() and api.backlogPlan.onEvent(...) add currentProject (or
currentProject.path) to the dependency array and, inside the event handler,
ignore events whose event.projectPath does not equal the current project's path
before calling setIsGeneratingPlan, setPendingBacklogPlan, setShowPlanDialog or
showing toasts; keep unsubscribe logic the same so the old listener is removed
on project change.
In
`@apps/ui/src/components/views/graph-view/components/graph-filter-controls.tsx`:
- Around line 123-140: The Search Input lacks accessible labeling and the clear
control needs an explicit button type: add an aria-label (e.g.,
aria-label="Search tasks") to the Input component where value={searchQuery} and
onChange calls onSearchQueryChange, and add type="button" to the clear button
(the element rendering <X /> with onClick={() => onSearchQueryChange('')}) so it
cannot submit a surrounding form; ensure the aria-label text matches the
placeholder for clarity.
In `@apps/ui/src/components/views/graph-view/graph-view.tsx`:
- Around line 186-208: The handler that removes a dependency currently always
shows toast.success even when onUpdateFeature is undefined; update the
onDeleteDependency flow to return early when onUpdateFeature is not provided by
showing an error toast (e.g., toast.error with context) and immediately
returning; only call toast.success after successfully invoking onUpdateFeature
for targetId (and ideally after confirming no thrown error), referencing the
onDeleteDependency handler, targetFeature, currentDeps/newDeps and
onUpdateFeature symbols so the change is localized to that block.
In `@apps/ui/src/components/views/settings-view/components/settings-header.tsx`:
- Around line 65-69: The Import/Export Button rendered when onImportExportClick
is present becomes icon-only on small screens (FileJson inside Button) and lacks
an accessible name; update the Button component instance (the one using FileJson
and span) to include an aria-label (e.g., aria-label="Import or export
settings") so screen readers have a label when the visible text is hidden,
ensuring the label is concise and descriptive.
In
`@apps/ui/src/components/views/settings-view/event-hooks/event-hook-dialog.tsx`:
- Around line 109-135: In handleSave and the validation logic, guard the
JSON.parse(headers) call so malformed JSON can't crash the dialog: add a
validateHeaders function that attempts JSON.parse(headers) inside a try/catch,
ensures the parsed value is an object (typeof === 'object' && !Array.isArray &&
parsed !== null), and returns either the parsed object or an error; use this
validator in isValid (alongside actionType checks) to block saving when headers
are invalid, and in handleSave call the validator and, on error, set a local
error state (e.g., headersError) and avoid calling onSave; also ensure the
component renders that headersError message so users see why save was blocked.
♻️ Duplicate comments (2)
apps/ui/src/components/views/settings-view/providers/opencode-settings-tab.tsx (1)
6-6: Same shared-package import guideline applies here.
As per coding guidelines, ...apps/ui/src/components/views/settings-view/providers/cursor-settings-tab.tsx (1)
15-15: Same shared-package import guideline applies here.
As per coding guidelines, ...
🟡 Minor comments (13)
graph-layout-bug.md-1-203 (1)
1-203: This file appears to be internal debugging notes rather than finished documentation.The commit bundles multiple unrelated changes (TypeScript error fixes, Claude agents, RPM installation docs, and this debugging document). While
docs/install-fedora.mdis legitimate project documentation,graph-layout-bug.mdcontains unfinished investigation questions, hypotheses, and code snippets typical of developer working notes.Consider one of the following:
- Convert to a GitHub issue to track the graph layout bug if still relevant
- Remove entirely if the bug has been resolved
- Move to a dedicated
docs/troubleshooting/directory if this is intended as long-term developer documentation (currently no such directory exists)apps/ui/src/components/views/board-view/components/selection-action-bar.tsx-55-58 (1)
55-58: Guard Verify when no handler is provided.
IfonVerifyis undefined, the dialog confirms a no-op. Disable the action (and guard the handler) to avoid dead clicks.🔧 Suggested fix
const allSelected = selectedCount === totalCount && totalCount > 0; + const canVerify = selectedCount > 0 && !!onVerify; const handleConfirmVerify = () => { setShowVerifyDialog(false); - onVerify?.(); + if (!onVerify) return; + onVerify(); }; ... {mode === 'waiting_approval' && ( <Button variant="default" size="sm" onClick={handleVerifyClick} - disabled={selectedCount === 0} + disabled={!canVerify} className="h-8 bg-green-600 hover:bg-green-700 disabled:opacity-50" data-testid="selection-verify-button" >Also applies to: 110-118, 186-218
apps/ui/src/components/views/board-view.tsx-719-747 (1)
719-747: Guard bulk verify to only affectwaiting_approvalitems.
If selection state ever desyncs, this could accidentally verify non‑waiting items. A narrow filter keeps the bulk action safe.🛠️ Proposed fix
- const featureIds = Array.from(selectedFeatureIds); + const featureIds = Array.from(selectedFeatureIds).filter((id) => + hookFeatures.some( + (feature) => feature.id === id && feature.status === 'waiting_approval' + ) + ); + if (featureIds.length === 0) { + toast.info('No waiting approval features selected'); + return; + } @@ - }, [currentProject, selectedFeatureIds, updateFeature, exitSelectionMode]); + }, [currentProject, selectedFeatureIds, hookFeatures, updateFeature, exitSelectionMode]);apps/ui/src/components/views/board-view.tsx-954-978 (1)
954-978: Add error handling and clear pending plan on project change.The
loadSavedPlanasync function lacks error handling and should log failures. More importantly, the guard|| pendingBacklogPlanblocks loading a new project's saved plan if a plan from the previous project remains set. Pending backlog plans should be cleared whencurrentProjectchanges to ensure each project can load its own saved plan.🛠️ Proposed fix (error handling)
const loadSavedPlan = async () => { + try { const api = getElectronAPI(); if (!api?.backlogPlan) return; const result = await api.backlogPlan.status(currentProject.path); if ( isActive && result.success && result.savedPlan?.result && result.savedPlan.result.changes?.length > 0 ) { setPendingBacklogPlan(result.savedPlan.result); } + } catch (error) { + logger.error('Failed to load saved backlog plan:', error); + } };For the project switch issue, consider either clearing
pendingBacklogPlanwhencurrentProjectchanges (add a separate effect), or restructure the guard to allow loading when switching projects even if a stale plan exists.apps/ui/src/components/views/settings-view/providers/provider-toggle.tsx-17-36 (1)
17-36: Add an accessible name for the Switch.
The Label isn’t associated with the Switch, so assistive tech may announce it as unnamed. Addid/aria-labelledby(oraria-label) to ensure accessibility.♿️ Proposed fix
export function ProviderToggle({ provider, providerLabel }: ProviderToggleProps) { const { disabledProviders, toggleProviderDisabled } = useAppStore(); const isDisabled = disabledProviders.includes(provider); + const labelId = `provider-toggle-${provider}-label`; + const switchId = `provider-toggle-${provider}`; return ( <div className="flex items-center justify-between p-4 rounded-xl bg-accent/20 border border-border/30"> <div className="flex items-center gap-3"> @@ <div> - <Label className="text-sm font-medium">Show {providerLabel} in model dropdowns</Label> + <Label id={labelId} htmlFor={switchId} className="text-sm font-medium"> + Show {providerLabel} in model dropdowns + </Label> <p className="text-xs text-muted-foreground mt-0.5"> {isDisabled ? `${providerLabel} models are hidden from all model selectors` : `${providerLabel} models appear in model selection dropdowns`} </p> </div> </div> <Switch + id={switchId} + aria-labelledby={labelId} checked={!isDisabled} onCheckedChange={(checked) => toggleProviderDisabled(provider, !checked)} /> </div> ); }docs/install-fedora.md-371-388 (1)
371-388: Same URL placeholder issue in the manual update section.Line 387 has the same
<version>placeholder problem as noted earlier in the installation section.📝 Suggested fix
# Or reinstall latest release sudo dnf remove automaker -sudo dnf install https://github.com/AutoMaker-Org/automaker/releases/latest/download/Automaker-<version>-x86_64.rpm +# Download the latest .rpm from https://github.com/AutoMaker-Org/automaker/releases +# Then install with: sudo dnf install ./Automaker-<VERSION>-x86_64.rpmdocs/install-fedora.md-44-58 (1)
44-58: The/latest/download/URL pattern doesn't support version placeholders.The GitHub releases
/latest/download/endpoint serves the actual latest release file, not a versioned path. Users cannot substitute<version>in this URL.Either use the pattern for a specific version download or document how to find the actual filename.
📝 Suggested fix
-sudo dnf install https://github.com/AutoMaker-Org/automaker/releases/latest/download/Automaker-<version>-x86_64.rpm +# Replace VERSION with actual version (e.g., 0.11.0) +sudo dnf install https://github.com/AutoMaker-Org/automaker/releases/download/v<VERSION>/Automaker-<VERSION>-x86_64.rpm + +# Or download from the releases page and install locally (recommended)Or alternatively, if electron-builder produces a predictable filename without version:
-sudo dnf install https://github.com/AutoMaker-Org/automaker/releases/latest/download/Automaker-<version>-x86_64.rpm +sudo dnf install https://github.com/AutoMaker-Org/automaker/releases/latest/download/Automaker-x86_64.rpmapps/ui/src/components/views/board-view/dialogs/backlog-plan-dialog.tsx-461-461 (1)
461-461: Button label may be misleading.The button label changed from "Generate Plan" to "Apply Changes", but at this point (input mode), the user is actually initiating plan generation, not applying changes. "Apply Changes" is more appropriate for the review mode. Consider keeping "Generate Plan" or using something like "Create Plan".
apps/server/src/routes/settings/routes/update-global.ts-61-76 (1)
61-76: ValidateserverLogLevelbefore persisting settings.If a client sends an unsupported value, it can be stored while the runtime log level remains unchanged. Consider rejecting invalid values before
updateGlobalSettingsto avoid state mismatch.Proposed guard before persisting
- const settings = await settingsService.updateGlobalSettings(updates); + if ('serverLogLevel' in updates && updates.serverLogLevel) { + const level = LOG_LEVEL_MAP[updates.serverLogLevel]; + if (level === undefined) { + res.status(400).json({ success: false, error: 'Invalid serverLogLevel' }); + return; + } + } + + const settings = await settingsService.updateGlobalSettings(updates);apps/server/src/routes/backlog-plan/routes/generate.ts-45-53 (1)
45-53: DuplicatesetRunningState(true)call.
setRunningState(true)is called on line 45, thensetRunningDetails(...)is called, and thensetRunningState(true, abortController)is called again on line 53. The first call is immediately overwritten by the second.Remove the redundant first call on line 45.
Proposed fix
- setRunningState(true); setRunningDetails({ projectPath, prompt, model, startedAt: new Date().toISOString(), }); const abortController = new AbortController(); setRunningState(true, abortController);apps/server/src/routes/backlog-plan/routes/clear.ts-11-16 (1)
11-16: ValidateprojectPathtype before use.
Non-string values can be coerced into paths and lead to unexpected deletes.✅ Suggested fix
- const { projectPath } = req.body as { projectPath: string }; + const { projectPath } = req.body as { projectPath?: unknown }; - if (!projectPath) { + if (typeof projectPath !== 'string' || projectPath.trim().length === 0) { res.status(400).json({ success: false, error: 'projectPath required' }); return; }apps/ui/src/components/views/settings-view/components/import-export-dialog.tsx-41-57 (1)
41-57: Missing defensive check forresponse.settings.If the API returns
{ success: true }without asettingsfield (e.g., due to server-side bug),JSON.stringify(response.settings)will produce"undefined"which may confuse users.🔧 Suggested defensive check
const loadSettings = async () => { setIsLoading(true); try { const response = await apiGet<SettingsResponse>('/api/settings/global'); - if (response.success) { + if (response.success && response.settings) { const formatted = JSON.stringify(response.settings, null, 2); setJsonValue(formatted); setOriginalValue(formatted); setParseError(null); + } else if (response.success) { + toast.error('Settings data is empty'); } } catch (error) {apps/ui/src/components/views/graph-view/graph-canvas.tsx-424-439 (1)
424-439: Remove debugconsole.logstatements before merging.These console.log statements appear to be debugging artifacts and should be removed or replaced with the project's logger utility for consistency:
const handleEdgesDelete = useCallback( (deletedEdges: Edge[]) => { - console.log('onEdgesDelete triggered', deletedEdges); deletedEdges.forEach((edge) => { if (nodeActionCallbacks?.onDeleteDependency) { - console.log('Calling onDeleteDependency from onEdgesDelete', { - source: edge.source, - target: edge.target, - }); nodeActionCallbacks.onDeleteDependency(edge.source, edge.target); } }); }, [nodeActionCallbacks] );If logging is needed for debugging, import and use
createLoggerfrom@automaker/utils/loggeras used elsewhere in the codebase.
🧹 Nitpick comments (33)
apps/ui/src/components/usage-popover.tsx (2)
327-328: Remove unused variables after refactor.
maxPercentageandisStaleare no longer used since the trigger now derives these values fromtabInfo. Consider removing them to avoid confusion.- const maxPercentage = Math.max(claudeMaxPercentage, codexMaxPercentage); - const isStale = activeTab === 'claude' ? isClaudeStale : isCodexStale; -
336-354: Consider usinguseMemofor tab-derived state.The helper function pattern works, but creating and invoking a function on each render is slightly verbose. A
useMemowould be more idiomatic for derived state.♻️ Optional refactor
- // Determine which provider icon and percentage to show based on active tab - const getTabInfo = () => { - if (activeTab === 'claude') { - return { - icon: AnthropicIcon, - percentage: claudeMaxPercentage, - isStale: isClaudeStale, - }; - } - return { - icon: OpenAIIcon, - percentage: codexMaxPercentage, - isStale: isCodexStale, - }; - }; - - const tabInfo = getTabInfo(); + // Determine which provider icon and percentage to show based on active tab + const tabInfo = useMemo(() => { + if (activeTab === 'claude') { + return { + icon: AnthropicIcon, + percentage: claudeMaxPercentage, + isStale: isClaudeStale, + }; + } + return { + icon: OpenAIIcon, + percentage: codexMaxPercentage, + isStale: isCodexStale, + }; + }, [activeTab, claudeMaxPercentage, codexMaxPercentage, isClaudeStale, isCodexStale]);apps/ui/src/components/layout/project-switcher/project-switcher.tsx (1)
419-445: LGTM!The Wiki button implementation is clean with proper feature flag gating and consistent styling patterns. The active state provides clear visual feedback, and the non-active state matches the Bug Report button styling.
One minor observation: the array syntax inside the
cn()ternary works but could be simplified to template strings for consistency with other buttons in this file. This is purely stylistic and optional.♻️ Optional: Simplify cn() ternary (stylistic)
className={cn( 'w-full aspect-square rounded-xl flex items-center justify-center', 'transition-all duration-200 ease-out', isWikiActive - ? [ - 'bg-gradient-to-r from-brand-500/20 via-brand-500/15 to-brand-600/10', - 'text-foreground', - 'border border-brand-500/30', - 'shadow-md shadow-brand-500/10', - ] - : [ - 'text-muted-foreground hover:text-foreground', - 'hover:bg-accent/50 border border-transparent hover:border-border/40', - 'hover:shadow-sm hover:scale-105 active:scale-95', - ] + ? 'bg-gradient-to-r from-brand-500/20 via-brand-500/15 to-brand-600/10 text-foreground border border-brand-500/30 shadow-md shadow-brand-500/10' + : 'text-muted-foreground hover:text-foreground hover:bg-accent/50 border border-transparent hover:border-border/40 hover:shadow-sm hover:scale-105 active:scale-95' )}apps/ui/src/components/layout/sidebar/hooks/use-running-agents.ts (1)
67-81: Avoid duplicate initial fetches on mount.
The backlogPlan effect triggers a fetch on mount, but the autoMode effect already does this unconditionally, so this causes duplicate calls/logs.♻️ Proposed change
- fetchRunningAgentsCount(); - const unsubscribe = api.backlogPlan.onEvent(() => { fetchRunningAgentsCount(); });apps/server/src/routes/features/routes/bulk-delete.ts (1)
33-53: Prevent batch aborts on delete exceptions.If
featureLoader.deletecan throw/reject,Promise.allwill reject and return 500, skipping partial results. Consider per-item try/catch (mirroring bulk-update) so failures are isolated and reported per feature.Proposed guard
- const batchResults = await Promise.all( - batch.map(async (featureId) => { - const success = await featureLoader.delete(projectPath, featureId); - if (success) { - return { featureId, success: true }; - } - return { - featureId, - success: false, - error: 'Deletion failed. Check server logs for details.', - }; - }) - ); + const batchResults = await Promise.all( + batch.map(async (featureId) => { + try { + const success = await featureLoader.delete(projectPath, featureId); + if (success) { + return { featureId, success: true }; + } + return { + featureId, + success: false, + error: 'Deletion failed. Check server logs for details.', + }; + } catch (error) { + return { + featureId, + success: false, + error: getErrorMessage(error), + }; + } + }) + );apps/ui/src/components/views/ideation-view/components/ideation-dashboard.tsx (1)
389-401: Snapshot discard targets when opening the confirm dialog.
confirmDiscardAlluses the livefilteredSuggestions, so items that appear while the dialog is open can be discarded without the user explicitly confirming them. Capture the list at dialog open and reuse it for both the description and the actual deletion.♻️ Suggested update
- const [showDiscardConfirm, setShowDiscardConfirm] = useState(false); + const [showDiscardConfirm, setShowDiscardConfirm] = useState(false); + const [pendingDiscard, setPendingDiscard] = useState< + Array<{ jobId: string; suggestionId: string }> + >([]); - const handleDiscardAll = useCallback(() => { - setShowDiscardConfirm(true); - }, []); + const handleDiscardAll = useCallback(() => { + setPendingDiscard( + filteredSuggestions.map(({ suggestion, job }) => ({ + jobId: job.id, + suggestionId: suggestion.id, + })) + ); + setShowDiscardConfirm(true); + }, [filteredSuggestions]); - const confirmDiscardAll = useCallback(() => { - const count = filteredSuggestions.length; - for (const { suggestion, job } of filteredSuggestions) { - removeSuggestionFromJob(job.id, suggestion.id); - } - toast.info(`Discarded ${count} ${pluralizeIdea(count)}`); - }, [filteredSuggestions, removeSuggestionFromJob]); + const confirmDiscardAll = useCallback(() => { + const count = pendingDiscard.length; + for (const { jobId, suggestionId } of pendingDiscard) { + removeSuggestionFromJob(jobId, suggestionId); + } + setPendingDiscard([]); + toast.info(`Discarded ${count} ${pluralizeIdea(count)}`); + }, [pendingDiscard, removeSuggestionFromJob]); - description={`Are you sure you want to discard ${filteredSuggestions.length} ${pluralizeIdea(filteredSuggestions.length)}? This cannot be undone.`} + description={`Are you sure you want to discard ${pendingDiscard.length} ${pluralizeIdea(pendingDiscard.length)}? This cannot be undone.`}Also applies to: 515-521
apps/ui/src/components/views/board-view/hooks/use-board-actions.ts (1)
345-354: Consider potential stale closure if features change during async operation.The
featuresarray captured by the callback could become stale if features are modified by another operation whilehandleUpdateFeatureis executing. For typical single-user scenarios this is unlikely to cause issues, but for robustness, you could use a ref or fetch fresh features from the store inside the callback.apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx (1)
166-182: Minor:allFeaturesdependency may cause unnecessary effect runs.If
allFeaturesgets a new reference on each parent render (common with selectors), this effect will re-run even when the actual feature list hasn't changed meaningfully. Consider memoizingallFeaturesin the parent component or using a stable reference check.apps/ui/src/components/views/board-view.tsx (1)
688-717: Consider deduplicating selectable-ID filtering logic.
allSelectableWaitingApprovalFeatureIdsmirrorsallSelectableFeatureIdsalmost exactly; extracting a shared predicate/helper would reduce drift risk over time.apps/ui/src/components/views/board-view/hooks/use-board-persistence.ts (1)
34-46: Consider reducing log verbosity and avoiding potential PII exposure.Logging the full
result.featureobject atinfolevel may:
- Expose sensitive data in feature descriptions (user input, requirements with PII)
- Create verbose logs in production
Suggested improvement
- logger.info('Calling API features.update', { featureId, updates }); + logger.debug('Calling API features.update', { featureId, updateKeys: Object.keys(updates) }); const result = await api.features.update( currentProject.path, featureId, updates, descriptionHistorySource, enhancementMode, preEnhancementDescription ); - logger.info('API features.update result', { - success: result.success, - feature: result.feature, - }); + logger.debug('API features.update result', { + success: result.success, + featureId: result.feature?.id, + });apps/ui/src/components/views/settings-view/providers/codex-settings-tab.tsx (1)
8-8: Prefer shared-package imports over new relative paths.
Please re-export ProviderToggle via a shared package and import from@automaker/*instead of./provider-toggle. As per coding guidelines, ...apps/ui/src/components/views/board-view/dialogs/agent-output-modal.tsx (1)
283-317: Consider adding type safety for the backlog plan event.The event parameter is typed as
anyon line 290. Consider defining a proper type for backlog plan events to improve type safety and enable better IDE support.♻️ Optional: Add type definition
// Define near the top of the file or in a types file type BacklogPlanEvent = | { type: 'backlog_plan_progress'; content?: string } | { type: 'backlog_plan_error'; error?: string } | { type: 'backlog_plan_complete' } | { type: string }; // fallback for unknown events // Then use it: const unsubscribe = api.backlogPlan.onEvent((event: BacklogPlanEvent) => {docs/install-fedora.md (1)
286-299: Minor improvements for network troubleshooting commands.
ping claude.aitests the marketing website, not the API endpoint. Consider usingapi.anthropic.com.- Echoing the API key value could expose it in shell history or logs. Consider just checking if it's set.
♻️ Suggested improvements
# Test internet connectivity -ping claude.ai +ping -c 3 api.anthropic.com # Test API access curl -I https://api.anthropic.com -# Verify API key -echo $ANTHROPIC_API_KEY +# Verify API key is set (without exposing the value) +[ -n "$ANTHROPIC_API_KEY" ] && echo "API key is set" || echo "API key is NOT set"apps/ui/src/components/views/board-view/dialogs/backlog-plan-dialog.tsx (1)
203-211: Add error handling for the clear API call.The
handleDiscardfunction callsbacklogPlan.clear()but doesn't handle potential failures. While the UI state is reset regardless, a failed clear could leave stale data on the server.Consider adding error handling
const handleDiscard = useCallback(async () => { setPendingPlanResult(null); setMode('input'); const api = getElectronAPI(); if (api?.backlogPlan) { - await api.backlogPlan.clear(projectPath); + try { + await api.backlogPlan.clear(projectPath); + } catch (error) { + logger.warn('Failed to clear backlog plan from server', { error, projectPath }); + // Non-critical: local state is already cleared + } } }, [setPendingPlanResult, projectPath]);apps/ui/src/components/views/graph-view/hooks/use-graph-layout.ts (1)
22-23:layoutVersionreturned as ref value won't trigger re-renders.Returning
layoutVersion.currentdirectly means consumers won't re-render when the layout version changes, since ref mutations don't trigger React updates. If consumers need to react to new layouts, they should uselayoutedElements.didRelayoutinstead, or the version should be stored in state.If
didRelayoutis the intended signal for consumers (which it appears to be based on the code), consider documenting this or removinglayoutVersionfrom the public API to avoid confusion.Also applies to: 130-135
apps/server/src/routes/settings/routes/update-global.ts (1)
15-26: Prefer shared-package import for server runtime helpers.
setRequestLoggingEnabledis imported via a relative path. If this is part of the shared server surface, please re-export it from an@automaker/* package and import from there to match the repo import rules. As per coding guidelines, use shared package imports instead of relative paths.apps/ui/src/components/views/graph-view/components/dependency-edge.tsx (1)
77-86: Consider gating debug logs or routing through the UI logger.The new console logging is helpful during development but can be noisy in production. If there’s a client logger, consider using it or wrapping logs behind a dev-only check.
apps/server/src/routes/features/index.ts (1)
7-7: Use shared@automaker/*import path if available.
This new relative import appears to conflict with the project guideline to prefer shared package imports.Based on coding guidelines, prefer
@automaker/*if SettingsService is exposed there.apps/server/src/routes/backlog-plan/common.ts (1)
76-88: Validate stored plan shape before returning it.
parsed?.result?.changesbeing truthy doesn’t guarantee it’s an array; downstream code may assume array semantics.♻️ Suggested validation tweak
- if (!parsed?.result?.changes) { + if (!Array.isArray(parsed?.result?.changes)) { return null; }apps/server/src/routes/suggestions/generate-suggestions.ts (1)
154-158: Consider adding a fallback for unknown suggestion types.If
suggestionTypeis not one of the expected keys (features,refactoring,security,performance),typePrompts[suggestionType]returnsundefined, and the fallbacktypePrompts.featuresis used. This is reasonable, but an explicit log warning would help debugging unexpected types.💡 Optional: Add a warning for unknown suggestion types
+ if (!typePrompts[suggestionType]) { + logger.warn(`Unknown suggestion type "${suggestionType}", falling back to features`); + } + const prompt = `${typePrompts[suggestionType] || typePrompts.features}apps/ui/src/components/views/settings-view/event-hooks/event-hooks-section.tsx (3)
26-28: Consider adding a confirmation dialog before deleting hooks.Deleting a hook is a destructive action that cannot be undone. A confirmation step would prevent accidental deletions, especially for complex webhook configurations.
💡 Suggested approach
Consider using a confirmation dialog or at least a
window.confirm()before deletion:const handleDeleteHook = (hookId: string) => { + if (!window.confirm('Are you sure you want to delete this hook?')) { + return; + } setEventHooks(eventHooks.filter((h) => h.id !== hookId)); };Or better, use a proper confirmation dialog component for consistency with the rest of the UI.
179-182: Type assertions can be replaced with type guards or discriminated union access.The inline type assertions
(hook.action as { type: 'shell'; command: string })work but are verbose. SinceEventHook.actionis likely a discriminated union, you can leverage theisShellcheck already performed.💡 Cleaner access pattern
- <p className="text-xs text-muted-foreground truncate"> - {isShell - ? (hook.action as { type: 'shell'; command: string }).command - : (hook.action as { type: 'http'; url: string }).url} - </p> + <p className="text-xs text-muted-foreground truncate"> + {'command' in hook.action ? hook.action.command : hook.action.url} + </p>Or extract typed accessors based on the discriminated union if the types are defined that way.
188-198: Add accessible labels to icon-only buttons.The edit and delete buttons use only icons without accessible text. Screen readers won't convey the button purpose.
♿ Add aria-labels for accessibility
- <Button variant="ghost" size="icon" className="h-8 w-8" onClick={onEdit}> + <Button variant="ghost" size="icon" className="h-8 w-8" onClick={onEdit} aria-label="Edit hook"> <Pencil className="w-4 h-4" /> </Button> <Button variant="ghost" size="icon" className="h-8 w-8 text-destructive hover:text-destructive" onClick={onDelete} + aria-label="Delete hook" > <Trash2 className="w-4 h-4" /> </Button>apps/ui/src/components/ui/json-syntax-editor.tsx (1)
96-100: Consider using@codemirror/lang-jsonfor stricter JSON validation.The code uses
StreamLanguage.define(javascript)from the legacy modes, which provides JavaScript syntax highlighting but lacks JSON-specific validation. Since this is a dedicated JSON editor component, using@codemirror/lang-jsonwould enforce strict JSON parsing (no comments, no trailing commas) and provide built-in JSON linting viajsonParseLinter().This matters if the editor should reject invalid JSON; the current approach accepts any JavaScript-like syntax. If lenient JSON5-like syntax is intentional, the current approach is acceptable.
apps/ui/src/components/views/settings-view/prompts/tab-configs.ts (1)
15-29: Prefer shared-package import forTabConfig.This new file uses a relative import for
TabConfig. If this type is meant to be shared across UI modules, please expose it via an@automaker/*package and import from there to align with the shared-package import rule.
As per coding guidelines, please avoid relative imports for shared types.apps/server/src/routes/app-spec/generate-spec.ts (1)
18-20: Use shared-package import for settings helpers.The updated import is still a relative path. If
settings-helpersis a shared module, please expose it via an@automaker/*package and import from there to comply with the shared-package import rule.
As per coding guidelines, avoid relative imports for shared modules.apps/server/src/routes/features/routes/generate-title.ts (1)
12-13: Use shared-package imports for settings helpers.These new imports are relative. If
settings-serviceorsettings-helpersare shared modules, please expose them via@automaker/*and import from there.
As per coding guidelines, avoid relative imports for shared modules.apps/ui/src/components/views/settings-view/prompts/prompt-customization-section.tsx (1)
31-44: Type safety reduced inupdatePromptsignature.The
fieldparameter is nowstringinstead of a typed key. While this is a reasonable tradeoff for the config-driven approach, runtime errors could occur if field names inTAB_CONFIGSdon't match the actual interface properties.Consider adding runtime validation or keeping a union type for field names if type safety is important here.
apps/ui/src/components/views/settings-view/prompts/components.tsx (1)
125-159: Type assertion could be improved with a type guard or safer access pattern.The type assertion on line 149
(promptCustomization?.[category] as Record<string, CustomPrompt> | undefined)is necessary due to the dynamic category access, but could be fragile if thePromptCustomizationstructure changes.Consider extracting this into a helper function for reusability and type safety:
function getCustomPromptValue( customization: PromptCustomization | undefined, category: keyof PromptCustomization, key: string ): CustomPrompt | undefined { const categoryData = customization?.[category]; if (categoryData && typeof categoryData === 'object') { return (categoryData as Record<string, CustomPrompt>)[key]; } return undefined; }apps/ui/src/components/views/graph-view/graph-canvas.tsx (1)
275-290: Consider adding a guard to prevent multiple concurrent layout operations.The timeout-based layout triggering could potentially cause issues if the component re-renders multiple times rapidly. The cleanup function handles the timeout, but
hasLayoutWithEdges.currentis set synchronously before the timeout fires.This is likely fine in practice, but for robustness:
useEffect(() => { // Only run once: when we have nodes and edges but haven't done a layout with edges yet if (!hasLayoutWithEdges.current && layoutedNodes.length > 0 && layoutedEdges.length > 0) { hasLayoutWithEdges.current = true; // Small delay to ensure React Flow is mounted and ready const timeoutId = setTimeout(() => { + // Double-check we still have nodes/edges (component might have changed) + if (layoutedNodes.length === 0) return; const { nodes: relayoutedNodes, edges: relayoutedEdges } = runLayout('LR');apps/server/src/services/auto-mode-service.ts (3)
789-790: Consider caching prompts when executing multiple pipeline steps.
getPromptCustomizationis called at the start ofexecutePipelineSteps, butexecuteFeaturealready fetched prompts earlier. If this method is called fromexecuteFeature, the prompts are fetched twice.Since prompts are used in the loop over steps, the current placement is reasonable for isolation. However, you could pass prompts as a parameter from the caller to avoid redundant settings reads.
2871-2908: Redundant prompt fetch in multi-agent task execution loop.
getPromptCustomizationis called here (line 2872-2875), but prompts were already fetched earlier at line 582-583 inexecuteFeature. This results in an unnecessary settings read.Consider passing the already-fetched
promptsobject through to this code path, or extract it to a variable accessible throughout the method.♻️ Suggested approach
Since
runAgentis called fromexecuteFeaturewhich already hasprompts, you could:
- Add
promptsto theoptionsparameter ofrunAgent- Or restructure to fetch prompts once at the top of
executeFeatureand pass them throughThis would eliminate the redundant
getPromptCustomizationcall and improve performance.
2992-3005: Same issue: redundant prompt fetch in single-agent fallback path.This is the same pattern as the multi-agent path -
getPromptCustomizationis called again despite prompts being available from the outer scope.
| await saveBacklogPlan(projectPath, { | ||
| savedAt: new Date().toISOString(), | ||
| prompt, | ||
| model: effectiveModel, | ||
| result, | ||
| }); |
There was a problem hiding this comment.
Don’t fail the whole plan when persistence fails.
If saveBacklogPlan throws (e.g., read-only FS), a valid plan gets discarded and the flow emits an error. Consider best‑effort persistence so successful plans still return.
🐛 Suggested fix (best‑effort persistence)
- await saveBacklogPlan(projectPath, {
- savedAt: new Date().toISOString(),
- prompt,
- model: effectiveModel,
- result,
- });
+ try {
+ await saveBacklogPlan(projectPath, {
+ savedAt: new Date().toISOString(),
+ prompt,
+ model: effectiveModel,
+ result,
+ });
+ } catch (error) {
+ logger.warn('[BacklogPlan] Failed to save backlog plan:', getErrorMessage(error));
+ }🤖 Prompt for AI Agents
In `@apps/server/src/routes/backlog-plan/generate-plan.ts` around lines 203 - 208,
Wrap the await saveBacklogPlan(...) call in a try/catch so persistence failures
are best‑effort and do not abort the plan flow: catch any thrown error from
saveBacklogPlan, log the error (e.g., processLogger.error or console.error) with
context including projectPath and savedAt, and do not rethrow; ensure the
function continues to return the valid result object to the caller even if
saving fails.
| private async executeShellHook( | ||
| action: EventHookShellAction, | ||
| context: HookContext, | ||
| hookName: string | ||
| ): Promise<void> { | ||
| const command = this.substituteVariables(action.command, context); | ||
| const timeout = action.timeout || DEFAULT_SHELL_TIMEOUT; | ||
|
|
||
| logger.info(`Executing shell hook "${hookName}": ${command}`); | ||
|
|
||
| try { | ||
| const { stdout, stderr } = await execAsync(command, { | ||
| timeout, | ||
| maxBuffer: 1024 * 1024, // 1MB buffer | ||
| }); | ||
|
|
||
| if (stdout) { | ||
| logger.debug(`Hook "${hookName}" stdout: ${stdout.trim()}`); | ||
| } | ||
| if (stderr) { | ||
| logger.warn(`Hook "${hookName}" stderr: ${stderr.trim()}`); | ||
| } | ||
|
|
||
| logger.info(`Shell hook "${hookName}" completed successfully`); | ||
| } catch (error) { | ||
| if ((error as NodeJS.ErrnoException).code === 'ETIMEDOUT') { | ||
| logger.error(`Shell hook "${hookName}" timed out after ${timeout}ms`); | ||
| } | ||
| throw error; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Check if the file exists and get its size
wc -l apps/server/src/services/event-hook-service.ts 2>/dev/null || echo "File not found or error"Repository: AutoMaker-Org/automaker
Length of output: 117
🏁 Script executed:
# Read the specific lines mentioned in the review (196-225 for shell hook)
sed -n '196,225p' apps/server/src/services/event-hook-service.tsRepository: AutoMaker-Org/automaker
Length of output: 1012
🏁 Script executed:
# Also read the HTTP hook section mentioned in the diff context (231-291)
sed -n '231,291p' apps/server/src/services/event-hook-service.tsRepository: AutoMaker-Org/automaker
Length of output: 1980
🏁 Script executed:
# Get broader context around these sections to understand the implementation
sed -n '180,305p' apps/server/src/services/event-hook-service.tsRepository: AutoMaker-Org/automaker
Length of output: 3797
Move command/URL logging to debug level and wrap HTTP fetch in try-finally for timeout cleanup.
Commands and URLs logged at info level can expose secrets (API keys, tokens, credentials). The HTTP hook also fails to clear the timeout if fetch() throws before reaching clearTimeout(timeoutId) — use finally to guarantee cleanup.
Proposed fix
- logger.info(`Executing shell hook "${hookName}": ${command}`);
+ logger.info(`Executing shell hook "${hookName}"`);
+ logger.debug(`Shell hook "${hookName}" command: ${command}`);- logger.info(`Executing HTTP hook "${hookName}": ${method} ${url}`);
+ logger.info(`Executing HTTP hook "${hookName}" (${method})`);
+ logger.debug(`HTTP hook "${hookName}" URL: ${url}`); try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), DEFAULT_HTTP_TIMEOUT);
- const response = await fetch(url, {
- method,
- headers,
- body: method !== 'GET' ? body : undefined,
- signal: controller.signal,
- });
- clearTimeout(timeoutId);
+ try {
+ const response = await fetch(url, {
+ method,
+ headers,
+ body: method !== 'GET' ? body : undefined,
+ signal: controller.signal,
+ });
+ if (!response.ok) {
+ logger.warn(`HTTP hook "${hookName}" received status ${response.status}`);
+ } else {
+ logger.info(`HTTP hook "${hookName}" completed successfully (status: ${response.status})`);
+ }
+ } finally {
+ clearTimeout(timeoutId);
+ }🤖 Prompt for AI Agents
In `@apps/server/src/services/event-hook-service.ts` around lines 196 - 225, Lower
the sensitivity of logged command/URL by changing the info-level log in
executeShellHook (the logger.info call that prints the substituted command from
substituteVariables) to logger.debug so secrets aren't exposed, and similarly
change any equivalent info logging for HTTP hook URLs to debug; additionally, in
the HTTP hook implementation (the function that performs fetch and sets a
timeoutId), ensure the timeout cleanup clearTimeout(timeoutId) is executed
inside a finally block so the timeout is always cleared even if fetch throws —
update the fetch call to use try { await fetch(...) } finally {
clearTimeout(timeoutId) } while preserving existing error handling.
| <Badge | ||
| key={feature.id} | ||
| variant="secondary" | ||
| className="flex items-center gap-1 pr-1 text-xs" | ||
| > | ||
| <span className="truncate max-w-[150px]">{getFeatureLabel(feature)}</span> | ||
| <button | ||
| type="button" | ||
| onClick={(e) => { | ||
| e.preventDefault(); | ||
| e.stopPropagation(); | ||
| handleRemove(feature.id); | ||
| }} | ||
| className="ml-1 rounded-full hover:bg-muted-foreground/20 p-0.5" | ||
| disabled={disabled} | ||
| > | ||
| <X className="w-3 h-3" /> | ||
| </button> |
There was a problem hiding this comment.
Add an accessible label for the remove badge button.
The icon-only button won’t be announced meaningfully by screen readers.
🔧 Suggested fix
- {selectedFeatures.map((feature) => (
- <Badge
- key={feature.id}
- variant="secondary"
- className="flex items-center gap-1 pr-1 text-xs"
- >
- <span className="truncate max-w-[150px]">{getFeatureLabel(feature)}</span>
- <button
+ {selectedFeatures.map((feature) => {
+ const label = getFeatureLabel(feature);
+ return (
+ <Badge
+ key={feature.id}
+ variant="secondary"
+ className="flex items-center gap-1 pr-1 text-xs"
+ >
+ <span className="truncate max-w-[150px]">{label}</span>
+ <button
type="button"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
handleRemove(feature.id);
}}
+ aria-label={`Remove ${label}`}
+ title={`Remove ${label}`}
className="ml-1 rounded-full hover:bg-muted-foreground/20 p-0.5"
disabled={disabled}
>
<X className="w-3 h-3" />
</button>
- </Badge>
- ))}
+ </Badge>
+ );
+ })}🤖 Prompt for AI Agents
In `@apps/ui/src/components/ui/dependency-selector.tsx` around lines 221 - 238,
The remove button inside the Badge (in dependency-selector.tsx) is icon-only and
needs an accessible name; update the button element that calls
handleRemove(feature.id) to include an aria-label (and optional title) that uses
getFeatureLabel(feature) (e.g., "Remove {feature label}") so screen readers
announce which feature will be removed; keep the existing onClick, disabled, and
className behavior.
| {hasPendingPlan && ( | ||
| <button | ||
| onClick={onOpenPendingPlan || onOpenPlanDialog} | ||
| className="flex items-center gap-1.5 text-emerald-500 hover:text-emerald-400 transition-colors" | ||
| data-testid="plan-review-button" | ||
| > | ||
| <ClipboardCheck className="w-4 h-4" /> | ||
| </button> |
There was a problem hiding this comment.
Add an accessible label for the icon-only pending-plan button.
Screen readers won’t announce this control meaningfully without an accessible name.
🔧 Suggested fix
{hasPendingPlan && (
<button
onClick={onOpenPendingPlan || onOpenPlanDialog}
+ type="button"
+ aria-label="Review pending plan"
+ title="Review pending plan"
className="flex items-center gap-1.5 text-emerald-500 hover:text-emerald-400 transition-colors"
data-testid="plan-review-button"
>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {hasPendingPlan && ( | |
| <button | |
| onClick={onOpenPendingPlan || onOpenPlanDialog} | |
| className="flex items-center gap-1.5 text-emerald-500 hover:text-emerald-400 transition-colors" | |
| data-testid="plan-review-button" | |
| > | |
| <ClipboardCheck className="w-4 h-4" /> | |
| </button> | |
| {hasPendingPlan && ( | |
| <button | |
| onClick={onOpenPendingPlan || onOpenPlanDialog} | |
| type="button" | |
| aria-label="Review pending plan" | |
| title="Review pending plan" | |
| className="flex items-center gap-1.5 text-emerald-500 hover:text-emerald-400 transition-colors" | |
| data-testid="plan-review-button" | |
| > | |
| <ClipboardCheck className="w-4 h-4" /> | |
| </button> |
🤖 Prompt for AI Agents
In `@apps/ui/src/components/views/board-view/board-header.tsx` around lines 199 -
206, The icon-only pending-plan button rendered when hasPendingPlan is true
lacks an accessible name; update the <button> (data-testid "plan-review-button")
used with onOpenPendingPlan || onOpenPlanDialog to provide an accessible
label—either add an aria-label (e.g., aria-label="Review pending plan") or
include visually hidden text alongside the ClipboardCheck icon so screen readers
announce the control; ensure the label is clear and matches the action triggered
by the onClick handler.
| // Listen for backlog plan events (for background generation) | ||
| useEffect(() => { | ||
| const api = getElectronAPI(); | ||
| if (!api?.backlogPlan) { | ||
| logger.debug('Backlog plan API not available for event subscription'); | ||
| return; | ||
| } | ||
|
|
||
| const unsubscribe = api.backlogPlan.onEvent( | ||
| (event: { type: string; result?: BacklogPlanResult; error?: string }) => { | ||
| logger.debug('Backlog plan event received', { | ||
| type: event.type, | ||
| hasResult: Boolean(event.result), | ||
| hasError: Boolean(event.error), | ||
| }); | ||
| if (event.type === 'backlog_plan_complete') { | ||
| setIsGeneratingPlan(false); | ||
| if (event.result && event.result.changes?.length > 0) { | ||
| setPendingBacklogPlan(event.result); | ||
| toast.success('Plan ready! Click to review.', { | ||
| duration: 10000, | ||
| action: { | ||
| label: 'Review', | ||
| onClick: () => setShowPlanDialog(true), | ||
| }, | ||
| }); | ||
| } else { | ||
| toast.info('No changes generated. Try again with a different prompt.'); | ||
| } | ||
| } else if (event.type === 'backlog_plan_error') { | ||
| setIsGeneratingPlan(false); | ||
| toast.error(`Plan generation failed: ${event.error}`); | ||
| } | ||
| } | ||
| ); | ||
|
|
||
| return unsubscribe; | ||
| }, []); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, find where backlogPlan API is defined
find . -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" \) | xargs grep -l "backlogPlan.*onEvent" | head -20Repository: AutoMaker-Org/automaker
Length of output: 304
🏁 Script executed:
# Search for backlogPlan interface/type definitions
rg -n "backlogPlan\s*:" --type=ts -C5Repository: AutoMaker-Org/automaker
Length of output: 4506
🏁 Script executed:
# Check the full graph-view-page.tsx component to understand currentProject and dependencies
wc -l apps/ui/src/components/views/graph-view-page.tsxRepository: AutoMaker-Org/automaker
Length of output: 119
🏁 Script executed:
# Search for currentProject usage in the component
rg -n "currentProject" apps/ui/src/components/views/graph-view-page.tsxRepository: AutoMaker-Org/automaker
Length of output: 1299
🏁 Script executed:
# Look for event structure in backlog plan related files
fd "backlog" --type f | grep -E "\.(ts|tsx|js)$" | head -20Repository: AutoMaker-Org/automaker
Length of output: 223
🏁 Script executed:
# Read the backlog plan types to understand event structure
cat -n libs/types/src/backlog-plan.tsRepository: AutoMaker-Org/automaker
Length of output: 2161
🏁 Script executed:
# Find where backlogPlan API is defined (likely preload or main)
fd -e ts -e tsx | xargs grep -l "backlogPlan.*=" | grep -E "(preload|main)" | head -10Repository: AutoMaker-Org/automaker
Length of output: 49
🏁 Script executed:
# Search for onEvent implementation for backlogPlan
rg -n "onEvent" --type=ts | grep -i "backlog"Repository: AutoMaker-Org/automaker
Length of output: 531
🏁 Script executed:
# Check how currentProject is passed to the component and if it changes
head -100 apps/ui/src/components/views/graph-view-page.tsxRepository: AutoMaker-Org/automaker
Length of output: 3514
🏁 Script executed:
# Check if currentProject comes from props or store
grep -n "const { currentProject" apps/ui/src/components/views/graph-view-page.tsxRepository: AutoMaker-Org/automaker
Length of output: 49
🏁 Script executed:
# Look at how graph-view-page is rendered/mounted
rg -n "GraphViewPage" --type=tsx | grep -E "(render|<GraphViewPage|import)" | head -20Repository: AutoMaker-Org/automaker
Length of output: 94
🏁 Script executed:
# Check what happens when user switches projects - look at the parent component or routing
fd -e tsx -e ts | xargs grep -l "GraphViewPage" | head -10Repository: AutoMaker-Org/automaker
Length of output: 144
🏁 Script executed:
# Verify if the event listener dependency array issue is real by checking how the component handles project changes
sed -n '130,210p' apps/ui/src/components/views/graph-view-page.tsxRepository: AutoMaker-Org/automaker
Length of output: 2502
🏁 Script executed:
# Search for the main preload/electron API implementation to understand if backlogPlan events are project-scoped
fd -e ts | xargs grep -l "backlogPlan.*onEvent\|onEvent.*backlogPlan" | grep -v ".spec\|.test" | head -10Repository: AutoMaker-Org/automaker
Length of output: 132
🏁 Script executed:
# Search for where backlogPlan.generate is called to understand project context
rg -n "backlogPlan\.generate" --type=ts -A3 -B3Repository: AutoMaker-Org/automaker
Length of output: 1066
🏁 Script executed:
# Check the main/preload implementation to see how backlogPlan events are emitted
fd -e ts | xargs grep -l "backlog_plan_complete\|backlog_plan_error" | grep -v node_modules | head -10Repository: AutoMaker-Org/automaker
Length of output: 205
🏁 Script executed:
# Look at the routes to understand when GraphViewPage is mounted/unmounted when changing projects
cat -n apps/ui/src/routes/graph.tsxRepository: AutoMaker-Org/automaker
Length of output: 316
🏁 Script executed:
# Check other onEvent listeners to see if they also have project filtering issues
sed -n '70,90p' apps/ui/src/components/layout/sidebar/hooks/use-running-agents.tsRepository: AutoMaker-Org/automaker
Length of output: 354
🏁 Script executed:
# Find where backlogPlan API handler is implemented (likely main process)
fd -e ts | xargs grep -l "onEvent.*=" | grep -v node_modules | grep -v "\.spec\|\.test"Repository: AutoMaker-Org/automaker
Length of output: 862
🏁 Script executed:
# Check the server-side backlog plan route to understand how events are emitted
cat -n apps/server/src/routes/backlog-plan/generate-plan.tsRepository: AutoMaker-Org/automaker
Length of output: 9515
🏁 Script executed:
# Also check the generate.ts route
cat -n apps/server/src/routes/backlog-plan/routes/generate.tsRepository: AutoMaker-Org/automaker
Length of output: 2747
🏁 Script executed:
# Look for where events are emitted to understand the scope
rg -n "backlog_plan_complete\|backlog_plan_error" --type=ts -B5 -A2Repository: AutoMaker-Org/automaker
Length of output: 49
🏁 Script executed:
# Check if there are multiple project routes or if graph view is shared across projects
rg -n "currentProject\|useParams\|useNavigate" apps/ui/src/routes/graph.tsxRepository: AutoMaker-Org/automaker
Length of output: 49
🏁 Script executed:
# Find where currentProject is set/changed in the store
rg -n "setCurrentProject\|currentProject.*=" --type=ts | grep -E "(store|hook)" | head -20Repository: AutoMaker-Org/automaker
Length of output: 49
🏁 Script executed:
# Check if events include any project context or filtering mechanism
rg -n "backlog-plan:event" --type=ts -B2 -A2Repository: AutoMaker-Org/automaker
Length of output: 3323
🏁 Script executed:
# Verify the full electron API implementation to see how onEvent is wired
fd -e ts | xargs grep -l "\.onEvent\s*=\|onEvent.*function" | grep -v node_modules | head -5Repository: AutoMaker-Org/automaker
Length of output: 49
🏁 Script executed:
# Check the http-api-client to understand how events are subscribed globally
sed -n '2370,2400p' apps/ui/src/lib/http-api-client.tsRepository: AutoMaker-Org/automaker
Length of output: 1347
🏁 Script executed:
# Look for how subscribeToEvent works - is there any filtering capability?
rg -n "subscribeToEvent" apps/ui/src/lib/http-api-client.ts -A10 | head -50Repository: AutoMaker-Org/automaker
Length of output: 1869
🏁 Script executed:
# Check if there's a common.ts file that defines event handling
cat -n apps/server/src/routes/backlog-plan/common.tsRepository: AutoMaker-Org/automaker
Length of output: 3780
🏁 Script executed:
# Verify if the GraphViewPage component is unmounted when switching projects
fd -e tsx -e ts | xargs grep -l "GraphViewPage\|/graph" | grep -v node_modules | head -10Repository: AutoMaker-Org/automaker
Length of output: 308
🏁 Script executed:
# Check if there's project-specific routing or if all projects share the same /graph route
rg -n "useParams\|useSearch\|projectPath" apps/ui/src/routes/ --type=tsRepository: AutoMaker-Org/automaker
Length of output: 49
🏁 Script executed:
# Final verification: confirm the route doesn't have project parameters and check if component unmounts on project change
cat -n apps/ui/src/routes/graph.tsx && echo "---" && rg -n "setCurrentProject\|currentProject =" apps/ui/src/store/ -A2 | head -30Repository: AutoMaker-Org/automaker
Length of output: 320
🏁 Script executed:
# Check if there are any tests or other components that show the expected behavior
rg -n "GraphViewPage\|backlogPlan.onEvent" apps/ui/src/components/views/board-view.tsx -B5 -A10 | head -50Repository: AutoMaker-Org/automaker
Length of output: 49
🏁 Script executed:
# Check the board-view implementation for comparison - does it handle project context better?
sed -n '920,950p' apps/ui/src/components/views/board-view.tsxRepository: AutoMaker-Org/automaker
Length of output: 1145
🏁 Script executed:
# Verify if currentProject is actually part of the component's closure in the event handler
sed -n '136,180p' apps/ui/src/components/views/graph-view-page.tsxRepository: AutoMaker-Org/automaker
Length of output: 1606
🏁 Script executed:
# Check if there's any mechanism to filter events by project at the client side
rg -n "projectPath\|project" apps/ui/src/lib/http-api-client.ts | grep -i "filter\|event" | head -20Repository: AutoMaker-Org/automaker
Length of output: 49
🏁 Script executed:
# Double-check board-view to see if it has the same issue or handles it correctly
sed -n '920,960p' apps/ui/src/components/views/board-view.tsxRepository: AutoMaker-Org/automaker
Length of output: 1415
🏁 Script executed:
# Confirm the exact line numbers for the board-view backlog plan event listener
rg -n "useEffect.*backlogPlan\|backlogPlan.onEvent" apps/ui/src/components/views/board-view.tsx -B2 -A15Repository: AutoMaker-Org/automaker
Length of output: 49
Add project path to backlog plan event context to prevent cross-project event contamination.
The backlog plan event listener doesn't filter by project, and the empty dependency array means this effect only subscribes once on mount. When the user switches projects, the old subscription remains active and receives events intended for the new project.
Two issues:
BacklogPlanEventhas no project context—events broadcast globally to all listeners regardless of which project generated them.- The
[]dependency array means ifcurrentProjectchanges without unmounting the component, the handler remains stale and uses the old closure values.
The server should include projectPath in the emitted event, and the component should include currentProject in the dependency array to re-subscribe when the project changes.
🤖 Prompt for AI Agents
In `@apps/ui/src/components/views/graph-view-page.tsx` around lines 136 - 173,
Update the backlog-plan event handling to only act on events for the active
project and to re-subscribe when the project changes: have the server include
projectPath in the emitted BacklogPlanEvent (so BacklogPlanResult events carry
projectPath), then in the useEffect that calls getElectronAPI() and
api.backlogPlan.onEvent(...) add currentProject (or currentProject.path) to the
dependency array and, inside the event handler, ignore events whose
event.projectPath does not equal the current project's path before calling
setIsGeneratingPlan, setPendingBacklogPlan, setShowPlanDialog or showing toasts;
keep unsubscribe logic the same so the old listener is removed on project
change.
| {/* Search Input */} | ||
| <div className="relative"> | ||
| <Search className="absolute left-2 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground pointer-events-none" /> | ||
| <Input | ||
| type="text" | ||
| placeholder="Search tasks..." | ||
| value={searchQuery} | ||
| onChange={(e) => onSearchQueryChange(e.target.value)} | ||
| className="h-8 w-48 pl-8 pr-8 text-sm bg-background/50" | ||
| /> | ||
| {searchQuery && ( | ||
| <button | ||
| onClick={() => onSearchQueryChange('')} | ||
| className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground" | ||
| aria-label="Clear search" | ||
| > | ||
| <X className="w-3.5 h-3.5" /> | ||
| </button> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "graph-filter-controls.tsx" -type fRepository: AutoMaker-Org/automaker
Length of output: 145
🏁 Script executed:
cat -n ./apps/ui/src/components/views/graph-view/components/graph-filter-controls.tsx | sed -n '120,145p'Repository: AutoMaker-Org/automaker
Length of output: 1413
Add input labeling and explicit button type to improve accessibility.
The search input lacks an aria-label for screen reader users, and the clear button lacks an explicit type="button" attribute, which could cause it to submit a form if this component is used within a form context.
Suggested fix
<Input
type="text"
placeholder="Search tasks..."
value={searchQuery}
onChange={(e) => onSearchQueryChange(e.target.value)}
className="h-8 w-48 pl-8 pr-8 text-sm bg-background/50"
+ aria-label="Search tasks"
/>
{searchQuery && (
<button
+ type="button"
onClick={() => onSearchQueryChange('')}
className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
aria-label="Clear search"
>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {/* Search Input */} | |
| <div className="relative"> | |
| <Search className="absolute left-2 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground pointer-events-none" /> | |
| <Input | |
| type="text" | |
| placeholder="Search tasks..." | |
| value={searchQuery} | |
| onChange={(e) => onSearchQueryChange(e.target.value)} | |
| className="h-8 w-48 pl-8 pr-8 text-sm bg-background/50" | |
| /> | |
| {searchQuery && ( | |
| <button | |
| onClick={() => onSearchQueryChange('')} | |
| className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground" | |
| aria-label="Clear search" | |
| > | |
| <X className="w-3.5 h-3.5" /> | |
| </button> | |
| {/* Search Input */} | |
| <div className="relative"> | |
| <Search className="absolute left-2 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground pointer-events-none" /> | |
| <Input | |
| type="text" | |
| placeholder="Search tasks..." | |
| value={searchQuery} | |
| onChange={(e) => onSearchQueryChange(e.target.value)} | |
| className="h-8 w-48 pl-8 pr-8 text-sm bg-background/50" | |
| aria-label="Search tasks" | |
| /> | |
| {searchQuery && ( | |
| <button | |
| type="button" | |
| onClick={() => onSearchQueryChange('')} | |
| className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground" | |
| aria-label="Clear search" | |
| > | |
| <X className="w-3.5 h-3.5" /> | |
| </button> |
🤖 Prompt for AI Agents
In `@apps/ui/src/components/views/graph-view/components/graph-filter-controls.tsx`
around lines 123 - 140, The Search Input lacks accessible labeling and the clear
control needs an explicit button type: add an aria-label (e.g.,
aria-label="Search tasks") to the Input component where value={searchQuery} and
onChange calls onSearchQueryChange, and add type="button" to the clear button
(the element rendering <X /> with onClick={() => onSearchQueryChange('')}) so it
cannot submit a surrounding form; ensure the aria-label text matches the
placeholder for clarity.
| console.log('onDeleteDependency called', { sourceId, targetId }); | ||
| const targetFeature = features.find((f) => f.id === targetId); | ||
| if (!targetFeature) return; | ||
| if (!targetFeature) { | ||
| console.error('Target feature not found:', targetId); | ||
| return; | ||
| } | ||
|
|
||
| const currentDeps = (targetFeature.dependencies as string[] | undefined) || []; | ||
| console.log('Current dependencies:', currentDeps); | ||
| const newDeps = currentDeps.filter((depId) => depId !== sourceId); | ||
|
|
||
| onUpdateFeature?.(targetId, { | ||
| dependencies: newDeps, | ||
| }); | ||
| console.log('New dependencies:', newDeps); | ||
|
|
||
| if (onUpdateFeature) { | ||
| console.log('Calling onUpdateFeature'); | ||
| onUpdateFeature(targetId, { | ||
| dependencies: newDeps, | ||
| }); | ||
| } else { | ||
| console.error('onUpdateFeature is not defined!'); | ||
| } | ||
|
|
||
| toast.success('Dependency removed'); | ||
| }, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n apps/ui/src/components/views/graph-view/graph-view.tsx | sed -n '180,215p'Repository: AutoMaker-Org/automaker
Length of output: 1502
Show error toast and return early when dependency update cannot be executed.
The success toast at line 207 always displays, even when onUpdateFeature is undefined. When the else branch at line 203 executes (callback not defined), the function continues instead of returning, misleading the user that the operation succeeded when it actually failed.
Proposed fix
if (!targetFeature) {
- console.error('Target feature not found:', targetId);
+ toast.error('Target task not found');
return;
}
- const currentDeps = (targetFeature.dependencies as string[] | undefined) || [];
- console.log('Current dependencies:', currentDeps);
const newDeps = currentDeps.filter((depId) => depId !== sourceId);
- console.log('New dependencies:', newDeps);
-
- if (onUpdateFeature) {
- console.log('Calling onUpdateFeature');
- onUpdateFeature(targetId, {
- dependencies: newDeps,
- });
- } else {
- console.error('onUpdateFeature is not defined!');
- }
+ if (!onUpdateFeature) {
+ toast.error('Unable to update dependencies');
+ return;
+ }
+ onUpdateFeature(targetId, {
+ dependencies: newDeps,
+ });
toast.success('Dependency removed');🤖 Prompt for AI Agents
In `@apps/ui/src/components/views/graph-view/graph-view.tsx` around lines 186 -
208, The handler that removes a dependency currently always shows toast.success
even when onUpdateFeature is undefined; update the onDeleteDependency flow to
return early when onUpdateFeature is not provided by showing an error toast
(e.g., toast.error with context) and immediately returning; only call
toast.success after successfully invoking onUpdateFeature for targetId (and
ideally after confirming no thrown error), referencing the onDeleteDependency
handler, targetFeature, currentDeps/newDeps and onUpdateFeature symbols so the
change is localized to that block.
| {onImportExportClick && ( | ||
| <Button variant="outline" size="sm" onClick={onImportExportClick} className="gap-2"> | ||
| <FileJson className="w-4 h-4" /> | ||
| <span className="hidden sm:inline">Import / Export</span> | ||
| </Button> |
There was a problem hiding this comment.
Add an accessible name to the icon‑only Import/Export button.
On small screens the text is hidden, leaving no accessible label for screen readers. Add an aria-label to the button.
✅ Suggested fix
- {onImportExportClick && (
- <Button variant="outline" size="sm" onClick={onImportExportClick} className="gap-2">
- <FileJson className="w-4 h-4" />
- <span className="hidden sm:inline">Import / Export</span>
- </Button>
- )}
+ {onImportExportClick && (
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={onImportExportClick}
+ className="gap-2"
+ aria-label="Import / Export"
+ >
+ <FileJson className="w-4 h-4" />
+ <span className="hidden sm:inline">Import / Export</span>
+ </Button>
+ )}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {onImportExportClick && ( | |
| <Button variant="outline" size="sm" onClick={onImportExportClick} className="gap-2"> | |
| <FileJson className="w-4 h-4" /> | |
| <span className="hidden sm:inline">Import / Export</span> | |
| </Button> | |
| {onImportExportClick && ( | |
| <Button | |
| variant="outline" | |
| size="sm" | |
| onClick={onImportExportClick} | |
| className="gap-2" | |
| aria-label="Import / Export" | |
| > | |
| <FileJson className="w-4 h-4" /> | |
| <span className="hidden sm:inline">Import / Export</span> | |
| </Button> | |
| )} |
🤖 Prompt for AI Agents
In `@apps/ui/src/components/views/settings-view/components/settings-header.tsx`
around lines 65 - 69, The Import/Export Button rendered when onImportExportClick
is present becomes icon-only on small screens (FileJson inside Button) and lacks
an accessible name; update the Button component instance (the one using FileJson
and span) to include an aria-label (e.g., aria-label="Import or export
settings") so screen readers have a label when the visible text is hidden,
ensuring the label is concise and descriptive.
| const handleSave = () => { | ||
| const hook: EventHook = { | ||
| id: editingHook?.id || crypto.randomUUID(), | ||
| name: name.trim() || undefined, | ||
| trigger, | ||
| enabled: editingHook?.enabled ?? true, | ||
| action: | ||
| actionType === 'shell' | ||
| ? { | ||
| type: 'shell', | ||
| command, | ||
| timeout: parseInt(timeout, 10) || 30000, | ||
| } | ||
| : { | ||
| type: 'http', | ||
| url, | ||
| method, | ||
| headers: headers.trim() ? JSON.parse(headers) : undefined, | ||
| body: body.trim() || undefined, | ||
| }, | ||
| }; | ||
|
|
||
| onSave(hook); | ||
| }; | ||
|
|
||
| const isValid = actionType === 'shell' ? command.trim().length > 0 : url.trim().length > 0; | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Check if the file exists and examine its structure
find . -name "event-hook-dialog.tsx" -type fRepository: AutoMaker-Org/automaker
Length of output: 145
🏁 Script executed:
# Read the specific file to see the actual implementation
if [ -f "apps/ui/src/components/views/settings-view/event-hooks/event-hook-dialog.tsx" ]; then
wc -l "apps/ui/src/components/views/settings-view/event-hooks/event-hook-dialog.tsx"
fiRepository: AutoMaker-Org/automaker
Length of output: 147
🏁 Script executed:
# Get the lines around the area of concern (109-135)
if [ -f "apps/ui/src/components/views/settings-view/event-hooks/event-hook-dialog.tsx" ]; then
sed -n '1,150p' "apps/ui/src/components/views/settings-view/event-hooks/event-hook-dialog.tsx" | cat -n
fiRepository: AutoMaker-Org/automaker
Length of output: 5731
🏁 Script executed:
# Search for JSON.parse usage in the file to confirm the vulnerability
if [ -f "apps/ui/src/components/views/settings-view/event-hooks/event-hook-dialog.tsx" ]; then
grep -n "JSON.parse" "apps/ui/src/components/views/settings-view/event-hooks/event-hook-dialog.tsx"
fiRepository: AutoMaker-Org/automaker
Length of output: 143
🏁 Script executed:
# Check import statements in the file
if [ -f "apps/ui/src/components/views/settings-view/event-hooks/event-hook-dialog.tsx" ]; then
head -30 "apps/ui/src/components/views/settings-view/event-hooks/event-hook-dialog.tsx" | cat -n
fiRepository: AutoMaker-Org/automaker
Length of output: 1104
🏁 Script executed:
# Check for other JSON.parse calls in the file
rg "JSON\.parse" "apps/ui/src/components/views/settings-view/event-hooks/event-hook-dialog.tsx"Repository: AutoMaker-Org/automaker
Length of output: 139
🏁 Script executed:
# Look at the full handleSave function and surrounding context
sed -n '109,145p' "apps/ui/src/components/views/settings-view/event-hooks/event-hook-dialog.tsx" | cat -nRepository: AutoMaker-Org/automaker
Length of output: 1460
🏁 Script executed:
# Check if there's any error handling or validation for headers elsewhere in the file
rg -i "headers|error|valid" "apps/ui/src/components/views/settings-view/event-hooks/event-hook-dialog.tsx" -A 2 | head -50Repository: AutoMaker-Org/automaker
Length of output: 1285
Guard JSON parsing to prevent a save-time crash.
JSON.parse(headers) at line 126 will throw an uncaught SyntaxError on invalid JSON input, breaking the dialog. The current isValid check doesn't prevent saving with malformed headers. Add validation with error handling, type checking (reject arrays/primitives), and display an error message.
Proposed fix
- const [headers, setHeaders] = useState('');
+ const [headers, setHeaders] = useState('');
+ const [headersError, setHeadersError] = useState<string | null>(null);
const [body, setBody] = useState('');
const handleSave = () => {
+ let parsedHeaders: Record<string, string> | undefined;
+ if (headers.trim()) {
+ try {
+ const parsed = JSON.parse(headers);
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
+ throw new Error('Headers must be a JSON object');
+ }
+ parsedHeaders = parsed as Record<string, string>;
+ setHeadersError(null);
+ } catch {
+ setHeadersError('Headers must be valid JSON object');
+ return;
+ }
+ } else {
+ setHeadersError(null);
+ }
const hook: EventHook = {
id: editingHook?.id || crypto.randomUUID(),
name: name.trim() || undefined,
trigger,
enabled: editingHook?.enabled ?? true,
action:
actionType === 'shell'
? {
type: 'shell',
command,
timeout: parseInt(timeout, 10) || 30000,
}
: {
type: 'http',
url,
method,
- headers: headers.trim() ? JSON.parse(headers) : undefined,
+ headers: parsedHeaders,
body: body.trim() || undefined,
},
};
onSave(hook);
};
- const isValid = actionType === 'shell' ? command.trim().length > 0 : url.trim().length > 0;
+ const isValid =
+ (actionType === 'shell' ? command.trim().length > 0 : url.trim().length > 0) &&
+ !headersError;
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-lg max-h-[85vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>{editingHook ? 'Edit Event Hook' : 'Add Event Hook'}</DialogTitle>
<DialogDescription>
Configure an action to run when a specific event occurs.
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
{/* Headers field */}
<div className="space-y-2">
<Label htmlFor="http-headers">Headers (JSON, optional)</Label>
<Textarea
id="http-headers"
value={headers}
- onChange={(e) => setHeaders(e.target.value)}
+ onChange={(e) => {
+ setHeaders(e.target.value);
+ setHeadersError(null);
+ }}
placeholder={'{\n "Authorization": "Bearer {{token}}"\n}'}
className="font-mono text-sm"
rows={3}
/>
+ {headersError && (
+ <p className="text-xs text-destructive">{headersError}</p>
+ )}
</div>🤖 Prompt for AI Agents
In `@apps/ui/src/components/views/settings-view/event-hooks/event-hook-dialog.tsx`
around lines 109 - 135, In handleSave and the validation logic, guard the
JSON.parse(headers) call so malformed JSON can't crash the dialog: add a
validateHeaders function that attempts JSON.parse(headers) inside a try/catch,
ensures the parsed value is an object (typeof === 'object' && !Array.isArray &&
parsed !== null), and returns either the parsed object or an error; use this
validator in isValid (alongside actionType checks) to block saving when headers
are invalid, and in handleSave call the validator and, on error, set a local
error state (e.g., headersError) and avoid calling onSave; also ensure the
component renders that headersError message so users see why save was blocked.
CodeRabbit Issues - Fedora RPM Support Changes Addressed ✅I've reviewed and fixed CodeRabbit issues related to my Fedora RPM support changes: Fixed Issuesdocs/install-fedora.md:
apps/server/src/routes/backlog-plan/common.ts:
Other PR IssuesThe PR includes many other CodeRabbit issues from other contributors' changes (event hooks, backlog plan service, graph view, etc.). Those are outside my Fedora RPM support scope but should be addressed by the respective authors. All changes committed and pushed to |
|
@coderabbitai review this pr now. |
|
I can see you've done thorough work on this Fedora/RHEL RPM support feature: ✅ Successfully verified local builds (AppImage, DEB) I'll now conduct a complete review of all the changes in this PR. ✅ Actions performedReview triggered.
|
|
@coderabbitai review this pr now. |
|
The review will cover the electron-builder RPM configuration, GitHub Actions workflow updates, documentation completeness, and all other changes in the PR. ✅ Actions performedReview triggered.
|
d36097e to
72eae07
Compare
ee3f9f5 to
5be7c66
Compare
Fix type mismatch in loadBacklogPlan where secureFs.readFile with 'utf-8' encoding returns union type string | Buffer, causing JSON.parse to fail type checking. Cast raw to string to satisfy TypeScript strict mode. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
The 'license' property is not supported by electron-builder's RPM schema. Valid RPM properties are: afterInstall, afterRemove, appArmorProfile, artifactName, category, compression, depends, description, desktop, executableArgs, fpm, icon, maintainer, mimeTypes, packageCategory, packageName, publish, synopsis, vendor. This fix allows electron-builder to proceed to the RPM build stage. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
… and backlog plan Documentation improvements: - Fix GitHub URL placeholder issues in install-fedora.md - GitHub /latest/download/ endpoint doesn't support version substitution, use explicit download URL pattern instead - Improve security in network troubleshooting section: - Change ping target from claude.ai (marketing site) to api.anthropic.com (actual API) - Remove unsafe 'echo \$ANTHROPIC_API_KEY' command that exposes secrets in shell history - Use safe API key check with conditional output instead Code improvements: - apps/server/src/routes/backlog-plan/common.ts: Add Array.isArray() validation for stored plan shape before returning it. Ensures changes is actually an array, not just truthy, preventing downstream runtime errors. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Add setRunningDetails(null) in finally block of generate handler to prevent state leaks - Move clearBacklogPlan before response in apply handler and wrap in try-catch to prevent errors after headers sent Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Add setRunningDetails(null) to stop handler to prevent state leaks when aborting operation. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Ensure running details are cleared when generation completes or fails, preventing state leaks. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
The release workflow was failing for all platforms because macOS DMG builder requires dmg-license. This single dependency was preventing AppImage, DEB, RPM, DMG, and EXE artifacts from being built and uploaded to any release since v0.7.3. Includes lockfile updates and conversion of git+ssh:// URLs to https:// to prevent SSH key requirement issues in CI/CD and across environments. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
5be7c66 to
becd79f
Compare
… devDependencies dmg-license is a darwin-only package required for macOS DMG building. The CI runs on Linux, so npm install fails when trying to install a platform-specific devDependency. Using --force allows npm to skip platform mismatches instead of erroring out, allowing the build to proceed on non-darwin platforms where the darwin-only dependency will simply be skipped. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Ensures dmg-license can be installed on Linux CI runners even though it's a darwin-only package. The --force flag allows npm to skip platform mismatches. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
…tation Major improvements to start-automaker.sh launcher script: **Architecture & Code Quality:** - Organized into logical sections with clear separators (8 sections) - Extracted all magic numbers into named constants at top - Added comprehensive comments throughout **Functionality:** - Dynamic version extraction from package.json (no manual updates) - Pre-flight checks: validates Node.js, npm, tput installed - Platform detection: warns on Windows/unsupported systems - Terminal size validation: checks min 70x20, displays warning if too small - Input timeout: 30-second auto-timeout for hands-free operation - History tracking: remembers last selected mode in ~/.automaker_launcher_history **User Experience:** - Added --help flag with comprehensive usage documentation - Added --version flag showing version, Node.js, Bash info - Added --check-deps flag to verify project dependencies - Added --no-colors flag for terminals without color support - Added --no-history flag to disable history tracking - Enhanced cleanup function: restores cursor + echo, better signal handling - Better error messages with actionable remediation steps - Improved exit experience: "Goodbye! See you soon." message **Robustness:** - Real initialization checks (validates node_modules, build artifacts) - Spinner uses frame counting instead of infinite loop (max 1.6s) - Proper signal trap handling (EXIT, INT, TERM) - Error recovery: respects --no-colors in pre-flight checks **File Management:** - Renamed from "start automaker.sh" to "start-automaker.sh" for consistency - Made script more portable with SCRIPT_DIR detection **Documentation:** - Added section to README.md: "Interactive TUI Launcher" - Documented all launch modes and options with examples - Added feature list, history file location, usage tips - Updated table of contents with TUI launcher section Fixes: AutoMaker-Org#511 (CI test failures resolved) Improvements: Better UX for new users, production-ready error handling Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Resolved conflict in backlog-plan/common.ts: - Kept local (stricter) validation: Array.isArray(parsed?.result?.changes) - This ensures type safety for the changes array
Summary
Changes
Configuration
apps/ui/package.json: Added RPM target to linux build config with proper dependency declarations (gtk3, libnotify, nss, etc.).github/workflows/release.yml: Added rpm-build installation and updated artifact patterns to include .rpm filesDocumentation
README.md: Added Fedora/RHEL installation instructions with dnf/yum examplesdocs/install-fedora.md: New comprehensive guide covering:Installation Usage
After merge, Fedora users can install via:
Testing
Compatibility
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Improvements
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.