Merge branch 'v0.14.0rc' of github.com:AutoMaker-Org/automaker into v0.14.0rc

This commit is contained in:
webdevcody
2026-01-23 12:57:46 -05:00
12 changed files with 782 additions and 37 deletions

View File

@@ -1,10 +1,11 @@
import { useState, useCallback, useEffect, useRef } from 'react';
import { useCallback, useEffect, useRef } from 'react';
import type { NavigateOptions } from '@tanstack/react-router';
import { ChevronDown, Wrench, Github } from 'lucide-react';
import { cn } from '@/lib/utils';
import { formatShortcut } from '@/store/app-store';
import { formatShortcut, useAppStore } from '@/store/app-store';
import type { NavSection } from '../types';
import type { Project } from '@/lib/electron';
import type { SidebarStyle } from '@automaker/types';
import { Spinner } from '@/components/ui/spinner';
import {
DropdownMenu,
@@ -23,6 +24,7 @@ const sectionIcons: Record<string, React.ComponentType<{ className?: string }>>
interface SidebarNavigationProps {
currentProject: Project | null;
sidebarOpen: boolean;
sidebarStyle: SidebarStyle;
navSections: NavSection[];
isActiveRoute: (id: string) => boolean;
navigate: (opts: NavigateOptions) => void;
@@ -32,6 +34,7 @@ interface SidebarNavigationProps {
export function SidebarNavigation({
currentProject,
sidebarOpen,
sidebarStyle,
navSections,
isActiveRoute,
navigate,
@@ -39,21 +42,26 @@ export function SidebarNavigation({
}: SidebarNavigationProps) {
const navRef = useRef<HTMLElement>(null);
// Track collapsed state for each collapsible section
const [collapsedSections, setCollapsedSections] = useState<Record<string, boolean>>({});
// Get collapsed state from store (persisted across restarts)
const { collapsedNavSections, setCollapsedNavSections, toggleNavSection } = useAppStore();
// Initialize collapsed state when sections change (e.g., GitHub section appears)
// Only set defaults for sections that don't have a persisted state
useEffect(() => {
setCollapsedSections((prev) => {
const updated = { ...prev };
navSections.forEach((section) => {
if (section.collapsible && section.label && !(section.label in updated)) {
updated[section.label] = section.defaultCollapsed ?? false;
}
});
return updated;
let hasNewSections = false;
const updated = { ...collapsedNavSections };
navSections.forEach((section) => {
if (section.collapsible && section.label && !(section.label in updated)) {
updated[section.label] = section.defaultCollapsed ?? false;
hasNewSections = true;
}
});
}, [navSections]);
if (hasNewSections) {
setCollapsedNavSections(updated);
}
}, [navSections, collapsedNavSections, setCollapsedNavSections]);
// Check scroll state
const checkScrollState = useCallback(() => {
@@ -77,14 +85,7 @@ export function SidebarNavigation({
nav.removeEventListener('scroll', checkScrollState);
resizeObserver.disconnect();
};
}, [checkScrollState, collapsedSections]);
const toggleSection = useCallback((label: string) => {
setCollapsedSections((prev) => ({
...prev,
[label]: !prev[label],
}));
}, []);
}, [checkScrollState, collapsedNavSections]);
// Filter sections: always show non-project sections, only show project sections when project exists
const visibleSections = navSections.filter((section) => {
@@ -97,10 +98,17 @@ export function SidebarNavigation({
});
return (
<nav ref={navRef} className={cn('flex-1 overflow-y-auto scrollbar-hide px-3 pb-2 mt-1')}>
<nav
ref={navRef}
className={cn(
'flex-1 overflow-y-auto scrollbar-hide px-3 pb-2',
// Add top padding in discord mode since there's no header
sidebarStyle === 'discord' ? 'pt-3' : 'mt-1'
)}
>
{/* Navigation sections */}
{visibleSections.map((section, sectionIdx) => {
const isCollapsed = section.label ? collapsedSections[section.label] : false;
const isCollapsed = section.label ? collapsedNavSections[section.label] : false;
const isCollapsible = section.collapsible && section.label && sidebarOpen;
const SectionIcon = section.label ? sectionIcons[section.label] : null;
@@ -110,21 +118,37 @@ export function SidebarNavigation({
{/* Section Label - clickable if collapsible (expanded sidebar) */}
{section.label && sidebarOpen && (
<button
onClick={() => isCollapsible && toggleSection(section.label!)}
onClick={() => isCollapsible && toggleNavSection(section.label!)}
className={cn(
'flex items-center w-full px-3 mb-1.5',
isCollapsible && 'cursor-pointer hover:text-foreground'
'group flex items-center w-full px-3 py-1.5 mb-1 rounded-md',
'transition-all duration-200 ease-out',
isCollapsible
? [
'cursor-pointer',
'hover:bg-accent/50 hover:text-foreground',
'border border-transparent hover:border-border/40',
]
: 'cursor-default'
)}
disabled={!isCollapsible}
>
<span className="text-[10px] font-semibold text-muted-foreground/70 uppercase tracking-widest">
<span
className={cn(
'text-[10px] font-semibold uppercase tracking-widest transition-colors duration-200',
isCollapsible
? 'text-muted-foreground/70 group-hover:text-foreground'
: 'text-muted-foreground/70'
)}
>
{section.label}
</span>
{isCollapsible && (
<ChevronDown
className={cn(
'w-3 h-3 ml-auto text-muted-foreground/50 transition-transform duration-200',
isCollapsed && '-rotate-90'
'w-3 h-3 ml-auto transition-all duration-200',
isCollapsed
? '-rotate-90 text-muted-foreground/50 group-hover:text-muted-foreground'
: 'text-muted-foreground/50 group-hover:text-muted-foreground'
)}
/>
)}

View File

@@ -53,6 +53,7 @@ export function Sidebar() {
trashedProjects,
currentProject,
sidebarOpen,
sidebarStyle,
mobileSidebarHidden,
projectHistory,
upsertAndSetCurrentProject,
@@ -381,17 +382,21 @@ export function Sidebar() {
)}
<div className="flex-1 flex flex-col overflow-hidden">
<SidebarHeader
sidebarOpen={sidebarOpen}
currentProject={currentProject}
onNewProject={handleNewProject}
onOpenFolder={handleOpenFolder}
onProjectContextMenu={handleContextMenu}
/>
{/* Only show header in unified mode - in discord mode, ProjectSwitcher has the logo */}
{sidebarStyle === 'unified' && (
<SidebarHeader
sidebarOpen={sidebarOpen}
currentProject={currentProject}
onNewProject={handleNewProject}
onOpenFolder={handleOpenFolder}
onProjectContextMenu={handleContextMenu}
/>
)}
<SidebarNavigation
currentProject={currentProject}
sidebarOpen={sidebarOpen}
sidebarStyle={sidebarStyle}
navSections={navSections}
isActiveRoute={isActiveRoute}
navigate={navigate}

View File

@@ -0,0 +1,150 @@
export const commitTemplate = {
id: 'commit',
name: 'Commit Changes',
colorClass: 'bg-purple-500/20',
instructions: `## Commit Changes Step
# ⚠️ CRITICAL REQUIREMENT: YOU MUST COMMIT ALL CHANGES USING CONVENTIONAL COMMIT FORMAT ⚠️
**THIS IS NOT OPTIONAL. YOU MUST CREATE AND EXECUTE A GIT COMMIT WITH ALL CHANGES.**
This step requires you to:
1. **REVIEW** all changes made in this feature
2. **CREATE** a conventional commit message
3. **EXECUTE** the git commit command
**You cannot complete this step by only reviewing changes. You MUST execute the git commit command.**
---
### Phase 1: Review Phase
Review all changes made in this feature:
- Review all modified files using \`git status\` and \`git diff\`
- Identify the scope and nature of changes
- Determine the appropriate conventional commit type
- Identify any breaking changes that need to be documented
---
### Phase 2: Commit Phase - ⚠️ MANDATORY ACTION REQUIRED ⚠️
**YOU MUST NOW CREATE AND EXECUTE A GIT COMMIT WITH ALL CHANGES.**
**This is not optional. You must stage all changes and commit them using conventional commit format.**
#### Conventional Commit Format
Follow this format for your commit message:
\`\`\`
<type>(<scope>): <subject>
<body>
<footer>
\`\`\`
#### Commit Types (choose the most appropriate):
- **feat**: A new feature
- **fix**: A bug fix
- **docs**: Documentation only changes
- **style**: Code style changes (formatting, missing semicolons, etc.)
- **refactor**: Code refactoring without changing functionality
- **perf**: Performance improvements
- **test**: Adding or updating tests
- **chore**: Changes to build process, dependencies, or tooling
- **ci**: Changes to CI configuration
- **build**: Changes to build system or dependencies
#### Scope (optional but recommended):
- Component/module name (e.g., \`ui\`, \`server\`, \`auth\`)
- Feature area (e.g., \`board\`, \`pipeline\`, \`agent\`)
- Package name (e.g., \`@automaker/types\`)
#### Subject:
- Use imperative mood: "add" not "added" or "adds"
- First letter lowercase
- No period at the end
- Maximum 72 characters
#### Body (optional but recommended for significant changes):
- Explain the "what" and "why" of the change
- Reference related issues or PRs
- Separate from subject with blank line
- Wrap at 72 characters
#### Footer (optional):
- Breaking changes: \`BREAKING CHANGE: <description>\`
- Issue references: \`Closes #123\`, \`Fixes #456\`
#### Action Steps (You MUST complete these):
1. **Stage All Changes** - PREPARE FOR COMMIT:
- ✅ Run \`git add .\` or \`git add -A\` to stage all changes
- ✅ Verify staged changes with \`git status\`
- ✅ Ensure all relevant changes are staged
2. **Create Commit Message** - FOLLOW CONVENTIONAL COMMIT FORMAT:
- ✅ Determine the appropriate commit type based on changes
- ✅ Identify the scope (component/module/feature)
- ✅ Write a clear, imperative subject line
- ✅ Add a body explaining the changes (if significant)
- ✅ Include breaking changes in footer if applicable
- ✅ Reference related issues if applicable
3. **Execute Commit** - COMMIT THE CHANGES:
- ✅ Run \`git commit -m "<type>(<scope>): <subject>" -m "<body>"\` or use a multi-line commit message
- ✅ Verify the commit was created with \`git log -1\`
- ✅ **EXECUTE THE ACTUAL GIT COMMIT COMMAND**
#### Example Commit Messages:
\`\`\`
feat(ui): add pipeline step commit template
Add a new pipeline step template for committing changes using
conventional commit format. This ensures all commits follow
a consistent pattern for better changelog generation.
Closes #123
\`\`\`
\`\`\`
fix(server): resolve agent session timeout issue
The agent session was timing out prematurely due to incorrect
WebSocket heartbeat configuration. Updated heartbeat interval
to match server expectations.
Fixes #456
\`\`\`
\`\`\`
refactor(pipeline): extract step template logic
Extract step template loading and validation into separate
utility functions to improve code organization and testability.
\`\`\`
---
### Summary Required
After completing BOTH review AND commit phases, provide:
- A summary of all changes that were committed
- **The exact commit message that was used (this proves you executed the commit)**
- The commit hash (if available)
- Any notes about the commit (breaking changes, related issues, etc.)
---
# ⚠️ FINAL REMINDER ⚠️
**Reviewing changes without committing is INCOMPLETE and UNACCEPTABLE.**
**You MUST stage all changes and execute a git commit command.**
**You MUST use conventional commit format for the commit message.**
**You MUST show evidence of the commit execution in your summary.**
**This step is only complete when changes have been committed to git.**`,
};

View File

@@ -4,6 +4,7 @@ import { uxReviewTemplate } from './ux-review';
import { testingTemplate } from './testing';
import { documentationTemplate } from './documentation';
import { optimizationTemplate } from './optimization';
import { commitTemplate } from './commit';
export interface PipelineStepTemplate {
id: string;
@@ -19,6 +20,7 @@ export const STEP_TEMPLATES: PipelineStepTemplate[] = [
testingTemplate,
documentationTemplate,
optimizationTemplate,
commitTemplate,
];
// Helper to get template color class

View File

@@ -1,7 +1,7 @@
import { useState, useEffect } from 'react';
import { Label } from '@/components/ui/label';
import { Switch } from '@/components/ui/switch';
import { Palette, Moon, Sun, Type, Sparkles } from 'lucide-react';
import { Palette, Moon, Sun, Type, Sparkles, PanelLeft, Columns2 } from 'lucide-react';
import { darkThemes, lightThemes } from '@/config/theme-options';
import {
UI_SANS_FONT_OPTIONS,
@@ -12,6 +12,7 @@ import { cn } from '@/lib/utils';
import { useAppStore } from '@/store/app-store';
import { FontSelector } from '@/components/shared';
import type { Theme } from '../shared/types';
import type { SidebarStyle } from '@automaker/types';
interface AppearanceSectionProps {
effectiveTheme: Theme;
@@ -26,6 +27,8 @@ export function AppearanceSection({ effectiveTheme, onThemeChange }: AppearanceS
setFontMono,
disableSplashScreen,
setDisableSplashScreen,
sidebarStyle,
setSidebarStyle,
} = useAppStore();
// Determine if current theme is light or dark
@@ -221,6 +224,94 @@ export function AppearanceSection({ effectiveTheme, onThemeChange }: AppearanceS
/>
</div>
</div>
{/* Sidebar Style Section */}
<div className="space-y-4 pt-6 border-t border-border/50">
<div className="flex items-center gap-2 mb-4">
<PanelLeft className="w-4 h-4 text-muted-foreground" />
<Label className="text-foreground font-medium">Sidebar Layout</Label>
</div>
<p className="text-xs text-muted-foreground -mt-2 mb-4">
Choose between a modern unified sidebar or classic Discord-style layout with a separate
project switcher.
</p>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Unified Sidebar Option */}
<button
onClick={() => setSidebarStyle('unified')}
className={cn(
'group flex flex-col items-center gap-3 p-4 rounded-xl',
'text-sm font-medium transition-all duration-200 ease-out',
sidebarStyle === 'unified'
? [
'bg-gradient-to-br from-brand-500/15 to-brand-600/10',
'border-2 border-brand-500/40',
'text-foreground',
'shadow-md shadow-brand-500/10',
]
: [
'bg-accent/30 hover:bg-accent/50',
'border border-border/50 hover:border-border',
'text-muted-foreground hover:text-foreground',
'hover:shadow-sm',
],
'hover:scale-[1.02] active:scale-[0.98]'
)}
data-testid="sidebar-style-unified"
>
<PanelLeft
className={cn(
'w-8 h-8 transition-all duration-200',
sidebarStyle === 'unified' ? 'text-brand-500' : 'text-muted-foreground'
)}
/>
<div className="text-center">
<div className="font-medium">Unified</div>
<div className="text-xs text-muted-foreground mt-1">
Single sidebar with project dropdown
</div>
</div>
</button>
{/* Discord-style Sidebar Option */}
<button
onClick={() => setSidebarStyle('discord')}
className={cn(
'group flex flex-col items-center gap-3 p-4 rounded-xl',
'text-sm font-medium transition-all duration-200 ease-out',
sidebarStyle === 'discord'
? [
'bg-gradient-to-br from-brand-500/15 to-brand-600/10',
'border-2 border-brand-500/40',
'text-foreground',
'shadow-md shadow-brand-500/10',
]
: [
'bg-accent/30 hover:bg-accent/50',
'border border-border/50 hover:border-border',
'text-muted-foreground hover:text-foreground',
'hover:shadow-sm',
],
'hover:scale-[1.02] active:scale-[0.98]'
)}
data-testid="sidebar-style-discord"
>
<Columns2
className={cn(
'w-8 h-8 transition-all duration-200',
sidebarStyle === 'discord' ? 'text-brand-500' : 'text-muted-foreground'
)}
/>
<div className="text-center">
<div className="font-medium">Classic</div>
<div className="text-xs text-muted-foreground mt-1">
Separate project switcher + sidebar
</div>
</div>
</button>
</div>
</div>
</div>
</div>
);

View File

@@ -699,6 +699,8 @@ export function hydrateStoreFromSettings(settings: GlobalSettings): void {
fontFamilySans: settings.fontFamilySans ?? null,
fontFamilyMono: settings.fontFamilyMono ?? null,
sidebarOpen: settings.sidebarOpen ?? true,
sidebarStyle: settings.sidebarStyle ?? 'unified',
collapsedNavSections: settings.collapsedNavSections ?? {},
chatHistoryOpen: settings.chatHistoryOpen ?? false,
maxConcurrency: settings.maxConcurrency ?? DEFAULT_MAX_CONCURRENCY,
autoModeByWorktree: restoredAutoModeByWorktree,

View File

@@ -53,6 +53,8 @@ const SETTINGS_FIELDS_TO_SYNC = [
'terminalFontFamily', // Maps to terminalState.fontFamily
'openTerminalMode', // Maps to terminalState.openTerminalMode
'sidebarOpen',
'sidebarStyle',
'collapsedNavSections',
'chatHistoryOpen',
'maxConcurrency',
'autoModeByWorktree', // Per-worktree auto mode settings (only maxConcurrency is persisted)
@@ -698,6 +700,8 @@ export async function refreshSettingsFromServer(): Promise<boolean> {
useAppStore.setState({
theme: serverSettings.theme as unknown as ThemeMode,
sidebarOpen: serverSettings.sidebarOpen,
sidebarStyle: serverSettings.sidebarStyle ?? 'unified',
collapsedNavSections: serverSettings.collapsedNavSections ?? {},
chatHistoryOpen: serverSettings.chatHistoryOpen,
maxConcurrency: serverSettings.maxConcurrency,
autoModeByWorktree: restoredAutoModeByWorktree,

View File

@@ -4,6 +4,7 @@ import { QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { createLogger } from '@automaker/utils/logger';
import { Sidebar } from '@/components/layout/sidebar';
import { ProjectSwitcher } from '@/components/layout/project-switcher';
import {
FileBrowserProvider,
useFileBrowser,
@@ -167,6 +168,7 @@ function RootLayoutContent() {
theme,
fontFamilySans,
fontFamilyMono,
sidebarStyle,
skipSandboxWarning,
setSkipSandboxWarning,
fetchCodexModels,
@@ -860,6 +862,8 @@ function RootLayoutContent() {
aria-hidden="true"
/>
)}
{/* Discord-style layout: narrow project switcher + expandable sidebar */}
{sidebarStyle === 'discord' && <ProjectSwitcher />}
<Sidebar />
<div
className="flex-1 flex flex-col overflow-hidden transition-all duration-300"

View File

@@ -36,6 +36,7 @@ import type {
EventHook,
ClaudeApiProfile,
ClaudeCompatibleProvider,
SidebarStyle,
} from '@automaker/types';
import {
getAllCursorModelIds,
@@ -610,6 +611,8 @@ export interface AppState {
// View state
currentView: ViewMode;
sidebarOpen: boolean;
sidebarStyle: SidebarStyle; // 'unified' (modern) or 'discord' (classic two-sidebar layout)
collapsedNavSections: Record<string, boolean>; // Collapsed state of nav sections (key: section label)
mobileSidebarHidden: boolean; // Completely hides sidebar on mobile
// Agent Session state (per-project, keyed by project path)
@@ -1049,6 +1052,9 @@ export interface AppActions {
setCurrentView: (view: ViewMode) => void;
toggleSidebar: () => void;
setSidebarOpen: (open: boolean) => void;
setSidebarStyle: (style: SidebarStyle) => void;
setCollapsedNavSections: (sections: Record<string, boolean>) => void;
toggleNavSection: (sectionLabel: string) => void;
toggleMobileSidebarHidden: () => void;
setMobileSidebarHidden: (hidden: boolean) => void;
@@ -1477,6 +1483,8 @@ const initialState: AppState = {
projectHistoryIndex: -1,
currentView: 'welcome',
sidebarOpen: true,
sidebarStyle: 'unified', // Default to modern unified sidebar
collapsedNavSections: {}, // Nav sections expanded by default (sections set their own defaults)
mobileSidebarHidden: false, // Sidebar visible by default on mobile
lastSelectedSessionByProject: {},
theme: getStoredTheme() || 'dark', // Use localStorage theme as initial value, fallback to 'dark'
@@ -1936,6 +1944,15 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
setCurrentView: (view) => set({ currentView: view }),
toggleSidebar: () => set({ sidebarOpen: !get().sidebarOpen }),
setSidebarOpen: (open) => set({ sidebarOpen: open }),
setSidebarStyle: (style) => set({ sidebarStyle: style }),
setCollapsedNavSections: (sections) => set({ collapsedNavSections: sections }),
toggleNavSection: (sectionLabel) =>
set((state) => ({
collapsedNavSections: {
...state.collapsedNavSections,
[sectionLabel]: !state.collapsedNavSections[sectionLabel],
},
})),
toggleMobileSidebarHidden: () => set({ mobileSidebarHidden: !get().mobileSidebarHidden }),
setMobileSidebarHidden: (hidden) => set({ mobileSidebarHidden: hidden }),

View File

@@ -0,0 +1,431 @@
# PRD to Automaker Features Guide
This guide helps Claude generate properly structured Automaker features from a Product Requirements Document (PRD). Use this in new projects to create feature folders that Automaker can execute.
## Quick Start
1. Place your PRD file in the project (e.g., `PRD.md` or `.automaker/context/PRD.md`)
2. Create `.automaker/features/` directory
3. Use this guide to generate `feature.json` files for each feature phase
4. Run features in Automaker sequentially or in parallel based on dependencies
---
## Feature JSON Schema
### Minimal Required Fields
```json
{
"id": "feature-unique-identifier",
"category": "Core",
"title": "Feature Title",
"description": "Detailed description of what needs to be implemented",
"status": "backlog",
"priority": 1,
"imagePaths": [],
"textFilePaths": []
}
```
### Complete Feature Schema
```json
{
"id": "feature-unique-identifier",
"category": "Core | UI/UX | AI Agent | Infrastructure | Testing | From GitHub",
"title": "Short descriptive title",
"description": "Detailed implementation description with requirements",
"status": "backlog | pending | running | completed | failed | verified | waiting_approval",
"priority": 1,
"complexity": "simple | moderate | complex",
"dependencies": ["feature-id-1", "feature-id-2"],
"createdAt": "2026-01-23T00:00:00.000Z",
"updatedAt": "2026-01-23T00:00:00.000Z",
"branchName": null,
"descriptionHistory": [],
"skipTests": false,
"model": "claude-sonnet | claude-opus | claude-haiku",
"thinkingLevel": "none | low | medium | high | ultrathink",
"reasoningEffort": "none | low | medium | high",
"imagePaths": [],
"textFilePaths": [],
"planningMode": "none | spec | full",
"requirePlanApproval": false,
"workMode": "auto | custom"
}
```
---
## Field Descriptions
### Core Fields
| Field | Type | Required | Description |
| --------------- | ------ | -------- | --------------------------------------------------------------------------------- |
| `id` | string | Yes | Unique identifier. Use format: `feature-{descriptive-name}` or `phase-{n}-{name}` |
| `category` | string | Yes | Grouping category for the feature |
| `title` | string | Yes | Short, descriptive title (3-8 words) |
| `description` | string | Yes | Detailed implementation requirements |
| `status` | string | Yes | Current state. **Must be `backlog` for Automaker to execute** |
| `priority` | number | Yes | Execution priority (1 = highest, higher numbers = lower priority) |
| `imagePaths` | array | Yes | Must be empty `[]` - Automaker populates this automatically |
| `textFilePaths` | array | Yes | Must be empty `[]` - Automaker populates this automatically |
### Optional Fields
| Field | Type | Default | Description |
| --------------------- | -------- | -------- | -------------------------------------------------------------------------------------- |
| `complexity` | string | moderate | `simple` (< 1 hour), `moderate` (1-4 hours), `complex` (> 4 hours) |
| `dependencies` | string[] | [] | Array of feature IDs that must complete first |
| `skipTests` | boolean | false | Skip test execution during verification |
| `model` | string | - | AI model: `claude-sonnet` (balanced), `claude-opus` (complex), `claude-haiku` (simple) |
| `thinkingLevel` | string | none | Extended thinking: `none`, `low`, `medium`, `high`, `ultrathink` |
| `planningMode` | string | none | `none` (direct), `spec` (generate spec first), `full` (spec + tool exploration) |
| `requirePlanApproval` | boolean | false | Pause for human approval before execution |
| `workMode` | string | auto | `auto` (continuous), `custom` (step-by-step) |
---
## Writing Effective Descriptions
### Structure for Complex Features
```markdown
## Overview
Brief summary of what this feature accomplishes.
## Requirements
- Requirement 1: Specific implementation detail
- Requirement 2: Another specific detail
- Requirement 3: Edge case to handle
## Technical Approach
- Use existing pattern from X
- Modify files A, B, C
- Follow the Y architectural pattern
## Acceptance Criteria
- GIVEN condition, WHEN action, THEN expected result
- GIVEN another condition, WHEN action, THEN expected result
## Files to Modify
- `path/to/file1.ts` - Purpose
- `path/to/file2.tsx` - Purpose
## Edge Cases
- Handle empty state
- Handle error conditions
- Handle concurrent operations
```
### Structure for Simple Features
```markdown
Add [feature] to [location].
Requirements:
- Specific requirement 1
- Specific requirement 2
Files: `path/to/main/file.ts`
```
---
## Phasing Strategy
### When to Create Phases
Create separate features (phases) when:
1. Features have clear dependencies (Phase 2 needs Phase 1's types)
2. Different complexity levels (separate simple setup from complex logic)
3. Different areas of codebase (backend vs frontend)
4. Risk isolation (core changes separate from UI changes)
### Recommended Phase Structure
```
Phase 1: Foundation / Types / Schema
Phase 2: Backend / Service Layer
Phase 3: API Routes / Endpoints
Phase 4: Frontend / UI Components
Phase 5: Integration / Testing
Phase 6: Polish / Documentation
```
### Phase Naming Convention
```
phase-1-foundation
phase-2-backend-service
phase-3-api-routes
phase-4-frontend-ui
phase-5-integration
```
---
## Example: Converting PRD to Features
### Input PRD Section
```markdown
## User Authentication Feature
Users should be able to log in with email/password and OAuth providers.
The system should support session management and secure token storage.
```
### Output Feature Files
**Phase 1: Types and Schema**
```json
{
"id": "phase-1-auth-types",
"category": "Core",
"title": "Authentication Types and Schema",
"description": "Define TypeScript types and database schema for authentication.\n\nRequirements:\n- Add User, Session, and AuthToken types to @automaker/types\n- Create database migration for users and sessions tables\n- Define AuthProvider enum (email, google, github)\n\nFiles:\n- libs/types/src/auth.ts\n- libs/types/src/index.ts\n- apps/server/src/db/migrations/",
"status": "backlog",
"priority": 1,
"complexity": "simple",
"dependencies": [],
"model": "claude-sonnet",
"planningMode": "none"
}
```
**Phase 2: Backend Service**
```json
{
"id": "phase-2-auth-service",
"category": "Core",
"title": "Authentication Service Layer",
"description": "Implement authentication service with email/password and OAuth support.\n\nRequirements:\n- Create AuthService class with login, logout, register methods\n- Implement password hashing with bcrypt\n- Add OAuth provider integration (Google, GitHub)\n- Session management with secure token generation\n\nAcceptance Criteria:\n- GIVEN valid credentials, WHEN user logs in, THEN session token is returned\n- GIVEN invalid credentials, WHEN user logs in, THEN appropriate error is returned\n- GIVEN OAuth callback, WHEN user authenticates, THEN user is created/updated and session started\n\nFiles:\n- apps/server/src/services/auth-service.ts\n- apps/server/src/services/oauth-service.ts\n- apps/server/src/lib/password.ts",
"status": "backlog",
"priority": 1,
"complexity": "complex",
"dependencies": ["phase-1-auth-types"],
"model": "claude-opus",
"thinkingLevel": "medium",
"planningMode": "spec"
}
```
**Phase 3: API Routes**
```json
{
"id": "phase-3-auth-routes",
"category": "Core",
"title": "Authentication API Endpoints",
"description": "Create REST API endpoints for authentication.\n\nEndpoints:\n- POST /api/auth/login - Email/password login\n- POST /api/auth/register - New user registration\n- POST /api/auth/logout - End session\n- GET /api/auth/me - Get current user\n- GET /api/auth/oauth/:provider - OAuth initiation\n- GET /api/auth/oauth/:provider/callback - OAuth callback\n\nFiles:\n- apps/server/src/routes/auth/index.ts\n- apps/server/src/routes/auth/routes/*.ts",
"status": "backlog",
"priority": 1,
"complexity": "moderate",
"dependencies": ["phase-2-auth-service"],
"model": "claude-sonnet",
"planningMode": "spec"
}
```
**Phase 4: Frontend UI**
```json
{
"id": "phase-4-auth-ui",
"category": "UI/UX",
"title": "Authentication UI Components",
"description": "Create login, register, and profile UI components.\n\nComponents:\n- LoginForm with email/password fields and OAuth buttons\n- RegisterForm with validation\n- UserMenu dropdown showing logged-in user\n- AuthProvider context for app-wide auth state\n\nRoutes:\n- /login - Login page\n- /register - Registration page\n- /profile - User profile page\n\nFiles:\n- apps/ui/src/components/auth/*.tsx\n- apps/ui/src/routes/login.tsx\n- apps/ui/src/routes/register.tsx\n- apps/ui/src/hooks/use-auth.ts\n- apps/ui/src/store/auth-store.ts",
"status": "backlog",
"priority": 1,
"complexity": "moderate",
"dependencies": ["phase-3-auth-routes"],
"model": "claude-sonnet",
"planningMode": "spec"
}
```
---
## Parallel vs Sequential Execution
### Features that CAN run in parallel
- Different areas of codebase with no shared files
- Independent bug fixes
- Documentation updates
- UI components that don't share state
- Separate service implementations
### Features that MUST run sequentially
- Type definitions before implementations
- Backend before frontend (if frontend calls backend)
- Database schema before data access
- Shared utilities before consumers
### Expressing Dependencies
```json
{
"id": "feature-frontend",
"dependencies": ["feature-types", "feature-backend"]
}
```
Features with dependencies won't start until all dependencies are completed.
---
## Model Selection Guide
| Complexity | Recommended Model | Thinking Level | Planning Mode |
| --------------------- | ----------------------------- | -------------- | ------------- |
| Simple (< 1 hour) | claude-haiku or claude-sonnet | none | none |
| Moderate (1-4 hours) | claude-sonnet | none or low | spec |
| Complex (> 4 hours) | claude-opus | medium or high | spec or full |
| Critical/Architecture | claude-opus | ultrathink | full |
---
## Directory Structure
```
.automaker/
└── features/
├── phase-1-foundation/
│ └── feature.json
├── phase-2-backend/
│ └── feature.json
├── phase-3-api/
│ └── feature.json
└── phase-4-frontend/
└── feature.json
```
Each feature gets its own directory. The directory name should match the feature ID.
---
## Automation Script
Create features programmatically with this pattern:
```bash
#!/bin/bash
# create-feature.sh
FEATURE_ID=$1
TITLE=$2
DESCRIPTION=$3
PRIORITY=${4:-1}
mkdir -p ".automaker/features/$FEATURE_ID"
cat > ".automaker/features/$FEATURE_ID/feature.json" << EOF
{
"id": "$FEATURE_ID",
"category": "Core",
"title": "$TITLE",
"description": "$DESCRIPTION",
"status": "backlog",
"priority": $PRIORITY,
"complexity": "moderate",
"dependencies": [],
"createdAt": "$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")",
"updatedAt": "$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")",
"model": "claude-sonnet",
"planningMode": "spec"
}
EOF
echo "Created feature: $FEATURE_ID"
```
---
## Best Practices
### DO
- Keep descriptions detailed but focused
- Include specific file paths when known
- Use GIVEN/WHEN/THEN format for acceptance criteria
- Set realistic complexity estimates
- Define clear dependencies between phases
- Use `spec` planning mode for moderate+ complexity
- Include edge cases in descriptions
### DON'T
- Create features that are too large (> 8 hours)
- Leave descriptions vague ("make it better")
- Skip dependency definitions
- Use `ultrathink` for simple tasks (wastes tokens)
- Create circular dependencies
- Put multiple unrelated changes in one feature
- Put values in `imagePaths` or `textFilePaths` (must be empty `[]`, Automaker populates them)
---
## Validation Checklist
Before running features, verify:
- [ ] Each feature has a unique ID
- [ ] All dependencies exist and are spelled correctly
- [ ] No circular dependencies
- [ ] Priorities are assigned meaningfully
- [ ] Complex features have appropriate model/thinking level
- [ ] Descriptions include enough context for implementation
- [ ] File paths match actual project structure
---
## Quick Reference: Status Flow
```
backlog → pending → running → completed → verified
↘ failed
↘ waiting_approval → completed
```
**Important:** Features must start in `backlog` status to be executable by Automaker. The system moves them through the pipeline automatically.
---
## Template: New Feature
Copy and customize:
```json
{
"id": "feature-CHANGE-ME",
"category": "Core",
"title": "CHANGE ME: Feature Title",
"description": "## Overview\nBrief description.\n\n## Requirements\n- Requirement 1\n- Requirement 2\n\n## Files\n- path/to/file.ts",
"status": "backlog",
"priority": 1,
"complexity": "moderate",
"dependencies": [],
"createdAt": "2026-01-23T00:00:00.000Z",
"updatedAt": "2026-01-23T00:00:00.000Z",
"imagePaths": [],
"textFilePaths": [],
"model": "claude-sonnet",
"planningMode": "spec",
"skipTests": false,
"workMode": "auto"
}
```

View File

@@ -145,6 +145,7 @@ export { DEFAULT_PROMPT_CUSTOMIZATION } from './prompts.js';
// Settings types and constants
export type {
ThemeMode,
SidebarStyle,
PlanningMode,
ThinkingLevel,
ServerLogLevel,

View File

@@ -78,6 +78,14 @@ export type ServerLogLevel = 'error' | 'warn' | 'info' | 'debug';
/** ThinkingLevel - Extended thinking levels for Claude models (reasoning intensity) */
export type ThinkingLevel = 'none' | 'low' | 'medium' | 'high' | 'ultrathink';
/**
* SidebarStyle - Sidebar layout style options
*
* - 'unified': Single sidebar with integrated project dropdown (default, modern)
* - 'discord': Two sidebars - narrow project switcher + expandable navigation sidebar (classic)
*/
export type SidebarStyle = 'unified' | 'discord';
/**
* Thinking token budget mapping based on Claude SDK documentation.
* @see https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking
@@ -836,6 +844,10 @@ export interface GlobalSettings {
// UI State Preferences
/** Whether sidebar is currently open */
sidebarOpen: boolean;
/** Sidebar layout style ('unified' = modern single sidebar, 'discord' = classic two-sidebar layout) */
sidebarStyle: SidebarStyle;
/** Collapsed state of sidebar navigation sections (key: section label, value: is collapsed) */
collapsedNavSections?: Record<string, boolean>;
/** Whether chat history panel is open */
chatHistoryOpen: boolean;
@@ -1314,6 +1326,8 @@ export const DEFAULT_GLOBAL_SETTINGS: GlobalSettings = {
skipClaudeSetup: false,
theme: 'dark',
sidebarOpen: true,
sidebarStyle: 'unified',
collapsedNavSections: {},
chatHistoryOpen: false,
maxConcurrency: DEFAULT_MAX_CONCURRENCY,
defaultSkipTests: true,