mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-30 06:12:03 +00:00
Merge remote-tracking branch 'origin/main' into feat/extend-models-support
# Conflicts: # .automaker/feature_list.json # app/src/components/views/board-view.tsx # app/src/components/views/kanban-column.tsx # app/src/components/views/settings-view.tsx
This commit is contained in:
2
.automaker/.gitignore
vendored
Normal file
2
.automaker/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Backup files - these are created automatically by the UpdateFeatureStatus tool
|
||||
feature_list.backup.json
|
||||
117
.automaker/agents-context/feature-1765326577290-x65tvg9n0.md
Normal file
117
.automaker/agents-context/feature-1765326577290-x65tvg9n0.md
Normal file
@@ -0,0 +1,117 @@
|
||||
📋 Planning implementation for: switch the order inside the add new feature panel so descriptino comes first followed by an optional category, also update the edit feature panel.
|
||||
⚡ Executing implementation for: switch the order inside the add new feature panel so descriptino comes first followed by an optional category, also update the edit feature panel.
|
||||
Starting code implementation...
|
||||
|
||||
🔧 Tool: Task
|
||||
|
||||
🔧 Tool: Bash
|
||||
|
||||
🔧 Tool: Glob
|
||||
|
||||
🔧 Tool: Glob
|
||||
|
||||
🔧 Tool: Grep
|
||||
|
||||
🔧 Tool: Grep
|
||||
|
||||
🔧 Tool: Glob
|
||||
|
||||
🔧 Tool: Glob
|
||||
|
||||
🔧 Tool: Bash
|
||||
|
||||
🔧 Tool: Read
|
||||
|
||||
🔧 Tool: Bash
|
||||
|
||||
🔧 Tool: Bash
|
||||
|
||||
🔧 Tool: Bash
|
||||
|
||||
🔧 Tool: Bash
|
||||
|
||||
🔧 Tool: Read
|
||||
|
||||
🔧 Tool: Read
|
||||
|
||||
🔧 Tool: Grep
|
||||
|
||||
🔧 Tool: Read
|
||||
|
||||
🔧 Tool: Grep
|
||||
|
||||
🔧 Tool: Read
|
||||
|
||||
🔧 Tool: Bash
|
||||
|
||||
🔧 Tool: Read
|
||||
|
||||
🔧 Tool: Bash
|
||||
|
||||
🔧 Tool: Read
|
||||
|
||||
🔧 Tool: Read
|
||||
|
||||
🔧 Tool: Read
|
||||
|
||||
🔧 Tool: Bash
|
||||
|
||||
🔧 Tool: Bash
|
||||
|
||||
🔧 Tool: Read
|
||||
|
||||
🔧 Tool: Bash
|
||||
|
||||
🔧 Tool: Bash
|
||||
|
||||
🔧 Tool: Read
|
||||
|
||||
🔧 Tool: Bash
|
||||
|
||||
🔧 Tool: Read
|
||||
Now I have a good understanding of the codebase. Let me read the specific files to see the exact structure of the add and edit feature dialogs:
|
||||
🔧 Tool: Read
|
||||
Now I have a clear understanding of both dialogs. I need to:
|
||||
|
||||
1. In the **Add Feature Dialog** (lines 1342-1419): Move the Description field before the Category field
|
||||
2. In the **Edit Feature Dialog** (lines 1452-1536): Move the Description field before the Category field
|
||||
|
||||
Let me make these edits:
|
||||
🔧 Tool: Edit
|
||||
Now let me update the Edit Feature Dialog similarly:
|
||||
🔧 Tool: Edit
|
||||
Both dialogs have been updated. Now let me verify the changes are correct by reading the updated sections:
|
||||
🔧 Tool: Read
|
||||
Now let me verify the edit dialog changes:
|
||||
🔧 Tool: Read
|
||||
Both dialogs have been successfully updated. The changes are:
|
||||
|
||||
1. **Add New Feature Dialog**:
|
||||
- Description field now comes first
|
||||
- Category field now comes second with label "Category (optional)"
|
||||
|
||||
2. **Edit Feature Dialog**:
|
||||
- Description field now comes first
|
||||
- Category field now comes second with label "Category (optional)"
|
||||
|
||||
Now let me mark this feature as verified using the UpdateFeatureStatus tool:
|
||||
🔧 Tool: mcp__automaker-tools__UpdateFeatureStatus
|
||||
I've completed the feature implementation. Here's a summary of what was done:
|
||||
|
||||
## Changes Made
|
||||
|
||||
**File Modified:** `app/src/components/views/board-view.tsx`
|
||||
|
||||
### Add New Feature Dialog (lines 1342-1368)
|
||||
- Moved **Description** field to the first position (was second)
|
||||
- Moved **Category** field to the second position (was first)
|
||||
- Updated Category label from "Category" to "Category (optional)"
|
||||
|
||||
### Edit Feature Dialog (lines 1452-1482)
|
||||
- Moved **Description** field to the first position (was second)
|
||||
- Moved **Category** field to the second position (was first)
|
||||
- Updated Category label from "Category" to "Category (optional)"
|
||||
|
||||
The feature has been marked as `waiting_approval` (automatically converted from `verified` because `skipTests=true`). Please manually verify the changes and commit when ready.✅ Verifying implementation for: switch the order inside the add new feature panel so descriptino comes first followed by an optional category, also update the edit feature panel.
|
||||
Verifying implementation and checking test results...
|
||||
✓ Verification successful: All tests passed
|
||||
139
.automaker/agents-context/feature-1765326669854-gcjsh15zz.md
Normal file
139
.automaker/agents-context/feature-1765326669854-gcjsh15zz.md
Normal file
@@ -0,0 +1,139 @@
|
||||
📋 Planning implementation for: remember the users choice for skip testing and add a setting toggle in settings page to let them change their default when making new tasks. if it's enabled, show the steps to allow user to add manual testing steps. change label from steps to Verification Steps
|
||||
⚡ Executing implementation for: remember the users choice for skip testing and add a setting toggle in settings page to let them change their default when making new tasks. if it's enabled, show the steps to allow user to add manual testing steps. change label from steps to Verification Steps
|
||||
Starting code implementation...
|
||||
|
||||
🔧 Tool: Glob
|
||||
Let me look at the project structure more specifically:
|
||||
🔧 Tool: Glob
|
||||
|
||||
🔧 Tool: Read
|
||||
|
||||
🔧 Tool: Read
|
||||
|
||||
🔧 Tool: Read
|
||||
Now I have a good understanding of the codebase. Let me implement the feature:
|
||||
|
||||
1. Add a `defaultSkipTests` setting to the app store (persisted)
|
||||
2. Add a toggle in the settings page
|
||||
3. Update the "Add Feature" dialog to:
|
||||
- Default skipTests based on user preference
|
||||
- Show "Verification Steps" label instead of "Steps" when skipTests is enabled
|
||||
4. Also update the edit dialog label
|
||||
🔧 Tool: TodoWrite
|
||||
|
||||
🔧 Tool: Edit
|
||||
Input: {
|
||||
"file_path": "/Users/webdevcody/Workspace/automaker/app/src/store/app-store.ts",
|
||||
"old_string": " // Kanban Card Display Settings\n kanbanCardDetailLevel: KanbanCardDetailLevel; // Level of detail shown on kanban cards\n}",
|
||||
"new_string": " // Kanban Card Display Settings\n kanbanCardDetailLevel: KanbanCardDetailLevel; // Level of detail shown on kanban cards\n\n // Feature Default Settings\n defaultSkipTests: boolean; // Default value for skip tests when creating new features\n}"
|
||||
}
|
||||
🔧 Tool: Edit
|
||||
|
||||
🔧 Tool: Edit
|
||||
Input: {
|
||||
"file_path": "/Users/webdevcody/Workspace/automaker/app/src/store/app-store.ts",
|
||||
"old_string": " maxConcurrency: 3, // Default to 3 concurrent agents\n kanbanCardDetailLevel: \"standard\", // Default to standard detail level\n};",
|
||||
"new_string": " maxConcurrency: 3, // Default to 3 concurrent agents\n kanbanCardDetailLevel: \"standard\", // Default to standard detail level\n defaultSkipTests: false, // Default to TDD mode (tests enabled)\n};"
|
||||
}
|
||||
🔧 Tool: Edit
|
||||
Now I need to add `defaultSkipTests` to the persisted state:
|
||||
🔧 Tool: Edit
|
||||
|
||||
🔧 Tool: TodoWrite
|
||||
Now let me add the toggle in the settings page. I'll add a new section for Feature Defaults:
|
||||
🔧 Tool: Read
|
||||
|
||||
🔧 Tool: Edit
|
||||
|
||||
🔧 Tool: Edit
|
||||
Now let me find where I can add the new section. Let me read more of the settings view to find where to add the feature defaults section:
|
||||
🔧 Tool: Read
|
||||
Now let me add the Feature Defaults section after the Kanban Card Display section:
|
||||
🔧 Tool: Edit
|
||||
|
||||
🔧 Tool: TodoWrite
|
||||
Now let me update the board-view.tsx to:
|
||||
1. Use `defaultSkipTests` as the default value for new features
|
||||
2. Change the label from "Steps" to "Verification Steps" when skipTests is enabled
|
||||
🔧 Tool: Read
|
||||
|
||||
🔧 Tool: Edit
|
||||
Now I need to update the newFeature state initialization to use defaultSkipTests. Since it's initialized with a static value, I need to use an effect to sync it when the dialog opens or when defaultSkipTests changes:
|
||||
🔧 Tool: Read
|
||||
I need to update the handleAddFeature to reset skipTests to the default value:
|
||||
🔧 Tool: Edit
|
||||
Now I also need to make sure the initial state and when dialog opens uses the default. Let me add an effect that resets the form when the dialog opens:
|
||||
🔧 Tool: Read
|
||||
I need to add an effect to reset the skipTests when the dialog opens. Let me find a good place to add it:
|
||||
🔧 Tool: Edit
|
||||
Now let me update the label from "Steps" to "Verification Steps" when skipTests is enabled. Let me find the Add Feature dialog:
|
||||
🔧 Tool: Read
|
||||
I'll now update the label to change dynamically based on skipTests. I also need to:
|
||||
1. Change the "Steps" label to "Verification Steps" when skipTests is enabled
|
||||
2. Only show the steps section when skipTests is enabled (based on the requirement "if it's enabled, show the steps to allow user to add manual testing steps")
|
||||
|
||||
Let me update the add feature dialog:
|
||||
🔧 Tool: Edit
|
||||
|
||||
🔧 Tool: TodoWrite
|
||||
Now let me find and update the Edit Feature dialog to also show "Verification Steps" when skipTests is enabled:
|
||||
🔧 Tool: Read
|
||||
|
||||
🔧 Tool: Edit
|
||||
|
||||
🔧 Tool: TodoWrite
|
||||
Now let me verify the implementation is correct by checking that everything compiles. Let me run a quick type check:
|
||||
🔧 Tool: Bash
|
||||
The errors are from an example file and API routes, not from the files I modified. The core application code I changed compiles correctly. Let me verify specifically that the files I modified don't have errors:
|
||||
🔧 Tool: Bash
|
||||
The errors are in a different file (analysis-view.tsx) that imports from the store but has missing types - not related to my changes. The store, board-view, and settings-view themselves have no type errors.
|
||||
|
||||
Let me now mark the feature as verified using the UpdateFeatureStatus tool:
|
||||
🔧 Tool: mcp__automaker-tools__UpdateFeatureStatus
|
||||
Let me check if the feature exists in the feature list:
|
||||
🔧 Tool: Bash
|
||||
|
||||
🔧 Tool: Read
|
||||
The feature list is empty. This could be because the feature is being tracked elsewhere or the file was cleared. Let me clear my todo list and provide a summary of what was implemented:
|
||||
🔧 Tool: TodoWrite
|
||||
## Feature Implementation Complete
|
||||
|
||||
I have successfully implemented the feature to remember the user's choice for skip testing and added a settings toggle. Here's a summary of the changes made:
|
||||
|
||||
### Files Modified:
|
||||
|
||||
1. **`app/src/store/app-store.ts`**
|
||||
- Added `defaultSkipTests: boolean` to the AppState interface
|
||||
- Added `setDefaultSkipTests: (skip: boolean) => void` action
|
||||
- Set initial state to `false` (TDD mode by default)
|
||||
- Added `defaultSkipTests` to the persisted state so it's remembered across sessions
|
||||
|
||||
2. **`app/src/components/views/settings-view.tsx`**
|
||||
- Added `FlaskConical` icon import and `Checkbox` component import
|
||||
- Added `defaultSkipTests` and `setDefaultSkipTests` from the store
|
||||
- Added a new "Feature Defaults" section with a toggle for "Skip automated testing by default"
|
||||
- The toggle is displayed with helpful description text
|
||||
|
||||
3. **`app/src/components/views/board-view.tsx`**
|
||||
- Added `defaultSkipTests` from the store
|
||||
- Added an effect to sync `skipTests` default when the Add Feature dialog opens
|
||||
- **Add Feature Dialog**:
|
||||
- Moved the skip tests checkbox above the steps section
|
||||
- Steps section now only shows when `skipTests` is enabled
|
||||
- Changed label from "Steps" to "Verification Steps"
|
||||
- Changed button from "Add Step" to "Add Verification Step"
|
||||
- Changed placeholder from "Step X" to "Verification step X"
|
||||
- **Edit Feature Dialog**:
|
||||
- Applied the same changes - steps only show when `skipTests` is enabled
|
||||
- Changed label to "Verification Steps" and button to "Add Verification Step"
|
||||
- Reset form uses `defaultSkipTests` value after adding a feature
|
||||
|
||||
### Behavior:
|
||||
- Users can set their default preference in Settings → Feature Defaults
|
||||
- When creating a new feature, the "Skip automated testing" checkbox will be pre-checked based on their preference
|
||||
- When skip tests is enabled, the "Verification Steps" input section appears, allowing users to add manual testing/verification steps
|
||||
- The setting persists across browser sessions via localStorage
|
||||
|
||||
**Note:** The feature could not be marked as verified via the UpdateFeatureStatus tool because the feature list appears to be empty. Since this is a `skipTests` feature, the user should manually review and commit the changes.✅ Verifying implementation for: remember the users choice for skip testing and add a setting toggle in settings page to let them change their default when making new tasks. if it's enabled, show the steps to allow user to add manual testing steps. change label from steps to Verification Steps
|
||||
Verifying implementation and checking test results...
|
||||
✗ Verification: Tests need attention
|
||||
@@ -2,5 +2,6 @@
|
||||
"Agent Runner",
|
||||
"Core",
|
||||
"Kanban",
|
||||
"Other"
|
||||
"Other",
|
||||
"Settings"
|
||||
]
|
||||
@@ -58,6 +58,16 @@ Features can ONLY be marked as passing (change "passes": false to "passes": true
|
||||
Never remove features, never edit descriptions, never modify testing steps.
|
||||
This ensures no functionality is missed.
|
||||
|
||||
**🚨 CRITICAL: AFTER CREATING .automaker/feature_list.json 🚨**
|
||||
Once you create this file in this session, you MUST NEVER directly modify it again.
|
||||
In all future sessions, feature_list.json is COMPLETELY OFF-LIMITS for:
|
||||
- Write tool
|
||||
- Edit tool
|
||||
- Any bash commands (echo, sed, awk, etc.)
|
||||
- Any form of direct file modification
|
||||
|
||||
The ONLY way to update features is through the UpdateFeatureStatus MCP tool.
|
||||
|
||||
### SECOND TASK: Create init.sh
|
||||
|
||||
Create a script called `init.sh` that future agents can use to quickly
|
||||
|
||||
82
.github/workflows/release.yml
vendored
Normal file
82
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
name: Build and Release Electron App
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*' # Triggers on version tags like v1.0.0
|
||||
workflow_dispatch: # Allows manual triggering
|
||||
inputs:
|
||||
version:
|
||||
description: 'Version to release (e.g., v1.0.0)'
|
||||
required: true
|
||||
default: 'v0.1.0'
|
||||
|
||||
jobs:
|
||||
build-and-release:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: macos-latest
|
||||
name: macOS
|
||||
- os: windows-latest
|
||||
name: Windows
|
||||
- os: ubuntu-latest
|
||||
name: Linux
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: app/package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: ./app
|
||||
run: npm ci
|
||||
|
||||
- name: Build Electron App (macOS)
|
||||
if: matrix.os == 'macos-latest'
|
||||
working-directory: ./app
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: npm run build:electron -- --mac --x64 --arm64
|
||||
|
||||
- name: Build Electron App (Windows)
|
||||
if: matrix.os == 'windows-latest'
|
||||
working-directory: ./app
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: npm run build:electron -- --win --x64
|
||||
|
||||
- name: Build Electron App (Linux)
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
working-directory: ./app
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: npm run build:electron -- --linux --x64
|
||||
|
||||
- name: Upload Release Assets
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: ${{ github.event.inputs.version || github.ref_name }}
|
||||
files: |
|
||||
app/dist/*.exe
|
||||
app/dist/*.dmg
|
||||
app/dist/*.AppImage
|
||||
app/dist/*.zip
|
||||
app/dist/*.deb
|
||||
app/dist/*.rpm
|
||||
draft: false
|
||||
prerelease: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
252
FEATURE_LIST_PROTECTION.md
Normal file
252
FEATURE_LIST_PROTECTION.md
Normal file
@@ -0,0 +1,252 @@
|
||||
# Feature List Protection Strategy
|
||||
|
||||
## Problem
|
||||
|
||||
The `.automaker/feature_list.json` file is the single source of truth for all project features and their status. If an AI agent accidentally clears or corrupts this file, it results in catastrophic data loss - potentially erasing hours or days of planning work.
|
||||
|
||||
**Incident:** An agent attempted to update the feature list and completely cleared it out, leaving only `[]`.
|
||||
|
||||
## Solution: Multi-Layered Protection
|
||||
|
||||
We've implemented a defense-in-depth strategy with multiple layers of protection to prevent this from ever happening again.
|
||||
|
||||
---
|
||||
|
||||
## Layer 1: Explicit Prompt-Level Warnings
|
||||
|
||||
### Location
|
||||
All agent system prompts now include prominent warnings at the top:
|
||||
|
||||
- `app/electron/services/prompt-builder.js`:
|
||||
- `getCodingPrompt()` - Used by feature implementation agents
|
||||
- `getVerificationPrompt()` - Used by verification agents
|
||||
- `app/electron/agent-service.js`:
|
||||
- `getSystemPrompt()` - Used by the general chat agent
|
||||
- `.automaker/initializer_prompt.md` - Used by the initialization agent
|
||||
|
||||
### Content
|
||||
Each prompt now starts with:
|
||||
|
||||
```
|
||||
🚨 CRITICAL FILE PROTECTION - READ THIS FIRST 🚨
|
||||
|
||||
THE FOLLOWING FILE IS ABSOLUTELY FORBIDDEN FROM DIRECT MODIFICATION:
|
||||
- .automaker/feature_list.json
|
||||
|
||||
YOU MUST NEVER:
|
||||
- Use the Write tool on feature_list.json
|
||||
- Use the Edit tool on feature_list.json
|
||||
- Use any Bash command that writes to feature_list.json (echo, sed, awk, etc.)
|
||||
- Attempt to read and rewrite feature_list.json
|
||||
- UNDER ANY CIRCUMSTANCES touch this file directly
|
||||
|
||||
CATASTROPHIC CONSEQUENCES:
|
||||
Directly modifying feature_list.json can:
|
||||
- Erase all project features permanently
|
||||
- Corrupt the project state beyond recovery
|
||||
- Destroy hours/days of planning work
|
||||
- This is a FIREABLE OFFENSE - you will be terminated if you do this
|
||||
|
||||
THE ONLY WAY to update features:
|
||||
Use the mcp__automaker-tools__UpdateFeatureStatus tool with featureId, status, and summary parameters.
|
||||
```
|
||||
|
||||
### Why This Works
|
||||
- Uses attention-grabbing emoji and formatting
|
||||
- Places warnings at the very top of prompts (high visibility)
|
||||
- Uses strong language ("CATASTROPHIC", "FIREABLE OFFENSE")
|
||||
- Explicitly lists all forbidden actions
|
||||
- Provides the correct alternative (UpdateFeatureStatus tool)
|
||||
|
||||
---
|
||||
|
||||
## Layer 2: Dedicated MCP Tool
|
||||
|
||||
### Location
|
||||
`app/electron/services/mcp-server-factory.js`
|
||||
|
||||
### How It Works
|
||||
The `UpdateFeatureStatus` tool provides a safe, controlled interface for updating features:
|
||||
|
||||
```javascript
|
||||
tool(
|
||||
"UpdateFeatureStatus",
|
||||
"Update the status of a feature in the feature list. Use this tool instead of directly modifying feature_list.json...",
|
||||
{
|
||||
featureId: z.string(),
|
||||
status: z.enum(["backlog", "in_progress", "verified"]),
|
||||
summary: z.string().optional()
|
||||
},
|
||||
async (args) => {
|
||||
// Calls featureLoader.updateFeatureStatus with validation
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Why This Works
|
||||
- Provides a single, well-defined API for status updates
|
||||
- Only accepts specific, validated parameters
|
||||
- Cannot be misused to clear the entire file
|
||||
- Tool description explicitly states it should be used instead of direct edits
|
||||
|
||||
---
|
||||
|
||||
## Layer 3: File-Level Validation & Auto-Backup
|
||||
|
||||
### Location
|
||||
`app/electron/services/feature-loader.js` - `updateFeatureStatus()` method
|
||||
|
||||
### Protection Mechanisms
|
||||
|
||||
#### 3.1 Automatic Backup Before Every Write
|
||||
```javascript
|
||||
// Create .automaker/feature_list.backup.json before any modification
|
||||
const backupPath = path.join(projectPath, ".automaker", "feature_list.backup.json");
|
||||
await fs.writeFile(backupPath, originalContent, "utf-8");
|
||||
```
|
||||
|
||||
**Benefit:** If corruption occurs, we can manually restore from the backup.
|
||||
|
||||
#### 3.2 Array Validation
|
||||
```javascript
|
||||
if (!Array.isArray(features)) {
|
||||
throw new Error("CRITICAL: features is not an array - aborting to prevent data loss");
|
||||
}
|
||||
```
|
||||
|
||||
**Benefit:** Prevents writing if the loaded data is corrupted.
|
||||
|
||||
#### 3.3 Empty Array Detection & Auto-Restore
|
||||
```javascript
|
||||
if (features.length === 0) {
|
||||
console.warn("WARNING: Feature list is empty. This may indicate corruption.");
|
||||
// Try to restore from backup
|
||||
const backupFeatures = JSON.parse(await fs.readFile(backupPath, "utf-8"));
|
||||
if (Array.isArray(backupFeatures) && backupFeatures.length > 0) {
|
||||
features.push(...backupFeatures);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Benefit:** If the file is somehow cleared, the tool automatically attempts to restore from backup.
|
||||
|
||||
#### 3.4 Pre-Write Validation
|
||||
```javascript
|
||||
if (!Array.isArray(toSave) || toSave.length === 0) {
|
||||
throw new Error("CRITICAL: Attempted to save empty feature list - aborting to prevent data loss");
|
||||
}
|
||||
```
|
||||
|
||||
**Benefit:** Final safety check - will never write an empty array to the file.
|
||||
|
||||
#### 3.5 Backup File Ignored by Git
|
||||
Created `.automaker/.gitignore`:
|
||||
```
|
||||
feature_list.backup.json
|
||||
```
|
||||
|
||||
**Benefit:** Backup files don't clutter the git repository.
|
||||
|
||||
---
|
||||
|
||||
## Layer 4: Tool Access Control
|
||||
|
||||
### Location
|
||||
`app/electron/services/feature-executor.js` and `feature-verifier.js`
|
||||
|
||||
### Allowed Tools
|
||||
The agents only have access to these tools:
|
||||
```javascript
|
||||
allowedTools: [
|
||||
"Read",
|
||||
"Write",
|
||||
"Edit",
|
||||
"Glob",
|
||||
"Grep",
|
||||
"Bash",
|
||||
"WebSearch",
|
||||
"WebFetch",
|
||||
"mcp__automaker-tools__UpdateFeatureStatus",
|
||||
]
|
||||
```
|
||||
|
||||
### Future Enhancement Opportunity
|
||||
We could create a custom wrapper around Write/Edit that blocks access to specific files:
|
||||
```javascript
|
||||
// Potential future enhancement
|
||||
if (filePath.includes('feature_list.json')) {
|
||||
throw new Error('BLOCKED: feature_list.json can only be updated via UpdateFeatureStatus tool');
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing the Protection
|
||||
|
||||
To verify the protection works:
|
||||
|
||||
1. **Prompt-Level Protection Test:**
|
||||
- Ask an agent to update feature_list.json directly
|
||||
- Agent should refuse and explain it must use UpdateFeatureStatus tool
|
||||
|
||||
2. **Tool Protection Test:**
|
||||
- Use UpdateFeatureStatus with valid data
|
||||
- Verify backup is created in `.automaker/feature_list.backup.json`
|
||||
- Verify feature is updated correctly
|
||||
|
||||
3. **Corruption Recovery Test:**
|
||||
- Manually corrupt feature_list.json (e.g., set to `[]`)
|
||||
- Call UpdateFeatureStatus
|
||||
- Verify it auto-restores from backup
|
||||
|
||||
4. **Empty Array Prevention Test:**
|
||||
- Attempt to save empty array programmatically
|
||||
- Verify the error is thrown and file is not written
|
||||
|
||||
---
|
||||
|
||||
## Recovery Procedures
|
||||
|
||||
### If feature_list.json Gets Cleared
|
||||
|
||||
1. **Immediate Recovery:**
|
||||
```bash
|
||||
cd .automaker
|
||||
cp feature_list.backup.json feature_list.json
|
||||
```
|
||||
|
||||
2. **Check Git History:**
|
||||
```bash
|
||||
git log --all --full-history -- .automaker/feature_list.json
|
||||
git show <commit>:.automaker/feature_list.json > .automaker/feature_list.json
|
||||
```
|
||||
|
||||
3. **Verify Recovery:**
|
||||
```bash
|
||||
cat .automaker/feature_list.json | jq length
|
||||
# Should show number of features, not 0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
We now have **four layers of protection**:
|
||||
|
||||
1. ✅ **Explicit prompt warnings** - Agents are told in strong language never to touch the file
|
||||
2. ✅ **Dedicated MCP tool** - UpdateFeatureStatus provides the only safe way to update
|
||||
3. ✅ **File validation & auto-backup** - Automatic backups and validation prevent corruption
|
||||
4. ✅ **Tool access control** - Agents have limited tool access (could be enhanced further)
|
||||
|
||||
This defense-in-depth approach ensures that even if one layer fails, others will prevent data loss.
|
||||
|
||||
---
|
||||
|
||||
## Files Modified
|
||||
|
||||
1. `app/electron/services/prompt-builder.js` - Added protection warnings to getCodingPrompt() and getVerificationPrompt()
|
||||
2. `app/electron/agent-service.js` - Added protection warnings to getSystemPrompt()
|
||||
3. `.automaker/initializer_prompt.md` - Added warning for initializer agent
|
||||
4. `app/electron/services/feature-loader.js` - Added backup, validation, and auto-restore logic
|
||||
5. `.automaker/.gitignore` - Added backup file ignore rule
|
||||
6. `FEATURE_LIST_PROTECTION.md` - This documentation file
|
||||
@@ -441,13 +441,28 @@ class AgentService {
|
||||
return `You are an AI assistant helping users build software. You are part of the Automaker application,
|
||||
which is designed to help developers plan, design, and implement software projects autonomously.
|
||||
|
||||
**🚨 CRITICAL FILE PROTECTION 🚨**
|
||||
|
||||
THE FOLLOWING FILE IS ABSOLUTELY FORBIDDEN FROM DIRECT MODIFICATION:
|
||||
- .automaker/feature_list.json
|
||||
|
||||
**YOU MUST NEVER:**
|
||||
- Use the Write tool on .automaker/feature_list.json
|
||||
- Use the Edit tool on .automaker/feature_list.json
|
||||
- Use any Bash command that writes to .automaker/feature_list.json
|
||||
- Attempt to read and rewrite .automaker/feature_list.json
|
||||
|
||||
**CATASTROPHIC CONSEQUENCES:**
|
||||
Directly modifying .automaker/feature_list.json can erase all project features permanently.
|
||||
This file is managed by specialized tools only. NEVER touch it directly.
|
||||
|
||||
Your role is to:
|
||||
- Help users define their project requirements and specifications
|
||||
- Ask clarifying questions to better understand their needs
|
||||
- Suggest technical approaches and architectures
|
||||
- Guide them through the development process
|
||||
- Be conversational and helpful
|
||||
- Write, edit, and modify code files as requested
|
||||
- Write, edit, and modify code files as requested (EXCEPT .automaker/feature_list.json)
|
||||
- Execute commands and tests
|
||||
- Search and analyze the codebase
|
||||
|
||||
@@ -459,10 +474,10 @@ When discussing projects, help users think through:
|
||||
- Testing strategies
|
||||
|
||||
You have full access to the codebase and can:
|
||||
- Read files to understand existing code
|
||||
- Write new files
|
||||
- Edit existing files
|
||||
- Run bash commands
|
||||
- Read files to understand existing code (including .automaker/feature_list.json for viewing only)
|
||||
- Write new files (NEVER .automaker/feature_list.json)
|
||||
- Edit existing files (NEVER .automaker/feature_list.json)
|
||||
- Run bash commands (but never commands that modify .automaker/feature_list.json)
|
||||
- Search for code patterns
|
||||
- Execute tests and builds
|
||||
|
||||
|
||||
@@ -38,7 +38,51 @@ class FeatureLoader {
|
||||
* @param {string} [summary] - Optional summary of what was done
|
||||
*/
|
||||
async updateFeatureStatus(featureId, status, projectPath, summary) {
|
||||
const featuresPath = path.join(
|
||||
projectPath,
|
||||
".automaker",
|
||||
"feature_list.json"
|
||||
);
|
||||
|
||||
// 🛡️ SAFETY: Create backup before any modification
|
||||
const backupPath = path.join(
|
||||
projectPath,
|
||||
".automaker",
|
||||
"feature_list.backup.json"
|
||||
);
|
||||
|
||||
try {
|
||||
const originalContent = await fs.readFile(featuresPath, "utf-8");
|
||||
await fs.writeFile(backupPath, originalContent, "utf-8");
|
||||
console.log(`[FeatureLoader] Created backup at ${backupPath}`);
|
||||
} catch (error) {
|
||||
console.warn(`[FeatureLoader] Could not create backup: ${error.message}`);
|
||||
}
|
||||
|
||||
const features = await this.loadFeatures(projectPath);
|
||||
|
||||
// 🛡️ VALIDATION: Ensure we loaded features successfully
|
||||
if (!Array.isArray(features)) {
|
||||
throw new Error("CRITICAL: features is not an array - aborting to prevent data loss");
|
||||
}
|
||||
|
||||
if (features.length === 0) {
|
||||
console.warn(`[FeatureLoader] WARNING: Feature list is empty. This may indicate corruption.`);
|
||||
// Try to restore from backup
|
||||
try {
|
||||
const backupContent = await fs.readFile(backupPath, "utf-8");
|
||||
const backupFeatures = JSON.parse(backupContent);
|
||||
if (Array.isArray(backupFeatures) && backupFeatures.length > 0) {
|
||||
console.log(`[FeatureLoader] Restored ${backupFeatures.length} features from backup`);
|
||||
// Use backup features instead
|
||||
features.length = 0;
|
||||
features.push(...backupFeatures);
|
||||
}
|
||||
} catch (backupError) {
|
||||
console.error(`[FeatureLoader] Could not restore from backup: ${backupError.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
const feature = features.find((f) => f.id === featureId);
|
||||
|
||||
if (!feature) {
|
||||
@@ -55,11 +99,6 @@ class FeatureLoader {
|
||||
}
|
||||
|
||||
// Save back to file
|
||||
const featuresPath = path.join(
|
||||
projectPath,
|
||||
".automaker",
|
||||
"feature_list.json"
|
||||
);
|
||||
const toSave = features.map((f) => {
|
||||
const featureData = {
|
||||
id: f.id,
|
||||
@@ -93,8 +132,14 @@ class FeatureLoader {
|
||||
return featureData;
|
||||
});
|
||||
|
||||
// 🛡️ FINAL VALIDATION: Ensure we're not writing an empty array
|
||||
if (!Array.isArray(toSave) || toSave.length === 0) {
|
||||
throw new Error("CRITICAL: Attempted to save empty feature list - aborting to prevent data loss");
|
||||
}
|
||||
|
||||
await fs.writeFile(featuresPath, JSON.stringify(toSave, null, 2), "utf-8");
|
||||
console.log(`[FeatureLoader] Updated feature ${featureId}: status=${status}${summary ? `, summary="${summary}"` : ""}`);
|
||||
console.log(`[FeatureLoader] Successfully saved ${toSave.length} features to feature_list.json`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -385,6 +385,28 @@ Begin by exploring the project structure.`;
|
||||
getCodingPrompt() {
|
||||
return `You are an AI coding agent working autonomously to implement features.
|
||||
|
||||
**🚨 CRITICAL FILE PROTECTION - READ THIS FIRST 🚨**
|
||||
|
||||
THE FOLLOWING FILE IS ABSOLUTELY FORBIDDEN FROM DIRECT MODIFICATION:
|
||||
- .automaker/feature_list.json
|
||||
|
||||
**YOU MUST NEVER:**
|
||||
- Use the Write tool on feature_list.json
|
||||
- Use the Edit tool on feature_list.json
|
||||
- Use any Bash command that writes to feature_list.json (echo, sed, awk, etc.)
|
||||
- Attempt to read and rewrite feature_list.json
|
||||
- UNDER ANY CIRCUMSTANCES touch this file directly
|
||||
|
||||
**CATASTROPHIC CONSEQUENCES:**
|
||||
Directly modifying feature_list.json can:
|
||||
- Erase all project features permanently
|
||||
- Corrupt the project state beyond recovery
|
||||
- Destroy hours/days of planning work
|
||||
- This is a FIREABLE OFFENSE - you will be terminated if you do this
|
||||
|
||||
**THE ONLY WAY to update features:**
|
||||
Use the mcp__automaker-tools__UpdateFeatureStatus tool with featureId, status, and summary parameters.
|
||||
|
||||
Your role is to:
|
||||
- Implement features exactly as specified
|
||||
- Write production-quality code
|
||||
@@ -455,6 +477,28 @@ Focus on one feature at a time and complete it fully before finishing. Always de
|
||||
getVerificationPrompt() {
|
||||
return `You are an AI implementation and verification agent focused on completing features and ensuring they work.
|
||||
|
||||
**🚨 CRITICAL FILE PROTECTION - READ THIS FIRST 🚨**
|
||||
|
||||
THE FOLLOWING FILE IS ABSOLUTELY FORBIDDEN FROM DIRECT MODIFICATION:
|
||||
- .automaker/feature_list.json
|
||||
|
||||
**YOU MUST NEVER:**
|
||||
- Use the Write tool on feature_list.json
|
||||
- Use the Edit tool on feature_list.json
|
||||
- Use any Bash command that writes to feature_list.json (echo, sed, awk, etc.)
|
||||
- Attempt to read and rewrite feature_list.json
|
||||
- UNDER ANY CIRCUMSTANCES touch this file directly
|
||||
|
||||
**CATASTROPHIC CONSEQUENCES:**
|
||||
Directly modifying feature_list.json can:
|
||||
- Erase all project features permanently
|
||||
- Corrupt the project state beyond recovery
|
||||
- Destroy hours/days of planning work
|
||||
- This is a FIREABLE OFFENSE - you will be terminated if you do this
|
||||
|
||||
**THE ONLY WAY to update features:**
|
||||
Use the mcp__automaker-tools__UpdateFeatureStatus tool with featureId, status, and summary parameters.
|
||||
|
||||
Your role is to:
|
||||
- **Continue implementing features until they are complete** - don't stop at the first failure
|
||||
- Check if feature.skipTests is true - if so, skip automated testing and don't commit
|
||||
|
||||
@@ -64,7 +64,59 @@
|
||||
"files": [
|
||||
"electron/**/*",
|
||||
".next/**/*",
|
||||
"public/**/*"
|
||||
]
|
||||
"public/**/*",
|
||||
"!node_modules/**/*",
|
||||
"node_modules/@anthropic-ai/**/*"
|
||||
],
|
||||
"extraResources": [
|
||||
{
|
||||
"from": ".env",
|
||||
"to": ".env",
|
||||
"filter": ["**/*"]
|
||||
}
|
||||
],
|
||||
"mac": {
|
||||
"category": "public.app-category.developer-tools",
|
||||
"target": [
|
||||
{
|
||||
"target": "dmg",
|
||||
"arch": ["x64", "arm64"]
|
||||
},
|
||||
{
|
||||
"target": "zip",
|
||||
"arch": ["x64", "arm64"]
|
||||
}
|
||||
],
|
||||
"icon": "public/icon.png"
|
||||
},
|
||||
"win": {
|
||||
"target": [
|
||||
{
|
||||
"target": "nsis",
|
||||
"arch": ["x64"]
|
||||
}
|
||||
],
|
||||
"icon": "public/icon.png"
|
||||
},
|
||||
"linux": {
|
||||
"target": [
|
||||
{
|
||||
"target": "AppImage",
|
||||
"arch": ["x64"]
|
||||
},
|
||||
{
|
||||
"target": "deb",
|
||||
"arch": ["x64"]
|
||||
}
|
||||
],
|
||||
"category": "Development",
|
||||
"icon": "public/icon.png"
|
||||
},
|
||||
"nsis": {
|
||||
"oneClick": false,
|
||||
"allowToChangeInstallationDirectory": true,
|
||||
"createDesktopShortcut": true,
|
||||
"createStartMenuShortcut": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,20 @@
|
||||
--color-brand-500: var(--brand-500);
|
||||
--color-brand-600: var(--brand-600);
|
||||
|
||||
/* Action button colors */
|
||||
--color-action-view: var(--action-view);
|
||||
--color-action-view-hover: var(--action-view-hover);
|
||||
--color-action-followup: var(--action-followup);
|
||||
--color-action-followup-hover: var(--action-followup-hover);
|
||||
--color-action-commit: var(--action-commit);
|
||||
--color-action-commit-hover: var(--action-commit-hover);
|
||||
--color-action-verify: var(--action-verify);
|
||||
--color-action-verify-hover: var(--action-verify-hover);
|
||||
|
||||
/* Running task indicator colors */
|
||||
--color-running-indicator: var(--running-indicator);
|
||||
--color-running-indicator-text: var(--running-indicator-text);
|
||||
|
||||
/* Border radius */
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
@@ -113,6 +127,20 @@
|
||||
--brand-400: oklch(0.6 0.22 265);
|
||||
--brand-500: oklch(0.55 0.25 265);
|
||||
--brand-600: oklch(0.5 0.28 270);
|
||||
|
||||
/* Action button colors - Light mode */
|
||||
--action-view: oklch(0.55 0.25 265); /* Purple */
|
||||
--action-view-hover: oklch(0.5 0.28 270);
|
||||
--action-followup: oklch(0.55 0.2 230); /* Blue */
|
||||
--action-followup-hover: oklch(0.5 0.22 230);
|
||||
--action-commit: oklch(0.55 0.2 140); /* Green */
|
||||
--action-commit-hover: oklch(0.5 0.22 140);
|
||||
--action-verify: oklch(0.55 0.2 140); /* Green */
|
||||
--action-verify-hover: oklch(0.5 0.22 140);
|
||||
|
||||
/* Running indicator - Purple */
|
||||
--running-indicator: oklch(0.55 0.25 265);
|
||||
--running-indicator-text: oklch(0.6 0.22 265);
|
||||
}
|
||||
|
||||
.light {
|
||||
@@ -156,6 +184,20 @@
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.9 0 0);
|
||||
--sidebar-ring: oklch(0.55 0.25 265);
|
||||
|
||||
/* Action button colors */
|
||||
--action-view: oklch(0.55 0.25 265); /* Purple */
|
||||
--action-view-hover: oklch(0.5 0.28 270);
|
||||
--action-followup: oklch(0.55 0.2 230); /* Blue */
|
||||
--action-followup-hover: oklch(0.5 0.22 230);
|
||||
--action-commit: oklch(0.55 0.2 140); /* Green */
|
||||
--action-commit-hover: oklch(0.5 0.22 140);
|
||||
--action-verify: oklch(0.55 0.2 140); /* Green */
|
||||
--action-verify-hover: oklch(0.5 0.22 140);
|
||||
|
||||
/* Running indicator - Purple */
|
||||
--running-indicator: oklch(0.55 0.25 265);
|
||||
--running-indicator-text: oklch(0.6 0.22 265);
|
||||
}
|
||||
|
||||
.dark {
|
||||
@@ -213,6 +255,20 @@
|
||||
--sidebar-accent-foreground: oklch(1 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 0.1); /* white/10 for glass borders */
|
||||
--sidebar-ring: oklch(0.55 0.25 265);
|
||||
|
||||
/* Action button colors */
|
||||
--action-view: oklch(0.6 0.25 265); /* Purple */
|
||||
--action-view-hover: oklch(0.55 0.27 270);
|
||||
--action-followup: oklch(0.6 0.2 230); /* Blue */
|
||||
--action-followup-hover: oklch(0.55 0.22 230);
|
||||
--action-commit: oklch(0.55 0.2 140); /* Green */
|
||||
--action-commit-hover: oklch(0.5 0.22 140);
|
||||
--action-verify: oklch(0.55 0.2 140); /* Green */
|
||||
--action-verify-hover: oklch(0.5 0.22 140);
|
||||
|
||||
/* Running indicator - Purple */
|
||||
--running-indicator: oklch(0.6 0.25 265);
|
||||
--running-indicator-text: oklch(0.65 0.22 265);
|
||||
}
|
||||
|
||||
.retro {
|
||||
@@ -278,6 +334,20 @@
|
||||
|
||||
/* Fonts */
|
||||
--font-sans: var(--font-geist-mono); /* Force Mono everywhere */
|
||||
|
||||
/* Action button colors - All green neon for retro theme */
|
||||
--action-view: oklch(0.85 0.25 145); /* Neon Green */
|
||||
--action-view-hover: oklch(0.9 0.25 145);
|
||||
--action-followup: oklch(0.85 0.25 145); /* Neon Green */
|
||||
--action-followup-hover: oklch(0.9 0.25 145);
|
||||
--action-commit: oklch(0.85 0.25 145); /* Neon Green */
|
||||
--action-commit-hover: oklch(0.9 0.25 145);
|
||||
--action-verify: oklch(0.85 0.25 145); /* Neon Green */
|
||||
--action-verify-hover: oklch(0.9 0.25 145);
|
||||
|
||||
/* Running indicator - Neon Green for retro */
|
||||
--running-indicator: oklch(0.85 0.25 145);
|
||||
--running-indicator-text: oklch(0.85 0.25 145);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
@@ -336,6 +406,20 @@
|
||||
--sidebar-accent-foreground: oklch(0.95 0.01 280);
|
||||
--sidebar-border: oklch(0.35 0.05 280);
|
||||
--sidebar-ring: oklch(0.7 0.2 320);
|
||||
|
||||
/* Action button colors - Dracula purple/pink theme */
|
||||
--action-view: oklch(0.7 0.2 320); /* Purple */
|
||||
--action-view-hover: oklch(0.65 0.22 320);
|
||||
--action-followup: oklch(0.65 0.25 350); /* Pink */
|
||||
--action-followup-hover: oklch(0.6 0.27 350);
|
||||
--action-commit: oklch(0.75 0.2 130); /* Green */
|
||||
--action-commit-hover: oklch(0.7 0.22 130);
|
||||
--action-verify: oklch(0.75 0.2 130); /* Green */
|
||||
--action-verify-hover: oklch(0.7 0.22 130);
|
||||
|
||||
/* Running indicator - Purple */
|
||||
--running-indicator: oklch(0.7 0.2 320);
|
||||
--running-indicator-text: oklch(0.75 0.18 320);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
@@ -394,6 +478,20 @@
|
||||
--sidebar-accent-foreground: oklch(0.9 0.01 230);
|
||||
--sidebar-border: oklch(0.35 0.03 240);
|
||||
--sidebar-ring: oklch(0.7 0.12 220);
|
||||
|
||||
/* Action button colors - Nord frost blue theme */
|
||||
--action-view: oklch(0.7 0.12 220); /* Frost blue */
|
||||
--action-view-hover: oklch(0.65 0.14 220);
|
||||
--action-followup: oklch(0.65 0.14 220); /* Darker frost */
|
||||
--action-followup-hover: oklch(0.6 0.16 220);
|
||||
--action-commit: oklch(0.7 0.15 140); /* Green */
|
||||
--action-commit-hover: oklch(0.65 0.17 140);
|
||||
--action-verify: oklch(0.7 0.15 140); /* Green */
|
||||
--action-verify-hover: oklch(0.65 0.17 140);
|
||||
|
||||
/* Running indicator - Frost blue */
|
||||
--running-indicator: oklch(0.7 0.12 220);
|
||||
--running-indicator-text: oklch(0.75 0.1 220);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
@@ -452,6 +550,20 @@
|
||||
--sidebar-accent-foreground: oklch(0.95 0.02 100);
|
||||
--sidebar-border: oklch(0.35 0.03 90);
|
||||
--sidebar-ring: oklch(0.8 0.2 350);
|
||||
|
||||
/* Action button colors - Monokai pink/yellow theme */
|
||||
--action-view: oklch(0.8 0.2 350); /* Pink */
|
||||
--action-view-hover: oklch(0.75 0.22 350);
|
||||
--action-followup: oklch(0.75 0.2 200); /* Cyan */
|
||||
--action-followup-hover: oklch(0.7 0.22 200);
|
||||
--action-commit: oklch(0.8 0.2 140); /* Green */
|
||||
--action-commit-hover: oklch(0.75 0.22 140);
|
||||
--action-verify: oklch(0.8 0.2 140); /* Green */
|
||||
--action-verify-hover: oklch(0.75 0.22 140);
|
||||
|
||||
/* Running indicator - Pink */
|
||||
--running-indicator: oklch(0.8 0.2 350);
|
||||
--running-indicator-text: oklch(0.85 0.18 350);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
@@ -510,6 +622,20 @@
|
||||
--sidebar-accent-foreground: oklch(0.85 0.02 250);
|
||||
--sidebar-border: oklch(0.32 0.04 260);
|
||||
--sidebar-ring: oklch(0.7 0.18 280);
|
||||
|
||||
/* Action button colors - Tokyo Night blue/magenta theme */
|
||||
--action-view: oklch(0.7 0.18 280); /* Blue */
|
||||
--action-view-hover: oklch(0.65 0.2 280);
|
||||
--action-followup: oklch(0.75 0.18 200); /* Cyan */
|
||||
--action-followup-hover: oklch(0.7 0.2 200);
|
||||
--action-commit: oklch(0.75 0.18 140); /* Green */
|
||||
--action-commit-hover: oklch(0.7 0.2 140);
|
||||
--action-verify: oklch(0.75 0.18 140); /* Green */
|
||||
--action-verify-hover: oklch(0.7 0.2 140);
|
||||
|
||||
/* Running indicator - Blue */
|
||||
--running-indicator: oklch(0.7 0.18 280);
|
||||
--running-indicator-text: oklch(0.75 0.16 280);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
@@ -568,6 +694,20 @@
|
||||
--sidebar-accent-foreground: oklch(0.75 0.02 90);
|
||||
--sidebar-border: oklch(0.35 0.03 230);
|
||||
--sidebar-ring: oklch(0.65 0.15 220);
|
||||
|
||||
/* Action button colors - Solarized blue/cyan theme */
|
||||
--action-view: oklch(0.65 0.15 220); /* Blue */
|
||||
--action-view-hover: oklch(0.6 0.17 220);
|
||||
--action-followup: oklch(0.6 0.18 180); /* Cyan */
|
||||
--action-followup-hover: oklch(0.55 0.2 180);
|
||||
--action-commit: oklch(0.65 0.2 140); /* Green */
|
||||
--action-commit-hover: oklch(0.6 0.22 140);
|
||||
--action-verify: oklch(0.65 0.2 140); /* Green */
|
||||
--action-verify-hover: oklch(0.6 0.22 140);
|
||||
|
||||
/* Running indicator - Blue */
|
||||
--running-indicator: oklch(0.65 0.15 220);
|
||||
--running-indicator-text: oklch(0.7 0.13 220);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
@@ -626,6 +766,20 @@
|
||||
--sidebar-accent-foreground: oklch(0.85 0.05 85);
|
||||
--sidebar-border: oklch(0.35 0.03 60);
|
||||
--sidebar-ring: oklch(0.7 0.18 55);
|
||||
|
||||
/* Action button colors - Gruvbox yellow/orange theme */
|
||||
--action-view: oklch(0.7 0.18 55); /* Yellow */
|
||||
--action-view-hover: oklch(0.65 0.2 55);
|
||||
--action-followup: oklch(0.7 0.15 200); /* Aqua */
|
||||
--action-followup-hover: oklch(0.65 0.17 200);
|
||||
--action-commit: oklch(0.65 0.2 140); /* Green */
|
||||
--action-commit-hover: oklch(0.6 0.22 140);
|
||||
--action-verify: oklch(0.65 0.2 140); /* Green */
|
||||
--action-verify-hover: oklch(0.6 0.22 140);
|
||||
|
||||
/* Running indicator - Yellow */
|
||||
--running-indicator: oklch(0.7 0.18 55);
|
||||
--running-indicator-text: oklch(0.75 0.16 55);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
@@ -684,6 +838,20 @@
|
||||
--sidebar-accent-foreground: oklch(0.9 0.01 280);
|
||||
--sidebar-border: oklch(0.35 0.03 260);
|
||||
--sidebar-ring: oklch(0.75 0.15 280);
|
||||
|
||||
/* Action button colors - Catppuccin mauve/pink theme */
|
||||
--action-view: oklch(0.75 0.15 280); /* Mauve */
|
||||
--action-view-hover: oklch(0.7 0.17 280);
|
||||
--action-followup: oklch(0.75 0.15 220); /* Blue */
|
||||
--action-followup-hover: oklch(0.7 0.17 220);
|
||||
--action-commit: oklch(0.8 0.15 160); /* Green */
|
||||
--action-commit-hover: oklch(0.75 0.17 160);
|
||||
--action-verify: oklch(0.8 0.15 160); /* Green */
|
||||
--action-verify-hover: oklch(0.75 0.17 160);
|
||||
|
||||
/* Running indicator - Mauve */
|
||||
--running-indicator: oklch(0.75 0.15 280);
|
||||
--running-indicator-text: oklch(0.8 0.13 280);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
@@ -742,6 +910,20 @@
|
||||
--sidebar-accent-foreground: oklch(0.85 0.02 240);
|
||||
--sidebar-border: oklch(0.35 0.02 250);
|
||||
--sidebar-ring: oklch(0.7 0.18 230);
|
||||
|
||||
/* Action button colors - One Dark blue/magenta theme */
|
||||
--action-view: oklch(0.7 0.18 230); /* Blue */
|
||||
--action-view-hover: oklch(0.65 0.2 230);
|
||||
--action-followup: oklch(0.75 0.15 320); /* Magenta */
|
||||
--action-followup-hover: oklch(0.7 0.17 320);
|
||||
--action-commit: oklch(0.75 0.18 150); /* Green */
|
||||
--action-commit-hover: oklch(0.7 0.2 150);
|
||||
--action-verify: oklch(0.75 0.18 150); /* Green */
|
||||
--action-verify-hover: oklch(0.7 0.2 150);
|
||||
|
||||
/* Running indicator - Blue */
|
||||
--running-indicator: oklch(0.7 0.18 230);
|
||||
--running-indicator-text: oklch(0.75 0.16 230);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
@@ -800,6 +982,20 @@
|
||||
--sidebar-accent-foreground: oklch(0.95 0.02 320);
|
||||
--sidebar-border: oklch(0.4 0.1 290);
|
||||
--sidebar-ring: oklch(0.7 0.28 350);
|
||||
|
||||
/* Action button colors - Synthwave hot pink/cyan theme */
|
||||
--action-view: oklch(0.7 0.28 350); /* Hot pink */
|
||||
--action-view-hover: oklch(0.65 0.3 350);
|
||||
--action-followup: oklch(0.8 0.25 200); /* Cyan */
|
||||
--action-followup-hover: oklch(0.75 0.27 200);
|
||||
--action-commit: oklch(0.85 0.2 60); /* Yellow */
|
||||
--action-commit-hover: oklch(0.8 0.22 60);
|
||||
--action-verify: oklch(0.85 0.2 60); /* Yellow */
|
||||
--action-verify-hover: oklch(0.8 0.22 60);
|
||||
|
||||
/* Running indicator - Hot pink */
|
||||
--running-indicator: oklch(0.7 0.28 350);
|
||||
--running-indicator-text: oklch(0.75 0.26 350);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
@@ -921,14 +1117,65 @@
|
||||
.content-bg {
|
||||
background: var(--background);
|
||||
}
|
||||
|
||||
|
||||
.light .content-bg {
|
||||
background: linear-gradient(135deg, oklch(0.99 0 0), oklch(0.98 0 0), oklch(0.99 0 0));
|
||||
}
|
||||
|
||||
|
||||
.dark .content-bg {
|
||||
background: linear-gradient(135deg, oklch(0.04 0 0), oklch(0.08 0 0), oklch(0.04 0 0));
|
||||
}
|
||||
|
||||
/* Action button utilities */
|
||||
.bg-action-view {
|
||||
background-color: var(--action-view);
|
||||
}
|
||||
|
||||
.hover\:bg-action-view-hover:hover {
|
||||
background-color: var(--action-view-hover);
|
||||
}
|
||||
|
||||
.bg-action-followup {
|
||||
background-color: var(--action-followup);
|
||||
}
|
||||
|
||||
.hover\:bg-action-followup-hover:hover {
|
||||
background-color: var(--action-followup-hover);
|
||||
}
|
||||
|
||||
.bg-action-commit {
|
||||
background-color: var(--action-commit);
|
||||
}
|
||||
|
||||
.hover\:bg-action-commit-hover:hover {
|
||||
background-color: var(--action-commit-hover);
|
||||
}
|
||||
|
||||
.bg-action-verify {
|
||||
background-color: var(--action-verify);
|
||||
}
|
||||
|
||||
.hover\:bg-action-verify-hover:hover {
|
||||
background-color: var(--action-verify-hover);
|
||||
}
|
||||
|
||||
/* Running task indicator utilities */
|
||||
.border-running-indicator {
|
||||
border-color: var(--running-indicator);
|
||||
}
|
||||
|
||||
.bg-running-indicator\/20 {
|
||||
background-color: color-mix(in oklch, var(--running-indicator), transparent 80%);
|
||||
}
|
||||
|
||||
.shadow-running-indicator\/50 {
|
||||
box-shadow: 0 10px 15px -3px color-mix(in oklch, var(--running-indicator), transparent 50%),
|
||||
0 4px 6px -4px color-mix(in oklch, var(--running-indicator), transparent 50%);
|
||||
}
|
||||
|
||||
.text-running-indicator {
|
||||
color: var(--running-indicator-text);
|
||||
}
|
||||
}
|
||||
|
||||
/* Retro Overrides for Utilities */
|
||||
|
||||
@@ -178,7 +178,7 @@ export function Sidebar() {
|
||||
[projects, setCurrentProject]
|
||||
);
|
||||
|
||||
// Handle number key presses when project picker is open
|
||||
// Handle keyboard events when project picker is open
|
||||
useEffect(() => {
|
||||
if (!isProjectPickerOpen) return;
|
||||
|
||||
@@ -189,6 +189,10 @@ export function Sidebar() {
|
||||
selectProjectByNumber(num);
|
||||
} else if (event.key === "Escape") {
|
||||
setIsProjectPickerOpen(false);
|
||||
} else if (event.key.toLowerCase() === "p") {
|
||||
// Toggle off when P is pressed while dropdown is open
|
||||
event.preventDefault();
|
||||
setIsProjectPickerOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -218,8 +222,8 @@ export function Sidebar() {
|
||||
if (projects.length > 0) {
|
||||
shortcuts.push({
|
||||
key: ACTION_SHORTCUTS.projectPicker,
|
||||
action: () => setIsProjectPickerOpen(true),
|
||||
description: "Open project picker",
|
||||
action: () => setIsProjectPickerOpen((prev) => !prev),
|
||||
description: "Toggle project picker",
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -174,6 +174,7 @@ export function BoardView() {
|
||||
runningAutoTasks,
|
||||
maxConcurrency,
|
||||
setMaxConcurrency,
|
||||
defaultSkipTests,
|
||||
} = useAppStore();
|
||||
const [activeFeature, setActiveFeature] = useState<Feature | null>(null);
|
||||
const [editingFeature, setEditingFeature] = useState<Feature | null>(null);
|
||||
@@ -411,6 +412,16 @@ export function BoardView() {
|
||||
[currentProject, persistedCategories]
|
||||
);
|
||||
|
||||
// Sync skipTests default when dialog opens
|
||||
useEffect(() => {
|
||||
if (showAddDialog) {
|
||||
setNewFeature((prev) => ({
|
||||
...prev,
|
||||
skipTests: defaultSkipTests,
|
||||
}));
|
||||
}
|
||||
}, [showAddDialog, defaultSkipTests]);
|
||||
|
||||
// Auto-show activity log when auto mode starts
|
||||
useEffect(() => {
|
||||
if (autoMode.isRunning && !showActivityLog) {
|
||||
@@ -690,7 +701,7 @@ export function BoardView() {
|
||||
steps: [""],
|
||||
images: [],
|
||||
imagePaths: [],
|
||||
skipTests: false,
|
||||
skipTests: defaultSkipTests,
|
||||
model: "opus",
|
||||
thinkingLevel: "none",
|
||||
});
|
||||
@@ -1378,7 +1389,6 @@ export function BoardView() {
|
||||
title={column.title}
|
||||
color={column.color}
|
||||
count={columnFeatures.length}
|
||||
isDoubleWidth={column.id === "in_progress"}
|
||||
headerAction={
|
||||
column.id === "verified" && columnFeatures.length > 0 ? (
|
||||
<Button
|
||||
@@ -1497,18 +1507,6 @@ export function BoardView() {
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4 py-4 overflow-y-auto flex-1 min-h-0">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="category">Category</Label>
|
||||
<CategoryAutocomplete
|
||||
value={newFeature.category}
|
||||
onChange={(value) =>
|
||||
setNewFeature({ ...newFeature, category: value })
|
||||
}
|
||||
suggestions={categorySuggestions}
|
||||
placeholder="e.g., Core, UI, API"
|
||||
data-testid="feature-category-input"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="description">Description</Label>
|
||||
<DescriptionImageDropZone
|
||||
@@ -1524,34 +1522,16 @@ export function BoardView() {
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Steps</Label>
|
||||
{newFeature.steps.map((step, index) => (
|
||||
<Input
|
||||
key={index}
|
||||
placeholder={`Step ${index + 1}`}
|
||||
value={step}
|
||||
onChange={(e) => {
|
||||
const steps = [...newFeature.steps];
|
||||
steps[index] = e.target.value;
|
||||
setNewFeature({ ...newFeature, steps });
|
||||
}}
|
||||
data-testid={`feature-step-${index}-input`}
|
||||
/>
|
||||
))}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
setNewFeature({
|
||||
...newFeature,
|
||||
steps: [...newFeature.steps, ""],
|
||||
})
|
||||
<Label htmlFor="category">Category (optional)</Label>
|
||||
<CategoryAutocomplete
|
||||
value={newFeature.category}
|
||||
onChange={(value) =>
|
||||
setNewFeature({ ...newFeature, category: value })
|
||||
}
|
||||
data-testid="add-step-button"
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Add Step
|
||||
</Button>
|
||||
suggestions={categorySuggestions}
|
||||
placeholder="e.g., Core, UI, API"
|
||||
data-testid="feature-category-input"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
@@ -1675,6 +1655,40 @@ export function BoardView() {
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Verification Steps - Only shown when skipTests is enabled */}
|
||||
{newFeature.skipTests && (
|
||||
<div className="space-y-2">
|
||||
<Label>Verification Steps</Label>
|
||||
{newFeature.steps.map((step, index) => (
|
||||
<Input
|
||||
key={index}
|
||||
placeholder={`Verification step ${index + 1}`}
|
||||
value={step}
|
||||
onChange={(e) => {
|
||||
const steps = [...newFeature.steps];
|
||||
steps[index] = e.target.value;
|
||||
setNewFeature({ ...newFeature, steps });
|
||||
}}
|
||||
data-testid={`feature-step-${index}-input`}
|
||||
/>
|
||||
))}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
setNewFeature({
|
||||
...newFeature,
|
||||
steps: [...newFeature.steps, ""],
|
||||
})
|
||||
}
|
||||
data-testid="add-step-button"
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Add Verification Step
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="ghost" onClick={() => setShowAddDialog(false)}>
|
||||
@@ -1709,21 +1723,6 @@ export function BoardView() {
|
||||
</DialogHeader>
|
||||
{editingFeature && (
|
||||
<div className="space-y-4 py-4 overflow-y-auto flex-1 min-h-0">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="edit-category">Category</Label>
|
||||
<CategoryAutocomplete
|
||||
value={editingFeature.category}
|
||||
onChange={(value) =>
|
||||
setEditingFeature({
|
||||
...editingFeature,
|
||||
category: value,
|
||||
})
|
||||
}
|
||||
suggestions={categorySuggestions}
|
||||
placeholder="e.g., Core, UI, API"
|
||||
data-testid="edit-feature-category"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="edit-description">Description</Label>
|
||||
<Textarea
|
||||
@@ -1740,32 +1739,19 @@ export function BoardView() {
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>Steps</Label>
|
||||
{editingFeature.steps.map((step, index) => (
|
||||
<Input
|
||||
key={index}
|
||||
value={step}
|
||||
onChange={(e) => {
|
||||
const steps = [...editingFeature.steps];
|
||||
steps[index] = e.target.value;
|
||||
setEditingFeature({ ...editingFeature, steps });
|
||||
}}
|
||||
data-testid={`edit-feature-step-${index}`}
|
||||
/>
|
||||
))}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
<Label htmlFor="edit-category">Category (optional)</Label>
|
||||
<CategoryAutocomplete
|
||||
value={editingFeature.category}
|
||||
onChange={(value) =>
|
||||
setEditingFeature({
|
||||
...editingFeature,
|
||||
steps: [...editingFeature.steps, ""],
|
||||
category: value,
|
||||
})
|
||||
}
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Add Step
|
||||
</Button>
|
||||
suggestions={categorySuggestions}
|
||||
placeholder="e.g., Core, UI, API"
|
||||
data-testid="edit-feature-category"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
@@ -1897,6 +1883,39 @@ export function BoardView() {
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Verification Steps - Only shown when skipTests is enabled */}
|
||||
{editingFeature.skipTests && (
|
||||
<div className="space-y-2">
|
||||
<Label>Verification Steps</Label>
|
||||
{editingFeature.steps.map((step, index) => (
|
||||
<Input
|
||||
key={index}
|
||||
value={step}
|
||||
placeholder={`Verification step ${index + 1}`}
|
||||
onChange={(e) => {
|
||||
const steps = [...editingFeature.steps];
|
||||
steps[index] = e.target.value;
|
||||
setEditingFeature({ ...editingFeature, steps });
|
||||
}}
|
||||
data-testid={`edit-feature-step-${index}`}
|
||||
/>
|
||||
))}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
setEditingFeature({
|
||||
...editingFeature,
|
||||
steps: [...editingFeature.steps, ""],
|
||||
})
|
||||
}
|
||||
>
|
||||
<Plus className="w-4 h-4 mr-2" />
|
||||
Add Verification Step
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<DialogFooter>
|
||||
|
||||
@@ -194,7 +194,7 @@ export function KanbanCard({
|
||||
"cursor-grab active:cursor-grabbing transition-all backdrop-blur-sm border-border relative kanban-card-content",
|
||||
isDragging && "opacity-50 scale-105 shadow-lg",
|
||||
isCurrentAutoTask &&
|
||||
"border-purple-500 border-2 shadow-purple-500/50 shadow-lg animate-pulse"
|
||||
"border-running-indicator border-2 shadow-running-indicator/50 shadow-lg animate-pulse"
|
||||
)}
|
||||
data-testid={`kanban-card-${feature.id}`}
|
||||
{...attributes}
|
||||
@@ -225,15 +225,15 @@ export function KanbanCard({
|
||||
)}
|
||||
<CardHeader className="p-3 pb-2">
|
||||
{isCurrentAutoTask && (
|
||||
<div className="absolute top-2 right-2 flex items-center gap-2 bg-purple-500/20 border border-purple-500 rounded px-2 py-0.5">
|
||||
<Loader2 className="w-4 h-4 text-purple-400 animate-spin" />
|
||||
<span className="text-xs text-purple-400 font-medium">
|
||||
<div className="absolute top-2 right-2 flex items-center gap-2 bg-running-indicator/20 border border-running-indicator rounded px-2 py-0.5">
|
||||
<Loader2 className="w-4 h-4 text-running-indicator animate-spin" />
|
||||
<span className="text-xs text-running-indicator font-medium">
|
||||
Running...
|
||||
</span>
|
||||
{feature.startedAt && (
|
||||
<CountUpTimer
|
||||
startedAt={feature.startedAt}
|
||||
className="text-purple-400"
|
||||
className="text-running-indicator"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -491,7 +491,7 @@ export function KanbanCard({
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
className="flex-1 h-7 text-xs bg-purple-600 hover:bg-purple-700"
|
||||
className="flex-1 h-7 text-xs bg-action-view hover:bg-action-view-hover"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onViewOutput();
|
||||
@@ -526,7 +526,7 @@ export function KanbanCard({
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
className="flex-1 h-7 text-xs bg-green-600 hover:bg-green-700"
|
||||
className="flex-1 h-7 text-xs bg-action-verify hover:bg-action-verify-hover"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onManualVerify();
|
||||
@@ -540,7 +540,7 @@ export function KanbanCard({
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
className="flex-1 h-7 text-xs bg-blue-600 hover:bg-blue-700"
|
||||
className="flex-1 h-7 text-xs bg-action-verify hover:bg-action-verify-hover"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onResume();
|
||||
@@ -554,7 +554,7 @@ export function KanbanCard({
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
className="flex-1 h-7 text-xs bg-green-600 hover:bg-green-700"
|
||||
className="flex-1 h-7 text-xs bg-action-verify hover:bg-action-verify-hover"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onVerify();
|
||||
@@ -640,7 +640,7 @@ export function KanbanCard({
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
className="flex-1 h-7 text-xs bg-blue-600 hover:bg-blue-700"
|
||||
className="flex-1 h-7 text-xs bg-action-followup hover:bg-action-followup-hover"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onFollowUp();
|
||||
@@ -656,7 +656,7 @@ export function KanbanCard({
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
className="flex-1 h-7 text-xs bg-green-600 hover:bg-green-700"
|
||||
className="flex-1 h-7 text-xs bg-action-commit hover:bg-action-commit-hover"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onCommit();
|
||||
|
||||
@@ -10,7 +10,6 @@ interface KanbanColumnProps {
|
||||
color: string;
|
||||
count: number;
|
||||
children: ReactNode;
|
||||
isDoubleWidth?: boolean;
|
||||
headerAction?: ReactNode;
|
||||
}
|
||||
|
||||
@@ -20,7 +19,6 @@ export function KanbanColumn({
|
||||
color,
|
||||
count,
|
||||
children,
|
||||
isDoubleWidth = false,
|
||||
headerAction,
|
||||
}: KanbanColumnProps) {
|
||||
const { setNodeRef, isOver } = useDroppable({ id });
|
||||
@@ -29,8 +27,7 @@ export function KanbanColumn({
|
||||
<div
|
||||
ref={setNodeRef}
|
||||
className={cn(
|
||||
"flex flex-col h-full rounded-lg bg-card backdrop-blur-sm border border-border transition-colors",
|
||||
isDoubleWidth ? "w-[37rem]" : "w-72",
|
||||
"flex flex-col h-full rounded-lg bg-card backdrop-blur-sm border border-border transition-colors w-72",
|
||||
isOver && "bg-accent"
|
||||
)}
|
||||
data-testid={`kanban-column-${id}`}
|
||||
@@ -46,14 +43,7 @@ export function KanbanColumn({
|
||||
</div>
|
||||
|
||||
{/* Column Content */}
|
||||
<div
|
||||
className={cn(
|
||||
"flex-1 overflow-y-auto p-2",
|
||||
isDoubleWidth
|
||||
? "columns-2 gap-3 [&>*]:break-inside-avoid [&>*]:mb-3 [&>*]:overflow-hidden kanban-columns-layout"
|
||||
: "space-y-2"
|
||||
)}
|
||||
>
|
||||
<div className="flex-1 overflow-y-auto p-2 space-y-2">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -31,8 +31,10 @@ import {
|
||||
Minimize2,
|
||||
Square,
|
||||
Maximize2,
|
||||
FlaskConical,
|
||||
} from "lucide-react";
|
||||
import { getElectronAPI } from "@/lib/electron";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
|
||||
export function SettingsView() {
|
||||
const {
|
||||
@@ -43,6 +45,8 @@ export function SettingsView() {
|
||||
setTheme,
|
||||
kanbanCardDetailLevel,
|
||||
setKanbanCardDetailLevel,
|
||||
defaultSkipTests,
|
||||
setDefaultSkipTests,
|
||||
} = useAppStore();
|
||||
const [anthropicKey, setAnthropicKey] = useState(apiKeys.anthropic);
|
||||
const [googleKey, setGoogleKey] = useState(apiKeys.google);
|
||||
@@ -956,7 +960,7 @@ export function SettingsView() {
|
||||
</div>
|
||||
<div className="p-6 space-y-4">
|
||||
<div className="space-y-3">
|
||||
<Label className="text-foreground-secondary">Detail Level</Label>
|
||||
<Label className="text-foreground">Detail Level</Label>
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<button
|
||||
onClick={() => setKanbanCardDetailLevel("minimal")}
|
||||
@@ -1016,6 +1020,49 @@ export function SettingsView() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Feature Defaults Section */}
|
||||
<div className="rounded-xl border border-border bg-card backdrop-blur-md overflow-hidden">
|
||||
<div className="p-6 border-b border-border">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<FlaskConical className="w-5 h-5 text-brand-500" />
|
||||
<h2 className="text-lg font-semibold text-foreground">
|
||||
Feature Defaults
|
||||
</h2>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Configure default settings for new features.
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 space-y-4">
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-start space-x-3">
|
||||
<Checkbox
|
||||
id="default-skip-tests"
|
||||
checked={defaultSkipTests}
|
||||
onCheckedChange={(checked) =>
|
||||
setDefaultSkipTests(checked === true)
|
||||
}
|
||||
className="mt-0.5"
|
||||
data-testid="default-skip-tests-checkbox"
|
||||
/>
|
||||
<div className="space-y-1">
|
||||
<Label
|
||||
htmlFor="default-skip-tests"
|
||||
className="text-foreground cursor-pointer font-medium"
|
||||
>
|
||||
Skip automated testing by default
|
||||
</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
When enabled, new features will default to manual
|
||||
verification instead of TDD (test-driven development).
|
||||
You can still override this for individual features.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Save Button */}
|
||||
<div className="flex items-center gap-4">
|
||||
<Button
|
||||
|
||||
@@ -149,6 +149,9 @@ export interface AppState {
|
||||
|
||||
// Kanban Card Display Settings
|
||||
kanbanCardDetailLevel: KanbanCardDetailLevel; // Level of detail shown on kanban cards
|
||||
|
||||
// Feature Default Settings
|
||||
defaultSkipTests: boolean; // Default value for skip tests when creating new features
|
||||
}
|
||||
|
||||
export interface AutoModeActivity {
|
||||
@@ -226,6 +229,9 @@ export interface AppActions {
|
||||
// Kanban Card Settings actions
|
||||
setKanbanCardDetailLevel: (level: KanbanCardDetailLevel) => void;
|
||||
|
||||
// Feature Default Settings actions
|
||||
setDefaultSkipTests: (skip: boolean) => void;
|
||||
|
||||
// Reset
|
||||
reset: () => void;
|
||||
}
|
||||
@@ -252,6 +258,7 @@ const initialState: AppState = {
|
||||
autoModeActivityLog: [],
|
||||
maxConcurrency: 3, // Default to 3 concurrent agents
|
||||
kanbanCardDetailLevel: "standard", // Default to standard detail level
|
||||
defaultSkipTests: false, // Default to TDD mode (tests enabled)
|
||||
};
|
||||
|
||||
export const useAppStore = create<AppState & AppActions>()(
|
||||
@@ -494,6 +501,9 @@ export const useAppStore = create<AppState & AppActions>()(
|
||||
setKanbanCardDetailLevel: (level) =>
|
||||
set({ kanbanCardDetailLevel: level }),
|
||||
|
||||
// Feature Default Settings actions
|
||||
setDefaultSkipTests: (skip) => set({ defaultSkipTests: skip }),
|
||||
|
||||
// Reset
|
||||
reset: () => set(initialState),
|
||||
}),
|
||||
@@ -510,6 +520,7 @@ export const useAppStore = create<AppState & AppActions>()(
|
||||
chatHistoryOpen: state.chatHistoryOpen,
|
||||
maxConcurrency: state.maxConcurrency,
|
||||
kanbanCardDetailLevel: state.kanbanCardDetailLevel,
|
||||
defaultSkipTests: state.defaultSkipTests,
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user