Compare commits
368 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d0b0b3df0d | |||
| 937a60f8bd | |||
| 7a51cbd5e5 | |||
| 26beefe39d | |||
| 8416f3076a | |||
| 217249c8a3 | |||
| 8bb3df917e | |||
| 5106c6e9b2 | |||
| 3ff612742f | |||
| 7f19c2e1f3 | |||
| d98e28305d | |||
| 0b874d4b33 | |||
| 50dd10788a | |||
| f1cc5a09f2 | |||
| b005fa5c86 | |||
| d79f2da411 | |||
| 5ac566e2a2 | |||
| f49d4817b1 | |||
| 1e1d7d9b68 | |||
| e74a3dffd2 | |||
| 6ac8250b5b | |||
| a2cee6941f | |||
| d4cad80a73 | |||
| 596e95137b | |||
| d42041c5b5 | |||
| a3f87540c6 | |||
| f843811292 | |||
| 5e8c53080f | |||
| 348de6dfe7 | |||
| 0f7d6e0530 | |||
| 5ccdb6abc5 | |||
| 6ec8549f63 | |||
| 53527293cd | |||
| 02bef954f3 | |||
| f168bdc3ac | |||
| e3eec68aab | |||
| 407a0bee5e | |||
| 8f766ad16e | |||
| ced2ad479f | |||
| 05f5d3038b | |||
| 6951251b33 | |||
| 30e7536b63 | |||
| 220faf0fb4 | |||
| f96c6301f4 | |||
| ebd8340d82 | |||
| df779530e7 | |||
| 0adaddacaa | |||
| 91f7051dda | |||
| 8db71f3dfb | |||
| 772a5006d4 | |||
| d23fcd8669 | |||
| 326118bd59 | |||
| 6afcc92215 | |||
| 2c9389012e | |||
| 0d95f747f1 | |||
| fe7290a850 | |||
| 8fb5f148fe | |||
| 185d520013 | |||
| 89978edf6d | |||
| 8f1f7a769b | |||
| bdca9af3b8 | |||
| 06fc5dab10 | |||
| a960f00307 | |||
| a05216590b | |||
| 57fcc2403b | |||
| c93fe96ee2 | |||
| 6ee5a731f4 | |||
| f6601efc8a | |||
| a03fa8bc80 | |||
| 50f739dc16 | |||
| 14238788c9 | |||
| 39a08f6117 | |||
| 87e12cf627 | |||
| 4c8dfcafa7 | |||
| 17b092ba39 | |||
| 4b09b0c47e | |||
| b64faed197 | |||
| 252d4ccfd8 | |||
| 721b12753c | |||
| 757e5e04d2 | |||
| 1d1e15446d | |||
| 69d5c7323f | |||
| 9a03814e14 | |||
| c52caa6b17 | |||
| 12c8519246 | |||
| 8bcd00e4a6 | |||
| 7649a607e6 | |||
| f89e4e6c56 | |||
| b9797cbe21 | |||
| cc38a0619c | |||
| aee0ba4cc5 | |||
| 9981ee4469 | |||
| 297d380f4c | |||
| 05062562f0 | |||
| 438f6e2237 | |||
| 458d4bb97a | |||
| 10949905f7 | |||
| 9ab5a4f2cc | |||
| f0a6a0a0af | |||
| cdda3ff277 | |||
| 08aa2ff02b | |||
| 37ace0a39a | |||
| 7f0eeba366 | |||
| f82bd5b871 | |||
| f117bccbbc | |||
| 8b59375404 | |||
| 91a1e3df6c | |||
| 7f12ef0355 | |||
| 30921550df | |||
| 2b96160ab0 | |||
| 7171589002 | |||
| 2a96f855ae | |||
| 3ac3f067cc | |||
| 535d58c80f | |||
| 2ef90b980f | |||
| e6654b96c5 | |||
| 5db28fd8e8 | |||
| c1207ef7fc | |||
| 70072e4281 | |||
| ba776a3fb8 | |||
| e2b24e2e25 | |||
| 7589046bbe | |||
| e248256649 | |||
| 76c1bd7578 | |||
| bcbced24e5 | |||
| a75c0a9965 | |||
| c505d6e32c | |||
| 7d053313b5 | |||
| e9535c8dc4 | |||
| 1642719445 | |||
| 3efab867c5 | |||
| 2ca89ce7c9 | |||
| 52e12d8d2a | |||
| c5b72451af | |||
| ffd8b153a5 | |||
| ee168d317f | |||
| a335925eae | |||
| cf1ba6b57b | |||
| ce7c95cae7 | |||
| 4b6a59826e | |||
| e6058168f0 | |||
| c7dde1f979 | |||
| 15a7585f6e | |||
| c486e5ba84 | |||
| d94833a678 | |||
| 376e950bd4 | |||
| 0959e790df | |||
| e134c4cba9 | |||
| 81e1536801 | |||
| 1a7cf409eb | |||
| 5f26d3964d | |||
| 4a4ad6b1df | |||
| 5702692940 | |||
| 6a4c1b452b | |||
| b75a09c88c | |||
| 0601520e9b | |||
| 412ed0be3c | |||
| 586aa9f8c3 | |||
| bc6470f5c3 | |||
| a107ed03a3 | |||
| 679b8cd948 | |||
| cece172df6 | |||
| 1a38a06e6e | |||
| fe691066dd | |||
| 0f47961a8c | |||
| 9299ee107a | |||
| 6680ed49f6 | |||
| a3eee9285e | |||
| 01a4eb6bbf | |||
| b8a419af5a | |||
| 2b61ebbfad | |||
| 4750869526 | |||
| c9745b6669 | |||
| 08b65f315a | |||
| e1aee6a44f | |||
| b3636a5bce | |||
| 908eebfb16 | |||
| 0e6b652dd7 | |||
| 0acdba6f01 | |||
| de2eccd209 | |||
| 873cafa46f | |||
| 948db57763 | |||
| f98a13eaa0 | |||
| 24ff491d3c | |||
| a8f2d0b110 | |||
| 46d2536600 | |||
| 71535581c2 | |||
| 26725286d5 | |||
| 569e921759 | |||
| 03ccce5cc1 | |||
| 64d5170c94 | |||
| 0710c13964 | |||
| 56cedec2ae | |||
| c0c8067bc5 | |||
| db3a034d75 | |||
| 059315d6ab | |||
| 8df7ba4f16 | |||
| 2bffea842b | |||
| 99cf21e61b | |||
| da5e26b923 | |||
| c957eaa3a1 | |||
| 73d01c0103 | |||
| 41a507fe8b | |||
| e02aa597f2 | |||
| 909305c82b | |||
| 121b2b294f | |||
| c2fe3322a7 | |||
| aac6b106aa | |||
| 7f6beba3ad | |||
| 4b354e7b9f | |||
| eed5297e9d | |||
| 9a5ca8c78f | |||
| 63a1d3c138 | |||
| 3caf9cf18e | |||
| 7d351e3422 | |||
| 9dea155505 | |||
| d3cdd3a1c7 | |||
| a9d1ddb84f | |||
| 6985934825 | |||
| 4f1766b501 | |||
| 7ff326d898 | |||
| 48f7c3cc61 | |||
| 892e01d608 | |||
| 54501cbd73 | |||
| f1d578fd18 | |||
| 2e3a5d9de5 | |||
| a6dad428e9 | |||
| 3d24f8f59e | |||
| 9106038a17 | |||
| 895ed9f605 | |||
| cbe14fda9b | |||
| 4c1dd89840 | |||
| d93eefe806 | |||
| e11f5fcd50 | |||
| 8e891dfcfe | |||
| c721dc23b6 | |||
| 1a2b7a1bbb | |||
| b194c0e11f | |||
| a20b8cf12a | |||
| 0ed6afb805 | |||
| 914a09d0da | |||
| e44202a066 | |||
| 42496446bc | |||
| b0fc497583 | |||
| 462edcdd93 | |||
| affbc48cbe | |||
| d7fd1a24da | |||
| 96dd04d411 | |||
| 1d0566fce7 | |||
| 7f3cd5969d | |||
| 0ef0e1588b | |||
| efc112a313 | |||
| d33a0aaff6 | |||
| 6cff4420c9 | |||
| 12bf69def6 | |||
| 3818b4641f | |||
| 219b66dcc6 | |||
| 4e63e8559d | |||
| 2fa3c51659 | |||
| 59b091a79f | |||
| a0c775f690 | |||
| 9babdc23c1 | |||
| dc886dce44 | |||
| 32760348ae | |||
| 395ba60098 | |||
| d85a1b4f63 | |||
| 43a593c1e1 | |||
| 5078191d74 | |||
| 99850512e6 | |||
| f92fcfa075 | |||
| f201f7e3a8 | |||
| 0da4bc4a84 | |||
| a0d142ba95 | |||
| 473b04530a | |||
| e5b9488a86 | |||
| de33b2c7c3 | |||
| eb77915879 | |||
| 7e09739f17 | |||
| e6d6cea9e4 | |||
| dfb5cf9c50 | |||
| 34631c38ea | |||
| 7c327ed007 | |||
| 5f9653a961 | |||
| 2d2a8131cc | |||
| ce9c2cddc1 | |||
| 460c76de42 | |||
| 4eb66f5004 | |||
| 788b8d0e3e | |||
| 957746e09c | |||
| 36338f3546 | |||
| 36a69fcf0d | |||
| afeb54fc2c | |||
| 6aea4bb830 | |||
| 140f11fccd | |||
| 11fcdf42f3 | |||
| 87f353c3af | |||
| feb0d4e686 | |||
| 31e4e87869 | |||
| d8e57845c3 | |||
| d735c5c303 | |||
| a891225b4e | |||
| 9403230ef0 | |||
| 131ec4cb62 | |||
| 2e151acc0e | |||
| 08dc24cc99 | |||
| f00aa33cda | |||
| ddf47ae5ec | |||
| 9d9cf163ec | |||
| 3eb2eadaf1 | |||
| 56ff586c4d | |||
| e409ae825d | |||
| 01e801aace | |||
| 579ea40bd2 | |||
| bf787ade0b | |||
| 4cd7500e96 | |||
| 4bba9d1aa7 | |||
| dbe27f0157 | |||
| 1d830ba59e | |||
| 7f6456facc | |||
| 901e83ac61 | |||
| 65937e198f | |||
| b94eb6589b | |||
| c5d33cd8b1 | |||
| e08ab62acb | |||
| e8b6669d92 | |||
| d8ba532beb | |||
| 488bbfa2c9 | |||
| 2ac00a9d8f | |||
| 3b832db0ec | |||
| 43a338ca11 | |||
| 3fc1592ce6 | |||
| 142cd67c88 | |||
| d8ad17dbfa | |||
| 7c01638a81 | |||
| a5a1eb1431 | |||
| 39dc91a12c | |||
| 9a59b7ebfe | |||
| 4b242c4a64 | |||
| f9ef7eae09 | |||
| b3f48037c1 | |||
| 555a46f68d | |||
| 6b5b7146c7 | |||
| 5e56890f78 | |||
| 5f989a4774 | |||
| f90fa80278 | |||
| 50f22dad14 | |||
| f57c28e5d8 | |||
| 9144e7fa86 | |||
| 779e36fb2b | |||
| b0af2dc2a7 | |||
| 3de8928a5a | |||
| aa0f608210 | |||
| 32f17a10fb | |||
| 61184b0469 | |||
| 79d622eeb9 | |||
| a97f697762 | |||
| b6e604cfbb | |||
| c5a0331d3f | |||
| 7c24b48ed8 | |||
| bb5c744613 | |||
| 03e6e84333 | |||
| 146e32a1e4 | |||
| e8b53d5a93 | |||
| c7d571fc4a | |||
| 2f9aa64b55 | |||
| 982c5e6063 | |||
| cfeb15726e | |||
| 92998dedd6 |
@@ -0,0 +1,65 @@
|
||||
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
|
||||
|
||||
# CodeRabbit Configuration
|
||||
# Documentation: https://docs.coderabbit.ai/reference/configuration
|
||||
|
||||
language: "en-US"
|
||||
|
||||
reviews:
|
||||
# Review profile: "chill" for fewer comments, "assertive" for more thorough feedback
|
||||
profile: "assertive"
|
||||
|
||||
# Generate high-level summary in PR description
|
||||
high_level_summary: true
|
||||
|
||||
# Automatic review settings
|
||||
auto_review:
|
||||
enabled: true
|
||||
auto_incremental_review: true
|
||||
# Target branches for review (in addition to default branch)
|
||||
base_branches:
|
||||
- develop
|
||||
- "release/*"
|
||||
- "hotfix/*"
|
||||
# Skip review for PRs with these title keywords (case-insensitive)
|
||||
ignore_title_keywords:
|
||||
- "[WIP]"
|
||||
- "WIP:"
|
||||
- "DO NOT MERGE"
|
||||
# Don't review draft PRs
|
||||
drafts: false
|
||||
|
||||
# Path filters - exclude generated/vendor files
|
||||
path_filters:
|
||||
- "!**/node_modules/**"
|
||||
- "!**/.venv/**"
|
||||
- "!**/dist/**"
|
||||
- "!**/build/**"
|
||||
- "!**/*.lock"
|
||||
- "!**/package-lock.json"
|
||||
- "!**/*.min.js"
|
||||
- "!**/*.min.css"
|
||||
|
||||
# Path-specific review instructions
|
||||
path_instructions:
|
||||
- path: "apps/backend/**/*.py"
|
||||
instructions: |
|
||||
Focus on Python best practices, type hints, and async patterns.
|
||||
Check for proper error handling and security considerations.
|
||||
Verify compatibility with Python 3.12+.
|
||||
- path: "apps/frontend/**/*.{ts,tsx}"
|
||||
instructions: |
|
||||
Review React patterns and TypeScript type safety.
|
||||
Check for proper state management and component composition.
|
||||
- path: "tests/**"
|
||||
instructions: |
|
||||
Ensure tests are comprehensive and follow pytest conventions.
|
||||
Check for proper mocking and test isolation.
|
||||
|
||||
chat:
|
||||
auto_reply: true
|
||||
|
||||
knowledge_base:
|
||||
opt_out: false
|
||||
learnings:
|
||||
scope: "auto"
|
||||
@@ -0,0 +1,166 @@
|
||||
# App.tsx Refactoring Summary
|
||||
|
||||
## Overview
|
||||
Successfully refactored the monolithic App.tsx file (2,217 lines) into a well-organized, modular structure with 488 lines in the main App.tsx file - a **78% reduction** in file size.
|
||||
|
||||
## File Size Comparison
|
||||
- **Original**: 2,217 lines
|
||||
- **Refactored**: 488 lines
|
||||
- **Reduction**: 1,729 lines (78%)
|
||||
|
||||
## New Directory Structure
|
||||
|
||||
```
|
||||
src/
|
||||
├── animations/
|
||||
│ ├── constants.ts # Animation variants and transition presets
|
||||
│ └── index.ts
|
||||
├── components/
|
||||
│ ├── Avatar.tsx # Avatar and AvatarGroup components
|
||||
│ ├── Badge.tsx # Badge component with variants
|
||||
│ ├── Button.tsx # Button component with sizes and variants
|
||||
│ ├── Card.tsx # Card container component
|
||||
│ ├── Input.tsx # Input field component
|
||||
│ ├── ProgressCircle.tsx # Circular progress indicator
|
||||
│ ├── Toggle.tsx # Toggle switch component
|
||||
│ └── index.ts
|
||||
├── demo-cards/
|
||||
│ ├── CalendarCard.tsx # Calendar widget demo
|
||||
│ ├── IntegrationsCard.tsx # Integrations panel demo
|
||||
│ ├── MilestoneCard.tsx # Milestone tracking demo
|
||||
│ ├── NotificationsCard.tsx # Notifications panel demo
|
||||
│ ├── ProfileCard.tsx # User profile card demo
|
||||
│ ├── ProjectStatusCard.tsx # Project status demo
|
||||
│ ├── TeamMembersCard.tsx # Team members list demo
|
||||
│ └── index.ts
|
||||
├── theme/
|
||||
│ ├── constants.ts # Theme definitions (7 color themes)
|
||||
│ ├── ThemeSelector.tsx # Theme dropdown and mode toggle UI
|
||||
│ ├── types.ts # TypeScript interfaces for themes
|
||||
│ ├── useTheme.ts # Custom hook for theme management
|
||||
│ └── index.ts
|
||||
├── lib/
|
||||
│ └── utils.ts # Utility functions (cn helper)
|
||||
├── sections/
|
||||
│ └── (empty - ready for future section extractions)
|
||||
└── App.tsx # Main application entry point (488 lines)
|
||||
```
|
||||
|
||||
## Extracted Modules
|
||||
|
||||
### 1. Theme System (`theme/`)
|
||||
- **types.ts**: ColorTheme, Mode, ThemeConfig, ThemePreviewColors, ColorThemeDefinition
|
||||
- **constants.ts**: COLOR_THEMES array with 7 themes (default, dusk, lime, ocean, retro, neo, forest)
|
||||
- **useTheme.ts**: Custom React hook for theme state management with localStorage persistence
|
||||
- **ThemeSelector.tsx**: UI component for theme switching with dropdown and light/dark toggle
|
||||
|
||||
### 2. Base Components (`components/`)
|
||||
All reusable UI components extracted with proper TypeScript interfaces:
|
||||
- **Button**: 5 variants (primary, secondary, ghost, success, danger), 3 sizes, pill option
|
||||
- **Badge**: 6 variants (default, primary, success, warning, error, outline)
|
||||
- **Avatar**: 6 sizes (xs, sm, md, lg, xl, 2xl), with AvatarGroup for multiple avatars
|
||||
- **Card**: Container with optional padding
|
||||
- **Input**: Text input with focus states and disabled support
|
||||
- **Toggle**: Switch component with checked state
|
||||
- **ProgressCircle**: SVG-based circular progress indicator with 3 sizes
|
||||
|
||||
### 3. Demo Cards (`demo-cards/`)
|
||||
Feature showcase components demonstrating the design system:
|
||||
- **ProfileCard**: User profile with avatar, name, role, and skill badges
|
||||
- **NotificationsCard**: Notification list with actions
|
||||
- **CalendarCard**: Interactive calendar widget
|
||||
- **TeamMembersCard**: Team member list with payment integrations
|
||||
- **ProjectStatusCard**: Project progress with team avatars
|
||||
- **MilestoneCard**: Milestone tracker with progress and assignees
|
||||
- **IntegrationsCard**: Integration toggles for Slack, Google Meet, GitHub
|
||||
|
||||
### 4. Animations (`animations/`)
|
||||
- **constants.ts**: Animation variants (fadeIn, scaleIn, slideUp, slideDown, slideLeft, slideRight, pop, bounce)
|
||||
- **constants.ts**: Transition presets (instant, fast, normal, slow, spring variants, easing functions)
|
||||
|
||||
## Benefits of Refactoring
|
||||
|
||||
### 1. Improved Maintainability
|
||||
- Each component is in its own file with clear responsibility
|
||||
- Easy to locate and modify specific functionality
|
||||
- Reduced cognitive load when working with the codebase
|
||||
|
||||
### 2. Better Code Organization
|
||||
- Logical grouping of related functionality
|
||||
- Clear separation of concerns (theme, components, demos, animations)
|
||||
- Consistent file naming conventions
|
||||
|
||||
### 3. Enhanced Reusability
|
||||
- Components can be easily imported and reused
|
||||
- Type definitions are shared across modules
|
||||
- Theme system can be used independently
|
||||
|
||||
### 4. Easier Testing
|
||||
- Individual components can be tested in isolation
|
||||
- Smaller files are easier to unit test
|
||||
- Mock dependencies are simpler to manage
|
||||
|
||||
### 5. Better TypeScript Support
|
||||
- Explicit type definitions in separate files
|
||||
- Improved IDE autocomplete and IntelliSense
|
||||
- Type safety across module boundaries
|
||||
|
||||
### 6. Scalability
|
||||
- Easy to add new components without cluttering App.tsx
|
||||
- Ready for future extractions (animations section, themes section)
|
||||
- Clear pattern for organizing new features
|
||||
|
||||
## What Remains in App.tsx
|
||||
|
||||
The refactored App.tsx now only contains:
|
||||
1. Import statements for all extracted modules
|
||||
2. Main App component with:
|
||||
- Section navigation state
|
||||
- Theme hook integration
|
||||
- Header with ThemeSelector
|
||||
- Section content (overview, colors, typography, components, animations, themes)
|
||||
- Inline section rendering (can be further extracted if needed)
|
||||
|
||||
## Build Verification
|
||||
|
||||
The refactored code successfully builds with no errors:
|
||||
```
|
||||
✓ 1723 modules transformed
|
||||
✓ built in 1.38s
|
||||
```
|
||||
|
||||
All functionality remains intact with the same user experience.
|
||||
|
||||
## Future Improvements
|
||||
|
||||
The codebase is now ready for additional refactoring:
|
||||
|
||||
1. **Section Components**: Extract remaining inline sections:
|
||||
- `ColorsSection.tsx`
|
||||
- `TypographySection.tsx`
|
||||
- `ComponentsSection.tsx`
|
||||
- `AnimationsSection.tsx` (with all animation demos)
|
||||
- `ThemesSection.tsx`
|
||||
|
||||
2. **Animation Demos**: Extract individual animation demo components:
|
||||
- `HoverCardDemo`, `ButtonPressDemo`, `StaggeredListDemo`
|
||||
- `ToastDemo`, `ModalDemo`, `CounterDemo`
|
||||
- `LoadingDemo`, `DragDemo`, `ProgressAnimationDemo`
|
||||
- `IconAnimationsDemo`, `AccordionDemo`
|
||||
|
||||
3. **Utilities**: Additional helper functions as the codebase grows
|
||||
|
||||
4. **Hooks**: Extract more custom hooks for common patterns
|
||||
|
||||
5. **Types**: Centralized type definitions file if needed
|
||||
|
||||
## Migration Notes
|
||||
|
||||
- Original file backed up as `App.tsx.original` and `App.tsx.backup`
|
||||
- All imports updated to use new module structure
|
||||
- No breaking changes to external API
|
||||
- Build process remains unchanged
|
||||
|
||||
## Conclusion
|
||||
|
||||
This refactoring significantly improves code quality and maintainability while preserving all functionality. The new modular structure makes the codebase easier to understand, test, and extend.
|
||||
Generated
+2544
File diff suppressed because it is too large
Load Diff
+191
-1919
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,75 @@
|
||||
export const animationVariants = {
|
||||
// Fade animations
|
||||
fadeIn: {
|
||||
initial: { opacity: 0 },
|
||||
animate: { opacity: 1 },
|
||||
exit: { opacity: 0 }
|
||||
},
|
||||
|
||||
// Scale animations
|
||||
scaleIn: {
|
||||
initial: { opacity: 0, scale: 0.9 },
|
||||
animate: { opacity: 1, scale: 1 },
|
||||
exit: { opacity: 0, scale: 0.9 }
|
||||
},
|
||||
|
||||
// Slide animations
|
||||
slideUp: {
|
||||
initial: { opacity: 0, y: 20 },
|
||||
animate: { opacity: 1, y: 0 },
|
||||
exit: { opacity: 0, y: -20 }
|
||||
},
|
||||
|
||||
slideDown: {
|
||||
initial: { opacity: 0, y: -20 },
|
||||
animate: { opacity: 1, y: 0 },
|
||||
exit: { opacity: 0, y: 20 }
|
||||
},
|
||||
|
||||
slideLeft: {
|
||||
initial: { opacity: 0, x: 20 },
|
||||
animate: { opacity: 1, x: 0 },
|
||||
exit: { opacity: 0, x: -20 }
|
||||
},
|
||||
|
||||
slideRight: {
|
||||
initial: { opacity: 0, x: -20 },
|
||||
animate: { opacity: 1, x: 0 },
|
||||
exit: { opacity: 0, x: 20 }
|
||||
},
|
||||
|
||||
// Spring pop
|
||||
pop: {
|
||||
initial: { opacity: 0, scale: 0.5 },
|
||||
animate: {
|
||||
opacity: 1,
|
||||
scale: 1,
|
||||
transition: { type: 'spring', stiffness: 500, damping: 25 }
|
||||
},
|
||||
exit: { opacity: 0, scale: 0.5 }
|
||||
},
|
||||
|
||||
// Bounce
|
||||
bounce: {
|
||||
initial: { opacity: 0, y: -50 },
|
||||
animate: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: { type: 'spring', stiffness: 300, damping: 10 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Transition presets
|
||||
export const transitions = {
|
||||
instant: { duration: 0.05 },
|
||||
fast: { duration: 0.15 },
|
||||
normal: { duration: 0.25 },
|
||||
slow: { duration: 0.4 },
|
||||
spring: { type: 'spring' as const, stiffness: 400, damping: 25 },
|
||||
springBouncy: { type: 'spring' as const, stiffness: 300, damping: 10 },
|
||||
springSmooth: { type: 'spring' as const, stiffness: 200, damping: 20 },
|
||||
easeOut: { duration: 0.25, ease: [0, 0, 0.2, 1] as [number, number, number, number] },
|
||||
easeIn: { duration: 0.25, ease: [0.4, 0, 1, 1] as [number, number, number, number] },
|
||||
easeInOut: { duration: 0.25, ease: [0.4, 0, 0.2, 1] as [number, number, number, number] }
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './constants'
|
||||
@@ -0,0 +1,67 @@
|
||||
import React from 'react'
|
||||
import { cn } from '../lib/utils'
|
||||
|
||||
export interface AvatarProps {
|
||||
src?: string
|
||||
name?: string
|
||||
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'
|
||||
color?: string
|
||||
}
|
||||
|
||||
export function Avatar({ src, name = 'User', size = 'md', color }: AvatarProps) {
|
||||
const sizes = {
|
||||
xs: 'w-6 h-6 text-[10px]',
|
||||
sm: 'w-8 h-8 text-xs',
|
||||
md: 'w-10 h-10 text-sm',
|
||||
lg: 'w-14 h-14 text-base',
|
||||
xl: 'w-20 h-20 text-xl',
|
||||
'2xl': 'w-[120px] h-[120px] text-3xl'
|
||||
}
|
||||
|
||||
const initials = name.split(' ').map(n => n[0]).join('').slice(0, 2).toUpperCase()
|
||||
|
||||
// Default to neutral gray, can be overridden with color prop
|
||||
const bgStyle = color
|
||||
? { backgroundColor: color }
|
||||
: {}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'rounded-full flex items-center justify-center font-semibold border-2 border-(--color-surface-card) overflow-hidden',
|
||||
!color && 'bg-(--color-border-default)',
|
||||
sizes[size]
|
||||
)}
|
||||
style={bgStyle}
|
||||
>
|
||||
{src ? (
|
||||
<img src={src} alt={name} className="w-full h-full object-cover" />
|
||||
) : (
|
||||
<span className={cn(color ? 'text-white' : 'text-(--color-text-primary)')}>{initials}</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface AvatarGroupProps {
|
||||
avatars: { name: string; src?: string }[]
|
||||
max?: number
|
||||
}
|
||||
|
||||
export function AvatarGroup({ avatars, max = 4 }: AvatarGroupProps) {
|
||||
const visible = avatars.slice(0, max)
|
||||
const remaining = avatars.length - max
|
||||
|
||||
return (
|
||||
<div className="flex -space-x-2">
|
||||
{visible.map((avatar, i) => (
|
||||
<Avatar key={i} {...avatar} size="sm" />
|
||||
))}
|
||||
{remaining > 0 && (
|
||||
<div className="w-8 h-8 rounded-full bg-(--color-background-secondary) flex items-center justify-center text-xs font-medium text-(--color-text-secondary) border-2 border-(--color-surface-card)">
|
||||
+{remaining}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import React from 'react'
|
||||
import { cn } from '../lib/utils'
|
||||
|
||||
export interface BadgeProps {
|
||||
children: React.ReactNode
|
||||
variant?: 'default' | 'primary' | 'success' | 'warning' | 'error' | 'outline'
|
||||
}
|
||||
|
||||
export function Badge({ children, variant = 'default' }: BadgeProps) {
|
||||
const variants = {
|
||||
default: 'bg-(--color-background-secondary) text-(--color-text-secondary)',
|
||||
primary: 'bg-(--color-accent-primary-light) text-(--color-accent-primary)',
|
||||
success: 'bg-(--color-semantic-success-light) text-(--color-semantic-success)',
|
||||
warning: 'bg-(--color-semantic-warning-light) text-(--color-semantic-warning)',
|
||||
error: 'bg-(--color-semantic-error-light) text-(--color-semantic-error)',
|
||||
outline: 'bg-transparent border border-(--color-border-default) text-(--color-text-secondary)'
|
||||
}
|
||||
|
||||
return (
|
||||
<span className={cn(
|
||||
'inline-flex items-center px-3 py-1 rounded-full text-label-small',
|
||||
variants[variant]
|
||||
)}>
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import React from 'react'
|
||||
import { cn } from '../lib/utils'
|
||||
|
||||
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
variant?: 'primary' | 'secondary' | 'ghost' | 'success' | 'danger'
|
||||
size?: 'sm' | 'md' | 'lg'
|
||||
pill?: boolean
|
||||
}
|
||||
|
||||
export function Button({
|
||||
children,
|
||||
variant = 'primary',
|
||||
size = 'md',
|
||||
pill = false,
|
||||
className,
|
||||
...props
|
||||
}: ButtonProps) {
|
||||
const baseStyles = 'inline-flex items-center justify-center font-medium transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2'
|
||||
|
||||
const variants = {
|
||||
primary: 'bg-(--color-accent-primary) text-(--color-text-inverse) hover:bg-(--color-accent-primary-hover) focus:ring-(--color-accent-primary)',
|
||||
secondary: 'bg-transparent border border-(--color-border-default) text-(--color-text-primary) hover:bg-(--color-background-secondary)',
|
||||
ghost: 'bg-transparent text-(--color-text-secondary) hover:bg-(--color-background-secondary)',
|
||||
success: 'bg-(--color-semantic-success) text-white hover:opacity-90',
|
||||
danger: 'bg-(--color-semantic-error) text-white hover:opacity-90'
|
||||
}
|
||||
|
||||
const sizes = {
|
||||
sm: 'h-8 px-3 text-xs',
|
||||
md: 'h-10 px-4 text-sm',
|
||||
lg: 'h-12 px-6 text-base'
|
||||
}
|
||||
|
||||
const radius = pill ? 'rounded-full' : 'rounded-md'
|
||||
|
||||
return (
|
||||
<button
|
||||
className={cn(baseStyles, variants[variant], sizes[size], radius, className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import React from 'react'
|
||||
import { cn } from '../lib/utils'
|
||||
|
||||
export interface CardProps {
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
padding?: boolean
|
||||
}
|
||||
|
||||
export function Card({
|
||||
children,
|
||||
className,
|
||||
padding = true
|
||||
}: CardProps) {
|
||||
return (
|
||||
<div className={cn(
|
||||
'bg-(--color-surface-card) rounded-xl shadow-md',
|
||||
padding && 'p-6',
|
||||
className
|
||||
)}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import React from 'react'
|
||||
import { cn } from '../lib/utils'
|
||||
|
||||
export function Input({
|
||||
placeholder,
|
||||
className,
|
||||
...props
|
||||
}: React.InputHTMLAttributes<HTMLInputElement>) {
|
||||
return (
|
||||
<input
|
||||
className={cn(
|
||||
'h-10 w-full px-4 rounded-md border border-(--color-border-default)',
|
||||
'bg-(--color-surface-card) text-(--color-text-primary) text-sm',
|
||||
'focus:outline-none focus:border-(--color-accent-primary) focus:ring-2 focus:ring-(--color-accent-primary)/20',
|
||||
'placeholder:text-(--color-text-tertiary)',
|
||||
'transition-all duration-200',
|
||||
'disabled:bg-(--color-background-secondary) disabled:opacity-60',
|
||||
className
|
||||
)}
|
||||
placeholder={placeholder}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import React from 'react'
|
||||
import { cn } from '../lib/utils'
|
||||
|
||||
export interface ProgressCircleProps {
|
||||
value: number
|
||||
size?: 'sm' | 'md' | 'lg'
|
||||
color?: string
|
||||
}
|
||||
|
||||
export function ProgressCircle({
|
||||
value,
|
||||
size = 'md',
|
||||
color = 'var(--color-accent-primary)'
|
||||
}: ProgressCircleProps) {
|
||||
const sizes = {
|
||||
sm: { width: 40, stroke: 4, fontSize: 'text-[10px]' },
|
||||
md: { width: 56, stroke: 5, fontSize: 'text-xs' },
|
||||
lg: { width: 80, stroke: 6, fontSize: 'text-base' }
|
||||
}
|
||||
|
||||
const { width, stroke, fontSize } = sizes[size]
|
||||
const radius = (width - stroke) / 2
|
||||
const circumference = 2 * Math.PI * radius
|
||||
const offset = circumference - (value / 100) * circumference
|
||||
|
||||
return (
|
||||
<div className="relative inline-flex items-center justify-center">
|
||||
<svg width={width} height={width} className="-rotate-90">
|
||||
<circle
|
||||
cx={width / 2}
|
||||
cy={width / 2}
|
||||
r={radius}
|
||||
fill="none"
|
||||
stroke="var(--color-border-default)"
|
||||
strokeWidth={stroke}
|
||||
/>
|
||||
<circle
|
||||
cx={width / 2}
|
||||
cy={width / 2}
|
||||
r={radius}
|
||||
fill="none"
|
||||
stroke={color}
|
||||
strokeWidth={stroke}
|
||||
strokeDasharray={circumference}
|
||||
strokeDashoffset={offset}
|
||||
strokeLinecap="round"
|
||||
className="transition-all duration-500"
|
||||
/>
|
||||
</svg>
|
||||
<span className={cn('absolute font-semibold', fontSize)}>
|
||||
{value}%
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import React from 'react'
|
||||
import { cn } from '../lib/utils'
|
||||
|
||||
export interface ToggleProps {
|
||||
checked: boolean
|
||||
onChange: (checked: boolean) => void
|
||||
}
|
||||
|
||||
export function Toggle({ checked, onChange }: ToggleProps) {
|
||||
return (
|
||||
<button
|
||||
role="switch"
|
||||
aria-checked={checked}
|
||||
onClick={() => onChange(!checked)}
|
||||
className={cn(
|
||||
'relative inline-flex h-6 w-11 items-center rounded-full transition-colors duration-200',
|
||||
checked ? 'bg-(--color-accent-primary)' : 'bg-(--color-border-default)'
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
'inline-block h-5 w-5 rounded-full bg-white shadow-sm transition-transform duration-200',
|
||||
checked ? 'translate-x-[22px]' : 'translate-x-[2px]'
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export * from './Button'
|
||||
export * from './Badge'
|
||||
export * from './Avatar'
|
||||
export * from './Card'
|
||||
export * from './Input'
|
||||
export * from './Toggle'
|
||||
export * from './ProgressCircle'
|
||||
@@ -0,0 +1,56 @@
|
||||
import { ChevronLeft, ChevronRight } from 'lucide-react'
|
||||
import { cn } from '../lib/utils'
|
||||
import { Card } from '../components'
|
||||
|
||||
export function CalendarCard() {
|
||||
const days = ['M', 'T', 'W', 'T', 'F', 'S', 'S']
|
||||
const dates = [
|
||||
[29, 30, 31, 1, 2, 3, 4],
|
||||
[5, 6, 7, 8, 9, 10, 11],
|
||||
[12, 13, 14, 15, 16, 17, 18],
|
||||
[19, 20, 21, 22, 23, 24, 25],
|
||||
[26, 27, 28, 29, 30, 31, 1]
|
||||
]
|
||||
|
||||
return (
|
||||
<Card className="w-[300px]">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<button className="p-1 hover:bg-(--color-background-secondary) rounded transition-colors">
|
||||
<ChevronLeft className="w-5 h-5 text-(--color-text-tertiary)" />
|
||||
</button>
|
||||
<h3 className="text-heading-small">February, 2021</h3>
|
||||
<button className="p-1 hover:bg-(--color-background-secondary) rounded transition-colors">
|
||||
<ChevronRight className="w-5 h-5 text-(--color-text-tertiary)" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-7 gap-1 text-center">
|
||||
{days.map((day, i) => (
|
||||
<div key={i} className="text-label-small text-(--color-text-tertiary) py-2">
|
||||
{day}
|
||||
</div>
|
||||
))}
|
||||
{dates.flat().map((date, i) => {
|
||||
const isCurrentMonth = (i < 3 && date > 20) || (i > 30 && date < 10) ? false : true
|
||||
const isSelected = date === 26 && isCurrentMonth
|
||||
const isToday = date === 16 && isCurrentMonth
|
||||
|
||||
return (
|
||||
<button
|
||||
key={i}
|
||||
className={cn(
|
||||
'w-9 h-9 rounded-md text-body-medium transition-colors',
|
||||
!isCurrentMonth && 'text-(--color-text-tertiary)',
|
||||
isSelected && 'bg-(--color-accent-primary) text-(--color-text-inverse) rounded-full',
|
||||
isToday && !isSelected && 'text-(--color-accent-primary) font-semibold',
|
||||
!isSelected && 'hover:bg-(--color-background-secondary)'
|
||||
)}
|
||||
>
|
||||
{date}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { useState } from 'react'
|
||||
import { Slack, Video, Github } from 'lucide-react'
|
||||
import { Card, Toggle } from '../components'
|
||||
|
||||
export function IntegrationsCard() {
|
||||
const [slack, setSlack] = useState(true)
|
||||
const [meet, setMeet] = useState(true)
|
||||
const [github, setGithub] = useState(false)
|
||||
|
||||
const integrations = [
|
||||
{ icon: Slack, name: 'Slack', desc: 'Used as a main source of communication', enabled: slack, toggle: setSlack, color: '#E91E63' },
|
||||
{ icon: Video, name: 'Google meet', desc: 'Used for all types of calls', enabled: meet, toggle: setMeet, color: '#00897B' },
|
||||
{ icon: Github, name: 'Github', desc: 'Enables automated workflows, code synchronization', enabled: github, toggle: setGithub, color: '#333' }
|
||||
]
|
||||
|
||||
return (
|
||||
<Card className="w-[320px]">
|
||||
<h3 className="text-heading-medium mb-4">Integrations</h3>
|
||||
|
||||
<div className="space-y-4">
|
||||
{integrations.map((int, i) => (
|
||||
<div key={i} className="flex items-center gap-3">
|
||||
<div
|
||||
className="w-10 h-10 rounded-lg flex items-center justify-center"
|
||||
style={{ backgroundColor: `${int.color}15` }}
|
||||
>
|
||||
<int.icon className="w-5 h-5" style={{ color: int.color }} />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-heading-small">{int.name}</p>
|
||||
<p className="text-body-small text-(--color-text-secondary) truncate">{int.desc}</p>
|
||||
</div>
|
||||
<Toggle checked={int.enabled} onChange={int.toggle} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Card, Button, ProgressCircle, AvatarGroup } from '../components'
|
||||
|
||||
export function MilestoneCard() {
|
||||
return (
|
||||
<Card className="w-[380px]">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-heading-medium">Wireframes milestone</h3>
|
||||
<Button variant="secondary" size="sm" pill>View details</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-6">
|
||||
<div>
|
||||
<p className="text-body-small text-(--color-text-secondary)">Due date:</p>
|
||||
<p className="text-heading-small">March 20th</p>
|
||||
</div>
|
||||
|
||||
<ProgressCircle value={39} size="lg" />
|
||||
|
||||
<div>
|
||||
<p className="text-body-small text-(--color-text-secondary)">Asignees:</p>
|
||||
<AvatarGroup
|
||||
avatars={[
|
||||
{ name: 'A' },
|
||||
{ name: 'B' },
|
||||
{ name: 'C' },
|
||||
{ name: 'D' },
|
||||
{ name: 'E' }
|
||||
]}
|
||||
max={4}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { MoreVertical, Check, X } from 'lucide-react'
|
||||
import { Card, Avatar, Badge, Button } from '../components'
|
||||
|
||||
export function NotificationsCard() {
|
||||
return (
|
||||
<Card className="w-[320px]" padding={false}>
|
||||
<div className="p-4 border-b border-(--color-border-default)">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-heading-small">Notifications</h3>
|
||||
<Badge variant="primary">6</Badge>
|
||||
</div>
|
||||
<p className="text-body-small text-(--color-text-tertiary) mt-1">Unread</p>
|
||||
</div>
|
||||
|
||||
<div className="divide-y divide-(--color-border-default)">
|
||||
<div className="p-4 flex gap-3">
|
||||
<Avatar size="sm" name="Ashlynn George" />
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-body-small">
|
||||
<span className="font-semibold">Ashlynn George</span>
|
||||
<span className="text-(--color-text-tertiary)"> · 1h</span>
|
||||
</p>
|
||||
<p className="text-body-small text-(--color-text-secondary)">
|
||||
has invited you to access "Magma project"
|
||||
</p>
|
||||
<div className="flex gap-2 mt-2">
|
||||
<Button size="sm" variant="success" pill>
|
||||
<Check className="w-3 h-3 mr-1" /> Accept
|
||||
</Button>
|
||||
<Button size="sm" variant="secondary" pill>
|
||||
<X className="w-3 h-3 mr-1" /> Deny request
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<button className="p-1 hover:bg-(--color-background-secondary) rounded self-start transition-colors">
|
||||
<MoreVertical className="w-4 h-4 text-(--color-text-tertiary)" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="p-4 flex gap-3">
|
||||
<Avatar size="sm" name="Ashlynn George" />
|
||||
<div className="flex-1">
|
||||
<p className="text-body-small">
|
||||
<span className="font-semibold">Ashlynn George</span>
|
||||
<span className="text-(--color-text-tertiary)"> · 1h</span>
|
||||
</p>
|
||||
<p className="text-body-small text-(--color-text-secondary)">
|
||||
changed status of task in "Magma project"
|
||||
</p>
|
||||
</div>
|
||||
<button className="p-1 hover:bg-(--color-background-secondary) rounded self-start transition-colors">
|
||||
<MoreVertical className="w-4 h-4 text-(--color-text-tertiary)" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 flex gap-2 border-t border-(--color-border-default)">
|
||||
<Button variant="secondary" className="flex-1" pill>Mark all as read</Button>
|
||||
<Button variant="primary" className="flex-1" pill>View all</Button>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { MoreVertical } from 'lucide-react'
|
||||
import { Card, Avatar, Badge } from '../components'
|
||||
|
||||
export function ProfileCard() {
|
||||
return (
|
||||
<Card className="w-[280px]">
|
||||
<div className="flex justify-end mb-4">
|
||||
<button className="p-1 hover:bg-(--color-background-secondary) rounded transition-colors">
|
||||
<MoreVertical className="w-5 h-5 text-(--color-text-tertiary)" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex flex-col items-center text-center">
|
||||
<Avatar size="2xl" name="Christine Thompson" />
|
||||
<h3 className="text-heading-large mt-4">Christine Thompson</h3>
|
||||
<p className="text-body-medium text-(--color-text-secondary)">Project manager</p>
|
||||
<div className="flex flex-wrap gap-2 mt-4 justify-center">
|
||||
<Badge variant="outline">UI/UX Design</Badge>
|
||||
<Badge variant="outline">Project management</Badge>
|
||||
<Badge variant="outline">Agile methodologies</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { MoreVertical } from 'lucide-react'
|
||||
import { Card, ProgressCircle, AvatarGroup } from '../components'
|
||||
|
||||
export function ProjectStatusCard() {
|
||||
return (
|
||||
<Card className="w-[380px]">
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<ProgressCircle value={43} size="md" />
|
||||
<button className="p-1 hover:bg-(--color-background-secondary) rounded transition-colors">
|
||||
<MoreVertical className="w-5 h-5 text-(--color-text-tertiary)" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h3 className="text-heading-large mb-2">Amber website redesign</h3>
|
||||
<p className="text-body-medium text-(--color-text-secondary) mb-4">
|
||||
In today's fast-paced digital landscape, our mission is to transform our website into a more intuitive, engaging, and user-friendly platfor...
|
||||
</p>
|
||||
|
||||
<AvatarGroup
|
||||
avatars={[
|
||||
{ name: 'User 1' },
|
||||
{ name: 'User 2' },
|
||||
{ name: 'User 3' },
|
||||
{ name: 'User 4' },
|
||||
{ name: 'User 5' }
|
||||
]}
|
||||
max={4}
|
||||
/>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { MoreVertical, MessageSquare } from 'lucide-react'
|
||||
import { Card, Avatar } from '../components'
|
||||
|
||||
export function TeamMembersCard() {
|
||||
const members = [
|
||||
{ name: 'Julie Andrews', role: 'Project manager' },
|
||||
{ name: 'Kevin Conroy', role: 'Project manager' },
|
||||
{ name: 'Jim Connor', role: 'Project manager' },
|
||||
{ name: 'Tom Kinley', role: 'Project manager' }
|
||||
]
|
||||
|
||||
return (
|
||||
<Card className="w-[320px]" padding={false}>
|
||||
<div className="divide-y divide-(--color-border-default)">
|
||||
{members.map((member, i) => (
|
||||
<div key={i} className="p-4 flex items-center gap-3">
|
||||
<Avatar name={member.name} />
|
||||
<div className="flex-1">
|
||||
<p className="text-heading-small">{member.name}</p>
|
||||
<p className="text-body-small text-(--color-text-secondary)">{member.role}</p>
|
||||
</div>
|
||||
<button className="p-1 hover:bg-(--color-background-secondary) rounded transition-colors">
|
||||
<MoreVertical className="w-4 h-4 text-(--color-text-tertiary)" />
|
||||
</button>
|
||||
<button className="p-2 bg-(--color-semantic-error-light) text-(--color-semantic-error) rounded-md hover:opacity-80 transition-opacity">
|
||||
<MessageSquare className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="p-4 border-t border-(--color-border-default) flex justify-center gap-3">
|
||||
<img src="https://upload.wikimedia.org/wikipedia/commons/b/ba/Stripe_Logo%2C_revised_2016.svg" alt="Stripe" className="h-6" />
|
||||
<div className="px-3 py-1 bg-[#1A1F71] text-white text-sm font-bold rounded">VISA</div>
|
||||
<div className="px-2 py-1 bg-[#003087] text-white text-xs font-bold rounded">PayPal</div>
|
||||
<div className="w-8 h-8 bg-gradient-to-r from-red-500 to-yellow-500 rounded-full" />
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export * from './ProfileCard'
|
||||
export * from './NotificationsCard'
|
||||
export * from './CalendarCard'
|
||||
export * from './TeamMembersCard'
|
||||
export * from './ProjectStatusCard'
|
||||
export * from './MilestoneCard'
|
||||
export * from './IntegrationsCard'
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Centralized Icon Exports for Design System
|
||||
*
|
||||
* This file serves as the single source of truth for all lucide-react icons used
|
||||
* throughout the design system demo app. By consolidating imports here, we enable:
|
||||
*
|
||||
* 1. Better tracking of which icons are actually used
|
||||
* 2. Potential code-splitting opportunities
|
||||
* 3. Easier future migration to alternative icon solutions
|
||||
* 4. Reduced bundle size through optimized tree-shaking
|
||||
*
|
||||
* Usage:
|
||||
* import { Check, ChevronLeft, X } from '../lib/icons';
|
||||
*
|
||||
* When adding new icons:
|
||||
* 1. Import the icon from 'lucide-react'
|
||||
* 2. Add it to the export statement in alphabetical order
|
||||
*/
|
||||
|
||||
export {
|
||||
Check,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Github,
|
||||
Heart,
|
||||
MessageSquare,
|
||||
Minus,
|
||||
Moon,
|
||||
MoreVertical,
|
||||
Plus,
|
||||
RotateCcw,
|
||||
Slack,
|
||||
Sparkles,
|
||||
Star,
|
||||
Sun,
|
||||
Video,
|
||||
X,
|
||||
Zap,
|
||||
} from 'lucide-react';
|
||||
@@ -0,0 +1,104 @@
|
||||
import { useState } from 'react'
|
||||
import { ChevronLeft, Check, Sun, Moon } from 'lucide-react'
|
||||
import { cn } from '../lib/utils'
|
||||
import { ColorTheme, Mode, ColorThemeDefinition } from './types'
|
||||
|
||||
interface ThemeSelectorProps {
|
||||
colorTheme: ColorTheme
|
||||
mode: Mode
|
||||
onColorThemeChange: (theme: ColorTheme) => void
|
||||
onModeToggle: () => void
|
||||
themes: ColorThemeDefinition[]
|
||||
}
|
||||
|
||||
export function ThemeSelector({
|
||||
colorTheme,
|
||||
mode,
|
||||
onColorThemeChange,
|
||||
onModeToggle,
|
||||
themes
|
||||
}: ThemeSelectorProps) {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
// Find theme with fallback to first theme (default)
|
||||
const currentTheme = themes.find(t => t.id === colorTheme) || themes[0]
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-3">
|
||||
{/* Color Theme Dropdown */}
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className="flex items-center gap-2 px-3 py-2 rounded-lg bg-(--color-background-secondary) hover:bg-(--color-border-default) transition-colors"
|
||||
>
|
||||
<div
|
||||
className="w-4 h-4 rounded-full border-2 border-white shadow-sm"
|
||||
style={{ backgroundColor: mode === 'dark' ? currentTheme.previewColors.accent : currentTheme.previewColors.bg }}
|
||||
/>
|
||||
<span className="text-body-medium font-medium">{currentTheme.name}</span>
|
||||
<ChevronLeft className={cn(
|
||||
"w-4 h-4 text-(--color-text-tertiary) transition-transform",
|
||||
isOpen ? "rotate-90" : "-rotate-90"
|
||||
)} />
|
||||
</button>
|
||||
|
||||
{isOpen && (
|
||||
<>
|
||||
<div
|
||||
className="fixed inset-0 z-40"
|
||||
onClick={() => setIsOpen(false)}
|
||||
/>
|
||||
<div className="absolute top-full right-0 mt-2 w-64 p-2 bg-(--color-surface-card) rounded-lg shadow-lg border border-(--color-border-default) z-50">
|
||||
{themes.map((theme) => (
|
||||
<button
|
||||
key={theme.id}
|
||||
onClick={() => {
|
||||
onColorThemeChange(theme.id)
|
||||
setIsOpen(false)
|
||||
}}
|
||||
className={cn(
|
||||
"w-full flex items-center gap-3 px-3 py-2 rounded-md transition-colors text-left",
|
||||
colorTheme === theme.id
|
||||
? "bg-(--color-accent-primary-light)"
|
||||
: "hover:bg-(--color-background-secondary)"
|
||||
)}
|
||||
>
|
||||
<div className="flex -space-x-1">
|
||||
<div
|
||||
className="w-5 h-5 rounded-full border-2 border-white shadow-sm"
|
||||
style={{ backgroundColor: theme.previewColors.bg }}
|
||||
/>
|
||||
<div
|
||||
className="w-5 h-5 rounded-full border-2 border-white shadow-sm"
|
||||
style={{ backgroundColor: theme.previewColors.accent }}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-body-medium font-medium">{theme.name}</p>
|
||||
<p className="text-body-small text-(--color-text-tertiary) truncate">{theme.description}</p>
|
||||
</div>
|
||||
{colorTheme === theme.id && (
|
||||
<Check className="w-4 h-4 text-(--color-accent-primary)" />
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Light/Dark Toggle */}
|
||||
<button
|
||||
onClick={onModeToggle}
|
||||
className="p-2 rounded-lg bg-(--color-background-secondary) hover:bg-(--color-border-default) transition-colors"
|
||||
aria-label={`Switch to ${mode === 'light' ? 'dark' : 'light'} mode`}
|
||||
>
|
||||
{mode === 'light' ? (
|
||||
<Moon className="w-5 h-5 text-(--color-text-secondary)" />
|
||||
) : (
|
||||
<Sun className="w-5 h-5 text-(--color-text-secondary)" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { ColorThemeDefinition } from './types'
|
||||
|
||||
export const COLOR_THEMES: ColorThemeDefinition[] = [
|
||||
{
|
||||
id: 'default',
|
||||
name: 'Default',
|
||||
description: 'Oscura-inspired with pale yellow accent',
|
||||
previewColors: { bg: '#F2F2ED', accent: '#E6E7A3', darkBg: '#0B0B0F', darkAccent: '#E6E7A3' }
|
||||
},
|
||||
{
|
||||
id: 'dusk',
|
||||
name: 'Dusk',
|
||||
description: 'Warmer variant with slightly lighter dark mode',
|
||||
previewColors: { bg: '#F5F5F0', accent: '#E6E7A3', darkBg: '#131419', darkAccent: '#E6E7A3' }
|
||||
},
|
||||
{
|
||||
id: 'lime',
|
||||
name: 'Lime',
|
||||
description: 'Fresh, energetic lime with purple accents',
|
||||
previewColors: { bg: '#E8F5A3', accent: '#7C3AED', darkBg: '#0F0F1A' }
|
||||
},
|
||||
{
|
||||
id: 'ocean',
|
||||
name: 'Ocean',
|
||||
description: 'Calm, professional blue tones',
|
||||
previewColors: { bg: '#E0F2FE', accent: '#0284C7', darkBg: '#082F49' }
|
||||
},
|
||||
{
|
||||
id: 'retro',
|
||||
name: 'Retro',
|
||||
description: 'Warm, nostalgic amber vibes',
|
||||
previewColors: { bg: '#FEF3C7', accent: '#D97706', darkBg: '#1C1917' }
|
||||
},
|
||||
{
|
||||
id: 'neo',
|
||||
name: 'Neo',
|
||||
description: 'Modern cyberpunk pink/magenta',
|
||||
previewColors: { bg: '#FDF4FF', accent: '#D946EF', darkBg: '#0F0720' }
|
||||
},
|
||||
{
|
||||
id: 'forest',
|
||||
name: 'Forest',
|
||||
description: 'Natural, earthy green tones',
|
||||
previewColors: { bg: '#DCFCE7', accent: '#16A34A', darkBg: '#052E16' }
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from './types'
|
||||
export * from './constants'
|
||||
export * from './useTheme'
|
||||
export * from './ThemeSelector'
|
||||
@@ -0,0 +1,21 @@
|
||||
export type ColorTheme = 'default' | 'dusk' | 'lime' | 'ocean' | 'retro' | 'neo' | 'forest'
|
||||
export type Mode = 'light' | 'dark'
|
||||
|
||||
export interface ThemeConfig {
|
||||
colorTheme: ColorTheme
|
||||
mode: Mode
|
||||
}
|
||||
|
||||
export interface ThemePreviewColors {
|
||||
bg: string
|
||||
accent: string
|
||||
darkBg: string
|
||||
darkAccent?: string
|
||||
}
|
||||
|
||||
export interface ColorThemeDefinition {
|
||||
id: ColorTheme
|
||||
name: string
|
||||
description: string
|
||||
previewColors: ThemePreviewColors
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { ThemeConfig, ColorTheme, Mode } from './types'
|
||||
import { COLOR_THEMES } from './constants'
|
||||
|
||||
export function useTheme() {
|
||||
const [config, setConfig] = useState<ThemeConfig>(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const stored = localStorage.getItem('design-system-theme-config')
|
||||
if (stored) {
|
||||
try {
|
||||
const parsed = JSON.parse(stored)
|
||||
// Validate that the stored theme still exists
|
||||
const themeExists = COLOR_THEMES.some(t => t.id === parsed.colorTheme)
|
||||
if (themeExists) {
|
||||
return parsed
|
||||
}
|
||||
// Fall back to default if theme was removed
|
||||
return {
|
||||
colorTheme: 'default' as ColorTheme,
|
||||
mode: parsed.mode || 'light'
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
return {
|
||||
colorTheme: 'default' as ColorTheme,
|
||||
mode: window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
||||
}
|
||||
}
|
||||
return { colorTheme: 'default', mode: 'light' }
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const root = document.documentElement
|
||||
|
||||
// Set color theme
|
||||
if (config.colorTheme === 'default') {
|
||||
root.removeAttribute('data-theme')
|
||||
} else {
|
||||
root.setAttribute('data-theme', config.colorTheme)
|
||||
}
|
||||
|
||||
// Set mode
|
||||
if (config.mode === 'dark') {
|
||||
root.classList.add('dark')
|
||||
} else {
|
||||
root.classList.remove('dark')
|
||||
}
|
||||
|
||||
localStorage.setItem('design-system-theme-config', JSON.stringify(config))
|
||||
}, [config])
|
||||
|
||||
const setColorTheme = (colorTheme: ColorTheme) => setConfig(c => ({ ...c, colorTheme }))
|
||||
const setMode = (mode: Mode) => setConfig(c => ({ ...c, mode }))
|
||||
const toggleMode = () => setConfig(c => ({ ...c, mode: c.mode === 'light' ? 'dark' : 'light' }))
|
||||
|
||||
return {
|
||||
colorTheme: config.colorTheme,
|
||||
mode: config.mode,
|
||||
setColorTheme,
|
||||
setMode,
|
||||
toggleMode,
|
||||
themes: COLOR_THEMES
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: AndyMik90
|
||||
@@ -0,0 +1,76 @@
|
||||
name: 🐛 Bug Report
|
||||
description: Something isn't working
|
||||
labels: ["bug", "needs-triage"]
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
label: Checklist
|
||||
options:
|
||||
- label: I searched existing issues and this hasn't been reported
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: area
|
||||
attributes:
|
||||
label: Area
|
||||
options:
|
||||
- Frontend
|
||||
- Backend
|
||||
- Fullstack
|
||||
- Not sure
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: os
|
||||
attributes:
|
||||
label: Operating System
|
||||
options:
|
||||
- macOS
|
||||
- Windows
|
||||
- Linux
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Version
|
||||
placeholder: "e.g., 2.5.5"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: What happened?
|
||||
placeholder: Describe the bug clearly and concisely. Include any error messages you encountered.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
placeholder: |
|
||||
1. Run command '...' or click on '...'
|
||||
2. Observe behavior '...'
|
||||
3. See error or unexpected result
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
placeholder: What did you expect to happen instead? Describe the correct behavior.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Logs / Screenshots
|
||||
description: Required for UI bugs. Attach relevant logs, screenshots, or error output.
|
||||
render: shell
|
||||
@@ -0,0 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 💡 Feature Request
|
||||
url: https://github.com/AndyMik90/Auto-Claude/discussions
|
||||
about: Suggest new features in GitHub Discussions
|
||||
- name: 💬 Discord Community
|
||||
url: https://discord.gg/QhRnz9m5HE
|
||||
about: Questions and discussions - join our Discord!
|
||||
@@ -0,0 +1,37 @@
|
||||
name: 📚 Documentation
|
||||
description: Improvements or additions to documentation
|
||||
labels: ["documentation", "needs-triage", "help wanted"]
|
||||
body:
|
||||
- type: dropdown
|
||||
id: type
|
||||
attributes:
|
||||
label: Type
|
||||
options:
|
||||
- Missing documentation
|
||||
- Incorrect/outdated info
|
||||
- Improvement suggestion
|
||||
- Typo/grammar fix
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: location
|
||||
attributes:
|
||||
label: Location
|
||||
description: Which file or page?
|
||||
placeholder: "e.g., README.md or guides/setup.md"
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: What needs to change?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
id: contribute
|
||||
attributes:
|
||||
label: Contribution
|
||||
options:
|
||||
- label: I'm willing to submit a PR for this
|
||||
@@ -0,0 +1,61 @@
|
||||
name: ❓ Question
|
||||
description: Needs clarification
|
||||
labels: ["question", "needs-triage"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
**Before asking:** Check [Discord](https://discord.gg/QhRnz9m5HE) - your question may already be answered there!
|
||||
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
label: Checklist
|
||||
options:
|
||||
- label: I searched existing issues and Discord for similar questions
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: area
|
||||
attributes:
|
||||
label: Area
|
||||
options:
|
||||
- Setup/Installation
|
||||
- Frontend
|
||||
- Backend
|
||||
- Configuration
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Version
|
||||
description: Which version are you using?
|
||||
placeholder: "e.g., 2.7.1"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: question
|
||||
attributes:
|
||||
label: Question
|
||||
placeholder: "Describe your question in detail..."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: context
|
||||
attributes:
|
||||
label: Context
|
||||
description: What are you trying to achieve?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: attempts
|
||||
attributes:
|
||||
label: What have you already tried?
|
||||
description: What steps have you taken to resolve this?
|
||||
placeholder: "e.g., I tried reading the docs, searched for..."
|
||||
@@ -0,0 +1,76 @@
|
||||
## Base Branch
|
||||
|
||||
- [ ] This PR targets the `develop` branch (required for all feature/fix PRs)
|
||||
- [ ] This PR targets `main` (hotfix only - maintainers)
|
||||
|
||||
## Description
|
||||
|
||||
<!-- What does this PR do? 2-3 sentences -->
|
||||
|
||||
## Related Issue
|
||||
|
||||
Closes #
|
||||
|
||||
## Type of Change
|
||||
|
||||
- [ ] 🐛 Bug fix
|
||||
- [ ] ✨ New feature
|
||||
- [ ] 📚 Documentation
|
||||
- [ ] ♻️ Refactor
|
||||
- [ ] 🧪 Test
|
||||
|
||||
## Area
|
||||
|
||||
- [ ] Frontend
|
||||
- [ ] Backend
|
||||
- [ ] Fullstack
|
||||
|
||||
## Commit Message Format
|
||||
|
||||
Follow conventional commits: `<type>: <subject>`
|
||||
|
||||
**Types:** feat, fix, docs, style, refactor, test, chore
|
||||
|
||||
**Example:** `feat: add user authentication system`
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] I've synced with `develop` branch
|
||||
- [ ] I've tested my changes locally
|
||||
- [ ] I've followed the code principles (SOLID, DRY, KISS)
|
||||
- [ ] My PR is small and focused (< 400 lines ideally)
|
||||
|
||||
## CI/Testing Requirements
|
||||
|
||||
- [ ] All CI checks pass
|
||||
- [ ] All existing tests pass
|
||||
- [ ] New features include test coverage
|
||||
- [ ] Bug fixes include regression tests
|
||||
|
||||
## Screenshots
|
||||
|
||||
<!-- Required for UI changes. Delete if not applicable. -->
|
||||
|
||||
| Before | After |
|
||||
|--------|-------|
|
||||
| | |
|
||||
|
||||
## Feature Toggle
|
||||
|
||||
<!-- If feature is incomplete or experimental, how is it hidden from users? -->
|
||||
<!-- This ensures incomplete work can be merged without affecting production. -->
|
||||
|
||||
- [ ] Behind localStorage flag: `use_feature_name`
|
||||
- [ ] Behind settings toggle
|
||||
- [ ] Behind environment variable/config
|
||||
- [ ] N/A - Feature is complete and ready for all users
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
<!-- Does this PR introduce breaking changes? If yes, describe what breaks and migration steps. -->
|
||||
<!-- Delete this section if not applicable. -->
|
||||
|
||||
**Breaking:** Yes / No
|
||||
|
||||
**Details:**
|
||||
<!-- What breaks? What do users/developers need to change? -->
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.0 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 834 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.7 MiB |
@@ -0,0 +1,37 @@
|
||||
version: 2
|
||||
updates:
|
||||
# Python dependencies
|
||||
- package-ecosystem: pip
|
||||
directory: /apps/backend
|
||||
schedule:
|
||||
interval: weekly
|
||||
open-pull-requests-limit: 5
|
||||
labels:
|
||||
- dependencies
|
||||
- python
|
||||
commit-message:
|
||||
prefix: "chore(deps)"
|
||||
|
||||
# npm dependencies
|
||||
- package-ecosystem: npm
|
||||
directory: /apps/frontend
|
||||
schedule:
|
||||
interval: weekly
|
||||
open-pull-requests-limit: 5
|
||||
labels:
|
||||
- dependencies
|
||||
- javascript
|
||||
commit-message:
|
||||
prefix: "chore(deps)"
|
||||
|
||||
# GitHub Actions
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
open-pull-requests-limit: 5
|
||||
labels:
|
||||
- dependencies
|
||||
- ci
|
||||
commit-message:
|
||||
prefix: "ci(deps)"
|
||||
@@ -0,0 +1,35 @@
|
||||
name-template: 'v$RESOLVED_VERSION'
|
||||
tag-template: 'v$RESOLVED_VERSION'
|
||||
|
||||
categories:
|
||||
- title: '## New Features'
|
||||
labels:
|
||||
- 'feature'
|
||||
- 'enhancement'
|
||||
- title: '## Bug Fixes'
|
||||
labels:
|
||||
- 'bug'
|
||||
- 'fix'
|
||||
- title: '## Improvements'
|
||||
labels:
|
||||
- 'improvement'
|
||||
- 'refactor'
|
||||
- title: '## Documentation'
|
||||
labels:
|
||||
- 'documentation'
|
||||
- title: '## Other Changes'
|
||||
labels:
|
||||
- '*'
|
||||
|
||||
change-template: '* $TITLE (#$NUMBER) @$AUTHOR'
|
||||
|
||||
sort-by: merged_at
|
||||
sort-direction: ascending
|
||||
|
||||
template: |
|
||||
$CHANGES
|
||||
|
||||
**Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...$RESOLVED_VERSION
|
||||
|
||||
## Contributors
|
||||
$CONTRIBUTORS
|
||||
@@ -0,0 +1,409 @@
|
||||
name: Beta Release
|
||||
|
||||
# Manual trigger for beta releases from develop branch
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Beta version (e.g., 2.8.0-beta.1)'
|
||||
required: true
|
||||
type: string
|
||||
dry_run:
|
||||
description: 'Test build without creating release'
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
validate-version:
|
||||
name: Validate beta version format
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Validate version format
|
||||
run: |
|
||||
VERSION="${{ github.event.inputs.version }}"
|
||||
|
||||
# Check if version matches beta semver pattern
|
||||
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+-(beta|alpha|rc)\.[0-9]+$ ]]; then
|
||||
echo "::error::Invalid version format: $VERSION"
|
||||
echo "Version must match pattern: X.Y.Z-beta.N (e.g., 2.8.0-beta.1)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Valid beta version: $VERSION"
|
||||
|
||||
create-tag:
|
||||
name: Create beta tag
|
||||
needs: validate-version
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
outputs:
|
||||
version: ${{ github.event.inputs.version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: develop
|
||||
|
||||
- name: Create and push tag
|
||||
if: ${{ github.event.inputs.dry_run != 'true' }}
|
||||
run: |
|
||||
VERSION="${{ github.event.inputs.version }}"
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git tag -a "v$VERSION" -m "Beta release v$VERSION"
|
||||
git push origin "v$VERSION"
|
||||
echo "Created tag v$VERSION"
|
||||
|
||||
# Intel build on Intel runner for native compilation
|
||||
build-macos-intel:
|
||||
needs: create-tag
|
||||
runs-on: macos-15-intel
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
# Use tag for real releases, develop branch for dry runs
|
||||
ref: ${{ github.event.inputs.dry_run == 'true' && 'develop' || format('v{0}', needs.create-tag.outputs.version) }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '24'
|
||||
|
||||
- name: Get npm cache directory
|
||||
id: npm-cache
|
||||
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.npm-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: ${{ runner.os }}-npm-
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd apps/frontend && npm ci
|
||||
|
||||
- name: Cache bundled Python
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: apps/frontend/python-runtime
|
||||
key: python-bundle-${{ runner.os }}-x64-3.12.8
|
||||
restore-keys: |
|
||||
python-bundle-${{ runner.os }}-x64-
|
||||
|
||||
- name: Build application
|
||||
run: cd apps/frontend && npm run build
|
||||
|
||||
- name: Package macOS (Intel)
|
||||
run: |
|
||||
VERSION="${{ needs.create-tag.outputs.version }}"
|
||||
cd apps/frontend && npm run package:mac -- --x64 --config.extraMetadata.version="$VERSION"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CSC_LINK: ${{ secrets.MAC_CERTIFICATE }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.MAC_CERTIFICATE_PASSWORD }}
|
||||
|
||||
- name: Notarize macOS Intel app
|
||||
env:
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
run: |
|
||||
if [ -z "$APPLE_ID" ]; then
|
||||
echo "Skipping notarization: APPLE_ID not configured"
|
||||
exit 0
|
||||
fi
|
||||
cd apps/frontend
|
||||
for dmg in dist/*.dmg; do
|
||||
echo "Notarizing $dmg..."
|
||||
xcrun notarytool submit "$dmg" \
|
||||
--apple-id "$APPLE_ID" \
|
||||
--password "$APPLE_APP_SPECIFIC_PASSWORD" \
|
||||
--team-id "$APPLE_TEAM_ID" \
|
||||
--wait
|
||||
xcrun stapler staple "$dmg"
|
||||
echo "Successfully notarized and stapled $dmg"
|
||||
done
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macos-intel-builds
|
||||
path: |
|
||||
apps/frontend/dist/*.dmg
|
||||
apps/frontend/dist/*.zip
|
||||
|
||||
# Apple Silicon build on ARM64 runner for native compilation
|
||||
build-macos-arm64:
|
||||
needs: create-tag
|
||||
runs-on: macos-15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
# Use tag for real releases, develop branch for dry runs
|
||||
ref: ${{ github.event.inputs.dry_run == 'true' && 'develop' || format('v{0}', needs.create-tag.outputs.version) }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '24'
|
||||
|
||||
- name: Get npm cache directory
|
||||
id: npm-cache
|
||||
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.npm-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: ${{ runner.os }}-npm-
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd apps/frontend && npm ci
|
||||
|
||||
- name: Cache bundled Python
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: apps/frontend/python-runtime
|
||||
key: python-bundle-${{ runner.os }}-arm64-3.12.8
|
||||
restore-keys: |
|
||||
python-bundle-${{ runner.os }}-arm64-
|
||||
|
||||
- name: Build application
|
||||
run: cd apps/frontend && npm run build
|
||||
|
||||
- name: Package macOS (Apple Silicon)
|
||||
run: |
|
||||
VERSION="${{ needs.create-tag.outputs.version }}"
|
||||
cd apps/frontend && npm run package:mac -- --arm64 --config.extraMetadata.version="$VERSION"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CSC_LINK: ${{ secrets.MAC_CERTIFICATE }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.MAC_CERTIFICATE_PASSWORD }}
|
||||
|
||||
- name: Notarize macOS ARM64 app
|
||||
env:
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
run: |
|
||||
if [ -z "$APPLE_ID" ]; then
|
||||
echo "Skipping notarization: APPLE_ID not configured"
|
||||
exit 0
|
||||
fi
|
||||
cd apps/frontend
|
||||
for dmg in dist/*.dmg; do
|
||||
echo "Notarizing $dmg..."
|
||||
xcrun notarytool submit "$dmg" \
|
||||
--apple-id "$APPLE_ID" \
|
||||
--password "$APPLE_APP_SPECIFIC_PASSWORD" \
|
||||
--team-id "$APPLE_TEAM_ID" \
|
||||
--wait
|
||||
xcrun stapler staple "$dmg"
|
||||
echo "Successfully notarized and stapled $dmg"
|
||||
done
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macos-arm64-builds
|
||||
path: |
|
||||
apps/frontend/dist/*.dmg
|
||||
apps/frontend/dist/*.zip
|
||||
|
||||
build-windows:
|
||||
needs: create-tag
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
# Use tag for real releases, develop branch for dry runs
|
||||
ref: ${{ github.event.inputs.dry_run == 'true' && 'develop' || format('v{0}', needs.create-tag.outputs.version) }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '24'
|
||||
|
||||
- name: Get npm cache directory
|
||||
id: npm-cache
|
||||
shell: bash
|
||||
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.npm-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: ${{ runner.os }}-npm-
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd apps/frontend && npm ci
|
||||
|
||||
- name: Cache bundled Python
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: apps/frontend/python-runtime
|
||||
key: python-bundle-${{ runner.os }}-x64-3.12.8
|
||||
restore-keys: |
|
||||
python-bundle-${{ runner.os }}-x64-
|
||||
|
||||
- name: Build application
|
||||
run: cd apps/frontend && npm run build
|
||||
|
||||
- name: Package Windows
|
||||
shell: bash
|
||||
run: |
|
||||
VERSION="${{ needs.create-tag.outputs.version }}"
|
||||
cd apps/frontend && npm run package:win -- --config.extraMetadata.version="$VERSION"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CSC_LINK: ${{ secrets.WIN_CERTIFICATE }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.WIN_CERTIFICATE_PASSWORD }}
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-builds
|
||||
path: |
|
||||
apps/frontend/dist/*.exe
|
||||
|
||||
build-linux:
|
||||
needs: create-tag
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
# Use tag for real releases, develop branch for dry runs
|
||||
ref: ${{ github.event.inputs.dry_run == 'true' && 'develop' || format('v{0}', needs.create-tag.outputs.version) }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '24'
|
||||
|
||||
- name: Get npm cache directory
|
||||
id: npm-cache
|
||||
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.npm-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: ${{ runner.os }}-npm-
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd apps/frontend && npm ci
|
||||
|
||||
- name: Cache bundled Python
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: apps/frontend/python-runtime
|
||||
key: python-bundle-${{ runner.os }}-x64-3.12.8
|
||||
restore-keys: |
|
||||
python-bundle-${{ runner.os }}-x64-
|
||||
|
||||
- name: Build application
|
||||
run: cd apps/frontend && npm run build
|
||||
|
||||
- name: Package Linux
|
||||
run: |
|
||||
VERSION="${{ needs.create-tag.outputs.version }}"
|
||||
cd apps/frontend && npm run package:linux -- --config.extraMetadata.version="$VERSION"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: linux-builds
|
||||
path: |
|
||||
apps/frontend/dist/*.AppImage
|
||||
apps/frontend/dist/*.deb
|
||||
|
||||
create-release:
|
||||
needs: [create-tag, build-macos-intel, build-macos-arm64, build-windows, build-linux]
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.inputs.dry_run != 'true' }}
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: v${{ needs.create-tag.outputs.version }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: dist
|
||||
|
||||
- name: Flatten and validate artifacts
|
||||
run: |
|
||||
mkdir -p release-assets
|
||||
find dist -type f \( -name "*.dmg" -o -name "*.zip" -o -name "*.exe" -o -name "*.AppImage" -o -name "*.deb" \) -exec cp {} release-assets/ \;
|
||||
|
||||
# Validate that at least one artifact was copied
|
||||
artifact_count=$(find release-assets -type f \( -name "*.dmg" -o -name "*.zip" -o -name "*.exe" -o -name "*.AppImage" -o -name "*.deb" \) | wc -l)
|
||||
if [ "$artifact_count" -eq 0 ]; then
|
||||
echo "::error::No build artifacts found! Expected .dmg, .zip, .exe, .AppImage, or .deb files."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Found $artifact_count artifact(s):"
|
||||
ls -la release-assets/
|
||||
|
||||
- name: Generate checksums
|
||||
run: |
|
||||
cd release-assets
|
||||
sha256sum ./* > checksums.sha256
|
||||
cat checksums.sha256
|
||||
|
||||
- name: Create Beta Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: v${{ needs.create-tag.outputs.version }}
|
||||
name: v${{ needs.create-tag.outputs.version }} (Beta)
|
||||
body: |
|
||||
## Beta Release v${{ needs.create-tag.outputs.version }}
|
||||
|
||||
This is a **beta release** for testing new features. It may contain bugs or incomplete functionality.
|
||||
|
||||
### How to opt-in to beta updates
|
||||
1. Open Auto Claude
|
||||
2. Go to Settings > Updates
|
||||
3. Enable "Beta Updates" toggle
|
||||
|
||||
### Reporting Issues
|
||||
Please report any issues at https://github.com/AndyMik90/Auto-Claude/issues
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: https://github.com/${{ github.repository }}/compare/main...v${{ needs.create-tag.outputs.version }}
|
||||
files: release-assets/*
|
||||
draft: false
|
||||
prerelease: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
dry-run-summary:
|
||||
needs: [create-tag, build-macos-intel, build-macos-arm64, build-windows, build-linux]
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.inputs.dry_run == 'true' }}
|
||||
steps:
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: dist
|
||||
|
||||
- name: Dry run summary
|
||||
run: |
|
||||
echo "## Beta Release Dry Run Complete" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Version:** ${{ needs.create-tag.outputs.version }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Build artifacts created successfully:" >> $GITHUB_STEP_SUMMARY
|
||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||
find dist -type f \( -name "*.dmg" -o -name "*.zip" -o -name "*.exe" -o -name "*.AppImage" -o -name "*.deb" \) >> $GITHUB_STEP_SUMMARY
|
||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "To create a real release, run this workflow again with dry_run unchecked." >> $GITHUB_STEP_SUMMARY
|
||||
@@ -0,0 +1,127 @@
|
||||
name: Build Native Module Prebuilds
|
||||
|
||||
on:
|
||||
# Build on releases
|
||||
release:
|
||||
types: [published]
|
||||
# Manual trigger for testing
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
electron_version:
|
||||
description: 'Electron version to build for'
|
||||
required: false
|
||||
default: '39.2.6'
|
||||
|
||||
env:
|
||||
# Default Electron version - update when upgrading Electron in package.json
|
||||
ELECTRON_VERSION: ${{ github.event.inputs.electron_version || '39.2.6' }}
|
||||
|
||||
jobs:
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [x64]
|
||||
# Add arm64 when GitHub Actions supports Windows ARM runners
|
||||
# arch: [x64, arm64]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '24'
|
||||
|
||||
- name: Install Visual Studio Build Tools
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
|
||||
- name: Install node-pty and rebuild for Electron
|
||||
working-directory: apps/frontend
|
||||
shell: pwsh
|
||||
run: |
|
||||
# Install only node-pty
|
||||
npm install node-pty@1.1.0-beta42
|
||||
|
||||
# Get Electron ABI version
|
||||
$electronAbi = (npx electron-abi $env:ELECTRON_VERSION)
|
||||
Write-Host "Building for Electron $env:ELECTRON_VERSION (ABI: $electronAbi)"
|
||||
|
||||
# Rebuild node-pty for Electron
|
||||
npx @electron/rebuild --version $env:ELECTRON_VERSION --module-dir node_modules/node-pty --arch ${{ matrix.arch }}
|
||||
|
||||
- name: Package prebuilt binaries
|
||||
working-directory: apps/frontend
|
||||
shell: pwsh
|
||||
run: |
|
||||
$electronAbi = (npx electron-abi $env:ELECTRON_VERSION)
|
||||
$prebuildDir = "prebuilds/win32-${{ matrix.arch }}-electron-$electronAbi"
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $prebuildDir
|
||||
|
||||
# Copy all built native files
|
||||
$buildDir = "node_modules/node-pty/build/Release"
|
||||
if (Test-Path $buildDir) {
|
||||
Copy-Item "$buildDir/*.node" $prebuildDir/ -Force
|
||||
Copy-Item "$buildDir/*.dll" $prebuildDir/ -Force -ErrorAction SilentlyContinue
|
||||
Copy-Item "$buildDir/*.exe" $prebuildDir/ -Force -ErrorAction SilentlyContinue
|
||||
|
||||
# Also copy conpty files if they exist in subdirectory
|
||||
if (Test-Path "$buildDir/conpty") {
|
||||
Copy-Item "$buildDir/conpty/*" $prebuildDir/ -Force
|
||||
}
|
||||
}
|
||||
|
||||
# List what we packaged
|
||||
Write-Host "Packaged prebuilds:"
|
||||
Get-ChildItem $prebuildDir
|
||||
|
||||
- name: Create archive
|
||||
working-directory: apps/frontend
|
||||
shell: pwsh
|
||||
run: |
|
||||
$electronAbi = (npx electron-abi $env:ELECTRON_VERSION)
|
||||
$archiveName = "node-pty-win32-${{ matrix.arch }}-electron-$electronAbi.zip"
|
||||
|
||||
Compress-Archive -Path "prebuilds/*" -DestinationPath $archiveName
|
||||
|
||||
Write-Host "Created archive: $archiveName"
|
||||
Get-ChildItem $archiveName
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: node-pty-win32-${{ matrix.arch }}
|
||||
path: apps/frontend/node-pty-*.zip
|
||||
retention-days: 90
|
||||
|
||||
- name: Upload to release
|
||||
if: github.event_name == 'release'
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: apps/frontend/node-pty-*.zip
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Create a combined prebuilds package
|
||||
package-prebuilds:
|
||||
needs: build-windows
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
- name: List artifacts
|
||||
run: |
|
||||
echo "Downloaded artifacts:"
|
||||
find artifacts -type f -name "*.zip"
|
||||
|
||||
- name: Upload combined artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: node-pty-prebuilds-all
|
||||
path: artifacts/**/*.zip
|
||||
retention-days: 90
|
||||
@@ -0,0 +1,109 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
pull_request:
|
||||
branches: [main, develop]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
# Python tests
|
||||
test-python:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ['3.12', '3.13']
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v4
|
||||
with:
|
||||
version: "latest"
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: apps/backend
|
||||
run: |
|
||||
uv venv
|
||||
uv pip install -r requirements.txt
|
||||
uv pip install -r ../../tests/requirements-test.txt
|
||||
|
||||
- name: Run tests
|
||||
if: matrix.python-version != '3.12'
|
||||
working-directory: apps/backend
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}/apps/backend
|
||||
run: |
|
||||
source .venv/bin/activate
|
||||
pytest ../../tests/ -v --tb=short -x
|
||||
|
||||
- name: Run tests with coverage
|
||||
if: matrix.python-version == '3.12'
|
||||
working-directory: apps/backend
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}/apps/backend
|
||||
run: |
|
||||
source .venv/bin/activate
|
||||
pytest ../../tests/ -v --cov=. --cov-report=xml --cov-report=term-missing --cov-fail-under=20
|
||||
|
||||
- name: Upload coverage reports
|
||||
if: matrix.python-version == '3.12'
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
file: ./apps/backend/coverage.xml
|
||||
fail_ci_if_error: false
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
# Frontend lint, typecheck, test, and build
|
||||
test-frontend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '24'
|
||||
|
||||
- name: Get npm cache directory
|
||||
id: npm-cache
|
||||
run: echo "dir=$(npm config get cache)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.npm-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: ${{ runner.os }}-npm-
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: apps/frontend
|
||||
run: npm ci --ignore-scripts
|
||||
|
||||
- name: Lint
|
||||
working-directory: apps/frontend
|
||||
run: npm run lint
|
||||
|
||||
- name: Type check
|
||||
working-directory: apps/frontend
|
||||
run: npm run typecheck
|
||||
|
||||
- name: Run tests
|
||||
working-directory: apps/frontend
|
||||
run: npm run test
|
||||
|
||||
- name: Build
|
||||
working-directory: apps/frontend
|
||||
run: npm run build
|
||||
@@ -0,0 +1,24 @@
|
||||
name: Discord Release Notification
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
discord-notification:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Send to Discord
|
||||
uses: SethCohen/github-releases-to-discord@v1.19.0
|
||||
with:
|
||||
webhook_url: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
color: "5793266"
|
||||
username: "Auto Claude Releases"
|
||||
avatar_url: "https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png"
|
||||
footer_title: "Auto Claude Changelog"
|
||||
footer_timestamp: true
|
||||
reduce_headings: true
|
||||
remove_github_reference_links: true
|
||||
@@ -0,0 +1,53 @@
|
||||
name: Issue Auto Label
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
label-area:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- name: Add area label from form
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const issue = context.payload.issue;
|
||||
const body = issue.body || '';
|
||||
|
||||
console.log(`Processing issue #${issue.number}: ${issue.title}`);
|
||||
|
||||
// Map form selection to label
|
||||
const areaMap = {
|
||||
'Frontend': 'area/frontend',
|
||||
'Backend': 'area/backend',
|
||||
'Fullstack': 'area/fullstack'
|
||||
};
|
||||
|
||||
const labels = [];
|
||||
|
||||
for (const [key, label] of Object.entries(areaMap)) {
|
||||
if (body.includes(key)) {
|
||||
console.log(`Found area: ${key}, adding label: ${label}`);
|
||||
labels.push(label);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (labels.length > 0) {
|
||||
try {
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
labels: labels
|
||||
});
|
||||
console.log(`Successfully added labels: ${labels.join(', ')}`);
|
||||
} catch (error) {
|
||||
core.setFailed(`Failed to add labels: ${error.message}`);
|
||||
}
|
||||
} else {
|
||||
console.log('No matching area found in issue body');
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
pull_request:
|
||||
branches: [main, develop]
|
||||
|
||||
jobs:
|
||||
# Python linting
|
||||
python:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Install ruff
|
||||
run: pip install ruff
|
||||
|
||||
- name: Run ruff check
|
||||
run: ruff check apps/backend/ --output-format=github
|
||||
|
||||
- name: Run ruff format check
|
||||
run: ruff format apps/backend/ --check --diff
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
name: PR Auto Label
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
# Cancel in-progress runs for the same PR
|
||||
concurrency:
|
||||
group: pr-auto-label-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
label:
|
||||
name: Auto Label PR
|
||||
runs-on: ubuntu-latest
|
||||
# Don't run on fork PRs (they can't write labels)
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Auto-label PR
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
retries: 3
|
||||
retry-exempt-status-codes: 400,401,403,404,422
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
const pr = context.payload.pull_request;
|
||||
const prNumber = pr.number;
|
||||
const title = pr.title;
|
||||
|
||||
console.log(`::group::PR #${prNumber} - Auto-labeling`);
|
||||
console.log(`Title: ${title}`);
|
||||
|
||||
const labelsToAdd = new Set();
|
||||
const labelsToRemove = new Set();
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// TYPE LABELS (from PR title - Conventional Commits)
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
const typeMap = {
|
||||
'feat': 'feature',
|
||||
'fix': 'bug',
|
||||
'docs': 'documentation',
|
||||
'refactor': 'refactor',
|
||||
'test': 'test',
|
||||
'ci': 'ci',
|
||||
'chore': 'chore',
|
||||
'perf': 'performance',
|
||||
'style': 'style',
|
||||
'build': 'build'
|
||||
};
|
||||
|
||||
const typeMatch = title.match(/^(\w+)(\(.+?\))?(!)?:/);
|
||||
if (typeMatch) {
|
||||
const type = typeMatch[1].toLowerCase();
|
||||
const isBreaking = typeMatch[3] === '!';
|
||||
|
||||
if (typeMap[type]) {
|
||||
labelsToAdd.add(typeMap[type]);
|
||||
console.log(` 📝 Type: ${type} → ${typeMap[type]}`);
|
||||
}
|
||||
|
||||
if (isBreaking) {
|
||||
labelsToAdd.add('breaking-change');
|
||||
console.log(` ⚠️ Breaking change detected`);
|
||||
}
|
||||
} else {
|
||||
console.log(` ⚠️ No conventional commit prefix found in title`);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// AREA LABELS (from changed files)
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
let files = [];
|
||||
try {
|
||||
const { data } = await github.rest.pulls.listFiles({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: prNumber,
|
||||
per_page: 100
|
||||
});
|
||||
files = data;
|
||||
} catch (e) {
|
||||
console.log(` ⚠️ Could not fetch files: ${e.message}`);
|
||||
}
|
||||
|
||||
const areas = {
|
||||
frontend: false,
|
||||
backend: false,
|
||||
ci: false,
|
||||
docs: false,
|
||||
tests: false
|
||||
};
|
||||
|
||||
for (const file of files) {
|
||||
const path = file.filename;
|
||||
if (path.startsWith('apps/frontend/')) areas.frontend = true;
|
||||
if (path.startsWith('apps/backend/')) areas.backend = true;
|
||||
if (path.startsWith('.github/')) areas.ci = true;
|
||||
if (path.endsWith('.md') || path.startsWith('docs/')) areas.docs = true;
|
||||
if (path.startsWith('tests/') || path.includes('.test.') || path.includes('.spec.')) areas.tests = true;
|
||||
}
|
||||
|
||||
// Determine area label (mutually exclusive)
|
||||
const areaLabels = ['area/frontend', 'area/backend', 'area/fullstack', 'area/ci'];
|
||||
|
||||
if (areas.frontend && areas.backend) {
|
||||
labelsToAdd.add('area/fullstack');
|
||||
areaLabels.filter(l => l !== 'area/fullstack').forEach(l => labelsToRemove.add(l));
|
||||
console.log(` 📁 Area: fullstack (${files.length} files)`);
|
||||
} else if (areas.frontend) {
|
||||
labelsToAdd.add('area/frontend');
|
||||
areaLabels.filter(l => l !== 'area/frontend').forEach(l => labelsToRemove.add(l));
|
||||
console.log(` 📁 Area: frontend (${files.length} files)`);
|
||||
} else if (areas.backend) {
|
||||
labelsToAdd.add('area/backend');
|
||||
areaLabels.filter(l => l !== 'area/backend').forEach(l => labelsToRemove.add(l));
|
||||
console.log(` 📁 Area: backend (${files.length} files)`);
|
||||
} else if (areas.ci) {
|
||||
labelsToAdd.add('area/ci');
|
||||
areaLabels.filter(l => l !== 'area/ci').forEach(l => labelsToRemove.add(l));
|
||||
console.log(` 📁 Area: ci (${files.length} files)`);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// SIZE LABELS (from lines changed)
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
const additions = pr.additions || 0;
|
||||
const deletions = pr.deletions || 0;
|
||||
const totalLines = additions + deletions;
|
||||
|
||||
const sizeLabels = ['size/XS', 'size/S', 'size/M', 'size/L', 'size/XL'];
|
||||
let sizeLabel;
|
||||
|
||||
if (totalLines < 10) sizeLabel = 'size/XS';
|
||||
else if (totalLines < 100) sizeLabel = 'size/S';
|
||||
else if (totalLines < 500) sizeLabel = 'size/M';
|
||||
else if (totalLines < 1000) sizeLabel = 'size/L';
|
||||
else sizeLabel = 'size/XL';
|
||||
|
||||
labelsToAdd.add(sizeLabel);
|
||||
sizeLabels.filter(l => l !== sizeLabel).forEach(l => labelsToRemove.add(l));
|
||||
console.log(` 📏 Size: ${sizeLabel} (+${additions}/-${deletions} = ${totalLines} lines)`);
|
||||
|
||||
console.log('::endgroup::');
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// APPLY LABELS
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
console.log(`::group::Applying labels`);
|
||||
|
||||
// Remove old labels (in parallel)
|
||||
const removeArray = [...labelsToRemove].filter(l => !labelsToAdd.has(l));
|
||||
if (removeArray.length > 0) {
|
||||
const removePromises = removeArray.map(async (label) => {
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
name: label
|
||||
});
|
||||
console.log(` ✓ Removed: ${label}`);
|
||||
} catch (e) {
|
||||
if (e.status !== 404) {
|
||||
console.log(` ⚠ Could not remove ${label}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
await Promise.all(removePromises);
|
||||
}
|
||||
|
||||
// Add new labels
|
||||
const addArray = [...labelsToAdd];
|
||||
if (addArray.length > 0) {
|
||||
try {
|
||||
await github.rest.issues.addLabels({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
labels: addArray
|
||||
});
|
||||
console.log(` ✓ Added: ${addArray.join(', ')}`);
|
||||
} catch (e) {
|
||||
// Some labels might not exist
|
||||
if (e.status === 404) {
|
||||
core.warning(`Some labels do not exist. Please create them in repository settings.`);
|
||||
// Try adding one by one
|
||||
for (const label of addArray) {
|
||||
try {
|
||||
await github.rest.issues.addLabels({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
labels: [label]
|
||||
});
|
||||
} catch (e2) {
|
||||
console.log(` ⚠ Label '${label}' does not exist`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('::endgroup::');
|
||||
|
||||
// Summary
|
||||
console.log(`✅ PR #${prNumber} labeled: ${addArray.join(', ')}`);
|
||||
|
||||
// Write job summary
|
||||
core.summary
|
||||
.addHeading(`PR #${prNumber} Auto-Labels`, 3)
|
||||
.addTable([
|
||||
[{data: 'Category', header: true}, {data: 'Label', header: true}],
|
||||
['Type', typeMatch ? typeMap[typeMatch[1].toLowerCase()] || 'none' : 'none'],
|
||||
['Area', areas.frontend && areas.backend ? 'fullstack' : areas.frontend ? 'frontend' : areas.backend ? 'backend' : 'other'],
|
||||
['Size', sizeLabel]
|
||||
])
|
||||
.addRaw(`\n**Files changed:** ${files.length}\n`)
|
||||
.addRaw(`**Lines:** +${additions} / -${deletions}\n`);
|
||||
await core.summary.write();
|
||||
@@ -0,0 +1,72 @@
|
||||
name: PR Status Check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
# Cancel in-progress runs for the same PR
|
||||
concurrency:
|
||||
group: pr-status-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
mark-checking:
|
||||
name: Set Checking Status
|
||||
runs-on: ubuntu-latest
|
||||
# Don't run on fork PRs (they can't write labels)
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Update PR status label
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
retries: 3
|
||||
retry-exempt-status-codes: 400,401,403,404,422
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
const prNumber = context.payload.pull_request.number;
|
||||
const statusLabels = ['🔄 Checking', '✅ Ready for Review', '❌ Checks Failed'];
|
||||
|
||||
console.log(`::group::PR #${prNumber} - Setting status to Checking`);
|
||||
|
||||
// Remove old status labels (parallel for speed)
|
||||
const removePromises = statusLabels.map(async (label) => {
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
name: label
|
||||
});
|
||||
console.log(` ✓ Removed: ${label}`);
|
||||
} catch (e) {
|
||||
if (e.status !== 404) {
|
||||
console.log(` ⚠ Could not remove ${label}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(removePromises);
|
||||
|
||||
// Add checking label
|
||||
try {
|
||||
await github.rest.issues.addLabels({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
labels: ['🔄 Checking']
|
||||
});
|
||||
console.log(` ✓ Added: 🔄 Checking`);
|
||||
} catch (e) {
|
||||
// Label might not exist - create helpful error
|
||||
if (e.status === 404) {
|
||||
core.warning(`Label '🔄 Checking' does not exist. Please create it in repository settings.`);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
console.log('::endgroup::');
|
||||
console.log(`✅ PR #${prNumber} marked as checking`);
|
||||
@@ -0,0 +1,195 @@
|
||||
name: PR Status Gate
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: [CI, Lint, Quality Security, Quality DCO, Quality Commit Lint]
|
||||
types: [completed]
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
checks: read
|
||||
|
||||
jobs:
|
||||
update-status:
|
||||
name: Update PR Status
|
||||
runs-on: ubuntu-latest
|
||||
# Only run if this workflow_run is associated with a PR
|
||||
if: github.event.workflow_run.pull_requests[0] != null
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Check all required checks and update label
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
retries: 3
|
||||
retry-exempt-status-codes: 400,401,403,404,422
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
const prNumber = context.payload.workflow_run.pull_requests[0].number;
|
||||
const headSha = context.payload.workflow_run.head_sha;
|
||||
const triggerWorkflow = context.payload.workflow_run.name;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// REQUIRED CHECK RUNS - Job-level checks (not workflow-level)
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// Format: "{Workflow Name} / {Job Name} (pull_request)"
|
||||
//
|
||||
// To find check names: Go to PR → Checks tab → copy exact name
|
||||
// To update: Edit this list when workflow jobs are added/renamed/removed
|
||||
//
|
||||
// Last validated: 2025-12-26
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
const requiredChecks = [
|
||||
// CI workflow (ci.yml) - 3 checks
|
||||
'CI / test-frontend (pull_request)',
|
||||
'CI / test-python (3.12) (pull_request)',
|
||||
'CI / test-python (3.13) (pull_request)',
|
||||
// Lint workflow (lint.yml) - 1 check
|
||||
'Lint / python (pull_request)',
|
||||
// Quality Security workflow (quality-security.yml) - 4 checks
|
||||
'Quality Security / CodeQL (javascript-typescript) (pull_request)',
|
||||
'Quality Security / CodeQL (python) (pull_request)',
|
||||
'Quality Security / Python Security (Bandit) (pull_request)',
|
||||
'Quality Security / Security Summary (pull_request)',
|
||||
// Quality DCO workflow (quality-dco.yml) - 1 check
|
||||
'Quality DCO / DCO Check (pull_request)',
|
||||
// Quality Commit Lint workflow (quality-commit-lint.yml) - 1 check
|
||||
'Quality Commit Lint / Conventional Commits (pull_request)'
|
||||
];
|
||||
|
||||
const statusLabels = {
|
||||
checking: '🔄 Checking',
|
||||
passed: '✅ Ready for Review',
|
||||
failed: '❌ Checks Failed'
|
||||
};
|
||||
|
||||
console.log(`::group::PR #${prNumber} - Checking required checks`);
|
||||
console.log(`Triggered by: ${triggerWorkflow}`);
|
||||
console.log(`Head SHA: ${headSha}`);
|
||||
console.log(`Required checks: ${requiredChecks.length}`);
|
||||
console.log('');
|
||||
|
||||
// Fetch all check runs for this commit
|
||||
let allCheckRuns = [];
|
||||
try {
|
||||
const { data } = await github.rest.checks.listForRef({
|
||||
owner,
|
||||
repo,
|
||||
ref: headSha,
|
||||
per_page: 100
|
||||
});
|
||||
allCheckRuns = data.check_runs;
|
||||
console.log(`Found ${allCheckRuns.length} total check runs`);
|
||||
} catch (error) {
|
||||
// Add warning annotation so maintainers are alerted
|
||||
core.warning(`Failed to fetch check runs for PR #${prNumber}: ${error.message}. PR label may be outdated.`);
|
||||
console.log(`::error::Failed to fetch check runs: ${error.message}`);
|
||||
console.log('::endgroup::');
|
||||
return;
|
||||
}
|
||||
|
||||
let allComplete = true;
|
||||
let anyFailed = false;
|
||||
const results = [];
|
||||
|
||||
// Check each required check
|
||||
for (const checkName of requiredChecks) {
|
||||
const check = allCheckRuns.find(c => c.name === checkName);
|
||||
|
||||
if (!check) {
|
||||
results.push({ name: checkName, status: '⏳ Pending', complete: false });
|
||||
allComplete = false;
|
||||
} else if (check.status !== 'completed') {
|
||||
results.push({ name: checkName, status: '🔄 Running', complete: false });
|
||||
allComplete = false;
|
||||
} else if (check.conclusion === 'success') {
|
||||
results.push({ name: checkName, status: '✅ Passed', complete: true });
|
||||
} else if (check.conclusion === 'skipped') {
|
||||
// Skipped checks are treated as passed (e.g., path filters, conditional jobs)
|
||||
results.push({ name: checkName, status: '⏭️ Skipped', complete: true, skipped: true });
|
||||
} else {
|
||||
results.push({ name: checkName, status: '❌ Failed', complete: true, failed: true });
|
||||
anyFailed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Print results table
|
||||
console.log('');
|
||||
console.log('Check Status:');
|
||||
console.log('─'.repeat(70));
|
||||
for (const r of results) {
|
||||
const shortName = r.name.length > 55 ? r.name.substring(0, 52) + '...' : r.name;
|
||||
console.log(` ${r.status.padEnd(12)} ${shortName}`);
|
||||
}
|
||||
console.log('─'.repeat(70));
|
||||
console.log('::endgroup::');
|
||||
|
||||
// Only update label if all required checks are complete
|
||||
if (!allComplete) {
|
||||
const pending = results.filter(r => !r.complete).length;
|
||||
console.log(`⏳ ${pending}/${requiredChecks.length} checks still pending - keeping current label`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine final label
|
||||
const newLabel = anyFailed ? statusLabels.failed : statusLabels.passed;
|
||||
|
||||
console.log(`::group::Updating PR #${prNumber} label`);
|
||||
|
||||
// Remove old status labels
|
||||
for (const label of Object.values(statusLabels)) {
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
name: label
|
||||
});
|
||||
console.log(` ✓ Removed: ${label}`);
|
||||
} catch (e) {
|
||||
if (e.status !== 404) {
|
||||
console.log(` ⚠ Could not remove ${label}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add final status label
|
||||
try {
|
||||
await github.rest.issues.addLabels({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
labels: [newLabel]
|
||||
});
|
||||
console.log(` ✓ Added: ${newLabel}`);
|
||||
} catch (e) {
|
||||
if (e.status === 404) {
|
||||
core.warning(`Label '${newLabel}' does not exist. Please create it in repository settings.`);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
console.log('::endgroup::');
|
||||
|
||||
// Summary
|
||||
const passedCount = results.filter(r => r.status === '✅ Passed').length;
|
||||
const skippedCount = results.filter(r => r.skipped).length;
|
||||
const failedCount = results.filter(r => r.failed).length;
|
||||
|
||||
if (anyFailed) {
|
||||
console.log(`❌ PR #${prNumber} has ${failedCount} failing check(s)`);
|
||||
core.summary.addRaw(`## ❌ PR #${prNumber} - Checks Failed\n\n`);
|
||||
core.summary.addRaw(`**${failedCount}** of **${requiredChecks.length}** required checks failed.\n\n`);
|
||||
} else {
|
||||
const skippedNote = skippedCount > 0 ? ` (${skippedCount} skipped)` : '';
|
||||
const totalSuccessful = passedCount + skippedCount;
|
||||
console.log(`✅ PR #${prNumber} is ready for review (${totalSuccessful}/${requiredChecks.length} checks succeeded${skippedNote})`);
|
||||
core.summary.addRaw(`## ✅ PR #${prNumber} - Ready for Review\n\n`);
|
||||
core.summary.addRaw(`All **${requiredChecks.length}** required checks succeeded${skippedNote}.\n\n`);
|
||||
}
|
||||
|
||||
// Add results to summary
|
||||
core.summary.addTable([
|
||||
[{data: 'Check', header: true}, {data: 'Status', header: true}],
|
||||
...results.map(r => [r.name, r.status])
|
||||
]);
|
||||
await core.summary.write();
|
||||
@@ -0,0 +1,109 @@
|
||||
name: Prepare Release
|
||||
|
||||
# Triggers when code is pushed to main (e.g., merging develop → main)
|
||||
# If package.json version is newer than the latest tag, creates a new tag
|
||||
# which then triggers the release.yml workflow
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'apps/frontend/package.json'
|
||||
- 'package.json'
|
||||
|
||||
jobs:
|
||||
check-and-tag:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
outputs:
|
||||
should_release: ${{ steps.check.outputs.should_release }}
|
||||
new_version: ${{ steps.check.outputs.new_version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get package version
|
||||
id: package
|
||||
run: |
|
||||
VERSION=$(node -p "require('./apps/frontend/package.json').version")
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Package version: $VERSION"
|
||||
|
||||
- name: Get latest tag version
|
||||
id: latest_tag
|
||||
run: |
|
||||
# Get the latest version tag (v*)
|
||||
LATEST_TAG=$(git tag -l 'v*' --sort=-version:refname | head -n1)
|
||||
if [ -z "$LATEST_TAG" ]; then
|
||||
echo "No existing tags found"
|
||||
echo "version=0.0.0" >> $GITHUB_OUTPUT
|
||||
else
|
||||
# Remove 'v' prefix
|
||||
LATEST_VERSION=${LATEST_TAG#v}
|
||||
echo "version=$LATEST_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Latest tag: $LATEST_TAG (version: $LATEST_VERSION)"
|
||||
fi
|
||||
|
||||
- name: Check if release needed
|
||||
id: check
|
||||
run: |
|
||||
PACKAGE_VERSION="${{ steps.package.outputs.version }}"
|
||||
LATEST_VERSION="${{ steps.latest_tag.outputs.version }}"
|
||||
|
||||
echo "Comparing: package=$PACKAGE_VERSION vs latest_tag=$LATEST_VERSION"
|
||||
|
||||
# Use sort -V for version comparison
|
||||
HIGHER=$(printf '%s\n%s' "$PACKAGE_VERSION" "$LATEST_VERSION" | sort -V | tail -n1)
|
||||
|
||||
if [ "$HIGHER" = "$PACKAGE_VERSION" ] && [ "$PACKAGE_VERSION" != "$LATEST_VERSION" ]; then
|
||||
echo "should_release=true" >> $GITHUB_OUTPUT
|
||||
echo "new_version=$PACKAGE_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "✅ New release needed: v$PACKAGE_VERSION"
|
||||
else
|
||||
echo "should_release=false" >> $GITHUB_OUTPUT
|
||||
echo "⏭️ No release needed (package version not newer than latest tag)"
|
||||
fi
|
||||
|
||||
- name: Create and push tag
|
||||
if: steps.check.outputs.should_release == 'true'
|
||||
run: |
|
||||
VERSION="${{ steps.check.outputs.new_version }}"
|
||||
TAG="v$VERSION"
|
||||
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
echo "Creating tag: $TAG"
|
||||
git tag -a "$TAG" -m "Release $TAG"
|
||||
git push origin "$TAG"
|
||||
|
||||
echo "✅ Tag $TAG created and pushed"
|
||||
echo "🚀 This will trigger the release workflow"
|
||||
|
||||
- name: Summary
|
||||
run: |
|
||||
if [ "${{ steps.check.outputs.should_release }}" = "true" ]; then
|
||||
echo "## 🚀 Release Triggered" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Version:** v${{ steps.check.outputs.new_version }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "The release workflow has been triggered and will:" >> $GITHUB_STEP_SUMMARY
|
||||
echo "1. Build binaries for all platforms" >> $GITHUB_STEP_SUMMARY
|
||||
echo "2. Generate changelog from PRs" >> $GITHUB_STEP_SUMMARY
|
||||
echo "3. Create GitHub release" >> $GITHUB_STEP_SUMMARY
|
||||
echo "4. Update README with new version" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "## ⏭️ No Release Needed" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Package version:** ${{ steps.package.outputs.version }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Latest tag:** v${{ steps.latest_tag.outputs.version }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "The package version is not newer than the latest tag." >> $GITHUB_STEP_SUMMARY
|
||||
echo "To trigger a release, bump the version using:" >> $GITHUB_STEP_SUMMARY
|
||||
echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY
|
||||
echo "node scripts/bump-version.js patch # or minor/major" >> $GITHUB_STEP_SUMMARY
|
||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
@@ -0,0 +1,115 @@
|
||||
name: Quality Commit Lint
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main, develop]
|
||||
types: [opened, edited, synchronize, reopened]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Conventional Commits
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Validate PR title
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
retries: 3
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
const title = pr.title;
|
||||
// Sanitize title for safe markdown interpolation (prevent injection)
|
||||
const sanitizedTitle = title.replace(/`/g, "'").replace(/\[/g, '\\[').replace(/\]/g, '\\]');
|
||||
|
||||
console.log(`::group::PR #${pr.number} - Validating PR title`);
|
||||
console.log(`Title: ${title}`);
|
||||
|
||||
// Conventional Commits pattern for PR title
|
||||
// type(scope)?: description (max 100 chars)
|
||||
// Optional ! for breaking changes: feat!: or feat(scope)!:
|
||||
// Scope allows: letters, numbers, hyphens, underscores, slashes, dots
|
||||
const pattern = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-zA-Z0-9_\-\/\.]+\))?!?: .{1,100}$/;
|
||||
|
||||
const isValid = pattern.test(title);
|
||||
console.log(`Valid: ${isValid}`);
|
||||
console.log('::endgroup::');
|
||||
|
||||
if (!isValid) {
|
||||
// Log helpful error message to console (visible in workflow logs)
|
||||
console.log('');
|
||||
console.log('❌ PR title does not follow Conventional Commits format');
|
||||
console.log('');
|
||||
console.log('Expected format: type(scope): description');
|
||||
console.log('');
|
||||
console.log('Valid types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert');
|
||||
console.log('');
|
||||
console.log('Examples of valid PR titles:');
|
||||
console.log(' ✓ feat(auth): add OAuth2 login support');
|
||||
console.log(' ✓ fix(api): handle null response correctly');
|
||||
console.log(' ✓ docs: update README installation steps');
|
||||
console.log(' ✓ chore: update dependencies');
|
||||
console.log('');
|
||||
console.log(`Your title: "${title}"`);
|
||||
console.log('');
|
||||
console.log('Suggested fix for your title:');
|
||||
// Try to suggest a fix based on the title
|
||||
const lowerTitle = title.toLowerCase();
|
||||
const placeholder = '[add description here]';
|
||||
if (lowerTitle.includes('fix') || lowerTitle.includes('bug')) {
|
||||
const cleaned = title.replace(/^(fix(ed|es|ing)?|bug)[:\s]*/i, '').trim();
|
||||
console.log(` → fix: ${cleaned || placeholder}`);
|
||||
} else if (lowerTitle.includes('add') || lowerTitle.includes('new') || lowerTitle.includes('feature')) {
|
||||
const cleaned = title.replace(/^(add(ed|s|ing)?|new|features?)[:\s]*/i, '').trim();
|
||||
console.log(` → feat: ${cleaned || placeholder}`);
|
||||
} else if (lowerTitle.includes('update') || lowerTitle.includes('change')) {
|
||||
const cleaned = title.replace(/^(update[ds]?|chang(ed|es|ing)?)[:\s]*/i, '').trim();
|
||||
console.log(` → chore: ${cleaned || placeholder}`);
|
||||
} else if (lowerTitle.includes('doc') || lowerTitle.includes('readme')) {
|
||||
const cleaned = title.replace(/^(docs?|readme)[:\s]*/i, '').trim();
|
||||
console.log(` → docs: ${cleaned || placeholder}`);
|
||||
} else {
|
||||
console.log(` → feat: ${title}`);
|
||||
console.log(` → fix: ${title}`);
|
||||
console.log(` → chore: ${title}`);
|
||||
}
|
||||
console.log('');
|
||||
|
||||
let errorMsg = '## ❌ PR Title Validation Failed\n\n';
|
||||
errorMsg += `Your PR title does not follow [Conventional Commits](https://www.conventionalcommits.org/) format:\n\n`;
|
||||
errorMsg += `> \`${sanitizedTitle}\`\n\n`;
|
||||
errorMsg += '### Expected Format\n\n';
|
||||
errorMsg += '```\ntype(scope): description\n```\n\n';
|
||||
errorMsg += '| Type | Description |\n';
|
||||
errorMsg += '|------|-------------|\n';
|
||||
errorMsg += '| `feat` | New feature |\n';
|
||||
errorMsg += '| `fix` | Bug fix |\n';
|
||||
errorMsg += '| `docs` | Documentation only |\n';
|
||||
errorMsg += '| `style` | Code style (formatting, etc.) |\n';
|
||||
errorMsg += '| `refactor` | Code refactoring |\n';
|
||||
errorMsg += '| `perf` | Performance improvement |\n';
|
||||
errorMsg += '| `test` | Adding/updating tests |\n';
|
||||
errorMsg += '| `build` | Build system changes |\n';
|
||||
errorMsg += '| `ci` | CI/CD changes |\n';
|
||||
errorMsg += '| `chore` | Maintenance tasks |\n';
|
||||
errorMsg += '| `revert` | Reverting changes |\n\n';
|
||||
errorMsg += '### Examples\n\n';
|
||||
errorMsg += '```\nfeat(auth): add OAuth2 login support\nfix(api/users): handle null response correctly\nfix(package.json): update dependencies\ndocs: update README installation steps\nci: add automated release workflow\n```\n\n';
|
||||
errorMsg += '### How to Fix\n\n';
|
||||
errorMsg += 'Edit your PR title to follow the format above.\n';
|
||||
|
||||
core.summary.addRaw(errorMsg);
|
||||
await core.summary.write();
|
||||
|
||||
core.setFailed('PR title does not follow Conventional Commits format');
|
||||
} else {
|
||||
console.log(`✅ PR title follows Conventional Commits format`);
|
||||
|
||||
core.summary
|
||||
.addHeading('✅ PR Title Valid', 3)
|
||||
.addRaw(`PR title follows Conventional Commits format: \`${sanitizedTitle}\``);
|
||||
await core.summary.write();
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
name: Quality DCO
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main, develop]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: DCO Check
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Check DCO Sign-off
|
||||
uses: dcoapp/dco-check@v1
|
||||
with:
|
||||
require-signoff: true
|
||||
|
||||
- name: DCO Help on Failure
|
||||
if: failure()
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const helpMsg = `## ❌ DCO Sign-off Required
|
||||
|
||||
This project requires all commits to be signed off with the Developer Certificate of Origin (DCO).
|
||||
|
||||
### How to Fix
|
||||
|
||||
**Option 1: Sign off your last commit**
|
||||
\`\`\`bash
|
||||
git commit --amend --signoff
|
||||
git push --force-with-lease
|
||||
\`\`\`
|
||||
|
||||
**Option 2: Sign off all commits in this PR**
|
||||
\`\`\`bash
|
||||
git rebase HEAD~N --signoff # Replace N with number of commits
|
||||
git push --force-with-lease
|
||||
\`\`\`
|
||||
|
||||
**Option 3: Configure git to always sign off**
|
||||
\`\`\`bash
|
||||
git config --global format.signoff true
|
||||
\`\`\`
|
||||
|
||||
### What is DCO?
|
||||
|
||||
The [Developer Certificate of Origin](https://developercertificate.org/) is a lightweight way for contributors to certify that they wrote or have the right to submit the code they are contributing.
|
||||
|
||||
By signing off, you agree to the DCO terms:
|
||||
- The contribution was created by you
|
||||
- You have the right to submit it under the project's license
|
||||
- You understand the contribution is public and recorded
|
||||
|
||||
### Sign-off Format
|
||||
|
||||
Your commit message should end with:
|
||||
\`\`\`
|
||||
Signed-off-by: Your Name <your.email@example.com>
|
||||
\`\`\`
|
||||
|
||||
This line is automatically added when you use \`git commit -s\` or \`git commit --signoff\`.
|
||||
`;
|
||||
|
||||
core.summary.addRaw(helpMsg);
|
||||
await core.summary.write();
|
||||
@@ -0,0 +1,178 @@
|
||||
name: Quality Security
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
pull_request:
|
||||
branches: [main, develop]
|
||||
schedule:
|
||||
- cron: '0 0 * * 1' # Weekly on Monday at midnight UTC
|
||||
|
||||
# Cancel in-progress runs for the same branch/PR
|
||||
concurrency:
|
||||
group: security-${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
codeql:
|
||||
name: CodeQL (${{ matrix.language }})
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [python, javascript-typescript]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
queries: +security-extended,security-and-quality
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "/language:${{ matrix.language }}"
|
||||
|
||||
python-security:
|
||||
name: Python Security (Bandit)
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Install Bandit
|
||||
run: pip install bandit
|
||||
|
||||
- name: Run Bandit security scan
|
||||
id: bandit
|
||||
run: |
|
||||
echo "::group::Running Bandit security scan"
|
||||
# Run Bandit; exit code 1 means issues found (expected), other codes are errors
|
||||
# Flags: -r=recursive, -ll=severity LOW+, -ii=confidence LOW+, -f=format, -o=output
|
||||
bandit -r apps/backend/ -ll -ii -f json -o bandit-report.json || BANDIT_EXIT=$?
|
||||
if [ "${BANDIT_EXIT:-0}" -gt 1 ]; then
|
||||
echo "::error::Bandit scan failed with exit code $BANDIT_EXIT"
|
||||
exit 1
|
||||
fi
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: Analyze Bandit results
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
|
||||
// Check if report exists
|
||||
if (!fs.existsSync('bandit-report.json')) {
|
||||
core.setFailed('Bandit report not found - scan may have failed');
|
||||
return;
|
||||
}
|
||||
|
||||
const report = JSON.parse(fs.readFileSync('bandit-report.json', 'utf8'));
|
||||
const results = report.results || [];
|
||||
|
||||
// Categorize by severity
|
||||
const high = results.filter(r => r.issue_severity === 'HIGH');
|
||||
const medium = results.filter(r => r.issue_severity === 'MEDIUM');
|
||||
const low = results.filter(r => r.issue_severity === 'LOW');
|
||||
|
||||
console.log(`::group::Bandit Security Scan Results`);
|
||||
console.log(`Found ${results.length} issues:`);
|
||||
console.log(` 🔴 HIGH: ${high.length}`);
|
||||
console.log(` 🟡 MEDIUM: ${medium.length}`);
|
||||
console.log(` 🟢 LOW: ${low.length}`);
|
||||
console.log('');
|
||||
|
||||
// Print high severity issues
|
||||
if (high.length > 0) {
|
||||
console.log('High Severity Issues:');
|
||||
console.log('─'.repeat(60));
|
||||
for (const issue of high) {
|
||||
console.log(` ${issue.filename}:${issue.line_number}`);
|
||||
console.log(` ${issue.issue_text}`);
|
||||
console.log(` Test: ${issue.test_id} (${issue.test_name})`);
|
||||
console.log('');
|
||||
}
|
||||
}
|
||||
console.log('::endgroup::');
|
||||
|
||||
// Build summary
|
||||
let summary = `## 🔒 Python Security Scan (Bandit)\n\n`;
|
||||
summary += `| Severity | Count |\n`;
|
||||
summary += `|----------|-------|\n`;
|
||||
summary += `| 🔴 High | ${high.length} |\n`;
|
||||
summary += `| 🟡 Medium | ${medium.length} |\n`;
|
||||
summary += `| 🟢 Low | ${low.length} |\n\n`;
|
||||
|
||||
if (high.length > 0) {
|
||||
summary += `### High Severity Issues\n\n`;
|
||||
for (const issue of high) {
|
||||
summary += `- **${issue.filename}:${issue.line_number}**\n`;
|
||||
summary += ` - ${issue.issue_text}\n`;
|
||||
summary += ` - Test: \`${issue.test_id}\` (${issue.test_name})\n\n`;
|
||||
}
|
||||
}
|
||||
|
||||
core.summary.addRaw(summary);
|
||||
await core.summary.write();
|
||||
|
||||
// Fail if high severity issues found
|
||||
if (high.length > 0) {
|
||||
core.setFailed(`Found ${high.length} high severity security issue(s)`);
|
||||
} else {
|
||||
console.log('✅ No high severity security issues found');
|
||||
}
|
||||
|
||||
# Summary job that waits for all security checks
|
||||
security-summary:
|
||||
name: Security Summary
|
||||
runs-on: ubuntu-latest
|
||||
needs: [codeql, python-security]
|
||||
if: always()
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Check security results
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const codeql = '${{ needs.codeql.result }}';
|
||||
const bandit = '${{ needs.python-security.result }}';
|
||||
|
||||
console.log('Security Check Results:');
|
||||
console.log(` CodeQL: ${codeql}`);
|
||||
console.log(` Bandit: ${bandit}`);
|
||||
|
||||
// Only 'failure' is a real failure; 'skipped' is acceptable (e.g., path filters)
|
||||
const acceptable = ['success', 'skipped'];
|
||||
const codeqlOk = acceptable.includes(codeql);
|
||||
const banditOk = acceptable.includes(bandit);
|
||||
const allPassed = codeqlOk && banditOk;
|
||||
|
||||
if (allPassed) {
|
||||
console.log('\n✅ All security checks passed');
|
||||
core.summary.addRaw('## ✅ Security Checks Passed\n\nAll security scans completed successfully.');
|
||||
} else {
|
||||
console.log('\n❌ Some security checks failed');
|
||||
core.summary.addRaw('## ❌ Security Checks Failed\n\nOne or more security scans found issues.');
|
||||
core.setFailed('Security checks failed');
|
||||
}
|
||||
|
||||
await core.summary.write();
|
||||
@@ -0,0 +1,534 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
dry_run:
|
||||
description: 'Test build without creating release'
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
# Intel build on Intel runner for native compilation
|
||||
# Note: macos-15-intel is the last Intel runner, supported until Fall 2027
|
||||
build-macos-intel:
|
||||
runs-on: macos-15-intel
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '24'
|
||||
|
||||
- name: Get npm cache directory
|
||||
id: npm-cache
|
||||
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.npm-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: ${{ runner.os }}-npm-
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd apps/frontend && npm ci
|
||||
|
||||
- name: Cache bundled Python
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: apps/frontend/python-runtime
|
||||
key: python-bundle-${{ runner.os }}-x64-3.12.8
|
||||
restore-keys: |
|
||||
python-bundle-${{ runner.os }}-x64-
|
||||
|
||||
- name: Build application
|
||||
run: cd apps/frontend && npm run build
|
||||
|
||||
- name: Package macOS (Intel)
|
||||
run: cd apps/frontend && npm run package:mac -- --x64
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CSC_LINK: ${{ secrets.MAC_CERTIFICATE }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.MAC_CERTIFICATE_PASSWORD }}
|
||||
|
||||
- name: Notarize macOS Intel app
|
||||
env:
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
run: |
|
||||
if [ -z "$APPLE_ID" ]; then
|
||||
echo "Skipping notarization: APPLE_ID not configured"
|
||||
exit 0
|
||||
fi
|
||||
cd apps/frontend
|
||||
for dmg in dist/*.dmg; do
|
||||
echo "Notarizing $dmg..."
|
||||
xcrun notarytool submit "$dmg" \
|
||||
--apple-id "$APPLE_ID" \
|
||||
--password "$APPLE_APP_SPECIFIC_PASSWORD" \
|
||||
--team-id "$APPLE_TEAM_ID" \
|
||||
--wait
|
||||
xcrun stapler staple "$dmg"
|
||||
echo "Successfully notarized and stapled $dmg"
|
||||
done
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macos-intel-builds
|
||||
path: |
|
||||
apps/frontend/dist/*.dmg
|
||||
apps/frontend/dist/*.zip
|
||||
|
||||
# Apple Silicon build on ARM64 runner for native compilation
|
||||
build-macos-arm64:
|
||||
runs-on: macos-15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '24'
|
||||
|
||||
- name: Get npm cache directory
|
||||
id: npm-cache
|
||||
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.npm-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: ${{ runner.os }}-npm-
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd apps/frontend && npm ci
|
||||
|
||||
- name: Cache bundled Python
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: apps/frontend/python-runtime
|
||||
key: python-bundle-${{ runner.os }}-arm64-3.12.8
|
||||
restore-keys: |
|
||||
python-bundle-${{ runner.os }}-arm64-
|
||||
|
||||
- name: Build application
|
||||
run: cd apps/frontend && npm run build
|
||||
|
||||
- name: Package macOS (Apple Silicon)
|
||||
run: cd apps/frontend && npm run package:mac -- --arm64
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CSC_LINK: ${{ secrets.MAC_CERTIFICATE }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.MAC_CERTIFICATE_PASSWORD }}
|
||||
|
||||
- name: Notarize macOS ARM64 app
|
||||
env:
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
run: |
|
||||
if [ -z "$APPLE_ID" ]; then
|
||||
echo "Skipping notarization: APPLE_ID not configured"
|
||||
exit 0
|
||||
fi
|
||||
cd apps/frontend
|
||||
for dmg in dist/*.dmg; do
|
||||
echo "Notarizing $dmg..."
|
||||
xcrun notarytool submit "$dmg" \
|
||||
--apple-id "$APPLE_ID" \
|
||||
--password "$APPLE_APP_SPECIFIC_PASSWORD" \
|
||||
--team-id "$APPLE_TEAM_ID" \
|
||||
--wait
|
||||
xcrun stapler staple "$dmg"
|
||||
echo "Successfully notarized and stapled $dmg"
|
||||
done
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macos-arm64-builds
|
||||
path: |
|
||||
apps/frontend/dist/*.dmg
|
||||
apps/frontend/dist/*.zip
|
||||
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '24'
|
||||
|
||||
- name: Get npm cache directory
|
||||
id: npm-cache
|
||||
shell: bash
|
||||
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.npm-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: ${{ runner.os }}-npm-
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd apps/frontend && npm ci
|
||||
|
||||
- name: Cache bundled Python
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: apps/frontend/python-runtime
|
||||
key: python-bundle-${{ runner.os }}-x64-3.12.8
|
||||
restore-keys: |
|
||||
python-bundle-${{ runner.os }}-x64-
|
||||
|
||||
- name: Build application
|
||||
run: cd apps/frontend && npm run build
|
||||
|
||||
- name: Package Windows
|
||||
run: cd apps/frontend && npm run package:win
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CSC_LINK: ${{ secrets.WIN_CERTIFICATE }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.WIN_CERTIFICATE_PASSWORD }}
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-builds
|
||||
path: |
|
||||
apps/frontend/dist/*.exe
|
||||
|
||||
build-linux:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '24'
|
||||
|
||||
- name: Get npm cache directory
|
||||
id: npm-cache
|
||||
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.npm-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: ${{ runner.os }}-npm-
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd apps/frontend && npm ci
|
||||
|
||||
- name: Cache bundled Python
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: apps/frontend/python-runtime
|
||||
key: python-bundle-${{ runner.os }}-x64-3.12.8
|
||||
restore-keys: |
|
||||
python-bundle-${{ runner.os }}-x64-
|
||||
|
||||
- name: Build application
|
||||
run: cd apps/frontend && npm run build
|
||||
|
||||
- name: Package Linux
|
||||
run: cd apps/frontend && npm run package:linux
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: linux-builds
|
||||
path: |
|
||||
apps/frontend/dist/*.AppImage
|
||||
apps/frontend/dist/*.deb
|
||||
|
||||
create-release:
|
||||
needs: [build-macos-intel, build-macos-arm64, build-windows, build-linux]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: dist
|
||||
|
||||
- name: Flatten and validate artifacts
|
||||
run: |
|
||||
mkdir -p release-assets
|
||||
find dist -type f \( -name "*.dmg" -o -name "*.zip" -o -name "*.exe" -o -name "*.AppImage" -o -name "*.deb" \) -exec cp {} release-assets/ \;
|
||||
|
||||
# Validate that at least one artifact was copied
|
||||
artifact_count=$(find release-assets -type f \( -name "*.dmg" -o -name "*.zip" -o -name "*.exe" -o -name "*.AppImage" -o -name "*.deb" \) | wc -l)
|
||||
if [ "$artifact_count" -eq 0 ]; then
|
||||
echo "::error::No build artifacts found! Expected .dmg, .zip, .exe, .AppImage, or .deb files."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Found $artifact_count artifact(s):"
|
||||
ls -la release-assets/
|
||||
|
||||
- name: Generate checksums
|
||||
run: |
|
||||
cd release-assets
|
||||
sha256sum ./* > checksums.sha256
|
||||
cat checksums.sha256
|
||||
|
||||
- name: Scan with VirusTotal
|
||||
id: virustotal
|
||||
continue-on-error: true
|
||||
if: ${{ github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.dry_run != true) }}
|
||||
env:
|
||||
VT_API_KEY: ${{ secrets.VIRUSTOTAL_API_KEY }}
|
||||
run: |
|
||||
if [ -z "$VT_API_KEY" ]; then
|
||||
echo "::warning::VIRUSTOTAL_API_KEY not configured, skipping scan"
|
||||
echo "vt_results=" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "## VirusTotal Scan Results" > vt_results.md
|
||||
echo "" >> vt_results.md
|
||||
|
||||
for file in release-assets/*.{exe,dmg,AppImage,deb}; do
|
||||
[ -f "$file" ] || continue
|
||||
filename=$(basename "$file")
|
||||
filesize=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file")
|
||||
echo "Scanning $filename (${filesize} bytes)..."
|
||||
|
||||
# For files > 32MB, get a special upload URL first
|
||||
if [ "$filesize" -gt 33554432 ]; then
|
||||
echo " Large file detected, requesting upload URL..."
|
||||
upload_url_response=$(curl -s --request GET \
|
||||
--url "https://www.virustotal.com/api/v3/files/upload_url" \
|
||||
--header "x-apikey: $VT_API_KEY")
|
||||
|
||||
upload_url=$(echo "$upload_url_response" | jq -r '.data // empty')
|
||||
if [ -z "$upload_url" ]; then
|
||||
echo "::warning::Failed to get upload URL for large file $filename"
|
||||
echo "Response: $upload_url_response"
|
||||
echo "- $filename - ⚠️ Upload failed (large file)" >> vt_results.md
|
||||
continue
|
||||
fi
|
||||
api_url="$upload_url"
|
||||
else
|
||||
api_url="https://www.virustotal.com/api/v3/files"
|
||||
fi
|
||||
|
||||
# Upload file to VirusTotal
|
||||
response=$(curl -s --request POST \
|
||||
--url "$api_url" \
|
||||
--header "x-apikey: $VT_API_KEY" \
|
||||
--form "file=@$file")
|
||||
|
||||
# Check if response is valid JSON before parsing
|
||||
if ! echo "$response" | jq -e . >/dev/null 2>&1; then
|
||||
echo "::warning::VirusTotal returned invalid JSON for $filename"
|
||||
echo "Response (first 500 chars): ${response:0:500}"
|
||||
echo "- $filename - ⚠️ Scan failed (invalid response)" >> vt_results.md
|
||||
continue
|
||||
fi
|
||||
|
||||
# Check for API error response
|
||||
error_code=$(echo "$response" | jq -r '.error.code // empty')
|
||||
if [ -n "$error_code" ]; then
|
||||
error_msg=$(echo "$response" | jq -r '.error.message // "Unknown error"')
|
||||
echo "::warning::VirusTotal API error for $filename: $error_code - $error_msg"
|
||||
echo "- $filename - ⚠️ Scan failed ($error_code)" >> vt_results.md
|
||||
continue
|
||||
fi
|
||||
|
||||
# Extract analysis ID
|
||||
analysis_id=$(echo "$response" | jq -r '.data.id // empty')
|
||||
|
||||
if [ -z "$analysis_id" ]; then
|
||||
echo "::warning::Failed to upload $filename to VirusTotal"
|
||||
echo "Response: $response"
|
||||
echo "- $filename - ⚠️ Upload failed" >> vt_results.md
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "Uploaded $filename, analysis ID: $analysis_id"
|
||||
|
||||
# Wait for analysis to complete (max 5 minutes per file)
|
||||
analysis=""
|
||||
for i in {1..30}; do
|
||||
sleep 10
|
||||
analysis=$(curl -s --request GET \
|
||||
--url "https://www.virustotal.com/api/v3/analyses/$analysis_id" \
|
||||
--header "x-apikey: $VT_API_KEY")
|
||||
|
||||
# Validate JSON response
|
||||
if ! echo "$analysis" | jq -e . >/dev/null 2>&1; then
|
||||
echo " Warning: Invalid JSON response on attempt $i, retrying..."
|
||||
continue
|
||||
fi
|
||||
|
||||
status=$(echo "$analysis" | jq -r '.data.attributes.status // "unknown"')
|
||||
echo " Status: $status (attempt $i/30)"
|
||||
|
||||
if [ "$status" = "completed" ]; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# Final validation that we have valid analysis data
|
||||
if ! echo "$analysis" | jq -e '.data.attributes.stats' >/dev/null 2>&1; then
|
||||
echo "::warning::Could not get complete analysis for $filename, using local hash"
|
||||
file_hash=$(sha256sum "$file" | cut -d' ' -f1)
|
||||
echo "- [$filename](https://www.virustotal.com/gui/file/$file_hash) - ⚠️ Analysis incomplete" >> vt_results.md
|
||||
continue
|
||||
fi
|
||||
|
||||
# Get file hash for permanent URL
|
||||
file_hash=$(echo "$analysis" | jq -r '.meta.file_info.sha256 // empty')
|
||||
|
||||
if [ -z "$file_hash" ]; then
|
||||
# Fallback: calculate hash locally
|
||||
file_hash=$(sha256sum "$file" | cut -d' ' -f1)
|
||||
fi
|
||||
|
||||
# Get detection stats
|
||||
malicious=$(echo "$analysis" | jq -r '.data.attributes.stats.malicious // 0')
|
||||
suspicious=$(echo "$analysis" | jq -r '.data.attributes.stats.suspicious // 0')
|
||||
undetected=$(echo "$analysis" | jq -r '.data.attributes.stats.undetected // 0')
|
||||
|
||||
vt_url="https://www.virustotal.com/gui/file/$file_hash"
|
||||
|
||||
if [ "$malicious" -gt 0 ] || [ "$suspicious" -gt 0 ]; then
|
||||
echo "::warning::$filename has $malicious malicious and $suspicious suspicious detections (likely false positives)"
|
||||
echo "- [$filename]($vt_url) - ⚠️ **$malicious malicious, $suspicious suspicious** detections (review recommended)" >> vt_results.md
|
||||
else
|
||||
echo "$filename is clean ($undetected engines, 0 detections)"
|
||||
echo "- [$filename]($vt_url) - ✅ Clean ($undetected engines, 0 detections)" >> vt_results.md
|
||||
fi
|
||||
done
|
||||
|
||||
echo "" >> vt_results.md
|
||||
|
||||
# Save results for release notes
|
||||
cat vt_results.md
|
||||
echo "vt_results<<EOF" >> $GITHUB_OUTPUT
|
||||
cat vt_results.md >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Dry run summary
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run == true }}
|
||||
run: |
|
||||
echo "## Dry Run Complete" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Build artifacts created successfully:" >> $GITHUB_STEP_SUMMARY
|
||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||
ls -la release-assets/ >> $GITHUB_STEP_SUMMARY
|
||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Checksums" >> $GITHUB_STEP_SUMMARY
|
||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||
cat release-assets/checksums.sha256 >> $GITHUB_STEP_SUMMARY
|
||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Generate changelog
|
||||
if: ${{ github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.dry_run != true) }}
|
||||
id: changelog
|
||||
uses: release-drafter/release-drafter@v6
|
||||
with:
|
||||
config-name: release-drafter.yml
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create Release
|
||||
if: ${{ github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.dry_run != true) }}
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
body: |
|
||||
${{ steps.changelog.outputs.body }}
|
||||
|
||||
${{ steps.virustotal.outputs.vt_results }}
|
||||
files: release-assets/*
|
||||
draft: false
|
||||
prerelease: ${{ contains(github.ref, 'beta') || contains(github.ref, 'alpha') }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Update README with new version after successful release
|
||||
update-readme:
|
||||
needs: [create-release]
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.dry_run != true) }}
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract version from tag
|
||||
id: version
|
||||
run: |
|
||||
# Extract version from tag (v2.7.2 -> 2.7.2)
|
||||
VERSION=${GITHUB_REF_NAME#v}
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Updating README to version: $VERSION"
|
||||
|
||||
- name: Update README.md
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
|
||||
# Update version badge: version-X.Y.Z-blue
|
||||
sed -i "s/version-[0-9]*\.[0-9]*\.[0-9]*-blue/version-${VERSION}-blue/g" README.md
|
||||
|
||||
# Update download links: Auto-Claude-X.Y.Z
|
||||
sed -i "s/Auto-Claude-[0-9]*\.[0-9]*\.[0-9]*/Auto-Claude-${VERSION}/g" README.md
|
||||
|
||||
echo "README.md updated to version $VERSION"
|
||||
grep -E "(version-|Auto-Claude-)" README.md | head -10
|
||||
|
||||
- name: Commit and push README update
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
# Check if there are changes to commit
|
||||
if git diff --quiet README.md; then
|
||||
echo "No changes to README.md, skipping commit"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git add README.md
|
||||
git commit -m "docs: update README to v${{ steps.version.outputs.version }} [skip ci]"
|
||||
git push origin main
|
||||
@@ -0,0 +1,25 @@
|
||||
name: Stale Issues
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * 0' # Every Sunday
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
stale-issue-message: |
|
||||
This issue has been inactive for 60 days. It will be closed in 14 days if there's no activity.
|
||||
|
||||
- If this is still relevant, please comment or update the issue
|
||||
- If you're working on this, add the `in-progress` label
|
||||
close-issue-message: 'Closed due to inactivity. Feel free to reopen if still relevant.'
|
||||
stale-issue-label: 'stale'
|
||||
days-before-stale: 60
|
||||
days-before-close: 14
|
||||
exempt-issue-labels: 'priority/critical,priority/high,in-progress,blocked'
|
||||
@@ -0,0 +1,63 @@
|
||||
name: Test on Tag
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
# Python tests
|
||||
test-python:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ['3.12', '3.13']
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v4
|
||||
with:
|
||||
version: "latest"
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: apps/backend
|
||||
run: |
|
||||
uv venv
|
||||
uv pip install -r requirements.txt
|
||||
uv pip install -r ../../tests/requirements-test.txt
|
||||
|
||||
- name: Run tests
|
||||
working-directory: apps/backend
|
||||
env:
|
||||
PYTHONPATH: ${{ github.workspace }}/apps/backend
|
||||
run: |
|
||||
source .venv/bin/activate
|
||||
pytest ../../tests/ -v --tb=short
|
||||
|
||||
# Frontend tests
|
||||
test-frontend:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '24'
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: apps/frontend
|
||||
run: npm ci --ignore-scripts
|
||||
|
||||
- name: Run tests
|
||||
working-directory: apps/frontend
|
||||
run: npm run test
|
||||
@@ -0,0 +1,71 @@
|
||||
name: Validate Version
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
validate-version:
|
||||
name: Validate package.json version matches tag
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Extract version from tag
|
||||
id: tag_version
|
||||
run: |
|
||||
# Extract version from tag (e.g., v2.5.5 -> 2.5.5)
|
||||
TAG_VERSION=${GITHUB_REF#refs/tags/v}
|
||||
echo "version=$TAG_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Tag version: $TAG_VERSION"
|
||||
|
||||
- name: Extract version from package.json
|
||||
id: package_version
|
||||
run: |
|
||||
# Read version from package.json
|
||||
PACKAGE_VERSION=$(node -p "require('./apps/frontend/package.json').version")
|
||||
echo "version=$PACKAGE_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Package.json version: $PACKAGE_VERSION"
|
||||
|
||||
- name: Compare versions
|
||||
run: |
|
||||
TAG_VERSION="${{ steps.tag_version.outputs.version }}"
|
||||
PACKAGE_VERSION="${{ steps.package_version.outputs.version }}"
|
||||
|
||||
echo "=========================================="
|
||||
echo "Version Validation"
|
||||
echo "=========================================="
|
||||
echo "Git tag version: v$TAG_VERSION"
|
||||
echo "package.json version: $PACKAGE_VERSION"
|
||||
echo "=========================================="
|
||||
|
||||
if [ "$TAG_VERSION" != "$PACKAGE_VERSION" ]; then
|
||||
echo ""
|
||||
echo "❌ ERROR: Version mismatch detected!"
|
||||
echo ""
|
||||
echo "The version in package.json ($PACKAGE_VERSION) does not match"
|
||||
echo "the git tag version ($TAG_VERSION)."
|
||||
echo ""
|
||||
echo "To fix this:"
|
||||
echo " 1. Delete this tag: git tag -d v$TAG_VERSION"
|
||||
echo " 2. Update package.json version to $TAG_VERSION"
|
||||
echo " 3. Commit the change"
|
||||
echo " 4. Recreate the tag: git tag -a v$TAG_VERSION -m 'Release v$TAG_VERSION'"
|
||||
echo ""
|
||||
echo "Or use the automated script:"
|
||||
echo " node scripts/bump-version.js $TAG_VERSION"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "✅ SUCCESS: Versions match!"
|
||||
echo ""
|
||||
|
||||
- name: Version validation result
|
||||
if: success()
|
||||
run: |
|
||||
echo "::notice::Version validation passed - package.json version matches tag v${{ steps.tag_version.outputs.version }}"
|
||||
@@ -0,0 +1,33 @@
|
||||
name: Welcome
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
welcome:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/first-interaction@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-message: |
|
||||
👋 Thanks for opening your first issue!
|
||||
|
||||
A maintainer will triage this soon. In the meantime:
|
||||
- Make sure you've provided all the requested info
|
||||
- Join our [Discord](https://discord.gg/QhRnz9m5HE) for faster help
|
||||
pr-message: |
|
||||
🎉 Thanks for your first PR!
|
||||
|
||||
A maintainer will review it soon. Please make sure:
|
||||
- Your branch is synced with `develop`
|
||||
- CI checks pass
|
||||
- You've followed our [contribution guide](https://github.com/AndyMik90/Auto-Claude/blob/develop/CONTRIBUTING.md)
|
||||
|
||||
Welcome to the Auto Claude community!
|
||||
+114
-26
@@ -1,27 +1,69 @@
|
||||
# OS
|
||||
# ===========================
|
||||
# OS Files
|
||||
# ===========================
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
|
||||
# Git worktrees (used by auto-build parallel mode)
|
||||
.worktrees/
|
||||
# ===========================
|
||||
# Security - Environment & Secrets
|
||||
# ===========================
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
*.pem
|
||||
*.key
|
||||
*.crt
|
||||
*.p12
|
||||
*.pfx
|
||||
.secrets
|
||||
secrets/
|
||||
credentials/
|
||||
|
||||
# IDE
|
||||
# ===========================
|
||||
# IDE & Editors
|
||||
# ===========================
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*.sublime-workspace
|
||||
*.sublime-project
|
||||
.project
|
||||
.classpath
|
||||
.settings/
|
||||
|
||||
# ===========================
|
||||
# Logs
|
||||
# ===========================
|
||||
logs/
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Personal notes
|
||||
OPUS_ANALYSIS_AND_IDEAS.md
|
||||
# ===========================
|
||||
# Git Worktrees (parallel builds)
|
||||
# ===========================
|
||||
.worktrees/
|
||||
|
||||
# Documentation
|
||||
docs/
|
||||
# ===========================
|
||||
# Auto Claude Generated
|
||||
# ===========================
|
||||
.auto-claude/
|
||||
.auto-build-security.json
|
||||
.auto-claude-security.json
|
||||
.auto-claude-status
|
||||
.claude_settings.json
|
||||
.update-metadata.json
|
||||
|
||||
# Python
|
||||
# ===========================
|
||||
# Python (apps/backend)
|
||||
# ===========================
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
@@ -29,25 +71,19 @@ __pycache__/
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
/lib/
|
||||
/lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# Virtual environments
|
||||
.venv/
|
||||
venv/
|
||||
ENV/
|
||||
env/
|
||||
.conda/
|
||||
|
||||
# Testing
|
||||
.pytest_cache/
|
||||
@@ -60,18 +96,70 @@ coverage.xml
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
|
||||
# mypy
|
||||
# Type checking
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
.pytype/
|
||||
.pyre/
|
||||
|
||||
# Auto-build generated files
|
||||
.auto-build-security.json
|
||||
.auto-claude-security.json
|
||||
.auto-claude-status
|
||||
.claude_settings.json
|
||||
# ===========================
|
||||
# Node.js (apps/frontend)
|
||||
# ===========================
|
||||
node_modules/
|
||||
.npm
|
||||
.yarn/
|
||||
.pnp.*
|
||||
|
||||
# Development of Auto Build with Auto Build
|
||||
# Build output
|
||||
dist/
|
||||
out/
|
||||
*.tsbuildinfo
|
||||
apps/frontend/python-runtime/
|
||||
|
||||
# Cache
|
||||
.cache/
|
||||
.parcel-cache/
|
||||
.turbo/
|
||||
.eslintcache
|
||||
.prettiercache
|
||||
|
||||
# ===========================
|
||||
# Electron
|
||||
# ===========================
|
||||
apps/frontend/dist/
|
||||
apps/frontend/out/
|
||||
*.asar
|
||||
*.blockmap
|
||||
*.snap
|
||||
*.deb
|
||||
*.rpm
|
||||
*.AppImage
|
||||
*.dmg
|
||||
*.exe
|
||||
*.msi
|
||||
|
||||
# ===========================
|
||||
# Testing
|
||||
# ===========================
|
||||
coverage/
|
||||
.nyc_output/
|
||||
test-results/
|
||||
playwright-report/
|
||||
playwright/.cache/
|
||||
|
||||
# ===========================
|
||||
# Misc
|
||||
# ===========================
|
||||
*.local
|
||||
*.bak
|
||||
*.tmp
|
||||
*.temp
|
||||
|
||||
# Development
|
||||
dev/
|
||||
|
||||
.auto-claude/
|
||||
_bmad/
|
||||
_bmad-output/
|
||||
.claude/
|
||||
/docs
|
||||
OPUS_ANALYSIS_AND_IDEAS.md
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Commit message validation
|
||||
# Enforces conventional commit format: type(scope): description
|
||||
#
|
||||
# Valid types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
|
||||
# Examples:
|
||||
# feat(tasks): add drag and drop support
|
||||
# fix(terminal): resolve scroll position issue
|
||||
# docs: update README with setup instructions
|
||||
# chore: update dependencies
|
||||
|
||||
commit_msg_file=$1
|
||||
commit_msg=$(cat "$commit_msg_file")
|
||||
|
||||
# Regex for conventional commits
|
||||
# Format: type(optional-scope): description
|
||||
pattern="^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-z0-9-]+\))?: .{1,100}$"
|
||||
|
||||
# Allow merge commits
|
||||
if echo "$commit_msg" | grep -qE "^Merge "; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Allow revert commits
|
||||
if echo "$commit_msg" | grep -qE "^Revert "; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check first line against pattern
|
||||
first_line=$(echo "$commit_msg" | head -n 1)
|
||||
|
||||
if ! echo "$first_line" | grep -qE "$pattern"; then
|
||||
echo ""
|
||||
echo "ERROR: Invalid commit message format!"
|
||||
echo ""
|
||||
echo "Your message: $first_line"
|
||||
echo ""
|
||||
echo "Expected format: type(scope): description"
|
||||
echo ""
|
||||
echo "Valid types:"
|
||||
echo " feat - A new feature"
|
||||
echo " fix - A bug fix"
|
||||
echo " docs - Documentation changes"
|
||||
echo " style - Code style changes (formatting, semicolons, etc.)"
|
||||
echo " refactor - Code refactoring (no feature/fix)"
|
||||
echo " perf - Performance improvements"
|
||||
echo " test - Adding or updating tests"
|
||||
echo " build - Build system or dependencies"
|
||||
echo " ci - CI/CD configuration"
|
||||
echo " chore - Other changes (maintenance)"
|
||||
echo " revert - Reverting a previous commit"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " feat(tasks): add drag and drop support"
|
||||
echo " fix(terminal): resolve scroll position issue"
|
||||
echo " docs: update README"
|
||||
echo " chore: update dependencies"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check description length (max 100 chars for first line)
|
||||
if [ ${#first_line} -gt 100 ]; then
|
||||
echo ""
|
||||
echo "ERROR: Commit message first line is too long!"
|
||||
echo "Maximum: 100 characters"
|
||||
echo "Current: ${#first_line} characters"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
Executable
+158
@@ -0,0 +1,158 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "Running pre-commit checks..."
|
||||
|
||||
# =============================================================================
|
||||
# VERSION SYNC - Keep all version references in sync with root package.json
|
||||
# =============================================================================
|
||||
|
||||
# Check if package.json is staged
|
||||
if git diff --cached --name-only | grep -q "^package.json$"; then
|
||||
echo "package.json changed, syncing version to all files..."
|
||||
|
||||
# Extract version from root package.json
|
||||
VERSION=$(node -p "require('./package.json').version")
|
||||
|
||||
if [ -n "$VERSION" ]; then
|
||||
# Sync to apps/frontend/package.json
|
||||
if [ -f "apps/frontend/package.json" ]; then
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const pkg = require('./apps/frontend/package.json');
|
||||
if (pkg.version !== '$VERSION') {
|
||||
pkg.version = '$VERSION';
|
||||
fs.writeFileSync('./apps/frontend/package.json', JSON.stringify(pkg, null, 2) + '\n');
|
||||
console.log(' Updated apps/frontend/package.json to $VERSION');
|
||||
}
|
||||
"
|
||||
git add apps/frontend/package.json
|
||||
fi
|
||||
|
||||
# Sync to apps/backend/__init__.py
|
||||
if [ -f "apps/backend/__init__.py" ]; then
|
||||
sed -i.bak "s/__version__ = \"[^\"]*\"/__version__ = \"$VERSION\"/" apps/backend/__init__.py
|
||||
rm -f apps/backend/__init__.py.bak
|
||||
git add apps/backend/__init__.py
|
||||
echo " Updated apps/backend/__init__.py to $VERSION"
|
||||
fi
|
||||
|
||||
# Sync to README.md
|
||||
if [ -f "README.md" ]; then
|
||||
# Update version badge
|
||||
sed -i.bak "s/version-[0-9]*\.[0-9]*\.[0-9]*-blue/version-$VERSION-blue/g" README.md
|
||||
# Update download links
|
||||
sed -i.bak "s/Auto-Claude-[0-9]*\.[0-9]*\.[0-9]*/Auto-Claude-$VERSION/g" README.md
|
||||
rm -f README.md.bak
|
||||
git add README.md
|
||||
echo " Updated README.md to $VERSION"
|
||||
fi
|
||||
|
||||
echo "Version sync complete: $VERSION"
|
||||
fi
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# BACKEND CHECKS (Python) - Run first, before frontend
|
||||
# =============================================================================
|
||||
|
||||
# Check if there are staged Python files in apps/backend
|
||||
if git diff --cached --name-only | grep -q "^apps/backend/.*\.py$"; then
|
||||
echo "Python changes detected, running backend checks..."
|
||||
|
||||
# Determine ruff command (venv or global)
|
||||
RUFF=""
|
||||
if [ -f "apps/backend/.venv/bin/ruff" ]; then
|
||||
RUFF="apps/backend/.venv/bin/ruff"
|
||||
elif [ -f "apps/backend/.venv/Scripts/ruff.exe" ]; then
|
||||
RUFF="apps/backend/.venv/Scripts/ruff.exe"
|
||||
elif command -v ruff >/dev/null 2>&1; then
|
||||
RUFF="ruff"
|
||||
fi
|
||||
|
||||
if [ -n "$RUFF" ]; then
|
||||
# Run ruff linting (auto-fix)
|
||||
echo "Running ruff lint..."
|
||||
$RUFF check apps/backend/ --fix
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Ruff lint failed. Please fix Python linting errors before committing."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Run ruff format (auto-fix)
|
||||
echo "Running ruff format..."
|
||||
$RUFF format apps/backend/
|
||||
|
||||
# Stage any files that were auto-fixed by ruff (POSIX-compliant)
|
||||
find apps/backend -name "*.py" -type f -exec git add {} + 2>/dev/null || true
|
||||
else
|
||||
echo "Warning: ruff not found, skipping Python linting. Install with: uv pip install ruff"
|
||||
fi
|
||||
|
||||
# Run pytest (skip slow/integration tests and Windows-incompatible tests for pre-commit speed)
|
||||
echo "Running Python tests..."
|
||||
cd apps/backend
|
||||
# Tests to skip: graphiti (external deps), merge_file_tracker/service_orchestrator/worktree/workspace (Windows path/git issues)
|
||||
IGNORE_TESTS="--ignore=../../tests/test_graphiti.py --ignore=../../tests/test_merge_file_tracker.py --ignore=../../tests/test_service_orchestrator.py --ignore=../../tests/test_worktree.py --ignore=../../tests/test_workspace.py"
|
||||
if [ -d ".venv" ]; then
|
||||
# Use venv if it exists
|
||||
if [ -f ".venv/bin/pytest" ]; then
|
||||
PYTHONPATH=. .venv/bin/pytest ../../tests/ -v --tb=short -x -m "not slow and not integration" $IGNORE_TESTS
|
||||
elif [ -f ".venv/Scripts/pytest.exe" ]; then
|
||||
# Windows
|
||||
PYTHONPATH=. .venv/Scripts/pytest.exe ../../tests/ -v --tb=short -x -m "not slow and not integration" $IGNORE_TESTS
|
||||
else
|
||||
PYTHONPATH=. python -m pytest ../../tests/ -v --tb=short -x -m "not slow and not integration" $IGNORE_TESTS
|
||||
fi
|
||||
else
|
||||
PYTHONPATH=. python -m pytest ../../tests/ -v --tb=short -x -m "not slow and not integration" $IGNORE_TESTS
|
||||
fi
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Python tests failed. Please fix failing tests before committing."
|
||||
exit 1
|
||||
fi
|
||||
cd ../..
|
||||
|
||||
echo "Backend checks passed!"
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
# FRONTEND CHECKS (TypeScript/React)
|
||||
# =============================================================================
|
||||
|
||||
# Check if there are staged files in apps/frontend
|
||||
if git diff --cached --name-only | grep -q "^apps/frontend/"; then
|
||||
echo "Frontend changes detected, running frontend checks..."
|
||||
cd apps/frontend
|
||||
|
||||
# Run lint-staged (handles staged .ts/.tsx files)
|
||||
npm exec lint-staged
|
||||
|
||||
# Run TypeScript type check
|
||||
echo "Running type check..."
|
||||
npm run typecheck
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Type check failed. Please fix TypeScript errors before committing."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Run linting
|
||||
echo "Running lint..."
|
||||
npm run lint
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Lint failed. Run 'npm run lint:fix' to auto-fix issues."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for vulnerabilities (only high severity)
|
||||
echo "Checking for vulnerabilities..."
|
||||
npm audit --audit-level=high
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "High severity vulnerabilities found. Run 'npm audit fix' to resolve."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd ../..
|
||||
echo "Frontend checks passed!"
|
||||
fi
|
||||
|
||||
echo "All pre-commit checks passed!"
|
||||
@@ -0,0 +1,70 @@
|
||||
repos:
|
||||
# Version sync - propagate root package.json version to all files
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: version-sync
|
||||
name: Version Sync
|
||||
entry: bash -c '
|
||||
VERSION=$(node -p "require(\"./package.json\").version");
|
||||
if [ -n "$VERSION" ]; then
|
||||
# Sync to apps/frontend/package.json
|
||||
node -e "const fs=require(\"fs\");const p=require(\"./apps/frontend/package.json\");if(p.version!==\"$VERSION\"){p.version=\"$VERSION\";fs.writeFileSync(\"./apps/frontend/package.json\",JSON.stringify(p,null,2)+\"\n\");}";
|
||||
# Sync to apps/backend/__init__.py
|
||||
sed -i.bak "s/__version__ = \"[^\"]*\"/__version__ = \"$VERSION\"/" apps/backend/__init__.py && rm -f apps/backend/__init__.py.bak;
|
||||
# Sync to README.md
|
||||
sed -i.bak "s/version-[0-9]*\.[0-9]*\.[0-9]*-blue/version-$VERSION-blue/g" README.md;
|
||||
sed -i.bak "s/Auto-Claude-[0-9]*\.[0-9]*\.[0-9]*/Auto-Claude-$VERSION/g" README.md && rm -f README.md.bak;
|
||||
git add apps/frontend/package.json apps/backend/__init__.py README.md 2>/dev/null || true;
|
||||
fi
|
||||
'
|
||||
language: system
|
||||
files: ^package\.json$
|
||||
pass_filenames: false
|
||||
|
||||
# Python linting (apps/backend/)
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.8.3
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--fix]
|
||||
files: ^apps/backend/
|
||||
- id: ruff-format
|
||||
files: ^apps/backend/
|
||||
|
||||
# Python tests (apps/backend/) - skip slow/integration tests for pre-commit speed
|
||||
# Tests to skip: graphiti (external deps), merge_file_tracker/service_orchestrator/worktree/workspace (Windows path/git issues)
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: pytest
|
||||
name: Python Tests
|
||||
entry: bash -c 'cd apps/backend && PYTHONPATH=. python -m pytest ../../tests/ -v --tb=short -x -m "not slow and not integration" --ignore=../../tests/test_graphiti.py --ignore=../../tests/test_merge_file_tracker.py --ignore=../../tests/test_service_orchestrator.py --ignore=../../tests/test_worktree.py --ignore=../../tests/test_workspace.py'
|
||||
language: system
|
||||
files: ^(apps/backend/.*\.py$|tests/.*\.py$)
|
||||
pass_filenames: false
|
||||
|
||||
# Frontend linting (apps/frontend/)
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: eslint
|
||||
name: ESLint
|
||||
entry: bash -c 'cd apps/frontend && npm run lint'
|
||||
language: system
|
||||
files: ^apps/frontend/.*\.(ts|tsx|js|jsx)$
|
||||
pass_filenames: false
|
||||
|
||||
- id: typecheck
|
||||
name: TypeScript Check
|
||||
entry: bash -c 'cd apps/frontend && npm run typecheck'
|
||||
language: system
|
||||
files: ^apps/frontend/.*\.(ts|tsx)$
|
||||
pass_filenames: false
|
||||
|
||||
# General checks
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v5.0.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: check-yaml
|
||||
exclude: pnpm-lock\.yaml$
|
||||
- id: check-added-large-files
|
||||
+914
-1
@@ -1,3 +1,916 @@
|
||||
## 2.7.1 - Build Pipeline Enhancements
|
||||
|
||||
### 🛠️ Improvements
|
||||
|
||||
- Enhanced VirusTotal scan error handling in release workflow with graceful failure recovery and improved reporting visibility
|
||||
|
||||
- Refactored macOS build workflow to support both Intel and ARM64 architectures with notarization for Intel builds and improved artifact handling
|
||||
|
||||
- Streamlined CI/CD processes with updated caching strategies and enhanced error handling for external API interactions
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
- Clarified README documentation
|
||||
|
||||
---
|
||||
|
||||
## What's Changed
|
||||
|
||||
- chore: Enhance VirusTotal scan error handling in release workflow by @AndyMik90 in d23fcd8
|
||||
|
||||
- chore: Refactor macOS build workflow to support Intel and ARM64 architectures by @AndyMik90 in 326118b
|
||||
|
||||
- docs: readme clarification by @AndyMik90 in 6afcc92
|
||||
|
||||
- fix: version by @AndyMik90 in 2c93890
|
||||
|
||||
## Thanks to all contributors
|
||||
|
||||
@AndyMik90
|
||||
|
||||
## 2.7.0 - Tab Persistence & Memory System Modernization
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- Project tab bar with persistent tab management and GitHub organization initialization on project creation
|
||||
|
||||
- Task creation enhanced with @ autocomplete for agent profiles and improved drag-and-drop support
|
||||
|
||||
- Keyboard shortcuts and tooltips added to project tabs for better navigation
|
||||
|
||||
- Agent task restart functionality with new profile support for flexible task recovery
|
||||
|
||||
- Ollama embedding model support with automatic dimension detection for self-hosted deployments
|
||||
|
||||
### 🛠️ Improvements
|
||||
|
||||
- Memory system completely redesigned with embedded LadybugDB, eliminating Docker/FalkorDB dependency and improving performance
|
||||
|
||||
- Tab persistence implemented via IPC-based mechanism for reliable session state management
|
||||
|
||||
- Terminal environment improved by using virtual environment Python for proper terminal name generation
|
||||
|
||||
- AI merge operations timeout increased from 2 to 10 minutes for reliability with larger changes
|
||||
|
||||
- Merge operations now use stored baseBranch metadata for consistent branch targeting
|
||||
|
||||
- Memory configuration UI simplified and rebranded with improved Ollama integration and detection
|
||||
|
||||
- CI/CD workflows enhanced with code signing support and automated release process
|
||||
|
||||
- Cross-platform compatibility improved by replacing Unix shell syntax with portable git commands
|
||||
|
||||
- Python venv created in userData for packaged applications to ensure proper environment isolation
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Task title no longer blocks edit/close buttons in UI
|
||||
|
||||
- Tab persistence and terminal shortcuts properly scoped to prevent conflicts
|
||||
|
||||
- Agent profile fallback corrected from 'Balanced' to 'Auto (Optimized)'
|
||||
|
||||
- macOS notarization made optional and improved with private artifact storage
|
||||
|
||||
- Embedding provider changes now properly detected during migration
|
||||
|
||||
- Memory query CLI respects user's memory enabled flag
|
||||
|
||||
- CodeRabbit review issues and linting errors resolved across codebase
|
||||
|
||||
- F-string prefixes removed from strings without placeholders
|
||||
|
||||
- Import ordering fixed for ruff compliance
|
||||
|
||||
- Preview panel now receives projectPath prop correctly for image component functionality
|
||||
|
||||
- Default database path unified to ~/.auto-claude/memories for consistency
|
||||
|
||||
- @lydell/node-pty build scripts compatibility improved for pnpm v10
|
||||
|
||||
---
|
||||
|
||||
## What's Changed
|
||||
|
||||
- feat(ui): add project tab bar from PR #101 by @AndyMik90 in c400fe9
|
||||
|
||||
- feat: improve task creation UX with @ autocomplete and better drag-drop by @AndyMik90 in 20d1487
|
||||
|
||||
- feat(ui): add keyboard shortcuts and tooltips for project tabs by @AndyMik90 in ed73265
|
||||
|
||||
- feat(agent): enhance task restart functionality with new profile support by @AndyMik90 in c8452a5
|
||||
|
||||
- feat: add Ollama embedding model support with auto-detected dimensions by @AndyMik90 in 45901f3
|
||||
|
||||
- feat(memory): replace FalkorDB with LadybugDB embedded database by @AndyMik90 in 87d0b52
|
||||
|
||||
- feat: add automated release workflow with code signing by @AndyMik90 in 6819b00
|
||||
|
||||
- feat: add embedding provider change detection and fix import ordering by @AndyMik90 in 36f8006
|
||||
|
||||
- fix(tests): update tab management tests for IPC-based persistence by @AndyMik90 in ea25d6e
|
||||
|
||||
- fix(ui): address CodeRabbit PR review issues by @AndyMik90 in 39ce754
|
||||
|
||||
- fix: address CodeRabbit review issues by @AndyMik90 in 95ae0b0
|
||||
|
||||
- fix: prevent task title from blocking edit/close buttons by @AndyMik90 in 8a0fb26
|
||||
|
||||
- fix: use venv Python for terminal name generation by @AndyMik90 in 325cb54
|
||||
|
||||
- fix(merge): increase AI merge timeout from 2 to 10 minutes by @AndyMik90 in 4477538
|
||||
|
||||
- fix(merge): use stored baseBranch from task metadata for merge operations by @AndyMik90 in 8d56474
|
||||
|
||||
- fix: unify default database path to ~/.auto-claude/memories by @AndyMik90 in 684e3f9
|
||||
|
||||
- fix(ui): fix tab persistence and scope terminal shortcuts by @AndyMik90 in 2d1168b
|
||||
|
||||
- fix: create Python venv in userData for packaged apps by @AndyMik90 in b83377c
|
||||
|
||||
- fix(ui): change agent profile fallback from 'Balanced' to 'Auto (Optimized)' by @AndyMik90 in 385dcc1
|
||||
|
||||
- fix: check APPLE_ID in shell instead of workflow if condition by @AndyMik90 in 9eece01
|
||||
|
||||
- fix: allow @lydell/node-pty build scripts in pnpm v10 by @AndyMik90 in 1f6963f
|
||||
|
||||
- fix: use shell guard for notarization credentials check by @AndyMik90 in 4cbddd3
|
||||
|
||||
- fix: improve migrate_embeddings robustness and correctness by @AndyMik90 in 61f0238
|
||||
|
||||
- fix: respect user's memory enabled flag in query_memory CLI by @AndyMik90 in 45b2c83
|
||||
|
||||
- fix: save notarization logs to private artifact instead of public logs by @AndyMik90 in a82525d
|
||||
|
||||
- fix: make macOS notarization optional by @AndyMik90 in f2b7b56
|
||||
|
||||
- fix: add author email for Linux builds by @AndyMik90 in 5f66127
|
||||
|
||||
- fix: add GH_TOKEN and homepage for release workflow by @AndyMik90 in 568ea18
|
||||
|
||||
- fix(ci): quote GITHUB_OUTPUT for shell safety by @AndyMik90 in 1e891e1
|
||||
|
||||
- fix: address CodeRabbit review feedback by @AndyMik90 in 8e4b1da
|
||||
|
||||
- fix: update test and apply ruff formatting by @AndyMik90 in a087ba3
|
||||
|
||||
- fix: address additional CodeRabbit review comments by @AndyMik90 in 461fad6
|
||||
|
||||
- fix: sort imports in memory.py for ruff I001 by @AndyMik90 in b3c257d
|
||||
|
||||
- fix: address CodeRabbit review comments from PR #100 by @AndyMik90 in 1ed237a
|
||||
|
||||
- fix: remove f-string prefixes from strings without placeholders by @AndyMik90 in bcd453a
|
||||
|
||||
- fix: resolve remaining CI failures by @AndyMik90 in cfbccda
|
||||
|
||||
- fix: resolve all CI failures in PR #100 by @AndyMik90 in c493d6c
|
||||
|
||||
- fix(cli): update graphiti status display for LadybugDB by @AndyMik90 in 049c60c
|
||||
|
||||
- fix(ui): replace Unix shell syntax with cross-platform git commands by @AndyMik90 in 83aa3f0
|
||||
|
||||
- fix: correct model name and release workflow conditionals by @AndyMik90 in de41dfc
|
||||
|
||||
- style: fix ruff linting errors in graphiti queries by @AndyMik90 in 127559f
|
||||
|
||||
- style: apply ruff formatting to 4 files by @AndyMik90 in 9d5d075
|
||||
|
||||
- refactor: update memory test suite for LadybugDB by @AndyMik90 in f0b5efc
|
||||
|
||||
- refactor(ui): simplify reference files and images handling in task modal by @AndyMik90 in 1975e4d
|
||||
|
||||
- refactor: rebrand memory system UI and simplify configuration by @AndyMik90 in 2b3cd49
|
||||
|
||||
- refactor: replace Docker/FalkorDB with embedded LadybugDB for memory system by @AndyMik90 in 325458d
|
||||
|
||||
- docs: add CodeRabbit review response tracking by @AndyMik90 in 3452548
|
||||
|
||||
- chore: use GitHub noreply email for author field by @AndyMik90 in 18f2045
|
||||
|
||||
- chore: simplify notarization step after successful setup by @AndyMik90 in e4fe7cd
|
||||
|
||||
- chore: update CI and release workflows, remove changelog config by @AndyMik90 in 6f891b7
|
||||
|
||||
- chore: remove docker-compose.yml (FalkorDB no longer used) by @AndyMik90 in 68f3f06
|
||||
|
||||
- fix: Replace space with hyphen in productName to fix PTY daemon spawn (#65) by @Craig Van in 8f1f7a7
|
||||
|
||||
- fix: update npm scripts to use hyphenated product name by @AndyMik90 in 89978ed
|
||||
|
||||
- fix(ui): improve Ollama UX in memory settings by @AndyMik90 in dea1711
|
||||
|
||||
- auto-claude: subtask-1-1 - Add projectPath prop to PreviewPanel and implement custom img component by @AndyMik90 in e6529e0
|
||||
|
||||
- Project tab persistence and github org init on project creation by @AndyMik90 in ae1dac9
|
||||
|
||||
- Readme for installors by @AndyMik90 in 1855d7d
|
||||
|
||||
---
|
||||
|
||||
## Thanks to all contributors
|
||||
|
||||
@AndyMik90, @Craig Van
|
||||
|
||||
## 2.6.0 - Improved User Experience and Agent Configuration
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- Add customizable phase configuration in app settings, allowing users to tailor the AI build pipeline to their workflow
|
||||
|
||||
- Implement parallel AI merge functionality for faster integration of completed builds
|
||||
|
||||
- Add Google AI as LLM and embedding provider for Graphiti memory system
|
||||
|
||||
- Implement device code authentication flow with timeout handling, browser launch fallback, and comprehensive testing
|
||||
|
||||
### 🛠️ Improvements
|
||||
|
||||
- Move Agent Profiles from dashboard to Settings for better organization and discoverability
|
||||
|
||||
- Default agent profile to 'Auto (Optimized)' for streamlined out-of-the-box experience
|
||||
|
||||
- Enhance WorkspaceStatus component UI with improved visual design
|
||||
|
||||
- Refactor task management from sidebar to modal interface for cleaner navigation
|
||||
|
||||
- Add comprehensive theme system with multiple color schemes (Forest, Neo, Retro, Dusk, Ocean, Lime) and light/dark mode support
|
||||
|
||||
- Extract human-readable feature titles from spec.md for better task identification
|
||||
|
||||
- Improve task description display for specs with compact markdown formatting
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Fix asyncio coroutine creation in worker threads to properly support async operations
|
||||
|
||||
- Improve UX for phase configuration in task creation workflow
|
||||
|
||||
- Address CodeRabbit PR #69 feedback and additional review comments
|
||||
|
||||
- Fix auto-close behavior for task modal when marking tasks as done
|
||||
|
||||
- Resolve Python lint errors and import sorting issues (ruff I001 compliance)
|
||||
|
||||
- Ensure planner agent properly writes implementation_plan.json
|
||||
|
||||
- Add platform detection for terminal profile commands on Windows
|
||||
|
||||
- Set default selected agent profile to 'auto' across all users
|
||||
|
||||
- Fix display of correct merge target branch in worktree UI
|
||||
|
||||
- Add validation for invalid colorTheme fallback to prevent UI errors
|
||||
|
||||
- Remove outdated Sun/Moon toggle button from sidebar
|
||||
|
||||
---
|
||||
|
||||
## What's Changed
|
||||
|
||||
- feat: add customizable phase configuration in app settings by @AndyMik90 in aee0ba4
|
||||
|
||||
- feat: implement parallel AI merge functionality by @AndyMik90 in 458d4bb
|
||||
|
||||
- feat(graphiti): add Google AI as LLM and embedding provider by @adryserage in fe69106
|
||||
|
||||
- fix: create coroutine inside worker thread for asyncio.run by @AndyMik90 in f89e4e6
|
||||
|
||||
- fix: improve UX for phase configuration in task creation by @AndyMik90 in b9797cb
|
||||
|
||||
- fix: address CodeRabbit PR #69 feedback by @AndyMik90 in cc38a06
|
||||
|
||||
- fix: sort imports in workspace.py to pass ruff I001 check by @AndyMik90 in 9981ee4
|
||||
|
||||
- fix(ui): auto-close task modal when marking task as done by @AndyMik90 in 297d380
|
||||
|
||||
- fix: resolve Python lint errors in workspace.py by @AndyMik90 in 0506256
|
||||
|
||||
- refactor: move Agent Profiles from dashboard to Settings by @AndyMik90 in 1094990
|
||||
|
||||
- fix(planning): ensure planner agent writes implementation_plan.json by @AndyMik90 in 9ab5a4f
|
||||
|
||||
- fix(windows): add platform detection for terminal profile commands by @AndyMik90 in f0a6a0a
|
||||
|
||||
- fix: default agent profile to 'Auto (Optimized)' for all users by @AndyMik90 in 08aa2ff
|
||||
|
||||
- fix: update default selected agent profile to 'auto' by @AndyMik90 in 37ace0a
|
||||
|
||||
- style: enhance WorkspaceStatus component UI by @AndyMik90 in 3092155
|
||||
|
||||
- fix: display correct merge target branch in worktree UI by @AndyMik90 in 2b96160
|
||||
|
||||
- Improvement/refactor task sidebar to task modal by @AndyMik90 in 2a96f85
|
||||
|
||||
- fix: extract human-readable title from spec.md when feature field is spec ID by @AndyMik90 in 8b59375
|
||||
|
||||
- fix: task descriptions not showing for specs with compact markdown by @AndyMik90 in 7f12ef0
|
||||
|
||||
- Add comprehensive theme system with Forest, Neo, Retro, Dusk, Ocean, and Lime color schemes by @AndyMik90 in ba776a3, e2b24e2, 7589046, e248256, 76c1bd7, bcbced2
|
||||
|
||||
- Add ColorTheme type and configuration to app settings by @AndyMik90 in 2ca89ce, c505d6e, a75c0a9
|
||||
|
||||
- Implement device code authentication flow with timeout handling and fallback URL display by @AndyMik90 in 5f26d39, 81e1536, 1a7cf40, 4a4ad6b, 6a4c1b4, b75a09c, e134c4c
|
||||
|
||||
- fix(graphiti): address CodeRabbit review comments by @adryserage in 679b8cd
|
||||
|
||||
- fix(lint): sort imports in Google provider files by @adryserage in 1a38a06
|
||||
|
||||
## 2.6.0 - Multi-Provider Graphiti Support & Platform Fixes
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- **Google AI Provider for Graphiti**: Full Google AI (Gemini) support for both LLM and embeddings in the Memory Layer
|
||||
- Add GoogleLLMClient with gemini-2.0-flash default model
|
||||
- Add GoogleEmbedder with text-embedding-004 default model
|
||||
- UI integration for Google API key configuration with link to Google AI Studio
|
||||
- **Ollama LLM Provider in UI**: Add Ollama as an LLM provider option in Graphiti onboarding wizard
|
||||
- Ollama runs locally and doesn't require an API key
|
||||
- Configure Base URL instead of API key for local inference
|
||||
- **LLM Provider Selection UI**: Add provider selection dropdown to Graphiti setup wizard for flexible backend configuration
|
||||
- **Per-Project GitHub Configuration**: UI clarity improvements for per-project GitHub org/repo settings
|
||||
|
||||
### 🛠️ Improvements
|
||||
|
||||
- Enhanced Graphiti provider factory to support Google AI alongside existing providers
|
||||
- Updated env-handlers to properly populate graphitiProviderConfig from .env files
|
||||
- Improved type definitions with proper Graphiti provider config properties in AppSettings
|
||||
- Better API key loading when switching between providers in settings
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **node-pty Migration**: Replaced node-pty with @lydell/node-pty for prebuilt Windows binaries
|
||||
- Updated all imports to use @lydell/node-pty directly
|
||||
- Fixed "Cannot find module 'node-pty'" startup error
|
||||
- **GitHub Organization Support**: Fixed repository support for GitHub organization accounts
|
||||
- Add defensive array validation for GitHub issues API response
|
||||
- **Asyncio Deprecation**: Fixed asyncio deprecation warning by using get_running_loop() instead of get_event_loop()
|
||||
- Applied ruff formatting and fixed import sorting (I001) in Google provider files
|
||||
|
||||
### 🔧 Other Changes
|
||||
|
||||
- Added google-generativeai dependency to requirements.txt
|
||||
- Updated provider validation to include Google/Groq/HuggingFace type assertions
|
||||
|
||||
---
|
||||
|
||||
## What's Changed
|
||||
|
||||
- fix(graphiti): address CodeRabbit review comments by @adryserage in 679b8cd
|
||||
- fix(lint): sort imports in Google provider files by @adryserage in 1a38a06
|
||||
- feat(graphiti): add Google AI as LLM and embedding provider by @adryserage in fe69106
|
||||
- fix: GitHub organization repository support by @mojaray2k in 873cafa
|
||||
- feat(ui): add LLM provider selection to Graphiti onboarding by @adryserage in 4750869
|
||||
- fix(types): add missing AppSettings properties for Graphiti providers by @adryserage in 6680ed4
|
||||
- feat(ui): add Ollama as LLM provider option for Graphiti by @adryserage in a3eee92
|
||||
- fix(ui): address PR review feedback for Graphiti provider selection by @adryserage in b8a419a
|
||||
- fix(deps): update imports to use @lydell/node-pty directly by @adryserage in 2b61ebb
|
||||
- fix(deps): replace node-pty with @lydell/node-pty for prebuilt binaries by @adryserage in e1aee6a
|
||||
- fix: add UI clarity for per-project GitHub configuration by @mojaray2k in c9745b6
|
||||
- fix: add defensive array validation for GitHub issues API response by @mojaray2k in b3636a5
|
||||
|
||||
---
|
||||
|
||||
## 2.5.5 - Enhanced Agent Reliability & Build Workflow
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- Required GitHub setup flow after Auto Claude initialization to ensure proper configuration
|
||||
- Atomic log saving mechanism to prevent log file corruption during concurrent operations
|
||||
- Per-session model and thinking level selection in insights management
|
||||
- Multi-auth token support and ANTHROPIC_BASE_URL passthrough for flexible authentication
|
||||
- Comprehensive DEBUG logging at Claude SDK invocation points for improved troubleshooting
|
||||
- Auto-download of prebuilt node-pty binaries for Windows environments
|
||||
- Enhanced merge workflow with current branch detection for accurate change previews
|
||||
- Phase configuration module and enhanced agent profiles for improved flexibility
|
||||
- Stage-only merge handling with comprehensive verification checks
|
||||
- Authentication failure detection system with patterns and validation checks across agent pipeline
|
||||
|
||||
### 🛠️ Improvements
|
||||
|
||||
- Changed default agent profile from 'balanced' to 'auto' for more adaptive behavior
|
||||
- Better GitHub issue tracking and improved user experience in issue management
|
||||
- Improved merge preview accuracy using git diff counts for file statistics
|
||||
- Preserved roadmap generation state when switching between projects
|
||||
- Enhanced agent profiles with phase configuration support
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Resolved CI test failures and improved merge preview reliability
|
||||
- Fixed CI failures related to linting, formatting, and tests
|
||||
- Prevented dialog skip during project initialization flow
|
||||
- Updated model IDs for Sonnet and Haiku to match current Claude versions
|
||||
- Fixed branch namespace conflict detection to prevent worktree creation failures
|
||||
- Removed duplicate LINEAR_API_KEY checks and consolidated imports
|
||||
- Python 3.10+ version requirement enforced with proper version checking
|
||||
- Prevented command injection vulnerabilities in GitHub API calls
|
||||
|
||||
### 🔧 Other Changes
|
||||
|
||||
- Code cleanup and test fixture updates
|
||||
- Removed redundant auto-claude/specs directory structure
|
||||
- Untracked .auto-claude directory to respect gitignore rules
|
||||
|
||||
---
|
||||
|
||||
## What's Changed
|
||||
|
||||
- fix: resolve CI test failures and improve merge preview by @AndyMik90 in de2eccd
|
||||
- chore: code cleanup and test fixture updates by @AndyMik90 in 948db57
|
||||
- refactor: change default agent profile from 'balanced' to 'auto' by @AndyMik90 in f98a13e
|
||||
- security: prevent command injection in GitHub API calls by @AndyMik90 in 24ff491
|
||||
- fix: resolve CI failures (lint, format, test) by @AndyMik90 in a8f2d0b
|
||||
- fix: use git diff count for totalFiles in merge preview by @AndyMik90 in 46d2536
|
||||
- feat: enhance stage-only merge handling with verification checks by @AndyMik90 in 7153558
|
||||
- feat: introduce phase configuration module and enhance agent profiles by @AndyMik90 in 2672528
|
||||
- fix: preserve roadmap generation state when switching projects by @AndyMik90 in 569e921
|
||||
- feat: add required GitHub setup flow after Auto Claude initialization by @AndyMik90 in 03ccce5
|
||||
- chore: remove redundant auto-claude/specs directory by @AndyMik90 in 64d5170
|
||||
- chore: untrack .auto-claude directory (should be gitignored) by @AndyMik90 in 0710c13
|
||||
- fix: prevent dialog skip during project initialization by @AndyMik90 in 56cedec
|
||||
- feat: enhance merge workflow by detecting current branch by @AndyMik90 in c0c8067
|
||||
- fix: update model IDs for Sonnet and Haiku by @AndyMik90 in 059315d
|
||||
- feat: add comprehensive DEBUG logging and fix lint errors by @AndyMik90 in 99cf21e
|
||||
- feat: implement atomic log saving to prevent corruption by @AndyMik90 in da5e26b
|
||||
- feat: add better github issue tracking and UX by @AndyMik90 in c957eaa
|
||||
- feat: add comprehensive DEBUG logging to Claude SDK invocation points by @AndyMik90 in 73d01c0
|
||||
- feat: auto-download prebuilt node-pty binaries for Windows by @AndyMik90 in 41a507f
|
||||
- feat(insights): add per-session model and thinking level selection by @AndyMik90 in e02aa59
|
||||
- fix: require Python 3.10+ and add version check by @AndyMik90 in 9a5ca8c
|
||||
- fix: detect branch namespace conflict blocking worktree creation by @AndyMik90 in 63a1d3c
|
||||
- fix: remove duplicate LINEAR_API_KEY check and consolidate imports by @Jacob in 7d351e3
|
||||
- feat: add multi-auth token support and ANTHROPIC_BASE_URL passthrough by @Jacob in 9dea155
|
||||
|
||||
## 2.5.0 - Roadmap Intelligence & Workflow Refinements
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- Interactive competitor analysis viewer for roadmap planning with real-time data visualization
|
||||
|
||||
- GitHub issue label mapping to task categories for improved organization and tracking
|
||||
|
||||
- GitHub issue comment selection in task creation workflow for better context integration
|
||||
|
||||
- TaskCreationWizard enhanced with drag-and-drop support for file references and inline @mentions
|
||||
|
||||
- Roadmap generation now includes stop functionality and comprehensive debug logging
|
||||
|
||||
### 🛠️ Improvements
|
||||
|
||||
- Refined visual drop zone feedback in file reference system for more subtle user guidance
|
||||
|
||||
- Remove auto-expand behavior for referenced files on draft restore to improve UX
|
||||
|
||||
- Always-visible referenced files section in TaskCreationWizard for better discoverability
|
||||
|
||||
- Drop zone wrapper added around main modal content area for improved drag-and-drop ergonomics
|
||||
|
||||
- Stuck task detection now enabled for ai_review status to better track blocked work
|
||||
|
||||
- Enhanced React component stability with proper key usage in RoadmapHeader and PhaseProgressIndicator
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Corrected CompetitorAnalysisViewer type definitions for proper TypeScript compliance
|
||||
|
||||
- Fixed multiple CodeRabbit review feedback items for improved code quality
|
||||
|
||||
- Resolved React key warnings in PhaseProgressIndicator component
|
||||
|
||||
- Fixed git status parsing in merge preview for accurate worktree state detection
|
||||
|
||||
- Corrected path resolution in runners for proper module imports and .env loading
|
||||
|
||||
- Resolved CI lint and TypeScript errors across codebase
|
||||
|
||||
- Fixed HTTP error handling and path resolution issues in core modules
|
||||
|
||||
- Corrected worktree test to match intended branch detection behavior
|
||||
|
||||
- Refined TaskReview component conditional rendering for proper staged task display
|
||||
|
||||
---
|
||||
|
||||
## What's Changed
|
||||
|
||||
- feat: add interactive competitor analysis viewer for roadmap by @AndyMik90 in 7ff326d
|
||||
|
||||
- fix: correct CompetitorAnalysisViewer to match type definitions by @AndyMik90 in 4f1766b
|
||||
|
||||
- fix: address multiple CodeRabbit review feedback items by @AndyMik90 in 48f7c3c
|
||||
|
||||
- fix: use stable React keys instead of array indices in RoadmapHeader by @AndyMik90 in 892e01d
|
||||
|
||||
- fix: additional fixes for http error handling and path resolution by @AndyMik90 in 54501cb
|
||||
|
||||
- fix: update worktree test to match intended branch detection behavior by @AndyMik90 in f1d578f
|
||||
|
||||
- fix: resolve CI lint and TypeScript errors by @AndyMik90 in 2e3a5d9
|
||||
|
||||
- feat: enhance roadmap generation with stop functionality and debug logging by @AndyMik90 in a6dad42
|
||||
|
||||
- fix: correct path resolution in runners for module imports and .env loading by @AndyMik90 in 3d24f8f
|
||||
|
||||
- fix: resolve React key warning in PhaseProgressIndicator by @AndyMik90 in 9106038
|
||||
|
||||
- fix: enable stuck task detection for ai_review status by @AndyMik90 in 895ed9f
|
||||
|
||||
- feat: map GitHub issue labels to task categories by @AndyMik90 in cbe14fd
|
||||
|
||||
- feat: add GitHub issue comment selection and fix auto-start bug by @AndyMik90 in 4c1dd89
|
||||
|
||||
- feat: enhance TaskCreationWizard with drag-and-drop support for file references and inline @mentions by @AndyMik90 in d93eefe
|
||||
|
||||
- cleanup docs by @AndyMik90 in 8e891df
|
||||
|
||||
- fix: correct git status parsing in merge preview by @AndyMik90 in c721dc2
|
||||
|
||||
- Update TaskReview component to refine conditional rendering for staged tasks, ensuring proper display when staging is unsuccessful by @AndyMik90 in 1a2b7a1
|
||||
|
||||
- auto-claude: subtask-2-3 - Refine visual drop zone feedback to be more subtle by @AndyMik90 in 6cff442
|
||||
|
||||
- auto-claude: subtask-2-1 - Remove showFiles auto-expand on draft restore by @AndyMik90 in 12bf69d
|
||||
|
||||
- auto-claude: subtask-1-3 - Create an always-visible referenced files section by @AndyMik90 in 3818b46
|
||||
|
||||
- auto-claude: subtask-1-2 - Add drop zone wrapper around main modal content area by @AndyMik90 in 219b66d
|
||||
|
||||
- auto-claude: subtask-1-1 - Remove Reference Files toggle button by @AndyMik90 in 4e63e85
|
||||
|
||||
## 2.4.0 - Enhanced Cross-Platform Experience with OAuth & Auto-Updates
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- Claude account OAuth implementation on onboarding for seamless token setup
|
||||
|
||||
- Integrated release workflow with AI-powered version suggestion capabilities
|
||||
|
||||
- Auto-upgrading functionality supporting Windows, Linux, and macOS with automatic app updates
|
||||
|
||||
- Git repository initialization on app startup with project addition checks
|
||||
|
||||
- Debug logging for app updater to track update processes
|
||||
|
||||
- Auto-open settings to updates section when app update is ready
|
||||
|
||||
### 🛠️ Improvements
|
||||
|
||||
- Major Windows and Linux compatibility enhancements for cross-platform reliability
|
||||
|
||||
- Enhanced task status handling to support 'done' status in limbo state with worktree existence checks
|
||||
|
||||
- Better handling of lock files from worktrees upon merging
|
||||
|
||||
- Improved README documentation and build process
|
||||
|
||||
- Refined visual drop zone feedback for more subtle user experience
|
||||
|
||||
- Removed showFiles auto-expand on draft restore for better UX consistency
|
||||
|
||||
- Created always-visible referenced files section in task creation wizard
|
||||
|
||||
- Removed Reference Files toggle button for streamlined interface
|
||||
|
||||
- Worktree manual deletion enforcement for early access safety (prevents accidental work loss)
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Corrected git status parsing in merge preview functionality
|
||||
|
||||
- Fixed ESLint warnings and failing tests
|
||||
|
||||
- Fixed Windows/Linux Python handling for cross-platform compatibility
|
||||
|
||||
- Fixed Windows/Linux source path detection
|
||||
|
||||
- Refined TaskReview component conditional rendering for proper staged task display
|
||||
|
||||
---
|
||||
|
||||
## What's Changed
|
||||
|
||||
- docs: cleanup docs by @AndyMik90 in 8e891df
|
||||
- fix: correct git status parsing in merge preview by @AndyMik90 in c721dc2
|
||||
- refactor: Update TaskReview component to refine conditional rendering for staged tasks by @AndyMik90 in 1a2b7a1
|
||||
- feat: Enhance task status handling to allow 'done' status in limbo state by @AndyMik90 in a20b8cf
|
||||
- improvement: Worktree needs to be manually deleted for early access safety by @AndyMik90 in 0ed6afb
|
||||
- feat: Claude account OAuth implementation on onboarding by @AndyMik90 in 914a09d
|
||||
- fix: Better handling of lock files from worktrees upon merging by @AndyMik90 in e44202a
|
||||
- feat: GitHub OAuth integration upon onboarding by @AndyMik90 in 4249644
|
||||
- chore: lock update by @AndyMik90 in b0fc497
|
||||
- improvement: Improved README and build process by @AndyMik90 in 462edcd
|
||||
- fix: ESLint warnings and failing tests by @AndyMik90 in affbc48
|
||||
- feat: Major Windows and Linux compatibility enhancements with auto-upgrade by @AndyMik90 in d7fd1a2
|
||||
- feat: Add debug logging to app updater by @AndyMik90 in 96dd04d
|
||||
- feat: Auto-open settings to updates section when app update is ready by @AndyMik90 in 1d0566f
|
||||
- feat: Add integrated release workflow with AI version suggestion by @AndyMik90 in 7f3cd59
|
||||
- fix: Windows/Linux Python handling by @AndyMik90 in 0ef0e15
|
||||
- feat: Implement Electron app auto-updater by @AndyMik90 in efc112a
|
||||
- fix: Windows/Linux source path detection by @AndyMik90 in d33a0aa
|
||||
- refactor: Refine visual drop zone feedback to be more subtle by @AndyMik90 in 6cff442
|
||||
- refactor: Remove showFiles auto-expand on draft restore by @AndyMik90 in 12bf69d
|
||||
- feat: Create always-visible referenced files section by @AndyMik90 in 3818b46
|
||||
- feat: Add drop zone wrapper around main modal content by @AndyMik90 in 219b66d
|
||||
- feat: Remove Reference Files toggle button by @AndyMik90 in 4e63e85
|
||||
- docs: Update README with git initialization and folder structure by @AndyMik90 in 2fa3c51
|
||||
- chore: Version bump to 2.3.2 by @AndyMik90 in 59b091a
|
||||
|
||||
## 2.3.2 - UI Polish & Build Improvements
|
||||
|
||||
### 🛠️ Improvements
|
||||
|
||||
- Restructured SortableFeatureCard badge layout for improved visual presentation
|
||||
|
||||
Bug Fixes:
|
||||
- Fixed spec runner path configuration for more reliable task execution
|
||||
|
||||
---
|
||||
|
||||
## What's Changed
|
||||
|
||||
- fix: fix to spec runner paths by @AndyMik90 in 9babdc2
|
||||
|
||||
- feat: auto-claude: subtask-1-1 - Restructure SortableFeatureCard badge layout by @AndyMik90 in dc886dc
|
||||
|
||||
## 2.3.1 - Linux Compatibility Fix
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Resolved path handling issues on Linux systems for improved cross-platform compatibility
|
||||
|
||||
---
|
||||
|
||||
## What's Changed
|
||||
|
||||
- fix: Fix to linux path issue by @AndyMik90 in 3276034
|
||||
|
||||
## 2.2.0 - 2025-12-17
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- Add usage monitoring with profile swap detection to prevent cascading resource issues
|
||||
|
||||
- Option to stash changes before merge operations for safer branch integration
|
||||
|
||||
- Add hideCloseButton prop to DialogContent component for improved UI flexibility
|
||||
|
||||
### 🛠️ Improvements
|
||||
|
||||
- Enhance AgentManager to manage task context cleanup and preserve swapCount on restarts
|
||||
|
||||
- Improve changelog feature with version tracking, markdown/preview, and persistent styling options
|
||||
|
||||
- Refactor merge conflict handling to use branch names instead of commit hashes for better clarity
|
||||
|
||||
- Streamline usage monitoring logic by removing unnecessary dynamic imports
|
||||
|
||||
- Better handling of lock files during merge conflicts
|
||||
|
||||
- Refactor code for improved readability and maintainability
|
||||
|
||||
- Refactor IdeationHeader and update handleDeleteSelected logic
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Fix worktree merge logic to correctly handle branch operations
|
||||
|
||||
- Fix spec_runner.py path resolution after move to runners/ directory
|
||||
|
||||
- Fix Discord release webhook failing on large changelogs
|
||||
|
||||
- Fix branch logic for merge AI operations
|
||||
|
||||
- Hotfix for spec-runner path location
|
||||
|
||||
---
|
||||
|
||||
## What's Changed
|
||||
|
||||
- fix: hotfix/spec-runner path location by @AndyMik90 in f201f7e
|
||||
|
||||
- refactor: Remove unnecessary dynamic imports of getUsageMonitor in terminal-handlers.ts to streamline usage monitoring logic by @AndyMik90 in 0da4bc4
|
||||
|
||||
- feat: Improve changelog feature, version tracking, markdown/preview, persistent styling options by @AndyMik90 in a0d142b
|
||||
|
||||
- refactor: Refactor code for improved readability and maintainability by @AndyMik90 in 473b045
|
||||
|
||||
- feat: Enhance AgentManager to manage task context cleanup and preserve swapCount on restarts. Update UsageMonitor to delay profile usage checks to prevent cascading swaps by @AndyMik90 in e5b9488
|
||||
|
||||
- feat: Usage-monitoring by @AndyMik90 in de33b2c
|
||||
|
||||
- feat: option to stash changes before merge by @AndyMik90 in 7e09739
|
||||
|
||||
- refactor: Refactor merge conflict check to use branch names instead of commit hashes by @AndyMik90 in e6d6cea
|
||||
|
||||
- fix: worktree merge logic by @AndyMik90 in dfb5cf9
|
||||
|
||||
- test: Sign off - all verification passed by @AndyMik90 in 34631c3
|
||||
|
||||
- feat: Pass hideCloseButton={showFileExplorer} to DialogContent by @AndyMik90 in 7c327ed
|
||||
|
||||
- feat: Add hideCloseButton prop to DialogContent component by @AndyMik90 in 5f9653a
|
||||
|
||||
- fix: branch logic for merge AI by @AndyMik90 in 2d2a813
|
||||
|
||||
- fix: spec_runner.py path resolution after move to runners/ directory by @AndyMik90 in ce9c2cd
|
||||
|
||||
- refactor: Better handling of lock files during merge conflicts by @AndyMik90 in 460c76d
|
||||
|
||||
- fix: Discord release webhook failing on large changelogs by @AndyMik90 in 4eb66f5
|
||||
|
||||
- chore: Update CHANGELOG with new features, improvements, bug fixes, and other changes by @AndyMik90 in 788b8d0
|
||||
|
||||
- refactor: Enhance merge conflict handling by excluding lock files by @AndyMik90 in 957746e
|
||||
|
||||
- refactor: Refactor IdeationHeader and update handleDeleteSelected logic by @AndyMik90 in 36338f3
|
||||
|
||||
## What's New
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- Added GitHub OAuth integration for seamless authentication
|
||||
|
||||
- Implemented roadmap feature management with kanban board and drag-and-drop support
|
||||
|
||||
- Added ability to select AI model during task creation with agent profiles
|
||||
|
||||
- Introduced file explorer integration and referenced files section in task creation wizard
|
||||
|
||||
- Added .gitignore entry management during project initialization
|
||||
|
||||
- Created comprehensive onboarding wizard with OAuth configuration, Graphiti setup, and first spec guidance
|
||||
|
||||
- Introduced Electron MCP for debugging and validation support
|
||||
|
||||
- Added BMM workflow status tracking and project scan reporting
|
||||
|
||||
### 🛠️ Improvements
|
||||
|
||||
- Refactored IdeationHeader component and improved deleteSelected logic
|
||||
|
||||
- Refactored backend for upcoming features with improved architecture
|
||||
|
||||
- Enhanced RouteDetector to exclude specific directories from route detection
|
||||
|
||||
- Improved merge conflict resolution with parallel processing and AI-assisted resolution
|
||||
|
||||
- Optimized merge conflict resolution performance and context sending
|
||||
|
||||
- Refactored AI resolver to use async context manager and Claude SDK patterns
|
||||
|
||||
- Enhanced merge orchestrator logic and frontend UX for conflict handling
|
||||
|
||||
- Refactored components for better maintainability and faster development
|
||||
|
||||
- Refactored changelog formatter for GitHub Release compatibility
|
||||
|
||||
- Enhanced onboarding wizard completion logic and step progression
|
||||
|
||||
- Updated README to clarify Auto Claude's role as an AI coding companion
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Fixed GraphitiStep TypeScript compilation error
|
||||
|
||||
- Added missing onRerunWizard prop to AppSettingsDialog
|
||||
|
||||
- Improved merge lock file conflict handling
|
||||
|
||||
### 🔧 Other Changes
|
||||
|
||||
- Removed .auto-claude and _bmad-output from git tracking (already in .gitignore)
|
||||
|
||||
- Updated Python versions in CI workflows
|
||||
|
||||
- General linting improvements and code cleanup
|
||||
|
||||
---
|
||||
|
||||
## What's Changed
|
||||
|
||||
- feat: New github oauth integration by @AndyMik90 in afeb54f
|
||||
- feat: Implement roadmap feature management kanban with drag-and-drop support by @AndyMik90 in 9403230
|
||||
- feat: Agent profiles, be able to select model on task creation by @AndyMik90 in d735c5c
|
||||
- feat: Add Referenced Files Section and File Explorer Integration in Task Creation Wizard by @AndyMik90 in 31e4e87
|
||||
- feat: Add functionality to manage .gitignore entries during project initialization by @AndyMik90 in 2ac00a9
|
||||
- feat: Introduce electron mcp for electron debugging/validation by @AndyMik90 in 3eb2ead
|
||||
- feat: Add BMM workflow status tracking and project scan report by @AndyMik90 in 7f6456f
|
||||
- refactor: Refactor IdeationHeader and update handleDeleteSelected logic by @AndyMik90 in 36338f3
|
||||
- refactor: Big backend refactor for upcoming features by @AndyMik90 in 11fcdf4
|
||||
- refactor: Refactoring for better codebase by @AndyMik90 in feb0d4e
|
||||
- refactor: Refactor Roadmap component to utilize RoadmapGenerationProgress for better status display by @AndyMik90 in d8e5784
|
||||
- refactor: refactoring components for better future maintence and more rapid coding by @AndyMik90 in 131ec4c
|
||||
- refactor: Enhance RouteDetector to exclude specific directories from route detection by @AndyMik90 in 08dc24c
|
||||
- refactor: Update AI resolver to use Claude Opus model and improve error logging by @AndyMik90 in 1d830ba
|
||||
- refactor: Use claude sdk pattern for ai resolver by @AndyMik90 in 4bba9d1
|
||||
- refactor: Refactor AI resolver to use async context manager for client connection by @AndyMik90 in 579ea40
|
||||
- refactor: Update changelog formatter for GitHub Release compatibility by @AndyMik90 in 3b832db
|
||||
- refactor: Enhance onboarding wizard completion logic by @AndyMik90 in 7c01638
|
||||
- refactor: Update GraphitiStep to proceed to the next step after successful configuration save by @AndyMik90 in a5a1eb1
|
||||
- fix: Add onRerunWizard prop to AppSettingsDialog (qa-requested) by @AndyMik90 in 6b5b714
|
||||
- fix: Add first-run detection to App.tsx by @AndyMik90 in 779e36f
|
||||
- fix: Add TypeScript compilation check - fix GraphitiStep type error by @AndyMik90 in f90fa80
|
||||
- improve: ideation improvements and linting by @AndyMik90 in 36a69fc
|
||||
- improve: improve merge conflicts for lock files by @AndyMik90 in a891225
|
||||
- improve: Roadmap competitor analysis by @AndyMik90 in ddf47ae
|
||||
- improve: parallell merge conflict resolution by @AndyMik90 in f00aa33
|
||||
- improve: improvement to speed of merge conflict resolution by @AndyMik90 in 56ff586
|
||||
- improve: improve context sending to merge agent by @AndyMik90 in e409ae8
|
||||
- improve: better conflict handling in the frontend app for merge contlicts (better UX) by @AndyMik90 in 65937e1
|
||||
- improve: resolve claude agent sdk by @AndyMik90 in 901e83a
|
||||
- improve: Getting ready for BMAD integration by @AndyMik90 in b94eb65
|
||||
- improve: Enhance AI resolver and debugging output by @AndyMik90 in bf787ad
|
||||
- improve: Integrate profile environment for OAuth token in task handlers by @AndyMik90 in 01e801a
|
||||
- chore: Remove .auto-claude from tracking (already in .gitignore) by @AndyMik90 in 87f353c
|
||||
- chore: Update Python versions in CI workflows by @AndyMik90 in 43a338c
|
||||
- chore: Linting gods pleased now? by @AndyMik90 in 6aea4bb
|
||||
- chore: Linting and test fixes by @AndyMik90 in 140f11f
|
||||
- chore: Remove _bmad-output from git tracking by @AndyMik90 in 4cd7500
|
||||
- chore: Add _bmad-output to .gitignore by @AndyMik90 in dbe27f0
|
||||
- chore: Linting gods are happy by @AndyMik90 in 3fc1592
|
||||
- chore: Getting ready for the lint gods by @AndyMik90 in 142cd67
|
||||
- chore: CLI testing/linting by @AndyMik90 in d8ad17d
|
||||
- chore: CLI and tests by @AndyMik90 in 9a59b7e
|
||||
- chore: Update implementation_plan.json - fixes applied by @AndyMik90 in 555a46f
|
||||
- chore: Update parallel merge conflict resolution metrics in workspace.py by @AndyMik90 in 2e151ac
|
||||
- chore: merge logic v0.3 by @AndyMik90 in c5d33cd
|
||||
- chore: merge orcehestrator logic by @AndyMik90 in e8b6669
|
||||
- chore: Merge-orchestrator by @AndyMik90 in d8ba532
|
||||
- chore: merge orcehstrator logic by @AndyMik90 in e8b6669
|
||||
- chore: Electron UI fix for merge orcehstrator by @AndyMik90 in e08ab62
|
||||
- chore: Frontend lints by @AndyMik90 in 488bbfa
|
||||
- docs: Revise README.md to enhance clarity and focus on Auto Claude's capabilities by @AndyMik90 in f9ef7ea
|
||||
- qa: Sign off - all verification passed by @AndyMik90 in b3f4803
|
||||
- qa: Rejected - fixes required by @AndyMik90 in 5e56890
|
||||
- qa: subtask-6-2 - Run existing tests to verify no regressions by @AndyMik90 in 5f989a4
|
||||
- qa: subtask-5-2 - Enhance OAuthStep to detect and display if token is already configured by @AndyMik90 in 50f22da
|
||||
- qa: subtask-5-1 - Add settings migration logic - set onboardingCompleted by @AndyMik90 in f57c28e
|
||||
- qa: subtask-4-1 - Add 'Re-run Wizard' button to AppSettings navigation by @AndyMik90 in 9144e7f
|
||||
- qa: subtask-3-1 - Add first-run detection to App.tsx by @AndyMik90 in 779e36f
|
||||
- qa: subtask-2-8 - Create index.ts barrel export for onboarding components by @AndyMik90 in b0af2dc
|
||||
- qa: subtask-2-7 - Create OnboardingWizard component by @AndyMik90 in 3de8928
|
||||
- qa: subtask-2-6 - Create CompletionStep component - success message by @AndyMik90 in aa0f608
|
||||
- qa: subtask-2-5 - Create FirstSpecStep component - guided first spec by @AndyMik90 in 32f17a1
|
||||
- qa: subtask-2-4 - Create GraphitiStep component - optional Graphiti/FalkorDB configuration by @AndyMik90 in 61184b0
|
||||
- qa: subtask-2-3 - Create OAuthStep component - Claude OAuth token configuration step by @AndyMik90 in 79d622e
|
||||
- qa: subtask-2-2 - Create WelcomeStep component by @AndyMik90 in a97f697
|
||||
- qa: subtask-2-1 - Create WizardProgress component - step progress indicator by @AndyMik90 in b6e604c
|
||||
- qa: subtask-1-2 - Add onboardingCompleted to DEFAULT_APP_SETTINGS by @AndyMik90 in c5a0331
|
||||
- qa: subtask-1-1 - Add onboardingCompleted to AppSettings type interface by @AndyMik90 in 7c24b48
|
||||
- chore: Version 2.0.1 by @AndyMik90 in 4b242c4
|
||||
- test: Merge-orchestrator by @AndyMik90 in d8ba532
|
||||
- test: test for ai merge AI by @AndyMik90 in 9d9cf16
|
||||
|
||||
## What's New in 2.0.1
|
||||
|
||||
### 🚀 New Features
|
||||
- **Update Check with Release URLs**: Enhanced update checking functionality to include release URLs, allowing users to easily access release information
|
||||
- **Markdown Renderer for Release Notes**: Added markdown renderer in advanced settings to properly display formatted release notes
|
||||
- **Terminal Name Generator**: New feature for generating terminal names
|
||||
|
||||
### 🔧 Improvements
|
||||
- **LLM Provider Naming**: Updated project settings to reflect new LLM provider name
|
||||
- **IPC Handlers**: Improved IPC handlers for external link management
|
||||
- **UI Simplification**: Refactored App component to simplify project selection display by removing unnecessary wrapper elements
|
||||
- **Docker Infrastructure**: Updated FalkorDB service container naming in docker-compose configuration
|
||||
- **Documentation**: Improved README with dedicated CLI documentation and infrastructure status information
|
||||
|
||||
### 📚 Documentation
|
||||
- Enhanced README with comprehensive CLI documentation and setup instructions
|
||||
- Added Docker infrastructure status documentation
|
||||
|
||||
## What's New in v2.0.0
|
||||
|
||||
### New Features
|
||||
- **Task Integration**: Connected ideas to tasks with "Go to Task" functionality across the UI
|
||||
- **File Explorer Panel**: Implemented file explorer panel with directory listing capabilities
|
||||
- **Terminal Task Selection**: Added task selection dropdown in terminal with auto-context loading
|
||||
- **Task Archiving**: Introduced task archiving functionality
|
||||
- **Graphiti MCP Server Integration**: Added support for Graphiti memory integration
|
||||
- **Roadmap Functionality**: New roadmap visualization and management features
|
||||
|
||||
### Improvements
|
||||
- **File Tree Virtualization**: Refactored FileTree component to use efficient virtualization for improved performance with large file structures
|
||||
- **Agent Parallelization**: Improved Claude Code agent decision-making for parallel task execution
|
||||
- **Terminal Experience**: Enhanced terminal with task features and visual feedback for better user experience
|
||||
- **Python Environment Detection**: Auto-detect Python environment readiness before task execution
|
||||
- **Version System**: Cleaner version management system
|
||||
- **Project Initialization**: Simpler project initialization process
|
||||
|
||||
### Bug Fixes
|
||||
- Fixed project settings bug
|
||||
- Fixed insight UI sidebar
|
||||
- Resolved Kanban and terminal integration issues
|
||||
|
||||
### Changed
|
||||
- Updated project-store.ts to use proper Dirent type for specDirs variable
|
||||
- Refactored codebase for better code quality
|
||||
- Removed worktree-worker logic in favor of Claude Code's internal agent system
|
||||
- Removed obsolete security configuration file (.auto-claude-security.json)
|
||||
|
||||
### Documentation
|
||||
- Added CONTRIBUTING.md with development guidelines
|
||||
|
||||
## What's New in v1.1.0
|
||||
|
||||
### New Features
|
||||
@@ -11,4 +924,4 @@
|
||||
- **Draft Auto-Save**: Task creation state is now automatically saved when you navigate away, preventing accidental loss of work-in-progress.
|
||||
|
||||
### Bug Fixes
|
||||
- Fixed task editing to support the same comprehensive options available in new task creation
|
||||
- Fixed task editing to support the same comprehensive options available in new task creation
|
||||
|
||||
@@ -4,80 +4,154 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
|
||||
## Project Overview
|
||||
|
||||
Auto Claude is a multi-agent autonomous coding framework that builds software through coordinated AI agent sessions. It uses the Claude Code SDK to run agents in isolated workspaces with security controls.
|
||||
Auto Claude is a multi-agent autonomous coding framework that builds software through coordinated AI agent sessions. It uses the Claude Agent SDK to run agents in isolated workspaces with security controls.
|
||||
|
||||
**CRITICAL: All AI interactions use the Claude Agent SDK (`claude-agent-sdk` package), NOT the Anthropic API directly.**
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
autonomous-coding/
|
||||
├── apps/
|
||||
│ ├── backend/ # Python backend/CLI - ALL agent logic lives here
|
||||
│ │ ├── core/ # Client, auth, security
|
||||
│ │ ├── agents/ # Agent implementations
|
||||
│ │ ├── spec_agents/ # Spec creation agents
|
||||
│ │ ├── integrations/ # Graphiti, Linear, GitHub
|
||||
│ │ └── prompts/ # Agent system prompts
|
||||
│ └── frontend/ # Electron desktop UI
|
||||
├── guides/ # Documentation
|
||||
├── tests/ # Test suite
|
||||
└── scripts/ # Build and utility scripts
|
||||
```
|
||||
|
||||
**When working with AI/LLM code:**
|
||||
- Look in `apps/backend/core/client.py` for the Claude SDK client setup
|
||||
- Reference `apps/backend/agents/` for working agent implementations
|
||||
- Check `apps/backend/spec_agents/` for spec creation agent examples
|
||||
- NEVER use `anthropic.Anthropic()` directly - always use `create_client()` from `core.client`
|
||||
|
||||
**Frontend (Electron Desktop App):**
|
||||
- Built with Electron, React, TypeScript
|
||||
- AI agents can perform E2E testing using the Electron MCP server
|
||||
- When bug fixing or implementing features, use the Electron MCP server for automated testing
|
||||
- See "End-to-End Testing" section below for details
|
||||
|
||||
## Commands
|
||||
|
||||
### Setup
|
||||
|
||||
**Requirements:**
|
||||
- Python 3.12+ (required for backend)
|
||||
- Node.js (for frontend)
|
||||
|
||||
```bash
|
||||
# Install dependencies (from auto-claude/)
|
||||
uv venv && uv pip install -r requirements.txt
|
||||
# Or: python3 -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt
|
||||
# Install all dependencies from root
|
||||
npm run install:all
|
||||
|
||||
# Or install separately:
|
||||
# Backend (from apps/backend/)
|
||||
cd apps/backend && uv venv && uv pip install -r requirements.txt
|
||||
|
||||
# Frontend (from apps/frontend/)
|
||||
cd apps/frontend && npm install
|
||||
|
||||
# Set up OAuth token
|
||||
claude setup-token
|
||||
# Add to auto-claude/.env: CLAUDE_CODE_OAUTH_TOKEN=your-token
|
||||
# Add to apps/backend/.env: CLAUDE_CODE_OAUTH_TOKEN=your-token
|
||||
```
|
||||
|
||||
### Creating and Running Specs
|
||||
```bash
|
||||
cd apps/backend
|
||||
|
||||
# Create a spec interactively
|
||||
python auto-claude/spec_runner.py --interactive
|
||||
python spec_runner.py --interactive
|
||||
|
||||
# Create spec from task description
|
||||
python auto-claude/spec_runner.py --task "Add user authentication"
|
||||
python spec_runner.py --task "Add user authentication"
|
||||
|
||||
# Force complexity level (simple/standard/complex)
|
||||
python auto-claude/spec_runner.py --task "Fix button" --complexity simple
|
||||
python spec_runner.py --task "Fix button" --complexity simple
|
||||
|
||||
# Run autonomous build
|
||||
python auto-claude/run.py --spec 001
|
||||
python run.py --spec 001
|
||||
|
||||
# List all specs
|
||||
python auto-claude/run.py --list
|
||||
python run.py --list
|
||||
```
|
||||
|
||||
### Workspace Management
|
||||
```bash
|
||||
cd apps/backend
|
||||
|
||||
# Review changes in isolated worktree
|
||||
python auto-claude/run.py --spec 001 --review
|
||||
python run.py --spec 001 --review
|
||||
|
||||
# Merge completed build into project
|
||||
python auto-claude/run.py --spec 001 --merge
|
||||
python run.py --spec 001 --merge
|
||||
|
||||
# Discard build
|
||||
python auto-claude/run.py --spec 001 --discard
|
||||
python run.py --spec 001 --discard
|
||||
```
|
||||
|
||||
### QA Validation
|
||||
```bash
|
||||
cd apps/backend
|
||||
|
||||
# Run QA manually
|
||||
python auto-claude/run.py --spec 001 --qa
|
||||
python run.py --spec 001 --qa
|
||||
|
||||
# Check QA status
|
||||
python auto-claude/run.py --spec 001 --qa-status
|
||||
python run.py --spec 001 --qa-status
|
||||
```
|
||||
|
||||
### Testing
|
||||
```bash
|
||||
# Run all tests
|
||||
pytest tests/ -v
|
||||
# Install test dependencies (required first time)
|
||||
cd apps/backend && uv pip install -r ../../tests/requirements-test.txt
|
||||
|
||||
# Run all tests (use virtual environment pytest)
|
||||
apps/backend/.venv/bin/pytest tests/ -v
|
||||
|
||||
# Run single test file
|
||||
pytest tests/test_security.py -v
|
||||
apps/backend/.venv/bin/pytest tests/test_security.py -v
|
||||
|
||||
# Run specific test
|
||||
pytest tests/test_security.py::test_bash_command_validation -v
|
||||
apps/backend/.venv/bin/pytest tests/test_security.py::test_bash_command_validation -v
|
||||
|
||||
# Skip slow tests
|
||||
pytest tests/ -m "not slow"
|
||||
apps/backend/.venv/bin/pytest tests/ -m "not slow"
|
||||
|
||||
# Or from root
|
||||
npm run test:backend
|
||||
```
|
||||
|
||||
### Spec Validation
|
||||
```bash
|
||||
python auto-claude/validate_spec.py --spec-dir auto-claude/specs/001-feature --checkpoint all
|
||||
python apps/backend/validate_spec.py --spec-dir apps/backend/specs/001-feature --checkpoint all
|
||||
```
|
||||
|
||||
### Releases
|
||||
```bash
|
||||
# 1. Bump version on your branch (creates commit, no tag)
|
||||
node scripts/bump-version.js patch # 2.8.0 -> 2.8.1
|
||||
node scripts/bump-version.js minor # 2.8.0 -> 2.9.0
|
||||
node scripts/bump-version.js major # 2.8.0 -> 3.0.0
|
||||
|
||||
# 2. Push and create PR to main
|
||||
git push origin your-branch
|
||||
gh pr create --base main
|
||||
|
||||
# 3. Merge PR → GitHub Actions automatically:
|
||||
# - Creates tag
|
||||
# - Builds all platforms
|
||||
# - Creates release with changelog
|
||||
# - Updates README
|
||||
```
|
||||
|
||||
See [RELEASE.md](RELEASE.md) for detailed release process documentation.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Core Pipeline
|
||||
@@ -90,21 +164,43 @@ python auto-claude/validate_spec.py --spec-dir auto-claude/specs/001-feature --c
|
||||
**Implementation (run.py → agent.py)** - Multi-session build:
|
||||
1. Planner Agent creates subtask-based implementation plan
|
||||
2. Coder Agent implements subtasks (can spawn subagents for parallel work)
|
||||
3. QA Reviewer validates acceptance criteria
|
||||
4. QA Fixer resolves issues in a loop
|
||||
3. QA Reviewer validates acceptance criteria (can perform E2E testing via Electron MCP for frontend changes)
|
||||
4. QA Fixer resolves issues in a loop (with E2E testing to verify fixes)
|
||||
|
||||
### Key Components
|
||||
### Key Components (apps/backend/)
|
||||
|
||||
- **client.py** - Claude SDK client with security hooks and tool permissions
|
||||
- **security.py** + **project_analyzer.py** - Dynamic command allowlisting based on detected project stack
|
||||
- **worktree.py** - Git worktree isolation for safe feature development
|
||||
- **memory.py** - File-based session memory (primary, always-available storage)
|
||||
- **graphiti_memory.py** - Optional graph-based cross-session memory with semantic search
|
||||
- **graphiti_providers.py** - Multi-provider factory for Graphiti (OpenAI, Anthropic, Azure, Ollama)
|
||||
**Core Infrastructure:**
|
||||
- **core/client.py** - Claude Agent SDK client factory with security hooks and tool permissions
|
||||
- **core/security.py** - Dynamic command allowlisting based on detected project stack
|
||||
- **core/auth.py** - OAuth token management for Claude SDK authentication
|
||||
- **agents/** - Agent implementations (planner, coder, qa_reviewer, qa_fixer)
|
||||
- **spec_agents/** - Spec creation agents (gatherer, researcher, writer, critic)
|
||||
|
||||
**Memory & Context:**
|
||||
- **integrations/graphiti/** - Graphiti memory system (mandatory)
|
||||
- `queries_pkg/graphiti.py` - Main GraphitiMemory class
|
||||
- `queries_pkg/client.py` - LadybugDB client wrapper
|
||||
- `queries_pkg/queries.py` - Graph query operations
|
||||
- `queries_pkg/search.py` - Semantic search logic
|
||||
- `queries_pkg/schema.py` - Graph schema definitions
|
||||
- **graphiti_config.py** - Configuration and validation for Graphiti integration
|
||||
- **linear_updater.py** - Optional Linear integration for progress tracking
|
||||
- **graphiti_providers.py** - Multi-provider factory (OpenAI, Anthropic, Azure, Ollama, Google AI)
|
||||
- **agents/memory_manager.py** - Session memory orchestration
|
||||
|
||||
### Agent Prompts (auto-claude/prompts/)
|
||||
**Workspace & Security:**
|
||||
- **cli/worktree.py** - Git worktree isolation for safe feature development
|
||||
- **context/project_analyzer.py** - Project stack detection for dynamic tooling
|
||||
- **auto_claude_tools.py** - Custom MCP tools integration
|
||||
|
||||
**Integrations:**
|
||||
- **linear_updater.py** - Optional Linear integration for progress tracking
|
||||
- **runners/github/** - GitHub Issues & PRs automation
|
||||
- **Electron MCP** - E2E testing integration for QA agents (Chrome DevTools Protocol)
|
||||
- Enabled with `ELECTRON_MCP_ENABLED=true` in `.env`
|
||||
- Allows QA agents to interact with running Electron app
|
||||
- See "End-to-End Testing" section for details
|
||||
|
||||
### Agent Prompts (apps/backend/prompts/)
|
||||
|
||||
| Prompt | Purpose |
|
||||
|--------|---------|
|
||||
@@ -121,7 +217,7 @@ python auto-claude/validate_spec.py --spec-dir auto-claude/specs/001-feature --c
|
||||
|
||||
### Spec Directory Structure
|
||||
|
||||
Each spec in `auto-claude/specs/XXX-name/` contains:
|
||||
Each spec in `.auto-claude/specs/XXX-name/` contains:
|
||||
- `spec.md` - Feature specification
|
||||
- `requirements.json` - Structured user requirements
|
||||
- `context.json` - Discovered codebase context
|
||||
@@ -161,38 +257,225 @@ Three-layer defense:
|
||||
|
||||
Security profile cached in `.auto-claude-security.json`.
|
||||
|
||||
### Memory System
|
||||
### Claude Agent SDK Integration
|
||||
|
||||
Dual-layer memory architecture:
|
||||
**CRITICAL: Auto Claude uses the Claude Agent SDK for ALL AI interactions. Never use the Anthropic API directly.**
|
||||
|
||||
**File-Based Memory (Primary)** - `memory.py`
|
||||
- Zero dependencies, always available
|
||||
- Human-readable files in `specs/XXX/memory/`
|
||||
- Session insights, patterns, gotchas, codebase map
|
||||
**Client Location:** `apps/backend/core/client.py`
|
||||
|
||||
**Graphiti Memory (Optional Enhancement)** - `graphiti_memory.py`
|
||||
- Graph database with semantic search (FalkorDB)
|
||||
- Cross-session context retrieval
|
||||
- Multi-provider support (V2):
|
||||
- LLM: OpenAI, Anthropic, Azure OpenAI, Ollama
|
||||
- Embedders: OpenAI, Voyage AI, Azure OpenAI, Ollama
|
||||
The `create_client()` function creates a configured `ClaudeSDKClient` instance with:
|
||||
- Multi-layered security (sandbox, permissions, security hooks)
|
||||
- Agent-specific tool permissions (planner, coder, qa_reviewer, qa_fixer)
|
||||
- Dynamic MCP server integration based on project capabilities
|
||||
- Extended thinking token budget control
|
||||
|
||||
Enable with: `GRAPHITI_ENABLED=true` + provider credentials. See `.env.example`.
|
||||
**Example usage in agents:**
|
||||
```python
|
||||
from core.client import create_client
|
||||
|
||||
## Project Structure
|
||||
# Create SDK client (NOT raw Anthropic API client)
|
||||
client = create_client(
|
||||
project_dir=project_dir,
|
||||
spec_dir=spec_dir,
|
||||
model="claude-sonnet-4-5-20250929",
|
||||
agent_type="coder",
|
||||
max_thinking_tokens=None # or 5000/10000/16000
|
||||
)
|
||||
|
||||
Auto Claude can be used in two ways:
|
||||
|
||||
**As a standalone CLI tool** (original project):
|
||||
```bash
|
||||
python auto-claude/run.py --spec 001
|
||||
# Run agent session
|
||||
response = client.create_agent_session(
|
||||
name="coder-agent-session",
|
||||
starting_message="Implement the authentication feature"
|
||||
)
|
||||
```
|
||||
|
||||
**With the optional Electron frontend** (`auto-claude-ui/`):
|
||||
- Provides a GUI for task management and progress tracking
|
||||
- Wraps the CLI commands - the backend works independently
|
||||
**Why use the SDK:**
|
||||
- Pre-configured security (sandbox, allowlists, hooks)
|
||||
- Automatic MCP server integration (Context7, Linear, Graphiti, Electron, Puppeteer)
|
||||
- Tool permissions based on agent role
|
||||
- Session management and recovery
|
||||
- Unified API across all agent types
|
||||
|
||||
**Directory layout:**
|
||||
- `auto-claude/` - Python backend/CLI (the framework code)
|
||||
- `auto-claude-ui/` - Optional Electron frontend
|
||||
- `.auto-claude/specs/` - Per-project data (specs, plans, QA reports) - gitignored
|
||||
**Where to find working examples:**
|
||||
- `apps/backend/agents/planner.py` - Planner agent
|
||||
- `apps/backend/agents/coder.py` - Coder agent
|
||||
- `apps/backend/agents/qa_reviewer.py` - QA reviewer
|
||||
- `apps/backend/agents/qa_fixer.py` - QA fixer
|
||||
- `apps/backend/spec_agents/` - Spec creation agents
|
||||
|
||||
### Memory System
|
||||
|
||||
**Graphiti Memory (Mandatory)** - `integrations/graphiti/`
|
||||
|
||||
Auto Claude uses Graphiti as its primary memory system with embedded LadybugDB (no Docker required):
|
||||
|
||||
- **Graph database with semantic search** - Knowledge graph for cross-session context
|
||||
- **Session insights** - Patterns, gotchas, discoveries automatically extracted
|
||||
- **Multi-provider support:**
|
||||
- LLM: OpenAI, Anthropic, Azure OpenAI, Ollama, Google AI (Gemini)
|
||||
- Embedders: OpenAI, Voyage AI, Azure OpenAI, Ollama, Google AI
|
||||
- **Modular architecture:** (`integrations/graphiti/queries_pkg/`)
|
||||
- `graphiti.py` - Main GraphitiMemory class
|
||||
- `client.py` - LadybugDB client wrapper
|
||||
- `queries.py` - Graph query operations
|
||||
- `search.py` - Semantic search logic
|
||||
- `schema.py` - Graph schema definitions
|
||||
|
||||
**Configuration:**
|
||||
- Set provider credentials in `apps/backend/.env` (see `.env.example`)
|
||||
- Required env vars: `GRAPHITI_ENABLED=true`, `ANTHROPIC_API_KEY` or other provider keys
|
||||
- Memory data stored in `.auto-claude/specs/XXX/graphiti/`
|
||||
|
||||
**Usage in agents:**
|
||||
```python
|
||||
from integrations.graphiti.memory import get_graphiti_memory
|
||||
|
||||
memory = get_graphiti_memory(spec_dir, project_dir)
|
||||
context = memory.get_context_for_session("Implementing feature X")
|
||||
memory.add_session_insight("Pattern: use React hooks for state")
|
||||
```
|
||||
|
||||
## Development Guidelines
|
||||
|
||||
### Frontend Internationalization (i18n)
|
||||
|
||||
**CRITICAL: Always use i18n translation keys for all user-facing text in the frontend.**
|
||||
|
||||
The frontend uses `react-i18next` for internationalization. All labels, buttons, messages, and user-facing text MUST use translation keys.
|
||||
|
||||
**Translation file locations:**
|
||||
- `apps/frontend/src/shared/i18n/locales/en/*.json` - English translations
|
||||
- `apps/frontend/src/shared/i18n/locales/fr/*.json` - French translations
|
||||
|
||||
**Translation namespaces:**
|
||||
- `common.json` - Shared labels, buttons, common terms
|
||||
- `navigation.json` - Sidebar navigation items, sections
|
||||
- `settings.json` - Settings page content
|
||||
- `dialogs.json` - Dialog boxes and modals
|
||||
- `tasks.json` - Task/spec related content
|
||||
- `onboarding.json` - Onboarding wizard content
|
||||
- `welcome.json` - Welcome screen content
|
||||
|
||||
**Usage pattern:**
|
||||
```tsx
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
// In component
|
||||
const { t } = useTranslation(['navigation', 'common']);
|
||||
|
||||
// Use translation keys, NOT hardcoded strings
|
||||
<span>{t('navigation:items.githubPRs')}</span> // ✅ CORRECT
|
||||
<span>GitHub PRs</span> // ❌ WRONG
|
||||
```
|
||||
|
||||
**When adding new UI text:**
|
||||
1. Add the translation key to ALL language files (at minimum: `en/*.json` and `fr/*.json`)
|
||||
2. Use `namespace:section.key` format (e.g., `navigation:items.githubPRs`)
|
||||
3. Never use hardcoded strings in JSX/TSX files
|
||||
|
||||
### End-to-End Testing (Electron App)
|
||||
|
||||
**IMPORTANT: When bug fixing or implementing new features in the frontend, AI agents can perform automated E2E testing using the Electron MCP server.**
|
||||
|
||||
The Electron MCP server allows QA agents to interact with the running Electron app via Chrome DevTools Protocol:
|
||||
|
||||
**Setup:**
|
||||
1. Start the Electron app with remote debugging enabled:
|
||||
```bash
|
||||
npm run dev # Already configured with --remote-debugging-port=9222
|
||||
```
|
||||
|
||||
2. Enable Electron MCP in `apps/backend/.env`:
|
||||
```bash
|
||||
ELECTRON_MCP_ENABLED=true
|
||||
ELECTRON_DEBUG_PORT=9222 # Default port
|
||||
```
|
||||
|
||||
**Available Testing Capabilities:**
|
||||
|
||||
QA agents (`qa_reviewer` and `qa_fixer`) automatically get access to Electron MCP tools:
|
||||
|
||||
1. **Window Management**
|
||||
- `mcp__electron__get_electron_window_info` - Get info about running windows
|
||||
- `mcp__electron__take_screenshot` - Capture screenshots for visual verification
|
||||
|
||||
2. **UI Interaction**
|
||||
- `mcp__electron__send_command_to_electron` with commands:
|
||||
- `click_by_text` - Click buttons/links by visible text
|
||||
- `click_by_selector` - Click elements by CSS selector
|
||||
- `fill_input` - Fill form fields by placeholder or selector
|
||||
- `select_option` - Select dropdown options
|
||||
- `send_keyboard_shortcut` - Send keyboard shortcuts (Enter, Ctrl+N, etc.)
|
||||
- `navigate_to_hash` - Navigate to hash routes (#settings, #create, etc.)
|
||||
|
||||
3. **Page Inspection**
|
||||
- `get_page_structure` - Get organized overview of page elements
|
||||
- `debug_elements` - Get debugging info about buttons and forms
|
||||
- `verify_form_state` - Check form state and validation
|
||||
- `eval` - Execute custom JavaScript code
|
||||
|
||||
4. **Logging**
|
||||
- `mcp__electron__read_electron_logs` - Read console logs for debugging
|
||||
|
||||
**Example E2E Test Flow:**
|
||||
|
||||
```python
|
||||
# 1. Agent takes screenshot to see current state
|
||||
agent: "Take a screenshot to see the current UI"
|
||||
# Uses: mcp__electron__take_screenshot
|
||||
|
||||
# 2. Agent inspects page structure
|
||||
agent: "Get page structure to find available buttons"
|
||||
# Uses: mcp__electron__send_command_to_electron (command: "get_page_structure")
|
||||
|
||||
# 3. Agent clicks a button to navigate
|
||||
agent: "Click the 'Create New Spec' button"
|
||||
# Uses: mcp__electron__send_command_to_electron (command: "click_by_text", args: {text: "Create New Spec"})
|
||||
|
||||
# 4. Agent fills out a form
|
||||
agent: "Fill the task description field"
|
||||
# Uses: mcp__electron__send_command_to_electron (command: "fill_input", args: {placeholder: "Describe your task", value: "Add login feature"})
|
||||
|
||||
# 5. Agent submits and verifies
|
||||
agent: "Click Submit and verify success"
|
||||
# Uses: click_by_text → take_screenshot → verify result
|
||||
```
|
||||
|
||||
**When to Use E2E Testing:**
|
||||
|
||||
- **Bug Fixes**: Reproduce the bug, apply fix, verify it's resolved
|
||||
- **New Features**: Implement feature, test the UI flow end-to-end
|
||||
- **UI Changes**: Verify visual changes and interactions work correctly
|
||||
- **Form Validation**: Test form submission, validation, error handling
|
||||
|
||||
**Configuration in `core/client.py`:**
|
||||
|
||||
The client automatically enables Electron MCP tools for QA agents when:
|
||||
- Project is detected as Electron (`is_electron` capability)
|
||||
- `ELECTRON_MCP_ENABLED=true` is set
|
||||
- Agent type is `qa_reviewer` or `qa_fixer`
|
||||
|
||||
**Note:** Screenshots are automatically compressed (1280x720, quality 60, JPEG) to stay under Claude SDK's 1MB JSON message buffer limit.
|
||||
|
||||
## Running the Application
|
||||
|
||||
**As a standalone CLI tool**:
|
||||
```bash
|
||||
cd apps/backend
|
||||
python run.py --spec 001
|
||||
```
|
||||
|
||||
**With the Electron frontend**:
|
||||
```bash
|
||||
npm start # Build and run desktop app
|
||||
npm run dev # Run in development mode (includes --remote-debugging-port=9222 for E2E testing)
|
||||
```
|
||||
|
||||
**For E2E Testing with QA Agents:**
|
||||
1. Start the Electron app: `npm run dev`
|
||||
2. Enable Electron MCP in `apps/backend/.env`: `ELECTRON_MCP_ENABLED=true`
|
||||
3. Run QA: `python run.py --spec 001 --qa`
|
||||
4. QA agents will automatically interact with the running app for testing
|
||||
|
||||
**Project data storage:**
|
||||
- `.auto-claude/specs/` - Per-project data (specs, plans, QA reports, memory) - gitignored
|
||||
|
||||
+408
-43
@@ -5,13 +5,23 @@ Thank you for your interest in contributing to Auto Claude! This document provid
|
||||
## Table of Contents
|
||||
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Quick Start](#quick-start)
|
||||
- [Development Setup](#development-setup)
|
||||
- [Python Backend](#python-backend)
|
||||
- [Electron Frontend](#electron-frontend)
|
||||
- [Running from Source](#running-from-source)
|
||||
- [Pre-commit Hooks](#pre-commit-hooks)
|
||||
- [Code Style](#code-style)
|
||||
- [Testing](#testing)
|
||||
- [Continuous Integration](#continuous-integration)
|
||||
- [Git Workflow](#git-workflow)
|
||||
- [Branch Overview](#branch-overview)
|
||||
- [Main Branches](#main-branches)
|
||||
- [Supporting Branches](#supporting-branches)
|
||||
- [Branch Naming](#branch-naming)
|
||||
- [Where to Branch From](#where-to-branch-from)
|
||||
- [Pull Request Targets](#pull-request-targets)
|
||||
- [Release Process](#release-process-maintainers)
|
||||
- [Commit Messages](#commit-messages)
|
||||
- [Pull Request Process](#pull-request-process)
|
||||
- [Issue Reporting](#issue-reporting)
|
||||
@@ -21,38 +31,77 @@ Thank you for your interest in contributing to Auto Claude! This document provid
|
||||
|
||||
Before contributing, ensure you have the following installed:
|
||||
|
||||
- **Python 3.8+** - For the backend framework
|
||||
- **Node.js 18+** - For the Electron frontend
|
||||
- **pnpm** - Package manager for the frontend (`npm install -g pnpm`)
|
||||
- **Python 3.12+** - For the backend framework
|
||||
- **Node.js 24+** - For the Electron frontend
|
||||
- **npm 10+** - Package manager for the frontend (comes with Node.js)
|
||||
- **uv** (recommended) or **pip** - Python package manager
|
||||
- **Git** - Version control
|
||||
- **Docker** (optional) - For running FalkorDB if using Graphiti memory
|
||||
|
||||
### Installing Python 3.12
|
||||
|
||||
**Windows:**
|
||||
```bash
|
||||
winget install Python.Python.3.12
|
||||
```
|
||||
|
||||
**macOS:**
|
||||
```bash
|
||||
brew install python@3.12
|
||||
```
|
||||
|
||||
**Linux (Ubuntu/Debian):**
|
||||
```bash
|
||||
sudo apt install python3.12 python3.12-venv
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
The fastest way to get started:
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/AndyMik90/Auto-Claude.git
|
||||
cd Auto-Claude
|
||||
|
||||
# Install all dependencies (cross-platform)
|
||||
npm run install:all
|
||||
|
||||
# Run in development mode
|
||||
npm run dev
|
||||
|
||||
# Or build and run production
|
||||
npm start
|
||||
```
|
||||
|
||||
## Development Setup
|
||||
|
||||
The project consists of two main components:
|
||||
|
||||
1. **Python Backend** (`auto-claude/`) - The core autonomous coding framework
|
||||
2. **Electron Frontend** (`auto-claude-ui/`) - Optional desktop UI
|
||||
1. **Python Backend** (`apps/backend/`) - The core autonomous coding framework
|
||||
2. **Electron Frontend** (`apps/frontend/`) - Optional desktop UI
|
||||
|
||||
### Python Backend
|
||||
|
||||
The recommended way is to use `npm run install:backend`, but you can also set up manually:
|
||||
|
||||
```bash
|
||||
# Navigate to the auto-claude directory
|
||||
cd auto-claude
|
||||
# Navigate to the backend directory
|
||||
cd apps/backend
|
||||
|
||||
# Create virtual environment (using uv - recommended)
|
||||
uv venv
|
||||
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
||||
uv pip install -r requirements.txt
|
||||
# Create virtual environment
|
||||
# Windows:
|
||||
py -3.12 -m venv .venv
|
||||
.venv\Scripts\activate
|
||||
|
||||
# Or using standard Python
|
||||
python3 -m venv .venv
|
||||
# macOS/Linux:
|
||||
python3.12 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
|
||||
# Install dependencies
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Install test dependencies
|
||||
pip install -r ../tests/requirements-test.txt
|
||||
pip install -r ../../tests/requirements-test.txt
|
||||
|
||||
# Set up environment
|
||||
cp .env.example .env
|
||||
@@ -62,22 +111,123 @@ cp .env.example .env
|
||||
### Electron Frontend
|
||||
|
||||
```bash
|
||||
# Navigate to the UI directory
|
||||
cd auto-claude-ui
|
||||
# Navigate to the frontend directory
|
||||
cd apps/frontend
|
||||
|
||||
# Install dependencies
|
||||
pnpm install
|
||||
npm install
|
||||
|
||||
# Start development server
|
||||
pnpm dev
|
||||
npm run dev
|
||||
|
||||
# Build for production
|
||||
pnpm build
|
||||
npm run build
|
||||
|
||||
# Package for distribution
|
||||
pnpm package
|
||||
npm run package
|
||||
```
|
||||
|
||||
## Running from Source
|
||||
|
||||
If you want to run Auto Claude from source (for development or testing unreleased features), follow these steps:
|
||||
|
||||
### Step 1: Clone and Set Up
|
||||
|
||||
```bash
|
||||
git clone https://github.com/AndyMik90/Auto-Claude.git
|
||||
cd Auto-Claude/apps/backend
|
||||
|
||||
# Using uv (recommended)
|
||||
uv venv && uv pip install -r requirements.txt
|
||||
|
||||
# Or using standard Python
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Set up environment
|
||||
cd apps/backend
|
||||
cp .env.example .env
|
||||
# Edit .env and add your CLAUDE_CODE_OAUTH_TOKEN (get it via: claude setup-token)
|
||||
```
|
||||
|
||||
### Step 2: Run the Desktop UI
|
||||
|
||||
```bash
|
||||
cd ../frontend
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Development mode (hot reload)
|
||||
npm run dev
|
||||
|
||||
# Or production build
|
||||
npm run build && npm run start
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary><b>Windows users:</b> If installation fails with node-gyp errors, click here</summary>
|
||||
|
||||
Auto Claude automatically downloads prebuilt binaries for Windows. If prebuilts aren't available for your Electron version yet, you'll need Visual Studio Build Tools:
|
||||
|
||||
1. Download [Visual Studio Build Tools 2022](https://visualstudio.microsoft.com/visual-cpp-build-tools/)
|
||||
2. Select "Desktop development with C++" workload
|
||||
3. In "Individual Components", add "MSVC v143 - VS 2022 C++ x64/x86 Spectre-mitigated libs"
|
||||
4. Restart terminal and run `npm install` again
|
||||
|
||||
</details>
|
||||
|
||||
> **Note:** For regular usage, we recommend downloading the pre-built releases from [GitHub Releases](https://github.com/AndyMik90/Auto-Claude/releases). Running from source is primarily for contributors and those testing unreleased features.
|
||||
|
||||
## Pre-commit Hooks
|
||||
|
||||
We use [pre-commit](https://pre-commit.com/) to run linting and formatting checks before each commit. This ensures code quality and consistency across the project.
|
||||
|
||||
### Setup
|
||||
|
||||
```bash
|
||||
# Install pre-commit
|
||||
pip install pre-commit
|
||||
|
||||
# Install the git hooks (run once after cloning)
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
### What Runs on Commit
|
||||
|
||||
When you commit, the following checks run automatically:
|
||||
|
||||
| Check | Scope | Description |
|
||||
|-------|-------|-------------|
|
||||
| **ruff** | `apps/backend/` | Python linter with auto-fix |
|
||||
| **ruff-format** | `apps/backend/` | Python code formatter |
|
||||
| **eslint** | `apps/frontend/` | TypeScript/React linter |
|
||||
| **typecheck** | `apps/frontend/` | TypeScript type checking |
|
||||
| **trailing-whitespace** | All files | Removes trailing whitespace |
|
||||
| **end-of-file-fixer** | All files | Ensures files end with newline |
|
||||
| **check-yaml** | All files | Validates YAML syntax |
|
||||
| **check-added-large-files** | All files | Prevents large file commits |
|
||||
|
||||
### Running Manually
|
||||
|
||||
```bash
|
||||
# Run all checks on all files
|
||||
pre-commit run --all-files
|
||||
|
||||
# Run a specific hook
|
||||
pre-commit run ruff --all-files
|
||||
|
||||
# Skip hooks temporarily (not recommended)
|
||||
git commit --no-verify -m "message"
|
||||
```
|
||||
|
||||
### If a Check Fails
|
||||
|
||||
1. **Ruff auto-fixes**: Some issues are fixed automatically. Stage the changes and commit again.
|
||||
2. **ESLint errors**: Fix the reported issues in your code.
|
||||
3. **Type errors**: Resolve TypeScript type issues before committing.
|
||||
|
||||
## Code Style
|
||||
|
||||
### Python
|
||||
@@ -110,7 +260,7 @@ def gnc(sd):
|
||||
### TypeScript/React
|
||||
|
||||
- Use TypeScript strict mode
|
||||
- Follow the existing component patterns in `auto-claude-ui/src/`
|
||||
- Follow the existing component patterns in `apps/frontend/src/`
|
||||
- Use functional components with hooks
|
||||
- Prefer named exports over default exports
|
||||
- Use the UI components from `src/renderer/components/ui/`
|
||||
@@ -140,20 +290,25 @@ export default function(props) {
|
||||
### Python Tests
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
pytest tests/ -v
|
||||
# Run all tests (from repository root)
|
||||
npm run test:backend
|
||||
|
||||
# Or manually with pytest
|
||||
cd apps/backend
|
||||
.venv/Scripts/pytest.exe ../tests -v # Windows
|
||||
.venv/bin/pytest ../tests -v # macOS/Linux
|
||||
|
||||
# Run a specific test file
|
||||
pytest tests/test_security.py -v
|
||||
npm run test:backend -- tests/test_security.py -v
|
||||
|
||||
# Run a specific test
|
||||
pytest tests/test_security.py::test_bash_command_validation -v
|
||||
npm run test:backend -- tests/test_security.py::test_bash_command_validation -v
|
||||
|
||||
# Skip slow tests
|
||||
pytest tests/ -m "not slow"
|
||||
npm run test:backend -- -m "not slow"
|
||||
|
||||
# Run with coverage
|
||||
pytest tests/ --cov=auto-claude --cov-report=html
|
||||
pytest tests/ --cov=apps/backend --cov-report=html
|
||||
```
|
||||
|
||||
Test configuration is in `tests/pytest.ini`.
|
||||
@@ -161,26 +316,26 @@ Test configuration is in `tests/pytest.ini`.
|
||||
### Frontend Tests
|
||||
|
||||
```bash
|
||||
cd auto-claude-ui
|
||||
cd apps/frontend
|
||||
|
||||
# Run unit tests
|
||||
pnpm test
|
||||
npm test
|
||||
|
||||
# Run tests in watch mode
|
||||
pnpm test:watch
|
||||
npm run test:watch
|
||||
|
||||
# Run with coverage
|
||||
pnpm test:coverage
|
||||
npm run test:coverage
|
||||
|
||||
# Run E2E tests (requires built app)
|
||||
pnpm build
|
||||
pnpm test:e2e
|
||||
npm run build
|
||||
npm run test:e2e
|
||||
|
||||
# Run linting
|
||||
pnpm lint
|
||||
npm run lint
|
||||
|
||||
# Run type checking
|
||||
pnpm typecheck
|
||||
npm run typecheck
|
||||
```
|
||||
|
||||
### Testing Requirements
|
||||
@@ -192,8 +347,76 @@ Before submitting a PR:
|
||||
3. **Bug fixes should include a regression test**
|
||||
4. **Test coverage should not decrease significantly**
|
||||
|
||||
## Continuous Integration
|
||||
|
||||
All pull requests and pushes to `main` trigger automated CI checks via GitHub Actions.
|
||||
|
||||
### Workflows
|
||||
|
||||
| Workflow | Trigger | What it checks |
|
||||
|----------|---------|----------------|
|
||||
| **CI** | Push to `main`, PRs | Python tests (3.11 & 3.12), Frontend tests |
|
||||
| **Lint** | Push to `main`, PRs | Ruff (Python), ESLint + TypeScript (Frontend) |
|
||||
| **Test on Tag** | Version tags (`v*`) | Full test suite before release |
|
||||
|
||||
### PR Requirements
|
||||
|
||||
Before a PR can be merged:
|
||||
|
||||
1. All CI checks must pass (green checkmarks)
|
||||
2. Python tests pass on both Python 3.11 and 3.12
|
||||
3. Frontend tests pass
|
||||
4. Linting passes (no ruff or eslint errors)
|
||||
5. TypeScript type checking passes
|
||||
|
||||
### Running CI Checks Locally
|
||||
|
||||
```bash
|
||||
# Python tests
|
||||
cd apps/backend
|
||||
source .venv/bin/activate
|
||||
pytest ../../tests/ -v
|
||||
|
||||
# Frontend tests
|
||||
cd apps/frontend
|
||||
npm test
|
||||
npm run lint
|
||||
npm run typecheck
|
||||
```
|
||||
|
||||
## Git Workflow
|
||||
|
||||
We use a **Git Flow** branching strategy to manage releases and parallel development.
|
||||
|
||||
### Branch Overview
|
||||
|
||||
```
|
||||
main (stable) ← Only released, tested code (tagged versions)
|
||||
│
|
||||
develop ← Integration branch - all PRs merge here first
|
||||
│
|
||||
├── feature/xxx ← New features
|
||||
├── fix/xxx ← Bug fixes
|
||||
├── release/vX.Y.Z ← Release preparation
|
||||
└── hotfix/xxx ← Emergency production fixes
|
||||
```
|
||||
|
||||
### Main Branches
|
||||
|
||||
| Branch | Purpose | Protected |
|
||||
|--------|---------|-----------|
|
||||
| `main` | Production-ready code. Only receives merges from `release/*` or `hotfix/*` branches. Every merge is tagged (v2.7.0, v2.8.0, etc.) | ✅ Yes |
|
||||
| `develop` | Integration branch where all features and fixes are combined. This is the default target for all PRs. | ✅ Yes |
|
||||
|
||||
### Supporting Branches
|
||||
|
||||
| Branch Type | Branch From | Merge To | Purpose |
|
||||
|-------------|-------------|----------|---------|
|
||||
| `feature/*` | `develop` | `develop` | New features and enhancements |
|
||||
| `fix/*` | `develop` | `develop` | Bug fixes (non-critical) |
|
||||
| `release/*` | `develop` | `main` + `develop` | Release preparation and final testing |
|
||||
| `hotfix/*` | `main` | `main` + `develop` | Critical production bug fixes |
|
||||
|
||||
### Branch Naming
|
||||
|
||||
Use descriptive branch names with a prefix indicating the type of change:
|
||||
@@ -202,10 +425,146 @@ Use descriptive branch names with a prefix indicating the type of change:
|
||||
|--------|---------|---------|
|
||||
| `feature/` | New feature | `feature/add-dark-mode` |
|
||||
| `fix/` | Bug fix | `fix/memory-leak-in-worker` |
|
||||
| `hotfix/` | Urgent production fix | `hotfix/critical-crash-fix` |
|
||||
| `docs/` | Documentation | `docs/update-readme` |
|
||||
| `refactor/` | Code refactoring | `refactor/simplify-auth-flow` |
|
||||
| `test/` | Test additions/fixes | `test/add-integration-tests` |
|
||||
| `chore/` | Maintenance tasks | `chore/update-dependencies` |
|
||||
| `release/` | Release preparation | `release/v2.8.0` |
|
||||
| `hotfix/` | Emergency fixes | `hotfix/critical-auth-bug` |
|
||||
|
||||
### Where to Branch From
|
||||
|
||||
```bash
|
||||
# For features and bug fixes - ALWAYS branch from develop
|
||||
git checkout develop
|
||||
git pull origin develop
|
||||
git checkout -b feature/my-new-feature
|
||||
|
||||
# For hotfixes only - branch from main
|
||||
git checkout main
|
||||
git pull origin main
|
||||
git checkout -b hotfix/critical-fix
|
||||
```
|
||||
|
||||
### Pull Request Targets
|
||||
|
||||
> ⚠️ **Important:** All PRs should target `develop`, NOT `main`!
|
||||
|
||||
| Your Branch Type | Target Branch |
|
||||
|------------------|---------------|
|
||||
| `feature/*` | `develop` |
|
||||
| `fix/*` | `develop` |
|
||||
| `docs/*` | `develop` |
|
||||
| `refactor/*` | `develop` |
|
||||
| `test/*` | `develop` |
|
||||
| `chore/*` | `develop` |
|
||||
| `hotfix/*` | `main` (maintainers only) |
|
||||
| `release/*` | `main` (maintainers only) |
|
||||
|
||||
### Release Process (Maintainers)
|
||||
|
||||
When ready to release a new version:
|
||||
|
||||
```bash
|
||||
# 1. Create release branch from develop
|
||||
git checkout develop
|
||||
git pull origin develop
|
||||
git checkout -b release/v2.8.0
|
||||
|
||||
# 2. Update version numbers, CHANGELOG, final fixes only
|
||||
# No new features allowed in release branches!
|
||||
|
||||
# 3. Merge to main and tag
|
||||
git checkout main
|
||||
git merge release/v2.8.0
|
||||
git tag v2.8.0
|
||||
git push origin main --tags
|
||||
|
||||
# 4. Merge back to develop (important!)
|
||||
git checkout develop
|
||||
git merge release/v2.8.0
|
||||
git push origin develop
|
||||
|
||||
# 5. Delete release branch
|
||||
git branch -d release/v2.8.0
|
||||
git push origin --delete release/v2.8.0
|
||||
```
|
||||
|
||||
### Beta Release Process (Maintainers)
|
||||
|
||||
Beta releases allow users to test new features before they're included in a stable release. Beta releases are published from the `develop` branch.
|
||||
|
||||
**Creating a Beta Release:**
|
||||
|
||||
1. Go to **Actions** → **Beta Release** workflow in GitHub
|
||||
2. Click **Run workflow**
|
||||
3. Enter the beta version (e.g., `2.8.0-beta.1`)
|
||||
4. Optionally enable dry run to test without publishing
|
||||
5. Click **Run workflow**
|
||||
|
||||
The workflow will:
|
||||
- Validate the version format
|
||||
- Update `package.json` on develop
|
||||
- Create and push a tag (e.g., `v2.8.0-beta.1`)
|
||||
- Build installers for all platforms
|
||||
- Create a GitHub pre-release
|
||||
|
||||
**Version Format:**
|
||||
```
|
||||
X.Y.Z-beta.N (e.g., 2.8.0-beta.1, 2.8.0-beta.2)
|
||||
X.Y.Z-alpha.N (e.g., 2.8.0-alpha.1)
|
||||
X.Y.Z-rc.N (e.g., 2.8.0-rc.1)
|
||||
```
|
||||
|
||||
**For Users:**
|
||||
Users can opt into beta updates in Settings → Updates → "Beta Updates" toggle. When enabled, the app will check for and install beta versions. Users can switch back to stable at any time.
|
||||
|
||||
### Hotfix Workflow
|
||||
|
||||
For urgent production fixes that can't wait for the normal release cycle:
|
||||
|
||||
**1. Create hotfix from main**
|
||||
|
||||
```bash
|
||||
git checkout main
|
||||
git pull origin main
|
||||
git checkout -b hotfix/150-critical-fix
|
||||
```
|
||||
|
||||
**2. Fix the issue**
|
||||
|
||||
```bash
|
||||
# ... make changes ...
|
||||
git commit -m "hotfix: fix critical crash on startup"
|
||||
```
|
||||
|
||||
**3. Open PR to main (fast-track review)**
|
||||
|
||||
```bash
|
||||
gh pr create --base main --title "hotfix: fix critical crash on startup"
|
||||
```
|
||||
|
||||
**4. After merge to main, sync to develop**
|
||||
|
||||
```bash
|
||||
git checkout develop
|
||||
git pull origin develop
|
||||
git merge main
|
||||
git push origin develop
|
||||
```
|
||||
|
||||
```
|
||||
main ─────●─────●─────●─────●───── (production)
|
||||
↑ ↑ ↑ ↑
|
||||
develop ──●─────●─────●─────●───── (integration)
|
||||
↑ ↑ ↑
|
||||
feature/123 ────●
|
||||
feature/124 ──────────●
|
||||
hotfix/125 ─────────────────●───── (from main, merge to both)
|
||||
```
|
||||
|
||||
> **Note:** Hotfixes branch FROM `main` and merge TO `main` first, then sync back to `develop` to keep branches aligned.
|
||||
|
||||
### Commit Messages
|
||||
|
||||
@@ -239,17 +598,23 @@ git commit -m "WIP"
|
||||
|
||||
## Pull Request Process
|
||||
|
||||
1. **Fork the repository** and create your branch from `main`
|
||||
1. **Fork the repository** and create your branch from `develop` (not main!)
|
||||
|
||||
```bash
|
||||
git checkout develop
|
||||
git pull origin develop
|
||||
git checkout -b feature/your-feature-name
|
||||
```
|
||||
|
||||
2. **Make your changes** following the code style guidelines
|
||||
|
||||
3. **Test thoroughly**:
|
||||
```bash
|
||||
# Python
|
||||
pytest tests/ -v
|
||||
# Python (from repository root)
|
||||
npm run test:backend
|
||||
|
||||
# Frontend
|
||||
cd auto-claude-ui && pnpm test && pnpm lint && pnpm typecheck
|
||||
cd apps/frontend && npm test && npm run lint && npm run typecheck
|
||||
```
|
||||
|
||||
4. **Update documentation** if your changes affect:
|
||||
@@ -308,7 +673,7 @@ When requesting a feature:
|
||||
|
||||
Auto Claude consists of two main parts:
|
||||
|
||||
### Python Backend (`auto-claude/`)
|
||||
### Python Backend (`apps/backend/`)
|
||||
|
||||
The core autonomous coding framework:
|
||||
|
||||
@@ -318,9 +683,9 @@ The core autonomous coding framework:
|
||||
- **Memory**: `memory.py` (file-based), `graphiti_memory.py` (graph-based)
|
||||
- **QA**: `qa_loop.py`, `prompts/qa_*.md`
|
||||
|
||||
### Electron Frontend (`auto-claude-ui/`)
|
||||
### Electron Frontend (`apps/frontend/`)
|
||||
|
||||
Optional desktop interface:
|
||||
Desktop interface:
|
||||
|
||||
- **Main Process**: `src/main/` - Electron main process, IPC handlers
|
||||
- **Renderer**: `src/renderer/` - React UI components
|
||||
|
||||
@@ -1,430 +1,222 @@
|
||||
# Auto Claude
|
||||
|
||||
A production-ready framework for autonomous multi-session AI coding. Build complete applications or add features to existing projects through coordinated AI agent sessions.
|
||||
**Autonomous multi-agent coding framework that plans, builds, and validates software for you.**
|
||||
|
||||
## What It Does
|
||||

|
||||
|
||||
Auto Claude uses a **multi-agent pattern** to build software autonomously:
|
||||
[](https://github.com/AndyMik90/Auto-Claude/releases/latest)
|
||||
[](./agpl-3.0.txt)
|
||||
[](https://discord.gg/KCXaPBr4Dj)
|
||||
[](https://github.com/AndyMik90/Auto-Claude/actions)
|
||||
|
||||
### Spec Creation Pipeline (8 phases)
|
||||
1. **Discovery** - Analyzes project structure
|
||||
2. **Requirements Gatherer** - Collects user requirements interactively
|
||||
3. **Research Agent** - Validates external integrations against documentation
|
||||
4. **Context Discovery** - Finds relevant files in codebase
|
||||
5. **Spec Writer** - Creates comprehensive spec.md
|
||||
6. **Spec Critic** - Uses ultrathink to find and fix issues before implementation
|
||||
7. **Planner** - Creates chunk-based implementation plan
|
||||
8. **Validation** - Ensures all outputs are valid
|
||||
---
|
||||
|
||||
### Implementation Pipeline
|
||||
1. **Planner Agent** (Session 1) - Analyzes spec, creates chunk-based implementation plan
|
||||
2. **Coder Agent** (Sessions 2+) - Implements chunks one-by-one with verification
|
||||
3. **QA Reviewer Agent** - Validates all acceptance criteria before sign-off
|
||||
4. **QA Fixer Agent** - Fixes issues found by QA in a self-validating loop
|
||||
## Download
|
||||
|
||||
Each session runs with a fresh context window. Progress is tracked via `implementation_plan.json` and Git commits.
|
||||
Get the latest pre-built release for your platform:
|
||||
|
||||
| Platform | Download | Notes |
|
||||
|----------|----------|-------|
|
||||
| **Windows** | [Auto-Claude-2.7.2.exe](https://github.com/AndyMik90/Auto-Claude/releases/latest) | Installer (NSIS) |
|
||||
| **macOS (Apple Silicon)** | [Auto-Claude-2.7.2-arm64.dmg](https://github.com/AndyMik90/Auto-Claude/releases/latest) | M1/M2/M3 Macs |
|
||||
| **macOS (Intel)** | [Auto-Claude-2.7.2-x64.dmg](https://github.com/AndyMik90/Auto-Claude/releases/latest) | Intel Macs |
|
||||
| **Linux** | [Auto-Claude-2.7.2.AppImage](https://github.com/AndyMik90/Auto-Claude/releases/latest) | Universal |
|
||||
| **Linux (Debian)** | [Auto-Claude-2.7.2.deb](https://github.com/AndyMik90/Auto-Claude/releases/latest) | Ubuntu/Debian |
|
||||
|
||||
> All releases include SHA256 checksums and VirusTotal scan results for security verification.
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
- **Claude Pro/Max subscription** - [Get one here](https://claude.ai/upgrade)
|
||||
- **Claude Code CLI** - `npm install -g @anthropic-ai/claude-code`
|
||||
- **Git repository** - Your project must be initialized as a git repo
|
||||
- **Python 3.12+** - Required for the backend and Memory Layer
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Prerequisites
|
||||
1. **Download and install** the app for your platform
|
||||
2. **Open your project** - Select a git repository folder
|
||||
3. **Connect Claude** - The app will guide you through OAuth setup
|
||||
4. **Create a task** - Describe what you want to build
|
||||
5. **Watch it work** - Agents plan, code, and validate autonomously
|
||||
|
||||
- Python 3.8+
|
||||
- Claude Code CLI (`npm install -g @anthropic-ai/claude-code`)
|
||||
---
|
||||
|
||||
### Setup
|
||||
## Features
|
||||
|
||||
**Step 1:** Copy the `auto-claude` folder into your project
|
||||
| Feature | Description |
|
||||
|---------|-------------|
|
||||
| **Autonomous Tasks** | Describe your goal; agents handle planning, implementation, and validation |
|
||||
| **Parallel Execution** | Run multiple builds simultaneously with up to 12 agent terminals |
|
||||
| **Isolated Workspaces** | All changes happen in git worktrees - your main branch stays safe |
|
||||
| **Self-Validating QA** | Built-in quality assurance loop catches issues before you review |
|
||||
| **AI-Powered Merge** | Automatic conflict resolution when integrating back to main |
|
||||
| **Memory Layer** | Agents retain insights across sessions for smarter builds |
|
||||
| **Cross-Platform** | Native desktop apps for Windows, macOS, and Linux |
|
||||
| **Auto-Updates** | App updates automatically when new versions are released |
|
||||
|
||||
```bash
|
||||
# Copy the auto-claude folder to your project root
|
||||
cp -r auto-claude /path/to/your/project/
|
||||
```
|
||||
---
|
||||
|
||||
**Step 2:** Set up Python environment
|
||||
## Interface
|
||||
|
||||
```bash
|
||||
cd your-project
|
||||
cd auto-claude
|
||||
### Kanban Board
|
||||
Visual task management from planning through completion. Create tasks and monitor agent progress in real-time.
|
||||
|
||||
# Using uv (recommended)
|
||||
uv venv && uv pip install -r requirements.txt
|
||||
### Agent Terminals
|
||||
AI-powered terminals with one-click task context injection. Spawn multiple agents for parallel work.
|
||||
|
||||
# Or using standard Python
|
||||
python3 -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt
|
||||
```
|
||||

|
||||
|
||||
**Step 3:** Configure environment
|
||||
### Roadmap
|
||||
AI-assisted feature planning with competitor analysis and audience targeting.
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||

|
||||
|
||||
# Get your OAuth token
|
||||
claude setup-token
|
||||
### Additional Features
|
||||
- **Insights** - Chat interface for exploring your codebase
|
||||
- **Ideation** - Discover improvements, performance issues, and vulnerabilities
|
||||
- **Changelog** - Generate release notes from completed tasks
|
||||
|
||||
# Add the token to .env
|
||||
# CLAUDE_CODE_OAUTH_TOKEN=your-token-here
|
||||
```
|
||||
|
||||
**Step 4:** Create a spec using the orchestrator
|
||||
|
||||
```bash
|
||||
# Activate the virtual environment
|
||||
source auto-claude/.venv/bin/activate
|
||||
|
||||
# Create a spec interactively
|
||||
python auto-claude/spec_runner.py --interactive
|
||||
|
||||
# Or with a task description
|
||||
python auto-claude/spec_runner.py --task "Add user authentication with OAuth"
|
||||
```
|
||||
|
||||
The spec orchestrator will:
|
||||
1. Analyze your project structure
|
||||
2. Gather requirements interactively
|
||||
3. **Research external integrations** against documentation
|
||||
4. Discover relevant codebase context
|
||||
5. Write the specification
|
||||
6. **Self-critique using ultrathink** to find and fix issues
|
||||
7. Generate an implementation plan
|
||||
8. Validate all outputs
|
||||
|
||||
**Step 5:** Run the autonomous build
|
||||
|
||||
```bash
|
||||
python auto-claude/run.py --spec 001
|
||||
```
|
||||
|
||||
### Managing Specs
|
||||
|
||||
```bash
|
||||
# List all specs and their status
|
||||
python auto-claude/run.py --list
|
||||
|
||||
# Run a specific spec
|
||||
python auto-claude/run.py --spec 001
|
||||
python auto-claude/run.py --spec 001-feature-name
|
||||
|
||||
# Run with parallel workers (2-3x speedup for independent phases)
|
||||
python auto-claude/run.py --spec 001 --parallel 2
|
||||
python auto-claude/run.py --spec 001 --parallel 3
|
||||
|
||||
# Limit iterations for testing
|
||||
python auto-claude/run.py --spec 001 --max-iterations 5
|
||||
```
|
||||
|
||||
### QA Validation
|
||||
|
||||
After all chunks are complete, QA validation runs automatically:
|
||||
|
||||
```bash
|
||||
# QA runs automatically after build completes
|
||||
# To skip automatic QA:
|
||||
python auto-claude/run.py --spec 001 --skip-qa
|
||||
|
||||
# Run QA validation manually on a completed build
|
||||
python auto-claude/run.py --spec 001 --qa
|
||||
|
||||
# Check QA status
|
||||
python auto-claude/run.py --spec 001 --qa-status
|
||||
```
|
||||
|
||||
The QA validation loop:
|
||||
1. **QA Reviewer** checks all acceptance criteria (unit tests, integration tests, E2E, browser verification, database migrations)
|
||||
2. If issues found → creates `QA_FIX_REQUEST.md`
|
||||
3. **QA Fixer** applies fixes
|
||||
4. Loop repeats until approved (up to 50 iterations)
|
||||
5. Final sign-off recorded in `implementation_plan.json`
|
||||
|
||||
### Spec Creation Pipeline (Dynamic Complexity)
|
||||
|
||||
The `spec_runner.py` orchestrator **automatically assesses task complexity** and adapts the number of phases accordingly:
|
||||
|
||||
```bash
|
||||
# Simple task (auto-detected) - runs 3 phases
|
||||
python auto-claude/spec_runner.py --task "Fix button color in Header"
|
||||
|
||||
# Complex task (auto-detected) - runs 8 phases
|
||||
python auto-claude/spec_runner.py --task "Add Graphiti memory integration with FalkorDB"
|
||||
|
||||
# Force a specific complexity level
|
||||
python auto-claude/spec_runner.py --task "Update text" --complexity simple
|
||||
|
||||
# Interactive mode
|
||||
python auto-claude/spec_runner.py --interactive
|
||||
|
||||
# Continue an interrupted spec
|
||||
python auto-claude/spec_runner.py --continue 001-feature
|
||||
```
|
||||
|
||||
**Complexity Tiers:**
|
||||
|
||||
| Tier | Phases | When Used |
|
||||
|------|--------|-----------|
|
||||
| **SIMPLE** | 3 | 1-2 files, single service, no integrations (UI fixes, text changes) |
|
||||
| **STANDARD** | 6 | 3-10 files, 1-2 services, minimal integrations (features, bug fixes) |
|
||||
| **COMPLEX** | 8 | 10+ files, multiple services, external integrations (integrations, migrations) |
|
||||
|
||||
**Phase Matrix:**
|
||||
|
||||
| Phase | Simple | Standard | Complex |
|
||||
|-------|--------|----------|---------|
|
||||
| Discovery | ✓ | ✓ | ✓ |
|
||||
| Requirements | - | ✓ | ✓ |
|
||||
| **Research** | - | - | ✓ |
|
||||
| Context | - | ✓ | ✓ |
|
||||
| Spec Writing | Quick | Full | Full |
|
||||
| **Self-Critique** | - | - | ✓ |
|
||||
| Planning | Auto | ✓ | ✓ |
|
||||
| Validation | ✓ | ✓ | ✓ |
|
||||
|
||||
**Complexity Detection Signals:**
|
||||
- Keywords: "fix", "typo", "color" → Simple | "integrate", "migrate", "oauth" → Complex
|
||||
- External integrations detected (redis, postgres, graphiti, etc.)
|
||||
- Number of files/services mentioned
|
||||
- Infrastructure changes (docker, deploy, schema)
|
||||
|
||||
**Manual validation:**
|
||||
```bash
|
||||
python auto-claude/validate_spec.py --spec-dir auto-claude/specs/001-feature --checkpoint all
|
||||
```
|
||||
|
||||
### Isolated Worktrees (Safe by Default)
|
||||
|
||||
Auto Claude uses Git worktrees to keep your work completely safe. All AI-generated code is built in a separate workspace (`.worktrees/auto-claude/`) - your current files are never touched until you explicitly merge.
|
||||
|
||||
**How it works:**
|
||||
|
||||
1. When you run auto-claude, it creates an isolated workspace
|
||||
2. All coding happens in `.worktrees/auto-claude/` on its own branch
|
||||
3. You can `cd` into the worktree to test the feature before accepting
|
||||
4. Only when you're satisfied, merge the changes into your project
|
||||
|
||||
**After a build completes, you can:**
|
||||
|
||||
```bash
|
||||
# Test the feature in the isolated workspace
|
||||
cd .worktrees/auto-claude/
|
||||
npm run dev # or your project's run command
|
||||
|
||||
# See what was changed
|
||||
python auto-claude/run.py --spec 001 --review
|
||||
|
||||
# Add changes to your project
|
||||
python auto-claude/run.py --spec 001 --merge
|
||||
|
||||
# Discard if you don't like it (requires confirmation)
|
||||
python auto-claude/run.py --spec 001 --discard
|
||||
```
|
||||
|
||||
**Key benefits:**
|
||||
|
||||
- **Safety**: Your uncommitted work is protected - auto-claude won't touch it
|
||||
- **Testability**: Run and test the feature before committing to it
|
||||
- **Easy rollback**: Don't like it? Just discard the worktree
|
||||
- **Parallel-safe**: Multiple workers can build without conflicts
|
||||
|
||||
If you have uncommitted changes, auto-claude automatically uses isolated mode. With a clean working directory, you can choose between isolated (recommended) or direct mode.
|
||||
|
||||
### Interactive Controls
|
||||
|
||||
While the agent is running, you can:
|
||||
|
||||
```bash
|
||||
# Pause and optionally add instructions
|
||||
Ctrl+C (once)
|
||||
# You'll be prompted to add instructions for the agent
|
||||
# The agent will read these instructions when you resume
|
||||
|
||||
# Exit immediately without prompting
|
||||
Ctrl+C (twice)
|
||||
# Press Ctrl+C again during the prompt to exit
|
||||
```
|
||||
|
||||
**Alternative (file-based):**
|
||||
```bash
|
||||
# Create PAUSE file to pause after current session
|
||||
touch auto-claude/specs/001-name/PAUSE
|
||||
|
||||
# Manually edit instructions file
|
||||
echo "Focus on fixing the login bug first" > auto-claude/specs/001-name/HUMAN_INPUT.md
|
||||
```
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
your-project/
|
||||
├── .worktrees/ # Created during build (git-ignored)
|
||||
│ └── auto-claude/ # Isolated workspace for AI coding
|
||||
├── auto-claude/
|
||||
│ ├── run.py # Build entry point
|
||||
│ ├── spec_runner.py # Spec creation orchestrator (8-phase pipeline)
|
||||
│ ├── validate_spec.py # Spec validation with JSON schemas
|
||||
│ ├── agent.py # Session orchestration
|
||||
│ ├── planner.py # Deterministic implementation planner
|
||||
│ ├── worktree.py # Git worktree management
|
||||
│ ├── workspace.py # Workspace selection UI
|
||||
│ ├── coordinator.py # Parallel execution coordinator
|
||||
│ ├── qa_loop.py # QA validation loop
|
||||
│ ├── client.py # Claude SDK configuration
|
||||
│ ├── memory.py # File-based session memory (primary storage)
|
||||
│ ├── graphiti_memory.py # Graphiti knowledge graph integration (optional)
|
||||
│ ├── spec_contract.json # Spec creation contract (required outputs)
|
||||
│ ├── prompts/
|
||||
│ │ ├── planner.md # Session 1 - creates implementation plan
|
||||
│ │ ├── coder.md # Sessions 2+ - implements chunks
|
||||
│ │ ├── spec_gatherer.md # Requirements gathering agent
|
||||
│ │ ├── spec_researcher.md # External integration research agent
|
||||
│ │ ├── spec_writer.md # Spec document creation agent
|
||||
│ │ ├── spec_critic.md # Self-critique agent (ultrathink)
|
||||
│ │ ├── qa_reviewer.md # QA validation agent
|
||||
│ │ └── qa_fixer.md # QA fix agent
|
||||
│ └── specs/
|
||||
│ └── 001-feature/ # Each spec in its own folder
|
||||
│ ├── spec.md
|
||||
│ ├── requirements.json # User requirements (structured)
|
||||
│ ├── research.json # External integration research
|
||||
│ ├── context.json # Codebase context
|
||||
│ ├── critique_report.json # Self-critique findings
|
||||
│ ├── implementation_plan.json
|
||||
│ ├── qa_report.md # QA validation report
|
||||
│ └── QA_FIX_REQUEST.md # Issues to fix (if rejected)
|
||||
└── [your project files]
|
||||
Auto-Claude/
|
||||
├── apps/
|
||||
│ ├── backend/ # Python agents, specs, QA pipeline
|
||||
│ └── frontend/ # Electron desktop application
|
||||
├── guides/ # Additional documentation
|
||||
├── tests/ # Test suite
|
||||
└── scripts/ # Build utilities
|
||||
```
|
||||
|
||||
## Key Features
|
||||
---
|
||||
|
||||
- **Domain Agnostic**: Works for any software project (web apps, APIs, CLIs, etc.)
|
||||
- **Multi-Session**: Unlimited sessions, each with fresh context
|
||||
- **Research-First Specs**: External integrations validated against documentation before implementation
|
||||
- **Self-Critique**: Specs are critiqued using ultrathink to find issues before coding begins
|
||||
- **Parallel Execution**: 2-3x speedup with multiple workers on independent phases
|
||||
- **Isolated Worktrees**: Build in a separate workspace - your current work is never touched
|
||||
- **Self-Verifying**: Agents test their work with browser automation before marking complete
|
||||
- **QA Validation Loop**: Automated QA agent validates all acceptance criteria before sign-off
|
||||
- **Self-Healing**: QA finds issues → Fixer agent resolves → QA re-validates (up to 50 iterations)
|
||||
- **8-Phase Spec Pipeline**: Discovery → Requirements → Research → Context → Spec → Critique → Plan → Validate
|
||||
- **Fix Bugs Immediately**: Agents fix discovered bugs in the same session, not later
|
||||
- **Defense-in-Depth Security**: OS sandbox, filesystem restrictions, command allowlist
|
||||
- **Secret Scanning**: Automatic pre-commit scanning blocks secrets with actionable fix instructions
|
||||
- **Human Intervention**: Pause, add instructions, or stop at any time
|
||||
- **Multiple Specs**: Track and run multiple specifications independently
|
||||
- **Graphiti Memory** (Optional): Persistent knowledge graph for cross-session context retention
|
||||
## CLI Usage
|
||||
|
||||
## Graphiti Memory Integration V2 (Optional)
|
||||
|
||||
Auto Claude includes an optional **Graphiti-based persistent memory layer** that enables context retention across coding sessions. This uses FalkorDB as a graph database to store codebase patterns, session insights, and cross-session learnings.
|
||||
|
||||
### Why Use Graphiti Memory?
|
||||
|
||||
- **Cross-session context**: Agents remember insights from previous sessions
|
||||
- **Pattern recognition**: Discovered codebase patterns persist and are reusable
|
||||
- **Smarter agents**: Context retrieval helps agents make better decisions
|
||||
- **Historical hints**: Spec creation, ideation, and roadmap phases receive relevant historical insights
|
||||
|
||||
### Multi-Provider Support (V2)
|
||||
|
||||
Graphiti Memory V2 supports multiple LLM and embedding providers:
|
||||
|
||||
| LLM Providers | Embedding Providers |
|
||||
|---------------|---------------------|
|
||||
| OpenAI (default) | OpenAI (default) |
|
||||
| Anthropic | Voyage AI |
|
||||
| Azure OpenAI | Azure OpenAI |
|
||||
| Ollama (local) | Ollama (local) |
|
||||
|
||||
**Provider Combinations:**
|
||||
- **OpenAI + OpenAI**: Simplest setup, single API key
|
||||
- **Anthropic + Voyage**: High-quality LLM with specialized embeddings
|
||||
- **Ollama + Ollama**: Fully offline, no API keys needed
|
||||
- **Azure OpenAI + Azure OpenAI**: Enterprise deployments
|
||||
|
||||
### Setup
|
||||
|
||||
**Step 1:** Install the Graphiti dependency
|
||||
For headless operation, CI/CD integration, or terminal-only workflows:
|
||||
|
||||
```bash
|
||||
# Uncomment the graphiti line in requirements.txt, or install directly:
|
||||
pip install graphiti-core[falkordb]
|
||||
cd apps/backend
|
||||
|
||||
# Create a spec interactively
|
||||
python spec_runner.py --interactive
|
||||
|
||||
# Run autonomous build
|
||||
python run.py --spec 001
|
||||
|
||||
# Review and merge
|
||||
python run.py --spec 001 --review
|
||||
python run.py --spec 001 --merge
|
||||
```
|
||||
|
||||
**Step 2:** Start FalkorDB via Docker
|
||||
See [guides/CLI-USAGE.md](guides/CLI-USAGE.md) for complete CLI documentation.
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
Create `apps/backend/.env` from the example:
|
||||
|
||||
```bash
|
||||
docker-compose up -d falkordb
|
||||
cp apps/backend/.env.example apps/backend/.env
|
||||
```
|
||||
|
||||
**Step 3:** Configure environment variables
|
||||
|
||||
Add to your `.env` file (see `.env.example` for full documentation):
|
||||
|
||||
```bash
|
||||
# Enable Graphiti integration
|
||||
GRAPHITI_ENABLED=true
|
||||
|
||||
# Provider selection (defaults to openai)
|
||||
GRAPHITI_LLM_PROVIDER=openai
|
||||
GRAPHITI_EMBEDDER_PROVIDER=openai
|
||||
|
||||
# Example 1: OpenAI (simplest)
|
||||
OPENAI_API_KEY=sk-your-openai-key-here
|
||||
|
||||
# Example 2: Anthropic + Voyage (high quality)
|
||||
# GRAPHITI_LLM_PROVIDER=anthropic
|
||||
# GRAPHITI_EMBEDDER_PROVIDER=voyage
|
||||
# ANTHROPIC_API_KEY=sk-ant-xxx
|
||||
# VOYAGE_API_KEY=pa-xxx
|
||||
|
||||
# Example 3: Ollama (fully offline)
|
||||
# GRAPHITI_LLM_PROVIDER=ollama
|
||||
# GRAPHITI_EMBEDDER_PROVIDER=ollama
|
||||
# OLLAMA_LLM_MODEL=deepseek-r1:7b
|
||||
# OLLAMA_EMBEDDING_MODEL=nomic-embed-text
|
||||
# OLLAMA_EMBEDDING_DIM=768
|
||||
```
|
||||
|
||||
**Step 4:** Verify it's working
|
||||
|
||||
```bash
|
||||
python auto-claude/run.py --list
|
||||
# Should show: "Graphiti memory: ENABLED"
|
||||
|
||||
# Test the full integration
|
||||
python auto-claude/test_graphiti_memory.py
|
||||
```
|
||||
|
||||
### When Disabled
|
||||
|
||||
When `GRAPHITI_ENABLED` is not set (default), Auto Claude uses file-based memory only. This is the zero-dependency default that works out of the box.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| `CLAUDE_CODE_OAUTH_TOKEN` | Yes | OAuth token from `claude setup-token` |
|
||||
| `AUTO_BUILD_MODEL` | No | Model override (default: claude-opus-4-5-20251101) |
|
||||
| `GRAPHITI_ENABLED` | No | Set to `true` to enable Graphiti memory |
|
||||
| `GRAPHITI_LLM_PROVIDER` | No | LLM provider: openai, anthropic, azure_openai, ollama |
|
||||
| `GRAPHITI_EMBEDDER_PROVIDER` | No | Embedder: openai, voyage, azure_openai, ollama |
|
||||
| `OPENAI_API_KEY` | For OpenAI | Required for OpenAI provider |
|
||||
| `ANTHROPIC_API_KEY` | For Anthropic | Required for Anthropic LLM |
|
||||
| `VOYAGE_API_KEY` | For Voyage | Required for Voyage embeddings |
|
||||
| `GRAPHITI_ENABLED` | No | Enable Memory Layer for cross-session context |
|
||||
| `AUTO_BUILD_MODEL` | No | Override the default Claude model |
|
||||
|
||||
See `auto-claude/.env.example` for complete provider configuration options.
|
||||
---
|
||||
|
||||
## Documentation
|
||||
## Building from Source
|
||||
|
||||
For parallel execution details:
|
||||
- How parallelism works
|
||||
- Performance analysis
|
||||
- Best practices
|
||||
- Troubleshooting
|
||||
For contributors and development:
|
||||
|
||||
See [auto-claude/PARALLEL_EXECUTION.md](auto-claude/PARALLEL_EXECUTION.md)
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/AndyMik90/Auto-Claude.git
|
||||
cd Auto-Claude
|
||||
|
||||
## Acknowledgments
|
||||
# Install all dependencies
|
||||
npm run install:all
|
||||
|
||||
This framework was inspired by Anthropic's [Autonomous Coding Agent](https://github.com/anthropics/claude-quickstarts/tree/main/autonomous-coding). Thank you to the Anthropic team for their innovative work on autonomous coding systems.
|
||||
# Run in development mode
|
||||
npm run dev
|
||||
|
||||
# Or build and run
|
||||
npm start
|
||||
```
|
||||
|
||||
**System requirements for building:**
|
||||
- Node.js 24+
|
||||
- Python 3.12+
|
||||
- npm 10+
|
||||
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed development setup.
|
||||
|
||||
---
|
||||
|
||||
## Security
|
||||
|
||||
Auto Claude uses a three-layer security model:
|
||||
|
||||
1. **OS Sandbox** - Bash commands run in isolation
|
||||
2. **Filesystem Restrictions** - Operations limited to project directory
|
||||
3. **Dynamic Command Allowlist** - Only approved commands based on detected project stack
|
||||
|
||||
All releases are:
|
||||
- Scanned with VirusTotal before publishing
|
||||
- Include SHA256 checksums for verification
|
||||
- Code-signed where applicable (macOS)
|
||||
|
||||
---
|
||||
|
||||
## Available Scripts
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `npm run install:all` | Install backend and frontend dependencies |
|
||||
| `npm start` | Build and run the desktop app |
|
||||
| `npm run dev` | Run in development mode with hot reload |
|
||||
| `npm run package` | Package for current platform |
|
||||
| `npm run package:mac` | Package for macOS |
|
||||
| `npm run package:win` | Package for Windows |
|
||||
| `npm run package:linux` | Package for Linux |
|
||||
| `npm run lint` | Run linter |
|
||||
| `npm test` | Run frontend tests |
|
||||
| `npm run test:backend` | Run backend tests |
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome contributions! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for:
|
||||
- Development setup instructions
|
||||
- Code style guidelines
|
||||
- Testing requirements
|
||||
- Pull request process
|
||||
|
||||
---
|
||||
|
||||
## Community
|
||||
|
||||
- **Discord** - [Join our community](https://discord.gg/KCXaPBr4Dj)
|
||||
- **Issues** - [Report bugs or request features](https://github.com/AndyMik90/Auto-Claude/issues)
|
||||
- **Discussions** - [Ask questions](https://github.com/AndyMik90/Auto-Claude/discussions)
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
MIT License
|
||||
**AGPL-3.0** - GNU Affero General Public License v3.0
|
||||
|
||||
Auto Claude is free to use. If you modify and distribute it, or run it as a service, your code must also be open source under AGPL-3.0.
|
||||
|
||||
Commercial licensing available for closed-source use cases.
|
||||
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
# Release Process
|
||||
|
||||
This document describes how releases are created for Auto Claude.
|
||||
|
||||
## Overview
|
||||
|
||||
Auto Claude uses an automated release pipeline that ensures releases are only published after all builds succeed. This prevents version mismatches between documentation and actual releases.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ RELEASE FLOW │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ develop branch main branch │
|
||||
│ ────────────── ─────────── │
|
||||
│ │ │ │
|
||||
│ │ 1. bump-version.js │ │
|
||||
│ │ (creates commit) │ │
|
||||
│ │ │ │
|
||||
│ ▼ │ │
|
||||
│ ┌─────────┐ │ │
|
||||
│ │ v2.8.0 │ 2. Create PR │ │
|
||||
│ │ commit │ ────────────────────► │ │
|
||||
│ └─────────┘ │ │
|
||||
│ │ │
|
||||
│ 3. Merge PR ▼ │
|
||||
│ ┌──────────┐ │
|
||||
│ │ v2.8.0 │ │
|
||||
│ │ on main │ │
|
||||
│ └────┬─────┘ │
|
||||
│ │ │
|
||||
│ ┌───────────────────┴───────────────────┐ │
|
||||
│ │ GitHub Actions (automatic) │ │
|
||||
│ ├───────────────────────────────────────┤ │
|
||||
│ │ 4. prepare-release.yml │ │
|
||||
│ │ - Detects version > latest tag │ │
|
||||
│ │ - Creates tag v2.8.0 │ │
|
||||
│ │ │ │
|
||||
│ │ 5. release.yml (triggered by tag) │ │
|
||||
│ │ - Builds macOS (Intel + ARM) │ │
|
||||
│ │ - Builds Windows │ │
|
||||
│ │ - Builds Linux │ │
|
||||
│ │ - Generates changelog │ │
|
||||
│ │ - Creates GitHub release │ │
|
||||
│ │ - Updates README │ │
|
||||
│ └───────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## For Maintainers: Creating a Release
|
||||
|
||||
### Step 1: Bump the Version
|
||||
|
||||
On your development branch (typically `develop` or a feature branch):
|
||||
|
||||
```bash
|
||||
# Navigate to project root
|
||||
cd /path/to/auto-claude
|
||||
|
||||
# Bump version (choose one)
|
||||
node scripts/bump-version.js patch # 2.7.1 -> 2.7.2 (bug fixes)
|
||||
node scripts/bump-version.js minor # 2.7.1 -> 2.8.0 (new features)
|
||||
node scripts/bump-version.js major # 2.7.1 -> 3.0.0 (breaking changes)
|
||||
node scripts/bump-version.js 2.8.0 # Set specific version
|
||||
```
|
||||
|
||||
This will:
|
||||
- Update `apps/frontend/package.json`
|
||||
- Update `package.json` (root)
|
||||
- Update `apps/backend/__init__.py`
|
||||
- Create a commit with message `chore: bump version to X.Y.Z`
|
||||
|
||||
### Step 2: Push and Create PR
|
||||
|
||||
```bash
|
||||
# Push your branch
|
||||
git push origin your-branch
|
||||
|
||||
# Create PR to main (via GitHub UI or gh CLI)
|
||||
gh pr create --base main --title "Release v2.8.0"
|
||||
```
|
||||
|
||||
### Step 3: Merge to Main
|
||||
|
||||
Once the PR is approved and merged to `main`, GitHub Actions will automatically:
|
||||
|
||||
1. **Detect the version bump** (`prepare-release.yml`)
|
||||
2. **Create a git tag** (e.g., `v2.8.0`)
|
||||
3. **Trigger the release workflow** (`release.yml`)
|
||||
4. **Build binaries** for all platforms:
|
||||
- macOS Intel (x64) - code signed & notarized
|
||||
- macOS Apple Silicon (arm64) - code signed & notarized
|
||||
- Windows (NSIS installer) - code signed
|
||||
- Linux (AppImage + .deb)
|
||||
5. **Generate changelog** from merged PRs (using release-drafter)
|
||||
6. **Scan binaries** with VirusTotal
|
||||
7. **Create GitHub release** with all artifacts
|
||||
8. **Update README** with new version badge and download links
|
||||
|
||||
### Step 4: Verify
|
||||
|
||||
After merging, check:
|
||||
- [GitHub Actions](https://github.com/AndyMik90/Auto-Claude/actions) - ensure all workflows pass
|
||||
- [Releases](https://github.com/AndyMik90/Auto-Claude/releases) - verify release was created
|
||||
- [README](https://github.com/AndyMik90/Auto-Claude#download) - confirm version updated
|
||||
|
||||
## Version Numbering
|
||||
|
||||
We follow [Semantic Versioning](https://semver.org/):
|
||||
|
||||
- **MAJOR** (X.0.0): Breaking changes, incompatible API changes
|
||||
- **MINOR** (0.X.0): New features, backwards compatible
|
||||
- **PATCH** (0.0.X): Bug fixes, backwards compatible
|
||||
|
||||
## Changelog Generation
|
||||
|
||||
Changelogs are automatically generated from merged PRs using [Release Drafter](https://github.com/release-drafter/release-drafter).
|
||||
|
||||
### PR Labels for Changelog Categories
|
||||
|
||||
| Label | Category |
|
||||
|-------|----------|
|
||||
| `feature`, `enhancement` | New Features |
|
||||
| `bug`, `fix` | Bug Fixes |
|
||||
| `improvement`, `refactor` | Improvements |
|
||||
| `documentation` | Documentation |
|
||||
| (any other) | Other Changes |
|
||||
|
||||
**Tip:** Add appropriate labels to your PRs for better changelog organization.
|
||||
|
||||
## Workflows
|
||||
|
||||
| Workflow | Trigger | Purpose |
|
||||
|----------|---------|---------|
|
||||
| `prepare-release.yml` | Push to `main` | Detects version bump, creates tag |
|
||||
| `release.yml` | Tag `v*` pushed | Builds binaries, creates release |
|
||||
| `validate-version.yml` | Tag `v*` pushed | Validates tag matches package.json |
|
||||
| `update-readme` (in release.yml) | After release | Updates README with new version |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Release didn't trigger after merge
|
||||
|
||||
1. Check if version in `package.json` is greater than latest tag:
|
||||
```bash
|
||||
git tag -l 'v*' --sort=-version:refname | head -1
|
||||
cat apps/frontend/package.json | grep version
|
||||
```
|
||||
|
||||
2. Ensure the merge commit touched `package.json`:
|
||||
```bash
|
||||
git diff HEAD~1 --name-only | grep package.json
|
||||
```
|
||||
|
||||
### Build failed after tag was created
|
||||
|
||||
- The release won't be published if builds fail
|
||||
- Fix the issue and create a new patch version
|
||||
- Don't reuse failed version numbers
|
||||
|
||||
### README shows wrong version
|
||||
|
||||
- README is only updated after successful release
|
||||
- If release failed, README keeps the previous version (this is intentional)
|
||||
- Once you successfully release, README will update automatically
|
||||
|
||||
## Manual Release (Emergency Only)
|
||||
|
||||
In rare cases where you need to bypass the automated flow:
|
||||
|
||||
```bash
|
||||
# Create tag manually (NOT RECOMMENDED)
|
||||
git tag -a v2.8.0 -m "Release v2.8.0"
|
||||
git push origin v2.8.0
|
||||
|
||||
# This will trigger release.yml directly
|
||||
```
|
||||
|
||||
**Warning:** Only do this if you're certain the version in package.json matches the tag.
|
||||
|
||||
## Security
|
||||
|
||||
- All macOS binaries are code signed with Apple Developer certificate
|
||||
- All macOS binaries are notarized by Apple
|
||||
- Windows binaries are code signed
|
||||
- All binaries are scanned with VirusTotal
|
||||
- SHA256 checksums are generated for all artifacts
|
||||
+661
@@ -0,0 +1,661 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
@@ -1,14 +1,50 @@
|
||||
# Auto Claude Environment Variables
|
||||
# Copy this file to .env and fill in your values
|
||||
|
||||
# Claude Code OAuth Token (REQUIRED)
|
||||
# Get this by running: claude setup-token
|
||||
CLAUDE_CODE_OAUTH_TOKEN=your-oauth-token-here
|
||||
# =============================================================================
|
||||
# AUTHENTICATION (REQUIRED)
|
||||
# =============================================================================
|
||||
# Auto Claude uses Claude Code OAuth authentication.
|
||||
# Direct API keys (ANTHROPIC_API_KEY) are NOT supported to prevent silent billing.
|
||||
#
|
||||
# Option 1: Run `claude setup-token` to save token to macOS Keychain (recommended)
|
||||
# Option 2: Set the token explicitly:
|
||||
# CLAUDE_CODE_OAUTH_TOKEN=your-oauth-token-here
|
||||
#
|
||||
# For enterprise/proxy setups (CCR):
|
||||
# ANTHROPIC_AUTH_TOKEN=sk-zcf-x-ccr
|
||||
|
||||
# =============================================================================
|
||||
# CUSTOM API ENDPOINT (OPTIONAL)
|
||||
# =============================================================================
|
||||
# Override the default Anthropic API endpoint. Useful for:
|
||||
# - Local proxies (ccr, litellm)
|
||||
# - API gateways
|
||||
# - Self-hosted Claude instances
|
||||
#
|
||||
# ANTHROPIC_BASE_URL=http://127.0.0.1:3456
|
||||
#
|
||||
# Related settings (usually set together with ANTHROPIC_BASE_URL):
|
||||
# NO_PROXY=127.0.0.1
|
||||
# DISABLE_TELEMETRY=true
|
||||
# DISABLE_COST_WARNINGS=true
|
||||
# API_TIMEOUT_MS=600000
|
||||
|
||||
# Model override (OPTIONAL)
|
||||
# Default: claude-opus-4-5-20251101
|
||||
# AUTO_BUILD_MODEL=claude-opus-4-5-20251101
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# GIT/WORKTREE SETTINGS (OPTIONAL)
|
||||
# =============================================================================
|
||||
# Configure how Auto Claude handles git worktrees for isolated builds.
|
||||
|
||||
# Default base branch for worktree creation (OPTIONAL)
|
||||
# If not set, Auto Claude will auto-detect main/master, or fall back to current branch.
|
||||
# Common values: main, master, develop
|
||||
# DEFAULT_BRANCH=main
|
||||
|
||||
# =============================================================================
|
||||
# DEBUG MODE (OPTIONAL)
|
||||
# =============================================================================
|
||||
@@ -49,24 +85,67 @@ CLAUDE_CODE_OAUTH_TOKEN=your-oauth-token-here
|
||||
# ENABLE_FANCY_UI=true
|
||||
|
||||
# =============================================================================
|
||||
# GRAPHITI MEMORY INTEGRATION V2 (OPTIONAL)
|
||||
# ELECTRON MCP SERVER (OPTIONAL)
|
||||
# =============================================================================
|
||||
# Enable Graphiti-based persistent memory layer for cross-session context
|
||||
# retention. Uses FalkorDB as the graph database backend.
|
||||
# Enable Electron MCP server for AI agents to interact with and validate
|
||||
# Electron desktop applications. This allows QA agents to capture screenshots,
|
||||
# inspect windows, and validate Electron apps during the review process.
|
||||
#
|
||||
# V2 supports multiple LLM and embedder providers:
|
||||
# The electron-mcp-server connects via Chrome DevTools Protocol to an Electron
|
||||
# app running with remote debugging enabled.
|
||||
#
|
||||
# Prerequisites:
|
||||
# 1. Start your Electron app with remote debugging:
|
||||
# ./YourElectronApp --remote-debugging-port=9222
|
||||
#
|
||||
# 2. For auto-claude-ui specifically (use the MCP-enabled scripts):
|
||||
# cd auto-claude-ui
|
||||
# pnpm run dev:mcp # Development mode with MCP debugging
|
||||
# # OR for production build:
|
||||
# pnpm run start:mcp # Production mode with MCP debugging
|
||||
#
|
||||
# Note: Only QA agents (qa_reviewer, qa_fixer) receive Electron MCP tools.
|
||||
# Coder and Planner agents do NOT have access to these tools to minimize
|
||||
# context token usage and keep agents focused on their roles.
|
||||
#
|
||||
# See: https://github.com/anthropics/anthropic-quickstarts/tree/main/mcp-electron-demo
|
||||
|
||||
# Enable Electron MCP integration (default: false)
|
||||
# ELECTRON_MCP_ENABLED=true
|
||||
|
||||
# Chrome DevTools debugging port for Electron connection (default: 9222)
|
||||
# ELECTRON_DEBUG_PORT=9222
|
||||
|
||||
# =============================================================================
|
||||
# GRAPHITI MEMORY INTEGRATION (REQUIRED)
|
||||
# =============================================================================
|
||||
# Graphiti-based persistent memory layer for cross-session context
|
||||
# retention. Uses LadybugDB as the embedded graph database.
|
||||
#
|
||||
# REQUIREMENTS:
|
||||
# - Python 3.12 or higher
|
||||
# - Install: pip install real_ladybug graphiti-core
|
||||
#
|
||||
# Supports multiple LLM and embedder providers:
|
||||
# - OpenAI (default)
|
||||
# - Anthropic (LLM only, use with Voyage for embeddings)
|
||||
# - Azure OpenAI
|
||||
# - Ollama (local, fully offline)
|
||||
#
|
||||
# Prerequisites:
|
||||
# 1. Start FalkorDB: docker-compose up -d falkordb
|
||||
# 2. Install Graphiti: pip install graphiti-core[falkordb]
|
||||
# 3. Configure your chosen provider (see below)
|
||||
# - Google AI (Gemini)
|
||||
|
||||
# Enable Graphiti integration (default: false)
|
||||
# GRAPHITI_ENABLED=true
|
||||
# Graphiti is enabled by default. Set to false to disable memory features.
|
||||
GRAPHITI_ENABLED=true
|
||||
|
||||
# =============================================================================
|
||||
# GRAPHITI: Database Settings
|
||||
# =============================================================================
|
||||
# LadybugDB stores data in a local directory (no Docker required).
|
||||
|
||||
# Database name (default: auto_claude_memory)
|
||||
# GRAPHITI_DATABASE=auto_claude_memory
|
||||
|
||||
# Database storage path (default: ~/.auto-claude/memories)
|
||||
# GRAPHITI_DB_PATH=~/.auto-claude/memories
|
||||
|
||||
# =============================================================================
|
||||
# GRAPHITI: Provider Selection
|
||||
@@ -74,10 +153,10 @@ CLAUDE_CODE_OAUTH_TOKEN=your-oauth-token-here
|
||||
# Choose which providers to use for LLM and embeddings.
|
||||
# Default is "openai" for both.
|
||||
|
||||
# LLM provider: openai | anthropic | azure_openai | ollama
|
||||
# LLM provider: openai | anthropic | azure_openai | ollama | google | openrouter
|
||||
# GRAPHITI_LLM_PROVIDER=openai
|
||||
|
||||
# Embedder provider: openai | voyage | azure_openai | ollama
|
||||
# Embedder provider: openai | voyage | azure_openai | ollama | google | openrouter
|
||||
# GRAPHITI_EMBEDDER_PROVIDER=openai
|
||||
|
||||
# =============================================================================
|
||||
@@ -89,8 +168,8 @@ CLAUDE_CODE_OAUTH_TOKEN=your-oauth-token-here
|
||||
# OpenAI API Key
|
||||
# OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
|
||||
# OpenAI Model for LLM (default: gpt-5-mini)
|
||||
# OPENAI_MODEL=gpt-5-mini
|
||||
# OpenAI Model for LLM (default: gpt-4o-mini)
|
||||
# OPENAI_MODEL=gpt-4o-mini
|
||||
|
||||
# OpenAI Model for embeddings (default: text-embedding-3-small)
|
||||
# Available: text-embedding-3-small (1536 dim), text-embedding-3-large (3072 dim)
|
||||
@@ -125,6 +204,45 @@ CLAUDE_CODE_OAUTH_TOKEN=your-oauth-token-here
|
||||
# Available: voyage-3 (1024 dim), voyage-3-lite (512 dim)
|
||||
# VOYAGE_EMBEDDING_MODEL=voyage-3
|
||||
|
||||
# =============================================================================
|
||||
# GRAPHITI: Google AI Provider
|
||||
# =============================================================================
|
||||
# Use Google AI (Gemini) for both LLM and embeddings.
|
||||
# Get API key from: https://aistudio.google.com/apikey
|
||||
#
|
||||
# Required: GOOGLE_API_KEY
|
||||
|
||||
# Google AI API Key
|
||||
# GOOGLE_API_KEY=AIzaSyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
|
||||
# Google LLM Model (default: gemini-2.0-flash)
|
||||
# GOOGLE_LLM_MODEL=gemini-2.0-flash
|
||||
|
||||
# Google Embedding Model (default: text-embedding-004)
|
||||
# GOOGLE_EMBEDDING_MODEL=text-embedding-004
|
||||
|
||||
# =============================================================================
|
||||
# GRAPHITI: OpenRouter Provider (Multi-provider aggregator)
|
||||
# =============================================================================
|
||||
# Use OpenRouter to access multiple LLM providers through a single API.
|
||||
# OpenRouter provides access to Anthropic, OpenAI, Google, and many other models.
|
||||
# Get API key from: https://openrouter.ai/keys
|
||||
#
|
||||
# Required: OPENROUTER_API_KEY
|
||||
|
||||
# OpenRouter API Key
|
||||
# OPENROUTER_API_KEY=sk-or-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
|
||||
# OpenRouter Base URL (default: https://openrouter.ai/api/v1)
|
||||
# OPENROUTER_BASE_URL=https://openrouter.ai/api/v1
|
||||
|
||||
# OpenRouter LLM Model (default: anthropic/claude-3.5-sonnet)
|
||||
# Popular choices: anthropic/claude-3.5-sonnet, openai/gpt-4o, google/gemini-2.0-flash
|
||||
# OPENROUTER_LLM_MODEL=anthropic/claude-3.5-sonnet
|
||||
|
||||
# OpenRouter Embedding Model (default: openai/text-embedding-3-small)
|
||||
# OPENROUTER_EMBEDDING_MODEL=openai/text-embedding-3-small
|
||||
|
||||
# =============================================================================
|
||||
# GRAPHITI: Azure OpenAI Provider
|
||||
# =============================================================================
|
||||
@@ -140,7 +258,7 @@ CLAUDE_CODE_OAUTH_TOKEN=your-oauth-token-here
|
||||
# AZURE_OPENAI_BASE_URL=https://your-resource.openai.azure.com/openai/deployments/your-deployment
|
||||
|
||||
# Azure OpenAI Deployment Names
|
||||
# AZURE_OPENAI_LLM_DEPLOYMENT=gpt-5
|
||||
# AZURE_OPENAI_LLM_DEPLOYMENT=gpt-4
|
||||
# AZURE_OPENAI_EMBEDDING_DEPLOYMENT=text-embedding-3-small
|
||||
|
||||
# =============================================================================
|
||||
@@ -172,54 +290,6 @@ CLAUDE_CODE_OAUTH_TOKEN=your-oauth-token-here
|
||||
# Common values: nomic-embed-text=768, mxbai-embed-large=1024, all-minilm=384
|
||||
# OLLAMA_EMBEDDING_DIM=768
|
||||
|
||||
# =============================================================================
|
||||
# GRAPHITI MCP SERVER (OPTIONAL - Agent-accessible knowledge graph)
|
||||
# =============================================================================
|
||||
# Enable Graphiti MCP server for direct agent access to knowledge graph.
|
||||
# This is SEPARATE from GRAPHITI_ENABLED (Python library integration).
|
||||
#
|
||||
# With MCP server enabled, Claude agents can directly:
|
||||
# - Search for relevant context (search_nodes, search_facts)
|
||||
# - Add discoveries to the graph (add_episode)
|
||||
# - Retrieve recent sessions (get_episodes)
|
||||
#
|
||||
# Prerequisites:
|
||||
# 1. Run the Graphiti MCP server via Docker:
|
||||
# docker run -d -p 8000:8000 \
|
||||
# -e OPENAI_API_KEY=$OPENAI_API_KEY \
|
||||
# -e FALKORDB_URI=redis://host.docker.internal:6380 \
|
||||
# falkordb/graphiti-knowledge-graph-mcp
|
||||
#
|
||||
# See: https://docs.falkordb.com/agentic-memory/graphiti-mcp-server.html
|
||||
|
||||
# Graphiti MCP Server URL (setting this enables MCP integration)
|
||||
# GRAPHITI_MCP_URL=http://localhost:8000/mcp/
|
||||
|
||||
# =============================================================================
|
||||
# GRAPHITI: FalkorDB Connection Settings
|
||||
# =============================================================================
|
||||
# Configure the FalkorDB graph database connection.
|
||||
# Start FalkorDB: docker run -p 6380:6379 falkordb/falkordb:latest
|
||||
|
||||
# FalkorDB Host (default: localhost)
|
||||
# GRAPHITI_FALKORDB_HOST=localhost
|
||||
|
||||
# FalkorDB Port (default: 6380)
|
||||
# GRAPHITI_FALKORDB_PORT=6380
|
||||
|
||||
# FalkorDB Password (default: empty)
|
||||
# GRAPHITI_FALKORDB_PASSWORD=
|
||||
|
||||
# Graph Database Name (default: auto_build_memory)
|
||||
# GRAPHITI_DATABASE=auto_build_memory
|
||||
|
||||
# =============================================================================
|
||||
# GRAPHITI: Miscellaneous Settings
|
||||
# =============================================================================
|
||||
|
||||
# Disable Graphiti telemetry (default: true)
|
||||
# GRAPHITI_TELEMETRY_ENABLED=false
|
||||
|
||||
# =============================================================================
|
||||
# GRAPHITI: Example Configurations
|
||||
# =============================================================================
|
||||
@@ -251,5 +321,19 @@ CLAUDE_CODE_OAUTH_TOKEN=your-oauth-token-here
|
||||
# GRAPHITI_EMBEDDER_PROVIDER=azure_openai
|
||||
# AZURE_OPENAI_API_KEY=xxxxxxxx
|
||||
# AZURE_OPENAI_BASE_URL=https://your-resource.openai.azure.com/...
|
||||
# AZURE_OPENAI_LLM_DEPLOYMENT=gpt-5
|
||||
# AZURE_OPENAI_LLM_DEPLOYMENT=gpt-4
|
||||
# AZURE_OPENAI_EMBEDDING_DEPLOYMENT=text-embedding-3-small
|
||||
#
|
||||
# --- Example 5: Google AI (Gemini) ---
|
||||
# GRAPHITI_ENABLED=true
|
||||
# GRAPHITI_LLM_PROVIDER=google
|
||||
# GRAPHITI_EMBEDDER_PROVIDER=google
|
||||
# GOOGLE_API_KEY=AIzaSyxxxxxxxx
|
||||
#
|
||||
# --- Example 6: OpenRouter (multi-provider aggregator) ---
|
||||
# GRAPHITI_ENABLED=true
|
||||
# GRAPHITI_LLM_PROVIDER=openrouter
|
||||
# GRAPHITI_EMBEDDER_PROVIDER=openrouter
|
||||
# OPENROUTER_API_KEY=sk-or-xxxxxxxx
|
||||
# OPENROUTER_LLM_MODEL=anthropic/claude-3.5-sonnet
|
||||
# OPENROUTER_EMBEDDING_MODEL=openai/text-embedding-3-small
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
# Virtual environment
|
||||
.venv/
|
||||
.venv*/
|
||||
venv/
|
||||
env/
|
||||
|
||||
@@ -60,3 +61,6 @@ Thumbs.db
|
||||
|
||||
# Tests (development only)
|
||||
tests/
|
||||
|
||||
# Auto Claude data directory
|
||||
.auto-claude/
|
||||
@@ -0,0 +1,120 @@
|
||||
# Auto Claude Backend
|
||||
|
||||
Autonomous coding framework powered by Claude AI. Builds software features through coordinated multi-agent sessions.
|
||||
|
||||
## Getting Started
|
||||
|
||||
### 1. Install
|
||||
|
||||
```bash
|
||||
cd apps/backend
|
||||
python -m pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 2. Configure
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Set your Claude API token in `.env`:
|
||||
```
|
||||
CLAUDE_CODE_OAUTH_TOKEN=your-token-here
|
||||
```
|
||||
|
||||
Get your token by running: `claude setup-token`
|
||||
|
||||
### 3. Run
|
||||
|
||||
```bash
|
||||
# List available specs
|
||||
python run.py --list
|
||||
|
||||
# Run a spec
|
||||
python run.py --spec 001
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- Python 3.10+
|
||||
- Claude API token
|
||||
|
||||
## Commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `--list` | List all specs |
|
||||
| `--spec 001` | Run spec 001 |
|
||||
| `--spec 001 --isolated` | Run in isolated workspace |
|
||||
| `--spec 001 --direct` | Run directly in repo |
|
||||
| `--spec 001 --merge` | Merge completed build |
|
||||
| `--spec 001 --review` | Review build changes |
|
||||
| `--spec 001 --discard` | Discard build |
|
||||
| `--spec 001 --qa` | Run QA validation |
|
||||
| `--list-worktrees` | List all worktrees |
|
||||
| `--help` | Show all options |
|
||||
|
||||
## Configuration
|
||||
|
||||
Optional `.env` settings:
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `AUTO_BUILD_MODEL` | Override Claude model |
|
||||
| `DEBUG=true` | Enable debug logging |
|
||||
| `LINEAR_API_KEY` | Enable Linear integration |
|
||||
| `GRAPHITI_ENABLED=true` | Enable memory system |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**"tree-sitter not available"** - Safe to ignore, uses regex fallback.
|
||||
|
||||
**Missing module errors** - Run `python -m pip install -r requirements.txt`
|
||||
|
||||
**Debug mode** - Set `DEBUG=true DEBUG_LEVEL=2` before running.
|
||||
|
||||
---
|
||||
|
||||
## For Developers
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
backend/
|
||||
├── agents/ # AI agent execution
|
||||
├── analysis/ # Code analysis
|
||||
├── cli/ # Command-line interface
|
||||
├── core/ # Core utilities
|
||||
├── integrations/ # External services (Linear, Graphiti)
|
||||
├── merge/ # Git merge handling
|
||||
├── project/ # Project detection
|
||||
├── prompts/ # Prompt templates
|
||||
├── qa/ # QA validation
|
||||
├── spec/ # Spec management
|
||||
└── ui/ # Terminal UI
|
||||
```
|
||||
|
||||
### Design Principles
|
||||
|
||||
- **SOLID** - Single responsibility, clean interfaces
|
||||
- **DRY** - Shared utilities in `core/`
|
||||
- **KISS** - Simple flat imports via facade modules
|
||||
|
||||
### Import Convention
|
||||
|
||||
```python
|
||||
# Use facade modules for clean imports
|
||||
from debug import debug, debug_error
|
||||
from progress import count_subtasks
|
||||
from workspace import setup_workspace
|
||||
```
|
||||
|
||||
### Adding Features
|
||||
|
||||
1. Create module in appropriate folder
|
||||
2. Export API in `__init__.py`
|
||||
3. Add facade module at root if commonly imported
|
||||
|
||||
## License
|
||||
|
||||
AGPL-3.0
|
||||
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
Auto Claude Backend - Autonomous Coding Framework
|
||||
==================================================
|
||||
|
||||
Multi-agent autonomous coding framework that builds software through
|
||||
coordinated AI agent sessions.
|
||||
|
||||
This package provides:
|
||||
- Autonomous agent execution for building features from specs
|
||||
- Workspace isolation via git worktrees
|
||||
- QA validation loops
|
||||
- Memory management (Graphiti + file-based)
|
||||
- Linear integration for project management
|
||||
|
||||
Quick Start:
|
||||
python run.py --spec 001 # Run a spec
|
||||
python run.py --list # List all specs
|
||||
|
||||
See README.md for full documentation.
|
||||
"""
|
||||
|
||||
__version__ = "2.7.2"
|
||||
__author__ = "Auto Claude Team"
|
||||
@@ -0,0 +1,3 @@
|
||||
"""Backward compatibility shim - import from core.agent instead."""
|
||||
|
||||
from core.agent import * # noqa: F403
|
||||
@@ -0,0 +1,92 @@
|
||||
"""
|
||||
Agents Module
|
||||
=============
|
||||
|
||||
Modular agent system for autonomous coding.
|
||||
|
||||
This module provides:
|
||||
- run_autonomous_agent: Main coder agent loop
|
||||
- run_followup_planner: Follow-up planner for completed specs
|
||||
- Memory management (Graphiti + file-based fallback)
|
||||
- Session management and post-processing
|
||||
- Utility functions for git and plan management
|
||||
|
||||
Uses lazy imports to avoid circular dependencies.
|
||||
"""
|
||||
|
||||
__all__ = [
|
||||
# Main API
|
||||
"run_autonomous_agent",
|
||||
"run_followup_planner",
|
||||
# Memory
|
||||
"debug_memory_system_status",
|
||||
"get_graphiti_context",
|
||||
"save_session_memory",
|
||||
"save_session_to_graphiti",
|
||||
# Session
|
||||
"run_agent_session",
|
||||
"post_session_processing",
|
||||
# Utils
|
||||
"get_latest_commit",
|
||||
"get_commit_count",
|
||||
"load_implementation_plan",
|
||||
"find_subtask_in_plan",
|
||||
"find_phase_for_subtask",
|
||||
"sync_plan_to_source",
|
||||
# Constants
|
||||
"AUTO_CONTINUE_DELAY_SECONDS",
|
||||
"HUMAN_INTERVENTION_FILE",
|
||||
]
|
||||
|
||||
|
||||
def __getattr__(name):
|
||||
"""Lazy imports to avoid circular dependencies."""
|
||||
if name in ("AUTO_CONTINUE_DELAY_SECONDS", "HUMAN_INTERVENTION_FILE"):
|
||||
from .base import AUTO_CONTINUE_DELAY_SECONDS, HUMAN_INTERVENTION_FILE
|
||||
|
||||
return locals()[name]
|
||||
elif name == "run_autonomous_agent":
|
||||
from .coder import run_autonomous_agent
|
||||
|
||||
return run_autonomous_agent
|
||||
elif name in (
|
||||
"debug_memory_system_status",
|
||||
"get_graphiti_context",
|
||||
"save_session_memory",
|
||||
"save_session_to_graphiti",
|
||||
):
|
||||
from .memory_manager import (
|
||||
debug_memory_system_status,
|
||||
get_graphiti_context,
|
||||
save_session_memory,
|
||||
save_session_to_graphiti,
|
||||
)
|
||||
|
||||
return locals()[name]
|
||||
elif name == "run_followup_planner":
|
||||
from .planner import run_followup_planner
|
||||
|
||||
return run_followup_planner
|
||||
elif name in ("post_session_processing", "run_agent_session"):
|
||||
from .session import post_session_processing, run_agent_session
|
||||
|
||||
return locals()[name]
|
||||
elif name in (
|
||||
"find_phase_for_subtask",
|
||||
"find_subtask_in_plan",
|
||||
"get_commit_count",
|
||||
"get_latest_commit",
|
||||
"load_implementation_plan",
|
||||
"sync_plan_to_source",
|
||||
):
|
||||
from .utils import (
|
||||
find_phase_for_subtask,
|
||||
find_subtask_in_plan,
|
||||
get_commit_count,
|
||||
get_latest_commit,
|
||||
load_implementation_plan,
|
||||
sync_plan_to_source,
|
||||
)
|
||||
|
||||
return locals()[name]
|
||||
raise AttributeError(f"module 'agents' has no attribute '{name}'")
|
||||
@@ -0,0 +1,62 @@
|
||||
"""
|
||||
Custom MCP Tools for Auto-Claude Agents
|
||||
========================================
|
||||
|
||||
DEPRECATED: This module is now a compatibility shim.
|
||||
Please import from the tools_pkg package instead:
|
||||
|
||||
from agents.tools_pkg import create_auto_claude_mcp_server, get_allowed_tools
|
||||
|
||||
This file remains for backward compatibility with existing imports.
|
||||
All functionality has been moved to the tools_pkg package for better
|
||||
organization and maintainability.
|
||||
"""
|
||||
|
||||
# Import everything from the package to maintain backward compatibility
|
||||
# Use try/except to handle both relative and absolute imports
|
||||
try:
|
||||
from .tools_pkg import (
|
||||
ELECTRON_TOOLS,
|
||||
TOOL_GET_BUILD_PROGRESS,
|
||||
TOOL_GET_SESSION_CONTEXT,
|
||||
TOOL_RECORD_DISCOVERY,
|
||||
TOOL_RECORD_GOTCHA,
|
||||
TOOL_UPDATE_QA_STATUS,
|
||||
TOOL_UPDATE_SUBTASK_STATUS,
|
||||
create_auto_claude_mcp_server,
|
||||
get_allowed_tools,
|
||||
is_electron_mcp_enabled,
|
||||
is_tools_available,
|
||||
)
|
||||
except ImportError:
|
||||
# Fallback for direct execution - import from tools_pkg directly
|
||||
from tools_pkg import (
|
||||
ELECTRON_TOOLS,
|
||||
TOOL_GET_BUILD_PROGRESS,
|
||||
TOOL_GET_SESSION_CONTEXT,
|
||||
TOOL_RECORD_DISCOVERY,
|
||||
TOOL_RECORD_GOTCHA,
|
||||
TOOL_UPDATE_QA_STATUS,
|
||||
TOOL_UPDATE_SUBTASK_STATUS,
|
||||
create_auto_claude_mcp_server,
|
||||
get_allowed_tools,
|
||||
is_electron_mcp_enabled,
|
||||
is_tools_available,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# Main API
|
||||
"create_auto_claude_mcp_server",
|
||||
"get_allowed_tools",
|
||||
"is_tools_available",
|
||||
# Tool name constants
|
||||
"TOOL_UPDATE_SUBTASK_STATUS",
|
||||
"TOOL_GET_BUILD_PROGRESS",
|
||||
"TOOL_RECORD_DISCOVERY",
|
||||
"TOOL_RECORD_GOTCHA",
|
||||
"TOOL_GET_SESSION_CONTEXT",
|
||||
"TOOL_UPDATE_QA_STATUS",
|
||||
# Electron MCP
|
||||
"ELECTRON_TOOLS",
|
||||
"is_electron_mcp_enabled",
|
||||
]
|
||||
@@ -6,8 +6,6 @@ Shared imports, types, and constants used across agent modules.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
# Configure logging
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -8,60 +8,61 @@ Main autonomous agent loop that runs the coder agent to implement subtasks.
|
||||
import asyncio
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from .base import AUTO_CONTINUE_DELAY_SECONDS, HUMAN_INTERVENTION_FILE
|
||||
from .session import run_agent_session, post_session_processing
|
||||
from .memory import debug_memory_system_status, get_graphiti_context
|
||||
from .utils import (
|
||||
get_latest_commit,
|
||||
get_commit_count,
|
||||
load_implementation_plan,
|
||||
find_phase_for_subtask,
|
||||
sync_plan_to_source,
|
||||
)
|
||||
from client import create_client
|
||||
from recovery import RecoveryManager
|
||||
from progress import (
|
||||
print_session_header,
|
||||
print_progress_summary,
|
||||
print_build_complete_banner,
|
||||
count_subtasks,
|
||||
count_subtasks_detailed,
|
||||
is_build_complete,
|
||||
get_next_subtask,
|
||||
get_current_phase,
|
||||
)
|
||||
from prompt_generator import (
|
||||
generate_subtask_prompt,
|
||||
generate_planner_prompt,
|
||||
load_subtask_context,
|
||||
format_context_for_prompt,
|
||||
)
|
||||
from prompts import is_first_run
|
||||
from core.client import create_client
|
||||
from linear_updater import (
|
||||
is_linear_enabled,
|
||||
LinearTaskState,
|
||||
linear_task_started,
|
||||
is_linear_enabled,
|
||||
linear_build_complete,
|
||||
linear_task_started,
|
||||
linear_task_stuck,
|
||||
)
|
||||
from ui import (
|
||||
Icons,
|
||||
icon,
|
||||
box,
|
||||
bold,
|
||||
muted,
|
||||
highlight,
|
||||
print_status,
|
||||
print_key_value,
|
||||
StatusManager,
|
||||
BuildState,
|
||||
from phase_config import get_phase_model, get_phase_thinking_budget
|
||||
from progress import (
|
||||
count_subtasks,
|
||||
count_subtasks_detailed,
|
||||
get_current_phase,
|
||||
get_next_subtask,
|
||||
is_build_complete,
|
||||
print_build_complete_banner,
|
||||
print_progress_summary,
|
||||
print_session_header,
|
||||
)
|
||||
from prompt_generator import (
|
||||
format_context_for_prompt,
|
||||
generate_planner_prompt,
|
||||
generate_subtask_prompt,
|
||||
load_subtask_context,
|
||||
)
|
||||
from prompts import is_first_run
|
||||
from recovery import RecoveryManager
|
||||
from task_logger import (
|
||||
LogPhase,
|
||||
get_task_logger,
|
||||
)
|
||||
from ui import (
|
||||
BuildState,
|
||||
Icons,
|
||||
StatusManager,
|
||||
bold,
|
||||
box,
|
||||
highlight,
|
||||
icon,
|
||||
muted,
|
||||
print_key_value,
|
||||
print_status,
|
||||
)
|
||||
|
||||
from .base import AUTO_CONTINUE_DELAY_SECONDS, HUMAN_INTERVENTION_FILE
|
||||
from .memory_manager import debug_memory_system_status, get_graphiti_context
|
||||
from .session import post_session_processing, run_agent_session
|
||||
from .utils import (
|
||||
find_phase_for_subtask,
|
||||
get_commit_count,
|
||||
get_latest_commit,
|
||||
load_implementation_plan,
|
||||
sync_plan_to_source,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -70,9 +71,9 @@ async def run_autonomous_agent(
|
||||
project_dir: Path,
|
||||
spec_dir: Path,
|
||||
model: str,
|
||||
max_iterations: Optional[int] = None,
|
||||
max_iterations: int | None = None,
|
||||
verbose: bool = False,
|
||||
source_spec_dir: Optional[Path] = None,
|
||||
source_spec_dir: Path | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
Run the autonomous agent loop with automatic memory management.
|
||||
@@ -130,7 +131,9 @@ async def run_autonomous_agent(
|
||||
is_planning_phase = False
|
||||
|
||||
if first_run:
|
||||
print_status("Fresh start - will use Planner Agent to create implementation plan", "info")
|
||||
print_status(
|
||||
"Fresh start - will use Planner Agent to create implementation plan", "info"
|
||||
)
|
||||
content = [
|
||||
bold(f"{icon(Icons.GEAR)} PLANNER SESSION"),
|
||||
"",
|
||||
@@ -148,7 +151,9 @@ async def run_autonomous_agent(
|
||||
|
||||
# Start planning phase in task logger
|
||||
if task_logger:
|
||||
task_logger.start_phase(LogPhase.PLANNING, "Starting implementation planning...")
|
||||
task_logger.start_phase(
|
||||
LogPhase.PLANNING, "Starting implementation planning..."
|
||||
)
|
||||
|
||||
# Update Linear to "In Progress" when build starts
|
||||
if linear_task and linear_task.task_id:
|
||||
@@ -195,9 +200,9 @@ async def run_autonomous_agent(
|
||||
if pause_content:
|
||||
print(f"\nMessage: {pause_content}")
|
||||
|
||||
print(f"\nTo resume, delete the PAUSE file:")
|
||||
print("\nTo resume, delete the PAUSE file:")
|
||||
print(f" rm {pause_file}")
|
||||
print(f"\nThen run again:")
|
||||
print("\nThen run again:")
|
||||
print(f" python auto-claude/run.py --spec {spec_dir.name}")
|
||||
return
|
||||
|
||||
@@ -231,15 +236,28 @@ async def run_autonomous_agent(
|
||||
subtask_id=subtask_id,
|
||||
subtask_desc=next_subtask.get("description") if next_subtask else None,
|
||||
phase_name=phase_name,
|
||||
attempt=recovery_manager.get_attempt_count(subtask_id) + 1 if subtask_id else 1,
|
||||
attempt=recovery_manager.get_attempt_count(subtask_id) + 1
|
||||
if subtask_id
|
||||
else 1,
|
||||
)
|
||||
|
||||
# Capture state before session for post-processing
|
||||
commit_before = get_latest_commit(project_dir)
|
||||
commit_count_before = get_commit_count(project_dir)
|
||||
|
||||
# Create client (fresh context)
|
||||
client = create_client(project_dir, spec_dir, model)
|
||||
# Get the phase-specific model and thinking level (respects task_metadata.json configuration)
|
||||
# first_run means we're in planning phase, otherwise coding phase
|
||||
current_phase = "planning" if first_run else "coding"
|
||||
phase_model = get_phase_model(spec_dir, current_phase, model)
|
||||
phase_thinking_budget = get_phase_thinking_budget(spec_dir, current_phase)
|
||||
|
||||
# Create client (fresh context) with phase-specific model and thinking
|
||||
client = create_client(
|
||||
project_dir,
|
||||
spec_dir,
|
||||
phase_model,
|
||||
max_thinking_tokens=phase_thinking_budget,
|
||||
)
|
||||
|
||||
# Generate appropriate prompt
|
||||
if first_run:
|
||||
@@ -256,8 +274,14 @@ async def run_autonomous_agent(
|
||||
is_planning_phase = False
|
||||
current_log_phase = LogPhase.CODING
|
||||
if task_logger:
|
||||
task_logger.end_phase(LogPhase.PLANNING, success=True, message="Implementation plan created")
|
||||
task_logger.start_phase(LogPhase.CODING, "Starting implementation...")
|
||||
task_logger.end_phase(
|
||||
LogPhase.PLANNING,
|
||||
success=True,
|
||||
message="Implementation plan created",
|
||||
)
|
||||
task_logger.start_phase(
|
||||
LogPhase.CODING, "Starting implementation..."
|
||||
)
|
||||
|
||||
if not next_subtask:
|
||||
print("No pending subtasks found - build may be complete!")
|
||||
@@ -265,7 +289,11 @@ async def run_autonomous_agent(
|
||||
|
||||
# Get attempt count for recovery context
|
||||
attempt_count = recovery_manager.get_attempt_count(subtask_id)
|
||||
recovery_hints = recovery_manager.get_recovery_hints(subtask_id) if attempt_count > 0 else None
|
||||
recovery_hints = (
|
||||
recovery_manager.get_recovery_hints(subtask_id)
|
||||
if attempt_count > 0
|
||||
else None
|
||||
)
|
||||
|
||||
# Find the phase for this subtask
|
||||
plan = load_implementation_plan(spec_dir)
|
||||
@@ -287,7 +315,9 @@ async def run_autonomous_agent(
|
||||
prompt += "\n\n" + format_context_for_prompt(context)
|
||||
|
||||
# Retrieve and append Graphiti memory context (if enabled)
|
||||
graphiti_context = await get_graphiti_context(spec_dir, project_dir, next_subtask)
|
||||
graphiti_context = await get_graphiti_context(
|
||||
spec_dir, project_dir, next_subtask
|
||||
)
|
||||
if graphiti_context:
|
||||
prompt += "\n\n" + graphiti_context
|
||||
print_status("Graphiti memory context loaded", "success")
|
||||
@@ -312,7 +342,9 @@ async def run_autonomous_agent(
|
||||
|
||||
# === POST-SESSION PROCESSING (100% reliable) ===
|
||||
if subtask_id and not first_run:
|
||||
linear_is_enabled = linear_task is not None and linear_task.task_id is not None
|
||||
linear_is_enabled = (
|
||||
linear_task is not None and linear_task.task_id is not None
|
||||
)
|
||||
success = await post_session_processing(
|
||||
spec_dir=spec_dir,
|
||||
project_dir=project_dir,
|
||||
@@ -330,11 +362,13 @@ async def run_autonomous_agent(
|
||||
attempt_count = recovery_manager.get_attempt_count(subtask_id)
|
||||
if not success and attempt_count >= 3:
|
||||
recovery_manager.mark_subtask_stuck(
|
||||
subtask_id,
|
||||
f"Failed after {attempt_count} attempts"
|
||||
subtask_id, f"Failed after {attempt_count} attempts"
|
||||
)
|
||||
print()
|
||||
print_status(f"Subtask {subtask_id} marked as STUCK after {attempt_count} attempts", "error")
|
||||
print_status(
|
||||
f"Subtask {subtask_id} marked as STUCK after {attempt_count} attempts",
|
||||
"error",
|
||||
)
|
||||
print(muted("Consider: manual intervention or skipping this subtask"))
|
||||
|
||||
# Record stuck subtask in Linear (if enabled)
|
||||
@@ -357,7 +391,11 @@ async def run_autonomous_agent(
|
||||
|
||||
# End coding phase in task logger
|
||||
if task_logger:
|
||||
task_logger.end_phase(LogPhase.CODING, success=True, message="All subtasks completed successfully")
|
||||
task_logger.end_phase(
|
||||
LogPhase.CODING,
|
||||
success=True,
|
||||
message="All subtasks completed successfully",
|
||||
)
|
||||
|
||||
# Notify Linear that build is complete (moving to QA)
|
||||
if linear_task and linear_task.task_id:
|
||||
@@ -367,7 +405,11 @@ async def run_autonomous_agent(
|
||||
break
|
||||
|
||||
elif status == "continue":
|
||||
print(muted(f"\nAgent will auto-continue in {AUTO_CONTINUE_DELAY_SECONDS}s..."))
|
||||
print(
|
||||
muted(
|
||||
f"\nAgent will auto-continue in {AUTO_CONTINUE_DELAY_SECONDS}s..."
|
||||
)
|
||||
)
|
||||
print_progress_summary(spec_dir)
|
||||
|
||||
# Update state back to building
|
||||
@@ -376,12 +418,16 @@ async def run_autonomous_agent(
|
||||
# Show next subtask info
|
||||
next_subtask = get_next_subtask(spec_dir)
|
||||
if next_subtask:
|
||||
subtask_id = next_subtask.get('id')
|
||||
print(f"\nNext: {highlight(subtask_id)} - {next_subtask.get('description')}")
|
||||
subtask_id = next_subtask.get("id")
|
||||
print(
|
||||
f"\nNext: {highlight(subtask_id)} - {next_subtask.get('description')}"
|
||||
)
|
||||
|
||||
attempt_count = recovery_manager.get_attempt_count(subtask_id)
|
||||
if attempt_count > 0:
|
||||
print_status(f"WARNING: {attempt_count} previous attempt(s)", "warning")
|
||||
print_status(
|
||||
f"WARNING: {attempt_count} previous attempt(s)", "warning"
|
||||
)
|
||||
|
||||
await asyncio.sleep(AUTO_CONTINUE_DELAY_SECONDS)
|
||||
|
||||
@@ -9,19 +9,21 @@ Handles session memory storage using dual-layer approach:
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from graphiti_config import is_graphiti_enabled, get_graphiti_status
|
||||
from memory import save_session_insights as save_file_based_memory
|
||||
from debug import (
|
||||
debug,
|
||||
debug_detailed,
|
||||
debug_success,
|
||||
debug_error,
|
||||
debug_warning,
|
||||
debug_section,
|
||||
debug_success,
|
||||
debug_warning,
|
||||
is_debug_enabled,
|
||||
)
|
||||
from graphiti_config import get_graphiti_status, is_graphiti_enabled
|
||||
|
||||
# Import from parent memory package
|
||||
# Now safe since this module is named memory_manager (not memory)
|
||||
from memory import save_session_insights as save_file_based_memory
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -40,36 +42,50 @@ def debug_memory_system_status() -> None:
|
||||
# Get Graphiti status
|
||||
graphiti_status = get_graphiti_status()
|
||||
|
||||
debug("memory", "Memory system configuration",
|
||||
primary_system="Graphiti" if graphiti_status.get("available") else "File-based (fallback)",
|
||||
graphiti_enabled=graphiti_status.get("enabled"),
|
||||
graphiti_available=graphiti_status.get("available"))
|
||||
debug(
|
||||
"memory",
|
||||
"Memory system configuration",
|
||||
primary_system="Graphiti"
|
||||
if graphiti_status.get("available")
|
||||
else "File-based (fallback)",
|
||||
graphiti_enabled=graphiti_status.get("enabled"),
|
||||
graphiti_available=graphiti_status.get("available"),
|
||||
)
|
||||
|
||||
if graphiti_status.get("enabled"):
|
||||
debug_detailed("memory", "Graphiti configuration",
|
||||
host=graphiti_status.get("host"),
|
||||
port=graphiti_status.get("port"),
|
||||
database=graphiti_status.get("database"),
|
||||
llm_provider=graphiti_status.get("llm_provider"),
|
||||
embedder_provider=graphiti_status.get("embedder_provider"))
|
||||
debug_detailed(
|
||||
"memory",
|
||||
"Graphiti configuration",
|
||||
host=graphiti_status.get("host"),
|
||||
port=graphiti_status.get("port"),
|
||||
database=graphiti_status.get("database"),
|
||||
llm_provider=graphiti_status.get("llm_provider"),
|
||||
embedder_provider=graphiti_status.get("embedder_provider"),
|
||||
)
|
||||
|
||||
if not graphiti_status.get("available"):
|
||||
debug_warning("memory", "Graphiti not available",
|
||||
reason=graphiti_status.get("reason"),
|
||||
errors=graphiti_status.get("errors"))
|
||||
debug_warning(
|
||||
"memory",
|
||||
"Graphiti not available",
|
||||
reason=graphiti_status.get("reason"),
|
||||
errors=graphiti_status.get("errors"),
|
||||
)
|
||||
debug("memory", "Will use file-based memory as fallback")
|
||||
else:
|
||||
debug_success("memory", "Graphiti ready as PRIMARY memory system")
|
||||
else:
|
||||
debug("memory", "Graphiti disabled, using file-based memory only",
|
||||
note="Set GRAPHITI_ENABLED=true to enable Graphiti")
|
||||
debug(
|
||||
"memory",
|
||||
"Graphiti disabled, using file-based memory only",
|
||||
note="Set GRAPHITI_ENABLED=true to enable Graphiti",
|
||||
)
|
||||
|
||||
|
||||
async def get_graphiti_context(
|
||||
spec_dir: Path,
|
||||
project_dir: Path,
|
||||
subtask: dict,
|
||||
) -> Optional[str]:
|
||||
) -> str | None:
|
||||
"""
|
||||
Retrieve relevant context from Graphiti for the current subtask.
|
||||
|
||||
@@ -85,9 +101,12 @@ async def get_graphiti_context(
|
||||
Formatted context string or None if unavailable
|
||||
"""
|
||||
if is_debug_enabled():
|
||||
debug("memory", "Retrieving Graphiti context for subtask",
|
||||
subtask_id=subtask.get("id", "unknown"),
|
||||
subtask_desc=subtask.get("description", "")[:100])
|
||||
debug(
|
||||
"memory",
|
||||
"Retrieving Graphiti context for subtask",
|
||||
subtask_id=subtask.get("id", "unknown"),
|
||||
subtask_desc=subtask.get("description", "")[:100],
|
||||
)
|
||||
|
||||
if not is_graphiti_enabled():
|
||||
if is_debug_enabled():
|
||||
@@ -117,9 +136,12 @@ async def get_graphiti_context(
|
||||
return None
|
||||
|
||||
if is_debug_enabled():
|
||||
debug_detailed("memory", "Searching Graphiti knowledge graph",
|
||||
query=query[:200],
|
||||
num_results=5)
|
||||
debug_detailed(
|
||||
"memory",
|
||||
"Searching Graphiti knowledge graph",
|
||||
query=query[:200],
|
||||
num_results=5,
|
||||
)
|
||||
|
||||
# Get relevant context
|
||||
context_items = await memory.get_relevant_context(query, num_results=5)
|
||||
@@ -130,9 +152,12 @@ async def get_graphiti_context(
|
||||
await memory.close()
|
||||
|
||||
if is_debug_enabled():
|
||||
debug("memory", "Graphiti context retrieval complete",
|
||||
context_items_found=len(context_items) if context_items else 0,
|
||||
session_history_found=len(session_history) if session_history else 0)
|
||||
debug(
|
||||
"memory",
|
||||
"Graphiti context retrieval complete",
|
||||
context_items_found=len(context_items) if context_items else 0,
|
||||
session_history_found=len(session_history) if session_history else 0,
|
||||
)
|
||||
|
||||
if not context_items and not session_history:
|
||||
if is_debug_enabled():
|
||||
@@ -162,8 +187,9 @@ async def get_graphiti_context(
|
||||
sections.append("")
|
||||
|
||||
if is_debug_enabled():
|
||||
debug_success("memory", "Graphiti context formatted",
|
||||
total_sections=len(sections))
|
||||
debug_success(
|
||||
"memory", "Graphiti context formatted", total_sections=len(sections)
|
||||
)
|
||||
|
||||
return "\n".join(sections)
|
||||
|
||||
@@ -184,7 +210,7 @@ async def save_session_memory(
|
||||
session_num: int,
|
||||
success: bool,
|
||||
subtasks_completed: list[str],
|
||||
discoveries: Optional[dict] = None,
|
||||
discoveries: dict | None = None,
|
||||
) -> tuple[bool, str]:
|
||||
"""
|
||||
Save session insights to memory.
|
||||
@@ -210,17 +236,21 @@ async def save_session_memory(
|
||||
# Debug: Log memory save start
|
||||
if is_debug_enabled():
|
||||
debug_section("memory", f"Saving Session {session_num} Memory")
|
||||
debug("memory", "Memory save initiated",
|
||||
subtask_id=subtask_id,
|
||||
session_num=session_num,
|
||||
success=success,
|
||||
subtasks_completed=subtasks_completed,
|
||||
spec_dir=str(spec_dir))
|
||||
debug(
|
||||
"memory",
|
||||
"Memory save initiated",
|
||||
subtask_id=subtask_id,
|
||||
session_num=session_num,
|
||||
success=success,
|
||||
subtasks_completed=subtasks_completed,
|
||||
spec_dir=str(spec_dir),
|
||||
)
|
||||
|
||||
# Build insights structure (same format for both storage systems)
|
||||
insights = {
|
||||
"subtasks_completed": subtasks_completed,
|
||||
"discoveries": discoveries or {
|
||||
"discoveries": discoveries
|
||||
or {
|
||||
"files_understood": {},
|
||||
"patterns_found": [],
|
||||
"gotchas_encountered": [],
|
||||
@@ -237,15 +267,18 @@ async def save_session_memory(
|
||||
graphiti_enabled = is_graphiti_enabled()
|
||||
if is_debug_enabled():
|
||||
graphiti_status = get_graphiti_status()
|
||||
debug("memory", "Graphiti status check",
|
||||
enabled=graphiti_status.get("enabled"),
|
||||
available=graphiti_status.get("available"),
|
||||
host=graphiti_status.get("host"),
|
||||
port=graphiti_status.get("port"),
|
||||
database=graphiti_status.get("database"),
|
||||
llm_provider=graphiti_status.get("llm_provider"),
|
||||
embedder_provider=graphiti_status.get("embedder_provider"),
|
||||
reason=graphiti_status.get("reason") or "OK")
|
||||
debug(
|
||||
"memory",
|
||||
"Graphiti status check",
|
||||
enabled=graphiti_status.get("enabled"),
|
||||
available=graphiti_status.get("available"),
|
||||
host=graphiti_status.get("host"),
|
||||
port=graphiti_status.get("port"),
|
||||
database=graphiti_status.get("database"),
|
||||
llm_provider=graphiti_status.get("llm_provider"),
|
||||
embedder_provider=graphiti_status.get("embedder_provider"),
|
||||
reason=graphiti_status.get("reason") or "OK",
|
||||
)
|
||||
|
||||
# PRIMARY: Try Graphiti if enabled
|
||||
if graphiti_enabled:
|
||||
@@ -258,9 +291,12 @@ async def save_session_memory(
|
||||
memory = GraphitiMemory(spec_dir, project_dir)
|
||||
|
||||
if is_debug_enabled():
|
||||
debug_detailed("memory", "GraphitiMemory instance created",
|
||||
is_enabled=memory.is_enabled,
|
||||
group_id=getattr(memory, 'group_id', 'unknown'))
|
||||
debug_detailed(
|
||||
"memory",
|
||||
"GraphitiMemory instance created",
|
||||
is_enabled=memory.is_enabled,
|
||||
group_id=getattr(memory, "group_id", "unknown"),
|
||||
)
|
||||
|
||||
if memory.is_enabled:
|
||||
if is_debug_enabled():
|
||||
@@ -270,7 +306,10 @@ async def save_session_memory(
|
||||
if discoveries and discoveries.get("file_insights"):
|
||||
# Rich insights from insight_extractor
|
||||
if is_debug_enabled():
|
||||
debug("memory", "Using save_structured_insights (rich data available)")
|
||||
debug(
|
||||
"memory",
|
||||
"Using save_structured_insights (rich data available)",
|
||||
)
|
||||
result = await memory.save_structured_insights(discoveries)
|
||||
else:
|
||||
# Fallback to basic session insights
|
||||
@@ -279,20 +318,33 @@ async def save_session_memory(
|
||||
await memory.close()
|
||||
|
||||
if result:
|
||||
logger.info(f"Session {session_num} insights saved to Graphiti (primary)")
|
||||
logger.info(
|
||||
f"Session {session_num} insights saved to Graphiti (primary)"
|
||||
)
|
||||
if is_debug_enabled():
|
||||
debug_success("memory", f"Session {session_num} saved to Graphiti (PRIMARY)",
|
||||
storage_type="graphiti",
|
||||
subtasks_saved=len(subtasks_completed))
|
||||
debug_success(
|
||||
"memory",
|
||||
f"Session {session_num} saved to Graphiti (PRIMARY)",
|
||||
storage_type="graphiti",
|
||||
subtasks_saved=len(subtasks_completed),
|
||||
)
|
||||
return True, "graphiti"
|
||||
else:
|
||||
logger.warning("Graphiti save returned False, falling back to file-based")
|
||||
logger.warning(
|
||||
"Graphiti save returned False, falling back to file-based"
|
||||
)
|
||||
if is_debug_enabled():
|
||||
debug_warning("memory", "Graphiti save returned False, using FALLBACK")
|
||||
debug_warning(
|
||||
"memory", "Graphiti save returned False, using FALLBACK"
|
||||
)
|
||||
else:
|
||||
logger.warning("Graphiti memory not enabled, falling back to file-based")
|
||||
logger.warning(
|
||||
"Graphiti memory not enabled, falling back to file-based"
|
||||
)
|
||||
if is_debug_enabled():
|
||||
debug_warning("memory", "GraphitiMemory.is_enabled=False, using FALLBACK")
|
||||
debug_warning(
|
||||
"memory", "GraphitiMemory.is_enabled=False, using FALLBACK"
|
||||
)
|
||||
|
||||
except ImportError as e:
|
||||
logger.debug("Graphiti packages not installed, falling back to file-based")
|
||||
@@ -313,18 +365,26 @@ async def save_session_memory(
|
||||
try:
|
||||
memory_dir = spec_dir / "memory" / "session_insights"
|
||||
if is_debug_enabled():
|
||||
debug_detailed("memory", "File-based memory path",
|
||||
memory_dir=str(memory_dir),
|
||||
session_file=f"session_{session_num:03d}.json")
|
||||
debug_detailed(
|
||||
"memory",
|
||||
"File-based memory path",
|
||||
memory_dir=str(memory_dir),
|
||||
session_file=f"session_{session_num:03d}.json",
|
||||
)
|
||||
|
||||
save_file_based_memory(spec_dir, session_num, insights)
|
||||
logger.info(f"Session {session_num} insights saved to file-based memory (fallback)")
|
||||
logger.info(
|
||||
f"Session {session_num} insights saved to file-based memory (fallback)"
|
||||
)
|
||||
|
||||
if is_debug_enabled():
|
||||
debug_success("memory", f"Session {session_num} saved to file-based (FALLBACK)",
|
||||
storage_type="file",
|
||||
file_path=str(memory_dir / f"session_{session_num:03d}.json"),
|
||||
subtasks_saved=len(subtasks_completed))
|
||||
debug_success(
|
||||
"memory",
|
||||
f"Session {session_num} saved to file-based (FALLBACK)",
|
||||
storage_type="file",
|
||||
file_path=str(memory_dir / f"session_{session_num:03d}.json"),
|
||||
subtasks_saved=len(subtasks_completed),
|
||||
)
|
||||
return True, "file"
|
||||
except Exception as e:
|
||||
logger.error(f"File-based memory save also failed: {e}")
|
||||
@@ -341,10 +401,16 @@ async def save_session_to_graphiti(
|
||||
session_num: int,
|
||||
success: bool,
|
||||
subtasks_completed: list[str],
|
||||
discoveries: Optional[dict] = None,
|
||||
discoveries: dict | None = None,
|
||||
) -> bool:
|
||||
"""Backwards compatibility wrapper for save_session_memory."""
|
||||
result, _ = await save_session_memory(
|
||||
spec_dir, project_dir, subtask_id, session_num, success, subtasks_completed, discoveries
|
||||
spec_dir,
|
||||
project_dir,
|
||||
subtask_id,
|
||||
session_num,
|
||||
success,
|
||||
subtasks_completed,
|
||||
discoveries,
|
||||
)
|
||||
return result
|
||||
@@ -8,24 +8,25 @@ Handles follow-up planner sessions for adding new subtasks to completed specs.
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from .base import AUTO_CONTINUE_DELAY_SECONDS
|
||||
from .session import run_agent_session
|
||||
from client import create_client
|
||||
from ui import (
|
||||
Icons,
|
||||
icon,
|
||||
box,
|
||||
bold,
|
||||
muted,
|
||||
highlight,
|
||||
print_status,
|
||||
StatusManager,
|
||||
BuildState,
|
||||
)
|
||||
from core.client import create_client
|
||||
from phase_config import get_phase_thinking_budget
|
||||
from task_logger import (
|
||||
LogPhase,
|
||||
get_task_logger,
|
||||
)
|
||||
from ui import (
|
||||
BuildState,
|
||||
Icons,
|
||||
StatusManager,
|
||||
bold,
|
||||
box,
|
||||
highlight,
|
||||
icon,
|
||||
muted,
|
||||
print_status,
|
||||
)
|
||||
|
||||
from .session import run_agent_session
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -60,8 +61,8 @@ async def run_followup_planner(
|
||||
Returns:
|
||||
bool: True if planning completed successfully
|
||||
"""
|
||||
from prompts import get_followup_planner_prompt
|
||||
from implementation_plan import ImplementationPlan
|
||||
from prompts import get_followup_planner_prompt
|
||||
|
||||
# Initialize status manager for ccstatusline
|
||||
status_manager = StatusManager(project_dir)
|
||||
@@ -88,8 +89,14 @@ async def run_followup_planner(
|
||||
task_logger.start_phase(LogPhase.PLANNING, "Starting follow-up planning...")
|
||||
task_logger.set_session(1)
|
||||
|
||||
# Create client (fresh context)
|
||||
client = create_client(project_dir, spec_dir, model)
|
||||
# Create client (fresh context) with planning phase thinking budget
|
||||
planning_thinking_budget = get_phase_thinking_budget(spec_dir, "planning")
|
||||
client = create_client(
|
||||
project_dir,
|
||||
spec_dir,
|
||||
model,
|
||||
max_thinking_tokens=planning_thinking_budget,
|
||||
)
|
||||
|
||||
# Generate follow-up planner prompt
|
||||
prompt = get_followup_planner_prompt(spec_dir)
|
||||
@@ -109,7 +116,7 @@ async def run_followup_planner(
|
||||
task_logger.end_phase(
|
||||
LogPhase.PLANNING,
|
||||
success=(status != "error"),
|
||||
message="Follow-up planning session completed"
|
||||
message="Follow-up planning session completed",
|
||||
)
|
||||
|
||||
if status == "error":
|
||||
@@ -148,14 +155,18 @@ async def run_followup_planner(
|
||||
return True
|
||||
else:
|
||||
print()
|
||||
print_status("Warning: No pending subtasks found after planning", "warning")
|
||||
print_status(
|
||||
"Warning: No pending subtasks found after planning", "warning"
|
||||
)
|
||||
print(muted("The planner may not have added new subtasks."))
|
||||
print(muted("Check implementation_plan.json manually."))
|
||||
status_manager.update(state=BuildState.PAUSED)
|
||||
return False
|
||||
else:
|
||||
print()
|
||||
print_status("Error: implementation_plan.json not found after planning", "error")
|
||||
print_status(
|
||||
"Error: implementation_plan.json not found after planning", "error"
|
||||
)
|
||||
status_manager.update(state=BuildState.ERROR)
|
||||
return False
|
||||
|
||||
@@ -8,39 +8,39 @@ memory updates, recovery tracking, and Linear integration.
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from claude_agent_sdk import ClaudeSDKClient
|
||||
|
||||
from .utils import (
|
||||
get_latest_commit,
|
||||
get_commit_count,
|
||||
load_implementation_plan,
|
||||
find_subtask_in_plan,
|
||||
sync_plan_to_source,
|
||||
)
|
||||
from .memory import save_session_memory
|
||||
from recovery import RecoveryManager
|
||||
from progress import (
|
||||
is_build_complete,
|
||||
count_subtasks_detailed,
|
||||
)
|
||||
from debug import debug, debug_detailed, debug_error, debug_section, debug_success
|
||||
from insight_extractor import extract_session_insights
|
||||
from linear_updater import (
|
||||
linear_subtask_completed,
|
||||
linear_subtask_failed,
|
||||
)
|
||||
from ui import (
|
||||
muted,
|
||||
print_status,
|
||||
print_key_value,
|
||||
StatusManager,
|
||||
from progress import (
|
||||
count_subtasks_detailed,
|
||||
is_build_complete,
|
||||
)
|
||||
from recovery import RecoveryManager
|
||||
from task_logger import (
|
||||
LogPhase,
|
||||
LogEntryType,
|
||||
LogPhase,
|
||||
get_task_logger,
|
||||
)
|
||||
from insight_extractor import extract_session_insights
|
||||
from ui import (
|
||||
StatusManager,
|
||||
muted,
|
||||
print_key_value,
|
||||
print_status,
|
||||
)
|
||||
|
||||
from .memory_manager import save_session_memory
|
||||
from .utils import (
|
||||
find_subtask_in_plan,
|
||||
get_commit_count,
|
||||
get_latest_commit,
|
||||
load_implementation_plan,
|
||||
sync_plan_to_source,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -50,12 +50,12 @@ async def post_session_processing(
|
||||
project_dir: Path,
|
||||
subtask_id: str,
|
||||
session_num: int,
|
||||
commit_before: Optional[str],
|
||||
commit_before: str | None,
|
||||
commit_count_before: int,
|
||||
recovery_manager: RecoveryManager,
|
||||
linear_enabled: bool = False,
|
||||
status_manager: Optional[StatusManager] = None,
|
||||
source_spec_dir: Optional[Path] = None,
|
||||
status_manager: StatusManager | None = None,
|
||||
source_spec_dir: Path | None = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Process session results and update memory automatically.
|
||||
@@ -181,7 +181,9 @@ async def post_session_processing(
|
||||
if storage_type == "graphiti":
|
||||
print_status("Session saved to Graphiti memory", "success")
|
||||
else:
|
||||
print_status("Session saved to file-based memory (fallback)", "info")
|
||||
print_status(
|
||||
"Session saved to file-based memory (fallback)", "info"
|
||||
)
|
||||
else:
|
||||
print_status("Failed to save session memory", "warning")
|
||||
except Exception as e:
|
||||
@@ -205,7 +207,9 @@ async def post_session_processing(
|
||||
# Still record commit if one was made (partial progress)
|
||||
if commit_after and commit_after != commit_before:
|
||||
recovery_manager.record_good_commit(commit_after, subtask_id)
|
||||
print_status(f"Recorded partial progress commit: {commit_after[:8]}", "info")
|
||||
print_status(
|
||||
f"Recorded partial progress commit: {commit_after[:8]}", "info"
|
||||
)
|
||||
|
||||
# Record Linear session result (if enabled)
|
||||
if linear_enabled:
|
||||
@@ -251,7 +255,9 @@ async def post_session_processing(
|
||||
|
||||
else:
|
||||
# Subtask still pending or failed
|
||||
print_status(f"Subtask {subtask_id} not completed (status: {subtask_status})", "error")
|
||||
print_status(
|
||||
f"Subtask {subtask_id} not completed (status: {subtask_status})", "error"
|
||||
)
|
||||
|
||||
recovery_manager.record_attempt(
|
||||
subtask_id=subtask_id,
|
||||
@@ -327,20 +333,40 @@ async def run_agent_session(
|
||||
- "complete" if all subtasks complete
|
||||
- "error" if an error occurred
|
||||
"""
|
||||
debug_section("session", f"Agent Session - {phase.value}")
|
||||
debug(
|
||||
"session",
|
||||
"Starting agent session",
|
||||
spec_dir=str(spec_dir),
|
||||
phase=phase.value,
|
||||
prompt_length=len(message),
|
||||
prompt_preview=message[:200] + "..." if len(message) > 200 else message,
|
||||
)
|
||||
print("Sending prompt to Claude Agent SDK...\n")
|
||||
|
||||
# Get task logger for this spec
|
||||
task_logger = get_task_logger(spec_dir)
|
||||
current_tool = None
|
||||
message_count = 0
|
||||
tool_count = 0
|
||||
|
||||
try:
|
||||
# Send the query
|
||||
debug("session", "Sending query to Claude SDK...")
|
||||
await client.query(message)
|
||||
debug_success("session", "Query sent successfully")
|
||||
|
||||
# Collect response text and show tool use
|
||||
response_text = ""
|
||||
debug("session", "Starting to receive response stream...")
|
||||
async for msg in client.receive_response():
|
||||
msg_type = type(msg).__name__
|
||||
message_count += 1
|
||||
debug_detailed(
|
||||
"session",
|
||||
f"Received message #{message_count}",
|
||||
msg_type=msg_type,
|
||||
)
|
||||
|
||||
# Handle AssistantMessage (text and tool use)
|
||||
if msg_type == "AssistantMessage" and hasattr(msg, "content"):
|
||||
@@ -352,10 +378,16 @@ async def run_agent_session(
|
||||
print(block.text, end="", flush=True)
|
||||
# Log text to task logger (persist without double-printing)
|
||||
if task_logger and block.text.strip():
|
||||
task_logger.log(block.text, LogEntryType.TEXT, phase, print_to_console=False)
|
||||
task_logger.log(
|
||||
block.text,
|
||||
LogEntryType.TEXT,
|
||||
phase,
|
||||
print_to_console=False,
|
||||
)
|
||||
elif block_type == "ToolUseBlock" and hasattr(block, "name"):
|
||||
tool_name = block.name
|
||||
tool_input = None
|
||||
tool_count += 1
|
||||
|
||||
# Extract meaningful tool input for display
|
||||
if hasattr(block, "input") and block.input:
|
||||
@@ -376,9 +408,20 @@ async def run_agent_session(
|
||||
elif "path" in inp:
|
||||
tool_input = inp["path"]
|
||||
|
||||
debug(
|
||||
"session",
|
||||
f"Tool call #{tool_count}: {tool_name}",
|
||||
tool_input=tool_input,
|
||||
full_input=str(block.input)[:500]
|
||||
if hasattr(block, "input")
|
||||
else None,
|
||||
)
|
||||
|
||||
# Log tool start (handles printing too)
|
||||
if task_logger:
|
||||
task_logger.tool_start(tool_name, tool_input, phase, print_to_console=True)
|
||||
task_logger.tool_start(
|
||||
tool_name, tool_input, phase, print_to_console=True
|
||||
)
|
||||
else:
|
||||
print(f"\n[Tool: {tool_name}]", flush=True)
|
||||
|
||||
@@ -401,18 +444,45 @@ async def run_agent_session(
|
||||
|
||||
# Check if command was blocked by security hook
|
||||
if "blocked" in str(result_content).lower():
|
||||
debug_error(
|
||||
"session",
|
||||
f"Tool BLOCKED: {current_tool}",
|
||||
result=str(result_content)[:300],
|
||||
)
|
||||
print(f" [BLOCKED] {result_content}", flush=True)
|
||||
if task_logger and current_tool:
|
||||
task_logger.tool_end(current_tool, success=False, result="BLOCKED", detail=str(result_content), phase=phase)
|
||||
task_logger.tool_end(
|
||||
current_tool,
|
||||
success=False,
|
||||
result="BLOCKED",
|
||||
detail=str(result_content),
|
||||
phase=phase,
|
||||
)
|
||||
elif is_error:
|
||||
# Show errors (truncated)
|
||||
error_str = str(result_content)[:500]
|
||||
debug_error(
|
||||
"session",
|
||||
f"Tool error: {current_tool}",
|
||||
error=error_str[:200],
|
||||
)
|
||||
print(f" [Error] {error_str}", flush=True)
|
||||
if task_logger and current_tool:
|
||||
# Store full error in detail for expandable view
|
||||
task_logger.tool_end(current_tool, success=False, result=error_str[:100], detail=str(result_content), phase=phase)
|
||||
task_logger.tool_end(
|
||||
current_tool,
|
||||
success=False,
|
||||
result=error_str[:100],
|
||||
detail=str(result_content),
|
||||
phase=phase,
|
||||
)
|
||||
else:
|
||||
# Tool succeeded
|
||||
debug_detailed(
|
||||
"session",
|
||||
f"Tool success: {current_tool}",
|
||||
result_length=len(str(result_content)),
|
||||
)
|
||||
if verbose:
|
||||
result_str = str(result_content)[:200]
|
||||
print(f" [Done] {result_str}", flush=True)
|
||||
@@ -422,12 +492,25 @@ async def run_agent_session(
|
||||
# Store full result in detail for expandable view (only for certain tools)
|
||||
# Skip storing for very large outputs like Glob results
|
||||
detail_content = None
|
||||
if current_tool in ("Read", "Grep", "Bash", "Edit", "Write"):
|
||||
if current_tool in (
|
||||
"Read",
|
||||
"Grep",
|
||||
"Bash",
|
||||
"Edit",
|
||||
"Write",
|
||||
):
|
||||
result_str = str(result_content)
|
||||
# Only store if not too large (detail truncation happens in logger)
|
||||
if len(result_str) < 50000: # 50KB max before truncation
|
||||
if (
|
||||
len(result_str) < 50000
|
||||
): # 50KB max before truncation
|
||||
detail_content = result_str
|
||||
task_logger.tool_end(current_tool, success=True, detail=detail_content, phase=phase)
|
||||
task_logger.tool_end(
|
||||
current_tool,
|
||||
success=True,
|
||||
detail=detail_content,
|
||||
phase=phase,
|
||||
)
|
||||
|
||||
current_tool = None
|
||||
|
||||
@@ -435,11 +518,32 @@ async def run_agent_session(
|
||||
|
||||
# Check if build is complete
|
||||
if is_build_complete(spec_dir):
|
||||
debug_success(
|
||||
"session",
|
||||
"Session completed - build is complete",
|
||||
message_count=message_count,
|
||||
tool_count=tool_count,
|
||||
response_length=len(response_text),
|
||||
)
|
||||
return "complete", response_text
|
||||
|
||||
debug_success(
|
||||
"session",
|
||||
"Session completed - continuing",
|
||||
message_count=message_count,
|
||||
tool_count=tool_count,
|
||||
response_length=len(response_text),
|
||||
)
|
||||
return "continue", response_text
|
||||
|
||||
except Exception as e:
|
||||
debug_error(
|
||||
"session",
|
||||
f"Session error: {e}",
|
||||
exception_type=type(e).__name__,
|
||||
message_count=message_count,
|
||||
tool_count=tool_count,
|
||||
)
|
||||
print(f"Error during agent session: {e}")
|
||||
if task_logger:
|
||||
task_logger.log_error(f"Session error: {e}", phase)
|
||||
@@ -21,36 +21,42 @@ def test_imports():
|
||||
|
||||
# Test base module
|
||||
from agents import base
|
||||
assert hasattr(base, 'AUTO_CONTINUE_DELAY_SECONDS')
|
||||
assert hasattr(base, 'HUMAN_INTERVENTION_FILE')
|
||||
|
||||
assert hasattr(base, "AUTO_CONTINUE_DELAY_SECONDS")
|
||||
assert hasattr(base, "HUMAN_INTERVENTION_FILE")
|
||||
print(" ✓ agents.base")
|
||||
|
||||
# Test utils module
|
||||
from agents import utils
|
||||
assert hasattr(utils, 'get_latest_commit')
|
||||
assert hasattr(utils, 'load_implementation_plan')
|
||||
|
||||
assert hasattr(utils, "get_latest_commit")
|
||||
assert hasattr(utils, "load_implementation_plan")
|
||||
print(" ✓ agents.utils")
|
||||
|
||||
# Test memory module
|
||||
from agents import memory
|
||||
assert hasattr(memory, 'save_session_memory')
|
||||
assert hasattr(memory, 'get_graphiti_context')
|
||||
|
||||
assert hasattr(memory, "save_session_memory")
|
||||
assert hasattr(memory, "get_graphiti_context")
|
||||
print(" ✓ agents.memory")
|
||||
|
||||
# Test session module
|
||||
from agents import session
|
||||
assert hasattr(session, 'run_agent_session')
|
||||
assert hasattr(session, 'post_session_processing')
|
||||
|
||||
assert hasattr(session, "run_agent_session")
|
||||
assert hasattr(session, "post_session_processing")
|
||||
print(" ✓ agents.session")
|
||||
|
||||
# Test planner module
|
||||
from agents import planner
|
||||
assert hasattr(planner, 'run_followup_planner')
|
||||
|
||||
assert hasattr(planner, "run_followup_planner")
|
||||
print(" ✓ agents.planner")
|
||||
|
||||
# Test coder module
|
||||
from agents import coder
|
||||
assert hasattr(coder, 'run_autonomous_agent')
|
||||
|
||||
assert hasattr(coder, "run_autonomous_agent")
|
||||
print(" ✓ agents.coder")
|
||||
|
||||
print("\n✓ All module imports successful!\n")
|
||||
@@ -64,14 +70,14 @@ def test_public_api():
|
||||
import agents
|
||||
|
||||
required_functions = [
|
||||
'run_autonomous_agent',
|
||||
'run_followup_planner',
|
||||
'save_session_memory',
|
||||
'get_graphiti_context',
|
||||
'run_agent_session',
|
||||
'post_session_processing',
|
||||
'get_latest_commit',
|
||||
'load_implementation_plan',
|
||||
"run_autonomous_agent",
|
||||
"run_followup_planner",
|
||||
"save_session_memory",
|
||||
"get_graphiti_context",
|
||||
"run_agent_session",
|
||||
"post_session_processing",
|
||||
"get_latest_commit",
|
||||
"load_implementation_plan",
|
||||
]
|
||||
|
||||
for func_name in required_functions:
|
||||
@@ -89,16 +95,18 @@ def test_backwards_compatibility():
|
||||
import agent
|
||||
|
||||
required_functions = [
|
||||
'run_autonomous_agent',
|
||||
'run_followup_planner',
|
||||
'save_session_memory',
|
||||
'save_session_to_graphiti',
|
||||
'run_agent_session',
|
||||
'post_session_processing',
|
||||
"run_autonomous_agent",
|
||||
"run_followup_planner",
|
||||
"save_session_memory",
|
||||
"save_session_to_graphiti",
|
||||
"run_agent_session",
|
||||
"post_session_processing",
|
||||
]
|
||||
|
||||
for func_name in required_functions:
|
||||
assert hasattr(agent, func_name), f"Missing function in agent module: {func_name}"
|
||||
assert hasattr(agent, func_name), (
|
||||
f"Missing function in agent module: {func_name}"
|
||||
)
|
||||
print(f" ✓ agent.{func_name}")
|
||||
|
||||
print("\n✓ Backwards compatibility maintained!\n")
|
||||
@@ -109,16 +117,17 @@ def test_module_structure():
|
||||
print("Testing module structure...")
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
agents_dir = Path(__file__).parent
|
||||
|
||||
required_files = [
|
||||
'__init__.py',
|
||||
'base.py',
|
||||
'utils.py',
|
||||
'memory.py',
|
||||
'session.py',
|
||||
'planner.py',
|
||||
'coder.py',
|
||||
"__init__.py",
|
||||
"base.py",
|
||||
"utils.py",
|
||||
"memory.py",
|
||||
"session.py",
|
||||
"planner.py",
|
||||
"coder.py",
|
||||
]
|
||||
|
||||
for filename in required_files:
|
||||
@@ -129,7 +138,7 @@ def test_module_structure():
|
||||
print("\n✓ Module structure correct!\n")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
test_module_structure()
|
||||
test_imports()
|
||||
@@ -0,0 +1,60 @@
|
||||
"""
|
||||
Custom MCP Tools for Auto-Claude Agents
|
||||
========================================
|
||||
|
||||
This module provides custom MCP tools that agents can use for reliable
|
||||
operations on auto-claude data structures. These tools replace prompt-based
|
||||
JSON manipulation with guaranteed-correct operations.
|
||||
|
||||
Benefits:
|
||||
- 100% reliable JSON operations (no malformed output)
|
||||
- Reduced context usage (tool definitions << prompt instructions)
|
||||
- Type-safe with proper error handling
|
||||
- Each agent only sees tools relevant to their role via allowed_tools
|
||||
|
||||
Usage:
|
||||
from auto_claude_tools import create_auto_claude_mcp_server, get_allowed_tools
|
||||
|
||||
# Create the MCP server
|
||||
mcp_server = create_auto_claude_mcp_server(spec_dir, project_dir)
|
||||
|
||||
# Get allowed tools for a specific agent type
|
||||
allowed_tools = get_allowed_tools("coder")
|
||||
|
||||
# Use in ClaudeAgentOptions
|
||||
options = ClaudeAgentOptions(
|
||||
mcp_servers={"auto-claude": mcp_server},
|
||||
allowed_tools=allowed_tools,
|
||||
...
|
||||
)
|
||||
"""
|
||||
|
||||
from .models import (
|
||||
ELECTRON_TOOLS,
|
||||
TOOL_GET_BUILD_PROGRESS,
|
||||
TOOL_GET_SESSION_CONTEXT,
|
||||
TOOL_RECORD_DISCOVERY,
|
||||
TOOL_RECORD_GOTCHA,
|
||||
TOOL_UPDATE_QA_STATUS,
|
||||
TOOL_UPDATE_SUBTASK_STATUS,
|
||||
is_electron_mcp_enabled,
|
||||
)
|
||||
from .permissions import get_allowed_tools
|
||||
from .registry import create_auto_claude_mcp_server, is_tools_available
|
||||
|
||||
__all__ = [
|
||||
# Main API
|
||||
"create_auto_claude_mcp_server",
|
||||
"get_allowed_tools",
|
||||
"is_tools_available",
|
||||
# Tool name constants
|
||||
"TOOL_UPDATE_SUBTASK_STATUS",
|
||||
"TOOL_GET_BUILD_PROGRESS",
|
||||
"TOOL_RECORD_DISCOVERY",
|
||||
"TOOL_RECORD_GOTCHA",
|
||||
"TOOL_GET_SESSION_CONTEXT",
|
||||
"TOOL_UPDATE_QA_STATUS",
|
||||
# Electron MCP
|
||||
"ELECTRON_TOOLS",
|
||||
"is_electron_mcp_enabled",
|
||||
]
|
||||
@@ -0,0 +1,63 @@
|
||||
"""
|
||||
Tool Models and Constants
|
||||
==========================
|
||||
|
||||
Defines tool name constants and configuration for auto-claude MCP tools.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
# =============================================================================
|
||||
# Tool Name Constants
|
||||
# =============================================================================
|
||||
|
||||
# Auto-Claude MCP tool names (prefixed with mcp__auto-claude__)
|
||||
TOOL_UPDATE_SUBTASK_STATUS = "mcp__auto-claude__update_subtask_status"
|
||||
TOOL_GET_BUILD_PROGRESS = "mcp__auto-claude__get_build_progress"
|
||||
TOOL_RECORD_DISCOVERY = "mcp__auto-claude__record_discovery"
|
||||
TOOL_RECORD_GOTCHA = "mcp__auto-claude__record_gotcha"
|
||||
TOOL_GET_SESSION_CONTEXT = "mcp__auto-claude__get_session_context"
|
||||
TOOL_UPDATE_QA_STATUS = "mcp__auto-claude__update_qa_status"
|
||||
|
||||
# Puppeteer MCP tools for web browser automation
|
||||
# Used for web frontend validation (non-Electron web apps)
|
||||
PUPPETEER_TOOLS = [
|
||||
"mcp__puppeteer__puppeteer_connect_active_tab",
|
||||
"mcp__puppeteer__puppeteer_navigate",
|
||||
"mcp__puppeteer__puppeteer_screenshot",
|
||||
"mcp__puppeteer__puppeteer_click",
|
||||
"mcp__puppeteer__puppeteer_fill",
|
||||
"mcp__puppeteer__puppeteer_select",
|
||||
"mcp__puppeteer__puppeteer_hover",
|
||||
"mcp__puppeteer__puppeteer_evaluate",
|
||||
]
|
||||
|
||||
# Electron MCP tools for desktop app automation (when ELECTRON_MCP_ENABLED is set)
|
||||
# Uses electron-mcp-server to connect to Electron apps via Chrome DevTools Protocol.
|
||||
# Electron app must be started with --remote-debugging-port=9222 (or ELECTRON_DEBUG_PORT).
|
||||
# These tools are only available to QA agents (qa_reviewer, qa_fixer), not Coder/Planner.
|
||||
ELECTRON_TOOLS = [
|
||||
"mcp__electron__get_electron_window_info", # Get info about running Electron windows
|
||||
"mcp__electron__take_screenshot", # Capture screenshot of Electron window
|
||||
"mcp__electron__send_command_to_electron", # Send commands (click, fill, evaluate JS)
|
||||
"mcp__electron__read_electron_logs", # Read console logs from Electron app
|
||||
]
|
||||
|
||||
# Base tools available to all agents
|
||||
BASE_READ_TOOLS = ["Read", "Glob", "Grep"]
|
||||
BASE_WRITE_TOOLS = ["Write", "Edit", "Bash"]
|
||||
|
||||
# =============================================================================
|
||||
# Configuration
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def is_electron_mcp_enabled() -> bool:
|
||||
"""
|
||||
Check if Electron MCP server integration is enabled.
|
||||
|
||||
Requires ELECTRON_MCP_ENABLED to be set to 'true'.
|
||||
When enabled, QA agents can use Electron MCP tools to connect to Electron apps
|
||||
via Chrome DevTools Protocol on the configured debug port.
|
||||
"""
|
||||
return os.environ.get("ELECTRON_MCP_ENABLED", "").lower() == "true"
|
||||
@@ -0,0 +1,147 @@
|
||||
"""
|
||||
Agent Tool Permissions
|
||||
======================
|
||||
|
||||
Manages which tools are allowed for each agent type to prevent context
|
||||
pollution and accidental misuse.
|
||||
|
||||
Supports dynamic tool filtering based on project capabilities to optimize
|
||||
context window usage. For example, Electron tools are only included for
|
||||
Electron projects, not for Next.js or CLI projects.
|
||||
"""
|
||||
|
||||
from .models import (
|
||||
BASE_READ_TOOLS,
|
||||
BASE_WRITE_TOOLS,
|
||||
ELECTRON_TOOLS,
|
||||
PUPPETEER_TOOLS,
|
||||
TOOL_GET_BUILD_PROGRESS,
|
||||
TOOL_GET_SESSION_CONTEXT,
|
||||
TOOL_RECORD_DISCOVERY,
|
||||
TOOL_RECORD_GOTCHA,
|
||||
TOOL_UPDATE_QA_STATUS,
|
||||
TOOL_UPDATE_SUBTASK_STATUS,
|
||||
is_electron_mcp_enabled,
|
||||
)
|
||||
|
||||
|
||||
def get_allowed_tools(
|
||||
agent_type: str,
|
||||
project_capabilities: dict | None = None,
|
||||
) -> list[str]:
|
||||
"""
|
||||
Get the list of allowed tools for a specific agent type.
|
||||
|
||||
This ensures each agent only sees tools relevant to their role,
|
||||
preventing context pollution and accidental misuse.
|
||||
|
||||
When project_capabilities is provided, MCP tools are filtered based on
|
||||
the project type. For example:
|
||||
- Electron projects get Electron MCP tools
|
||||
- Web frontends (non-Electron) get Puppeteer MCP tools
|
||||
- CLI projects get neither
|
||||
|
||||
Args:
|
||||
agent_type: One of 'planner', 'coder', 'qa_reviewer', 'qa_fixer'
|
||||
project_capabilities: Optional dict from detect_project_capabilities()
|
||||
containing flags like is_electron, is_web_frontend, etc.
|
||||
|
||||
Returns:
|
||||
List of allowed tool names
|
||||
"""
|
||||
# Auto-claude tool mappings by agent type
|
||||
tool_mappings = {
|
||||
"planner": {
|
||||
"base": BASE_READ_TOOLS + BASE_WRITE_TOOLS,
|
||||
"auto_claude": [
|
||||
TOOL_GET_BUILD_PROGRESS,
|
||||
TOOL_GET_SESSION_CONTEXT,
|
||||
TOOL_RECORD_DISCOVERY,
|
||||
],
|
||||
},
|
||||
"coder": {
|
||||
"base": BASE_READ_TOOLS + BASE_WRITE_TOOLS,
|
||||
"auto_claude": [
|
||||
TOOL_UPDATE_SUBTASK_STATUS,
|
||||
TOOL_GET_BUILD_PROGRESS,
|
||||
TOOL_RECORD_DISCOVERY,
|
||||
TOOL_RECORD_GOTCHA,
|
||||
TOOL_GET_SESSION_CONTEXT,
|
||||
],
|
||||
},
|
||||
"qa_reviewer": {
|
||||
"base": BASE_READ_TOOLS + ["Bash"], # Can run tests but not edit
|
||||
"auto_claude": [
|
||||
TOOL_GET_BUILD_PROGRESS,
|
||||
TOOL_UPDATE_QA_STATUS,
|
||||
TOOL_GET_SESSION_CONTEXT,
|
||||
],
|
||||
},
|
||||
"pr_reviewer": {
|
||||
# PR reviewers can ONLY read - no bash, no edits, no writes
|
||||
# This prevents the agent from switching branches or making changes
|
||||
"base": BASE_READ_TOOLS,
|
||||
"auto_claude": [], # No auto-claude tools needed for PR review
|
||||
},
|
||||
"qa_fixer": {
|
||||
"base": BASE_READ_TOOLS + BASE_WRITE_TOOLS,
|
||||
"auto_claude": [
|
||||
TOOL_UPDATE_SUBTASK_STATUS,
|
||||
TOOL_GET_BUILD_PROGRESS,
|
||||
TOOL_UPDATE_QA_STATUS,
|
||||
TOOL_RECORD_GOTCHA,
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
if agent_type not in tool_mappings:
|
||||
# Default to coder tools
|
||||
agent_type = "coder"
|
||||
|
||||
mapping = tool_mappings[agent_type]
|
||||
tools = mapping["base"] + mapping["auto_claude"]
|
||||
|
||||
# Add MCP tools for QA agents only, based on project capabilities
|
||||
if agent_type in ("qa_reviewer", "qa_fixer"):
|
||||
tools.extend(_get_qa_mcp_tools(project_capabilities))
|
||||
|
||||
return tools
|
||||
|
||||
|
||||
def _get_qa_mcp_tools(project_capabilities: dict | None) -> list[str]:
|
||||
"""
|
||||
Get the list of MCP tools for QA agents based on project capabilities.
|
||||
|
||||
This function determines which MCP tools to include based on:
|
||||
1. Project type detection (Electron, web frontend, etc.)
|
||||
2. Environment variables (ELECTRON_MCP_ENABLED)
|
||||
|
||||
Args:
|
||||
project_capabilities: Dict from detect_project_capabilities() or None
|
||||
|
||||
Returns:
|
||||
List of MCP tool names to include
|
||||
"""
|
||||
tools = []
|
||||
|
||||
# If no capabilities provided, fall back to legacy behavior
|
||||
# (check env var only)
|
||||
if project_capabilities is None:
|
||||
if is_electron_mcp_enabled():
|
||||
tools.extend(ELECTRON_TOOLS)
|
||||
return tools
|
||||
|
||||
# Project-capability-based tool selection
|
||||
is_electron = project_capabilities.get("is_electron", False)
|
||||
is_web_frontend = project_capabilities.get("is_web_frontend", False)
|
||||
|
||||
# Electron projects get Electron MCP tools (if enabled)
|
||||
if is_electron and is_electron_mcp_enabled():
|
||||
tools.extend(ELECTRON_TOOLS)
|
||||
|
||||
# Web frontends (non-Electron) get Puppeteer tools
|
||||
# Puppeteer is always available, no env var check needed
|
||||
if is_web_frontend and not is_electron:
|
||||
tools.extend(PUPPETEER_TOOLS)
|
||||
|
||||
return tools
|
||||
@@ -0,0 +1,72 @@
|
||||
"""
|
||||
Tool Registry
|
||||
=============
|
||||
|
||||
Central registry for creating and managing auto-claude MCP tools.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
from claude_agent_sdk import create_sdk_mcp_server
|
||||
|
||||
SDK_TOOLS_AVAILABLE = True
|
||||
except ImportError:
|
||||
SDK_TOOLS_AVAILABLE = False
|
||||
create_sdk_mcp_server = None
|
||||
|
||||
from .tools import (
|
||||
create_memory_tools,
|
||||
create_progress_tools,
|
||||
create_qa_tools,
|
||||
create_subtask_tools,
|
||||
)
|
||||
|
||||
|
||||
def create_all_tools(spec_dir: Path, project_dir: Path) -> list:
|
||||
"""
|
||||
Create all custom tools with the given spec and project directories.
|
||||
|
||||
Args:
|
||||
spec_dir: Path to the spec directory
|
||||
project_dir: Path to the project root
|
||||
|
||||
Returns:
|
||||
List of all tool functions
|
||||
"""
|
||||
if not SDK_TOOLS_AVAILABLE:
|
||||
return []
|
||||
|
||||
all_tools = []
|
||||
|
||||
# Create tools by category
|
||||
all_tools.extend(create_subtask_tools(spec_dir, project_dir))
|
||||
all_tools.extend(create_progress_tools(spec_dir, project_dir))
|
||||
all_tools.extend(create_memory_tools(spec_dir, project_dir))
|
||||
all_tools.extend(create_qa_tools(spec_dir, project_dir))
|
||||
|
||||
return all_tools
|
||||
|
||||
|
||||
def create_auto_claude_mcp_server(spec_dir: Path, project_dir: Path):
|
||||
"""
|
||||
Create an MCP server with auto-claude custom tools.
|
||||
|
||||
Args:
|
||||
spec_dir: Path to the spec directory
|
||||
project_dir: Path to the project root
|
||||
|
||||
Returns:
|
||||
MCP server instance, or None if SDK tools not available
|
||||
"""
|
||||
if not SDK_TOOLS_AVAILABLE:
|
||||
return None
|
||||
|
||||
tools = create_all_tools(spec_dir, project_dir)
|
||||
|
||||
return create_sdk_mcp_server(name="auto-claude", version="1.0.0", tools=tools)
|
||||
|
||||
|
||||
def is_tools_available() -> bool:
|
||||
"""Check if SDK tools functionality is available."""
|
||||
return SDK_TOOLS_AVAILABLE
|
||||
@@ -0,0 +1,18 @@
|
||||
"""
|
||||
Auto-Claude MCP Tools
|
||||
=====================
|
||||
|
||||
Individual tool implementations organized by functionality.
|
||||
"""
|
||||
|
||||
from .memory import create_memory_tools
|
||||
from .progress import create_progress_tools
|
||||
from .qa import create_qa_tools
|
||||
from .subtask import create_subtask_tools
|
||||
|
||||
__all__ = [
|
||||
"create_subtask_tools",
|
||||
"create_progress_tools",
|
||||
"create_memory_tools",
|
||||
"create_qa_tools",
|
||||
]
|
||||
@@ -0,0 +1,216 @@
|
||||
"""
|
||||
Session Memory Tools
|
||||
====================
|
||||
|
||||
Tools for recording and retrieving session memory, including discoveries,
|
||||
gotchas, and patterns.
|
||||
"""
|
||||
|
||||
import json
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
try:
|
||||
from claude_agent_sdk import tool
|
||||
|
||||
SDK_TOOLS_AVAILABLE = True
|
||||
except ImportError:
|
||||
SDK_TOOLS_AVAILABLE = False
|
||||
tool = None
|
||||
|
||||
|
||||
def create_memory_tools(spec_dir: Path, project_dir: Path) -> list:
|
||||
"""
|
||||
Create session memory tools.
|
||||
|
||||
Args:
|
||||
spec_dir: Path to the spec directory
|
||||
project_dir: Path to the project root
|
||||
|
||||
Returns:
|
||||
List of memory tool functions
|
||||
"""
|
||||
if not SDK_TOOLS_AVAILABLE:
|
||||
return []
|
||||
|
||||
tools = []
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Tool: record_discovery
|
||||
# -------------------------------------------------------------------------
|
||||
@tool(
|
||||
"record_discovery",
|
||||
"Record a codebase discovery to session memory. Use this when you learn something important about the codebase.",
|
||||
{"file_path": str, "description": str, "category": str},
|
||||
)
|
||||
async def record_discovery(args: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Record a discovery to the codebase map."""
|
||||
file_path = args["file_path"]
|
||||
description = args["description"]
|
||||
category = args.get("category", "general")
|
||||
|
||||
memory_dir = spec_dir / "memory"
|
||||
memory_dir.mkdir(exist_ok=True)
|
||||
|
||||
codebase_map_file = memory_dir / "codebase_map.json"
|
||||
|
||||
try:
|
||||
# Load existing map or create new
|
||||
if codebase_map_file.exists():
|
||||
with open(codebase_map_file) as f:
|
||||
codebase_map = json.load(f)
|
||||
else:
|
||||
codebase_map = {
|
||||
"discovered_files": {},
|
||||
"last_updated": None,
|
||||
}
|
||||
|
||||
# Add or update the discovery
|
||||
codebase_map["discovered_files"][file_path] = {
|
||||
"description": description,
|
||||
"category": category,
|
||||
"discovered_at": datetime.now(timezone.utc).isoformat(),
|
||||
}
|
||||
codebase_map["last_updated"] = datetime.now(timezone.utc).isoformat()
|
||||
|
||||
with open(codebase_map_file, "w") as f:
|
||||
json.dump(codebase_map, f, indent=2)
|
||||
|
||||
return {
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": f"Recorded discovery for '{file_path}': {description}",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"content": [{"type": "text", "text": f"Error recording discovery: {e}"}]
|
||||
}
|
||||
|
||||
tools.append(record_discovery)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Tool: record_gotcha
|
||||
# -------------------------------------------------------------------------
|
||||
@tool(
|
||||
"record_gotcha",
|
||||
"Record a gotcha or pitfall to avoid. Use this when you encounter something that future sessions should know.",
|
||||
{"gotcha": str, "context": str},
|
||||
)
|
||||
async def record_gotcha(args: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Record a gotcha to session memory."""
|
||||
gotcha = args["gotcha"]
|
||||
context = args.get("context", "")
|
||||
|
||||
memory_dir = spec_dir / "memory"
|
||||
memory_dir.mkdir(exist_ok=True)
|
||||
|
||||
gotchas_file = memory_dir / "gotchas.md"
|
||||
|
||||
try:
|
||||
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M")
|
||||
|
||||
entry = f"\n## [{timestamp}]\n{gotcha}"
|
||||
if context:
|
||||
entry += f"\n\n_Context: {context}_"
|
||||
entry += "\n"
|
||||
|
||||
with open(gotchas_file, "a") as f:
|
||||
if not gotchas_file.exists() or gotchas_file.stat().st_size == 0:
|
||||
f.write(
|
||||
"# Gotchas & Pitfalls\n\nThings to watch out for in this codebase.\n"
|
||||
)
|
||||
f.write(entry)
|
||||
|
||||
return {"content": [{"type": "text", "text": f"Recorded gotcha: {gotcha}"}]}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"content": [{"type": "text", "text": f"Error recording gotcha: {e}"}]
|
||||
}
|
||||
|
||||
tools.append(record_gotcha)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Tool: get_session_context
|
||||
# -------------------------------------------------------------------------
|
||||
@tool(
|
||||
"get_session_context",
|
||||
"Get context from previous sessions including discoveries, gotchas, and patterns.",
|
||||
{},
|
||||
)
|
||||
async def get_session_context(args: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Get accumulated session context."""
|
||||
memory_dir = spec_dir / "memory"
|
||||
|
||||
if not memory_dir.exists():
|
||||
return {
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "No session memory found. This appears to be the first session.",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
result_parts = []
|
||||
|
||||
# Load codebase map
|
||||
codebase_map_file = memory_dir / "codebase_map.json"
|
||||
if codebase_map_file.exists():
|
||||
try:
|
||||
with open(codebase_map_file) as f:
|
||||
codebase_map = json.load(f)
|
||||
|
||||
discoveries = codebase_map.get("discovered_files", {})
|
||||
if discoveries:
|
||||
result_parts.append("## Codebase Discoveries")
|
||||
for path, info in list(discoveries.items())[:20]: # Limit to 20
|
||||
desc = info.get("description", "No description")
|
||||
result_parts.append(f"- `{path}`: {desc}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Load gotchas
|
||||
gotchas_file = memory_dir / "gotchas.md"
|
||||
if gotchas_file.exists():
|
||||
try:
|
||||
content = gotchas_file.read_text()
|
||||
if content.strip():
|
||||
result_parts.append("\n## Gotchas")
|
||||
# Take last 1000 chars to avoid too much context
|
||||
result_parts.append(
|
||||
content[-1000:] if len(content) > 1000 else content
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Load patterns
|
||||
patterns_file = memory_dir / "patterns.md"
|
||||
if patterns_file.exists():
|
||||
try:
|
||||
content = patterns_file.read_text()
|
||||
if content.strip():
|
||||
result_parts.append("\n## Patterns")
|
||||
result_parts.append(
|
||||
content[-1000:] if len(content) > 1000 else content
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not result_parts:
|
||||
return {
|
||||
"content": [
|
||||
{"type": "text", "text": "No session context available yet."}
|
||||
]
|
||||
}
|
||||
|
||||
return {"content": [{"type": "text", "text": "\n".join(result_parts)}]}
|
||||
|
||||
tools.append(get_session_context)
|
||||
|
||||
return tools
|
||||
@@ -0,0 +1,142 @@
|
||||
"""
|
||||
Build Progress Tools
|
||||
====================
|
||||
|
||||
Tools for tracking and reporting build progress.
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
try:
|
||||
from claude_agent_sdk import tool
|
||||
|
||||
SDK_TOOLS_AVAILABLE = True
|
||||
except ImportError:
|
||||
SDK_TOOLS_AVAILABLE = False
|
||||
tool = None
|
||||
|
||||
|
||||
def create_progress_tools(spec_dir: Path, project_dir: Path) -> list:
|
||||
"""
|
||||
Create build progress tracking tools.
|
||||
|
||||
Args:
|
||||
spec_dir: Path to the spec directory
|
||||
project_dir: Path to the project root
|
||||
|
||||
Returns:
|
||||
List of progress tool functions
|
||||
"""
|
||||
if not SDK_TOOLS_AVAILABLE:
|
||||
return []
|
||||
|
||||
tools = []
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Tool: get_build_progress
|
||||
# -------------------------------------------------------------------------
|
||||
@tool(
|
||||
"get_build_progress",
|
||||
"Get the current build progress including completed subtasks, pending subtasks, and next subtask to work on.",
|
||||
{},
|
||||
)
|
||||
async def get_build_progress(args: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Get current build progress."""
|
||||
plan_file = spec_dir / "implementation_plan.json"
|
||||
|
||||
if not plan_file.exists():
|
||||
return {
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "No implementation plan found. Run the planner first.",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
try:
|
||||
with open(plan_file) as f:
|
||||
plan = json.load(f)
|
||||
|
||||
stats = {
|
||||
"total": 0,
|
||||
"completed": 0,
|
||||
"in_progress": 0,
|
||||
"pending": 0,
|
||||
"failed": 0,
|
||||
}
|
||||
|
||||
phases_summary = []
|
||||
next_subtask = None
|
||||
|
||||
for phase in plan.get("phases", []):
|
||||
phase_id = phase.get("id") or phase.get("phase")
|
||||
phase_name = phase.get("name", phase_id)
|
||||
phase_subtasks = phase.get("subtasks", [])
|
||||
|
||||
phase_stats = {"completed": 0, "total": len(phase_subtasks)}
|
||||
|
||||
for subtask in phase_subtasks:
|
||||
stats["total"] += 1
|
||||
status = subtask.get("status", "pending")
|
||||
|
||||
if status == "completed":
|
||||
stats["completed"] += 1
|
||||
phase_stats["completed"] += 1
|
||||
elif status == "in_progress":
|
||||
stats["in_progress"] += 1
|
||||
elif status == "failed":
|
||||
stats["failed"] += 1
|
||||
else:
|
||||
stats["pending"] += 1
|
||||
# Track next subtask to work on
|
||||
if next_subtask is None:
|
||||
next_subtask = {
|
||||
"id": subtask.get("id"),
|
||||
"description": subtask.get("description"),
|
||||
"phase": phase_name,
|
||||
}
|
||||
|
||||
phases_summary.append(
|
||||
f" {phase_name}: {phase_stats['completed']}/{phase_stats['total']}"
|
||||
)
|
||||
|
||||
progress_pct = (
|
||||
(stats["completed"] / stats["total"] * 100) if stats["total"] > 0 else 0
|
||||
)
|
||||
|
||||
result = f"""Build Progress: {stats["completed"]}/{stats["total"]} subtasks ({progress_pct:.0f}%)
|
||||
|
||||
Status breakdown:
|
||||
Completed: {stats["completed"]}
|
||||
In Progress: {stats["in_progress"]}
|
||||
Pending: {stats["pending"]}
|
||||
Failed: {stats["failed"]}
|
||||
|
||||
Phases:
|
||||
{chr(10).join(phases_summary)}"""
|
||||
|
||||
if next_subtask:
|
||||
result += f"""
|
||||
|
||||
Next subtask to work on:
|
||||
ID: {next_subtask["id"]}
|
||||
Phase: {next_subtask["phase"]}
|
||||
Description: {next_subtask["description"]}"""
|
||||
elif stats["completed"] == stats["total"]:
|
||||
result += "\n\nAll subtasks completed! Build is ready for QA."
|
||||
|
||||
return {"content": [{"type": "text", "text": result}]}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"content": [
|
||||
{"type": "text", "text": f"Error reading build progress: {e}"}
|
||||
]
|
||||
}
|
||||
|
||||
tools.append(get_build_progress)
|
||||
|
||||
return tools
|
||||
@@ -0,0 +1,140 @@
|
||||
"""
|
||||
QA Management Tools
|
||||
===================
|
||||
|
||||
Tools for managing QA status and sign-off in implementation_plan.json.
|
||||
"""
|
||||
|
||||
import json
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
try:
|
||||
from claude_agent_sdk import tool
|
||||
|
||||
SDK_TOOLS_AVAILABLE = True
|
||||
except ImportError:
|
||||
SDK_TOOLS_AVAILABLE = False
|
||||
tool = None
|
||||
|
||||
|
||||
def create_qa_tools(spec_dir: Path, project_dir: Path) -> list:
|
||||
"""
|
||||
Create QA management tools.
|
||||
|
||||
Args:
|
||||
spec_dir: Path to the spec directory
|
||||
project_dir: Path to the project root
|
||||
|
||||
Returns:
|
||||
List of QA tool functions
|
||||
"""
|
||||
if not SDK_TOOLS_AVAILABLE:
|
||||
return []
|
||||
|
||||
tools = []
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Tool: update_qa_status
|
||||
# -------------------------------------------------------------------------
|
||||
@tool(
|
||||
"update_qa_status",
|
||||
"Update the QA sign-off status in implementation_plan.json. Use after QA review.",
|
||||
{"status": str, "issues": str, "tests_passed": str},
|
||||
)
|
||||
async def update_qa_status(args: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Update QA status in the implementation plan."""
|
||||
status = args["status"]
|
||||
issues_str = args.get("issues", "[]")
|
||||
tests_str = args.get("tests_passed", "{}")
|
||||
|
||||
valid_statuses = [
|
||||
"pending",
|
||||
"in_review",
|
||||
"approved",
|
||||
"rejected",
|
||||
"fixes_applied",
|
||||
]
|
||||
if status not in valid_statuses:
|
||||
return {
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": f"Error: Invalid QA status '{status}'. Must be one of: {valid_statuses}",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
plan_file = spec_dir / "implementation_plan.json"
|
||||
if not plan_file.exists():
|
||||
return {
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Error: implementation_plan.json not found",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
try:
|
||||
# Parse issues and tests
|
||||
try:
|
||||
issues = json.loads(issues_str) if issues_str else []
|
||||
except json.JSONDecodeError:
|
||||
issues = [{"description": issues_str}] if issues_str else []
|
||||
|
||||
try:
|
||||
tests_passed = json.loads(tests_str) if tests_str else {}
|
||||
except json.JSONDecodeError:
|
||||
tests_passed = {}
|
||||
|
||||
with open(plan_file) as f:
|
||||
plan = json.load(f)
|
||||
|
||||
# Get current QA session number
|
||||
current_qa = plan.get("qa_signoff", {})
|
||||
qa_session = current_qa.get("qa_session", 0)
|
||||
if status in ["in_review", "rejected"]:
|
||||
qa_session += 1
|
||||
|
||||
plan["qa_signoff"] = {
|
||||
"status": status,
|
||||
"qa_session": qa_session,
|
||||
"issues_found": issues,
|
||||
"tests_passed": tests_passed,
|
||||
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||
"ready_for_qa_revalidation": status == "fixes_applied",
|
||||
}
|
||||
|
||||
# Update plan status to match QA result
|
||||
# This ensures the UI shows the correct column after QA
|
||||
if status == "approved":
|
||||
plan["status"] = "human_review"
|
||||
plan["planStatus"] = "review"
|
||||
elif status == "rejected":
|
||||
plan["status"] = "human_review"
|
||||
plan["planStatus"] = "review"
|
||||
|
||||
plan["last_updated"] = datetime.now(timezone.utc).isoformat()
|
||||
|
||||
with open(plan_file, "w") as f:
|
||||
json.dump(plan, f, indent=2)
|
||||
|
||||
return {
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": f"Updated QA status to '{status}' (session {qa_session})",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"content": [{"type": "text", "text": f"Error updating QA status: {e}"}]
|
||||
}
|
||||
|
||||
tools.append(update_qa_status)
|
||||
|
||||
return tools
|
||||
@@ -0,0 +1,135 @@
|
||||
"""
|
||||
Subtask Management Tools
|
||||
========================
|
||||
|
||||
Tools for managing subtask status in implementation_plan.json.
|
||||
"""
|
||||
|
||||
import json
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
try:
|
||||
from claude_agent_sdk import tool
|
||||
|
||||
SDK_TOOLS_AVAILABLE = True
|
||||
except ImportError:
|
||||
SDK_TOOLS_AVAILABLE = False
|
||||
tool = None
|
||||
|
||||
|
||||
def create_subtask_tools(spec_dir: Path, project_dir: Path) -> list:
|
||||
"""
|
||||
Create subtask management tools.
|
||||
|
||||
Args:
|
||||
spec_dir: Path to the spec directory
|
||||
project_dir: Path to the project root
|
||||
|
||||
Returns:
|
||||
List of subtask tool functions
|
||||
"""
|
||||
if not SDK_TOOLS_AVAILABLE:
|
||||
return []
|
||||
|
||||
tools = []
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Tool: update_subtask_status
|
||||
# -------------------------------------------------------------------------
|
||||
@tool(
|
||||
"update_subtask_status",
|
||||
"Update the status of a subtask in implementation_plan.json. Use this when completing or starting a subtask.",
|
||||
{"subtask_id": str, "status": str, "notes": str},
|
||||
)
|
||||
async def update_subtask_status(args: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Update subtask status in the implementation plan."""
|
||||
subtask_id = args["subtask_id"]
|
||||
status = args["status"]
|
||||
notes = args.get("notes", "")
|
||||
|
||||
valid_statuses = ["pending", "in_progress", "completed", "failed"]
|
||||
if status not in valid_statuses:
|
||||
return {
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": f"Error: Invalid status '{status}'. Must be one of: {valid_statuses}",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
plan_file = spec_dir / "implementation_plan.json"
|
||||
if not plan_file.exists():
|
||||
return {
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Error: implementation_plan.json not found",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
try:
|
||||
with open(plan_file) as f:
|
||||
plan = json.load(f)
|
||||
|
||||
# Find and update the subtask
|
||||
subtask_found = False
|
||||
for phase in plan.get("phases", []):
|
||||
for subtask in phase.get("subtasks", []):
|
||||
if subtask.get("id") == subtask_id:
|
||||
subtask["status"] = status
|
||||
if notes:
|
||||
subtask["notes"] = notes
|
||||
subtask["updated_at"] = datetime.now(timezone.utc).isoformat()
|
||||
subtask_found = True
|
||||
break
|
||||
if subtask_found:
|
||||
break
|
||||
|
||||
if not subtask_found:
|
||||
return {
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": f"Error: Subtask '{subtask_id}' not found in implementation plan",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Update plan metadata
|
||||
plan["last_updated"] = datetime.now(timezone.utc).isoformat()
|
||||
|
||||
with open(plan_file, "w") as f:
|
||||
json.dump(plan, f, indent=2)
|
||||
|
||||
return {
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": f"Successfully updated subtask '{subtask_id}' to status '{status}'",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
return {
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": f"Error: Invalid JSON in implementation_plan.json: {e}",
|
||||
}
|
||||
]
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"content": [
|
||||
{"type": "text", "text": f"Error updating subtask status: {e}"}
|
||||
]
|
||||
}
|
||||
|
||||
tools.append(update_subtask_status)
|
||||
|
||||
return tools
|
||||
@@ -10,12 +10,11 @@ import logging
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_latest_commit(project_dir: Path) -> Optional[str]:
|
||||
def get_latest_commit(project_dir: Path) -> str | None:
|
||||
"""Get the hash of the latest git commit."""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
@@ -45,7 +44,7 @@ def get_commit_count(project_dir: Path) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def load_implementation_plan(spec_dir: Path) -> Optional[dict]:
|
||||
def load_implementation_plan(spec_dir: Path) -> dict | None:
|
||||
"""Load the implementation plan JSON."""
|
||||
plan_file = spec_dir / "implementation_plan.json"
|
||||
if not plan_file.exists():
|
||||
@@ -53,11 +52,11 @@ def load_implementation_plan(spec_dir: Path) -> Optional[dict]:
|
||||
try:
|
||||
with open(plan_file) as f:
|
||||
return json.load(f)
|
||||
except (json.JSONDecodeError, IOError):
|
||||
except (OSError, json.JSONDecodeError):
|
||||
return None
|
||||
|
||||
|
||||
def find_subtask_in_plan(plan: dict, subtask_id: str) -> Optional[dict]:
|
||||
def find_subtask_in_plan(plan: dict, subtask_id: str) -> dict | None:
|
||||
"""Find a subtask by ID in the plan."""
|
||||
for phase in plan.get("phases", []):
|
||||
for subtask in phase.get("subtasks", []):
|
||||
@@ -66,7 +65,7 @@ def find_subtask_in_plan(plan: dict, subtask_id: str) -> Optional[dict]:
|
||||
return None
|
||||
|
||||
|
||||
def find_phase_for_subtask(plan: dict, subtask_id: str) -> Optional[dict]:
|
||||
def find_phase_for_subtask(plan: dict, subtask_id: str) -> dict | None:
|
||||
"""Find the phase containing a subtask."""
|
||||
for phase in plan.get("phases", []):
|
||||
for subtask in phase.get("subtasks", []):
|
||||
@@ -75,7 +74,7 @@ def find_phase_for_subtask(plan: dict, subtask_id: str) -> Optional[dict]:
|
||||
return None
|
||||
|
||||
|
||||
def sync_plan_to_source(spec_dir: Path, source_spec_dir: Optional[Path]) -> bool:
|
||||
def sync_plan_to_source(spec_dir: Path, source_spec_dir: Path | None) -> bool:
|
||||
"""
|
||||
Sync implementation_plan.json from worktree back to source spec directory.
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
"""
|
||||
Analysis Module
|
||||
===============
|
||||
|
||||
Code analysis and project scanning tools.
|
||||
"""
|
||||
|
||||
# Import from analyzers subpackage (these are the modular analyzers)
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from .analyzers import (
|
||||
ProjectAnalyzer as ModularProjectAnalyzer,
|
||||
)
|
||||
from .analyzers import (
|
||||
ServiceAnalyzer,
|
||||
analyze_project,
|
||||
analyze_service,
|
||||
)
|
||||
from .ci_discovery import CIDiscovery
|
||||
|
||||
# Import from analysis module root (these are other analysis tools)
|
||||
from .project_analyzer import ProjectAnalyzer
|
||||
from .risk_classifier import RiskClassifier
|
||||
from .security_scanner import SecurityScanner
|
||||
from .test_discovery import TestDiscovery
|
||||
|
||||
# insight_extractor is a module with functions, not a class, so don't import it here
|
||||
# Import it directly when needed: from analysis import insight_extractor
|
||||
|
||||
__all__ = [
|
||||
"ProjectAnalyzer",
|
||||
"ModularProjectAnalyzer",
|
||||
"ServiceAnalyzer",
|
||||
"analyze_project",
|
||||
"analyze_service",
|
||||
"RiskClassifier",
|
||||
"SecurityScanner",
|
||||
"CIDiscovery",
|
||||
"TestDiscovery",
|
||||
]
|
||||
@@ -27,11 +27,13 @@ This module now serves as a facade to the modular analyzer system in the analyze
|
||||
All actual implementation is in focused submodules for better maintainability.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
# Import from the new modular structure
|
||||
from analyzers import (
|
||||
from .analyzers import (
|
||||
ProjectAnalyzer,
|
||||
ServiceAnalyzer,
|
||||
analyze_project,
|
||||
@@ -11,6 +11,8 @@ Main exports:
|
||||
- analyze_service: Convenience function for service analysis
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
@@ -51,7 +53,9 @@ def analyze_project(project_dir: Path, output_file: Path | None = None) -> dict:
|
||||
return results
|
||||
|
||||
|
||||
def analyze_service(project_dir: Path, service_name: str, output_file: Path | None = None) -> dict:
|
||||
def analyze_service(
|
||||
project_dir: Path, service_name: str, output_file: Path | None = None
|
||||
) -> dict:
|
||||
"""
|
||||
Analyze a specific service within a project.
|
||||
|
||||
@@ -5,9 +5,10 @@ Base Analyzer Module
|
||||
Provides common constants, utilities, and base functionality shared across all analyzers.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
# Directories to skip during analysis
|
||||
SKIP_DIRS = {
|
||||
@@ -41,17 +42,47 @@ SKIP_DIRS = {
|
||||
|
||||
# Common service directory names
|
||||
SERVICE_INDICATORS = {
|
||||
"backend", "frontend", "api", "web", "app", "server", "client",
|
||||
"worker", "workers", "services", "packages", "apps", "libs",
|
||||
"scraper", "crawler", "proxy", "gateway", "admin", "dashboard",
|
||||
"mobile", "desktop", "cli", "sdk", "core", "shared", "common",
|
||||
"backend",
|
||||
"frontend",
|
||||
"api",
|
||||
"web",
|
||||
"app",
|
||||
"server",
|
||||
"client",
|
||||
"worker",
|
||||
"workers",
|
||||
"services",
|
||||
"packages",
|
||||
"apps",
|
||||
"libs",
|
||||
"scraper",
|
||||
"crawler",
|
||||
"proxy",
|
||||
"gateway",
|
||||
"admin",
|
||||
"dashboard",
|
||||
"mobile",
|
||||
"desktop",
|
||||
"cli",
|
||||
"sdk",
|
||||
"core",
|
||||
"shared",
|
||||
"common",
|
||||
}
|
||||
|
||||
# Files that indicate a service root
|
||||
SERVICE_ROOT_FILES = {
|
||||
"package.json", "requirements.txt", "pyproject.toml", "Cargo.toml",
|
||||
"go.mod", "Gemfile", "composer.json", "pom.xml", "build.gradle",
|
||||
"Makefile", "Dockerfile",
|
||||
"package.json",
|
||||
"requirements.txt",
|
||||
"pyproject.toml",
|
||||
"Cargo.toml",
|
||||
"go.mod",
|
||||
"Gemfile",
|
||||
"composer.json",
|
||||
"pom.xml",
|
||||
"build.gradle",
|
||||
"Makefile",
|
||||
"Dockerfile",
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +100,7 @@ class BaseAnalyzer:
|
||||
"""Read a file relative to the analyzer's path."""
|
||||
try:
|
||||
return (self.path / path).read_text()
|
||||
except (IOError, UnicodeDecodeError):
|
||||
except (OSError, UnicodeDecodeError):
|
||||
return ""
|
||||
|
||||
def _read_json(self, path: str) -> dict | None:
|
||||
@@ -88,7 +119,7 @@ class BaseAnalyzer:
|
||||
return "string"
|
||||
|
||||
# Boolean
|
||||
if value.lower() in ['true', 'false', '1', '0', 'yes', 'no']:
|
||||
if value.lower() in ["true", "false", "1", "0", "yes", "no"]:
|
||||
return "boolean"
|
||||
|
||||
# Number
|
||||
@@ -96,15 +127,25 @@ class BaseAnalyzer:
|
||||
return "number"
|
||||
|
||||
# URL
|
||||
if value.startswith(('http://', 'https://', 'postgres://', 'postgresql://', 'mysql://', 'mongodb://', 'redis://')):
|
||||
if value.startswith(
|
||||
(
|
||||
"http://",
|
||||
"https://",
|
||||
"postgres://",
|
||||
"postgresql://",
|
||||
"mysql://",
|
||||
"mongodb://",
|
||||
"redis://",
|
||||
)
|
||||
):
|
||||
return "url"
|
||||
|
||||
# Email
|
||||
if '@' in value and '.' in value:
|
||||
if "@" in value and "." in value:
|
||||
return "email"
|
||||
|
||||
# Path
|
||||
if '/' in value or '\\' in value:
|
||||
if "/" in value or "\\" in value:
|
||||
return "path"
|
||||
|
||||
return "string"
|
||||
@@ -0,0 +1,26 @@
|
||||
"""
|
||||
Context Analyzer Package
|
||||
=========================
|
||||
|
||||
Contains specialized detectors for comprehensive project context analysis.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from .api_docs_detector import ApiDocsDetector
|
||||
from .auth_detector import AuthDetector
|
||||
from .env_detector import EnvironmentDetector
|
||||
from .jobs_detector import JobsDetector
|
||||
from .migrations_detector import MigrationsDetector
|
||||
from .monitoring_detector import MonitoringDetector
|
||||
from .services_detector import ServicesDetector
|
||||
|
||||
__all__ = [
|
||||
"ApiDocsDetector",
|
||||
"AuthDetector",
|
||||
"EnvironmentDetector",
|
||||
"JobsDetector",
|
||||
"MigrationsDetector",
|
||||
"MonitoringDetector",
|
||||
"ServicesDetector",
|
||||
]
|
||||
@@ -0,0 +1,95 @@
|
||||
"""
|
||||
API Documentation Detector Module
|
||||
==================================
|
||||
|
||||
Detects API documentation tools and configurations:
|
||||
- OpenAPI/Swagger (FastAPI auto-generated, swagger-ui-express)
|
||||
- GraphQL playground
|
||||
- API documentation endpoints
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from ..base import BaseAnalyzer
|
||||
|
||||
|
||||
class ApiDocsDetector(BaseAnalyzer):
|
||||
"""Detects API documentation setup."""
|
||||
|
||||
def __init__(self, path: Path, analysis: dict[str, Any]):
|
||||
super().__init__(path)
|
||||
self.analysis = analysis
|
||||
|
||||
def detect(self) -> None:
|
||||
"""
|
||||
Detect API documentation setup.
|
||||
|
||||
Detects: OpenAPI/Swagger, GraphQL playground, API docs endpoints.
|
||||
"""
|
||||
docs_info = {}
|
||||
|
||||
# Detect OpenAPI/Swagger
|
||||
openapi_info = self._detect_fastapi() or self._detect_swagger_nodejs()
|
||||
if openapi_info:
|
||||
docs_info.update(openapi_info)
|
||||
|
||||
# Detect GraphQL
|
||||
graphql_info = self._detect_graphql()
|
||||
if graphql_info:
|
||||
docs_info["graphql"] = graphql_info
|
||||
|
||||
if docs_info:
|
||||
self.analysis["api_documentation"] = docs_info
|
||||
|
||||
def _detect_fastapi(self) -> dict[str, Any] | None:
|
||||
"""Detect FastAPI auto-generated OpenAPI docs."""
|
||||
if self.analysis.get("framework") != "FastAPI":
|
||||
return None
|
||||
|
||||
return {
|
||||
"type": "openapi",
|
||||
"auto_generated": True,
|
||||
"docs_url": "/docs",
|
||||
"redoc_url": "/redoc",
|
||||
"openapi_url": "/openapi.json",
|
||||
}
|
||||
|
||||
def _detect_swagger_nodejs(self) -> dict[str, Any] | None:
|
||||
"""Detect Swagger for Node.js projects."""
|
||||
if not self._exists("package.json"):
|
||||
return None
|
||||
|
||||
pkg = self._read_json("package.json")
|
||||
if not pkg:
|
||||
return None
|
||||
|
||||
deps = {**pkg.get("dependencies", {}), **pkg.get("devDependencies", {})}
|
||||
if "swagger-ui-express" in deps or "swagger-jsdoc" in deps:
|
||||
return {
|
||||
"type": "openapi",
|
||||
"library": "swagger-ui-express",
|
||||
"docs_url": "/api-docs",
|
||||
}
|
||||
|
||||
return None
|
||||
|
||||
def _detect_graphql(self) -> dict[str, str] | None:
|
||||
"""Detect GraphQL API and playground."""
|
||||
if not self._exists("package.json"):
|
||||
return None
|
||||
|
||||
pkg = self._read_json("package.json")
|
||||
if not pkg:
|
||||
return None
|
||||
|
||||
deps = {**pkg.get("dependencies", {}), **pkg.get("devDependencies", {})}
|
||||
if "graphql" in deps or "apollo-server" in deps or "@apollo/server" in deps:
|
||||
return {
|
||||
"playground_url": "/graphql",
|
||||
"library": "apollo-server" if "apollo-server" in deps else "graphql",
|
||||
}
|
||||
|
||||
return None
|
||||
@@ -0,0 +1,141 @@
|
||||
"""
|
||||
Authentication Patterns Detector Module
|
||||
========================================
|
||||
|
||||
Detects authentication and authorization patterns:
|
||||
- JWT authentication
|
||||
- OAuth providers
|
||||
- Session-based authentication
|
||||
- API key authentication
|
||||
- User models
|
||||
- Auth middleware and decorators
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from ..base import BaseAnalyzer
|
||||
|
||||
|
||||
class AuthDetector(BaseAnalyzer):
|
||||
"""Detects authentication and authorization patterns."""
|
||||
|
||||
JWT_LIBS = ["python-jose", "pyjwt", "jsonwebtoken", "jose"]
|
||||
OAUTH_LIBS = ["authlib", "passport", "next-auth", "@auth/core", "oauth2"]
|
||||
SESSION_LIBS = ["flask-login", "express-session", "django.contrib.auth"]
|
||||
|
||||
USER_MODEL_FILES = [
|
||||
"models/user.py",
|
||||
"models/User.py",
|
||||
"app/models/user.py",
|
||||
"models/user.ts",
|
||||
"models/User.ts",
|
||||
"src/models/user.ts",
|
||||
]
|
||||
|
||||
def __init__(self, path: Path, analysis: dict[str, Any]):
|
||||
super().__init__(path)
|
||||
self.analysis = analysis
|
||||
|
||||
def detect(self) -> None:
|
||||
"""
|
||||
Detect authentication and authorization patterns.
|
||||
|
||||
Detects: JWT, OAuth, session-based, API keys, user models, protected routes.
|
||||
"""
|
||||
auth_info = {
|
||||
"strategies": [],
|
||||
"libraries": [],
|
||||
"user_model": None,
|
||||
"middleware": [],
|
||||
}
|
||||
|
||||
# Get all dependencies
|
||||
all_deps = self._get_all_dependencies()
|
||||
|
||||
# Detect auth strategies and libraries
|
||||
self._detect_jwt(all_deps, auth_info)
|
||||
self._detect_oauth(all_deps, auth_info)
|
||||
self._detect_session(all_deps, auth_info)
|
||||
|
||||
# Find user model
|
||||
auth_info["user_model"] = self._find_user_model()
|
||||
|
||||
# Detect auth middleware/decorators
|
||||
auth_info["middleware"] = self._find_auth_middleware()
|
||||
|
||||
# Remove duplicates from strategies
|
||||
auth_info["strategies"] = list(set(auth_info["strategies"]))
|
||||
|
||||
if auth_info["strategies"] or auth_info["libraries"]:
|
||||
self.analysis["auth"] = auth_info
|
||||
|
||||
def _get_all_dependencies(self) -> set[str]:
|
||||
"""Extract all dependencies from Python and Node.js projects."""
|
||||
all_deps = set()
|
||||
|
||||
if self._exists("requirements.txt"):
|
||||
content = self._read_file("requirements.txt")
|
||||
all_deps.update(re.findall(r"^([a-zA-Z0-9_-]+)", content, re.MULTILINE))
|
||||
|
||||
pkg = self._read_json("package.json")
|
||||
if pkg:
|
||||
all_deps.update(pkg.get("dependencies", {}).keys())
|
||||
|
||||
return all_deps
|
||||
|
||||
def _detect_jwt(self, all_deps: set[str], auth_info: dict[str, Any]) -> None:
|
||||
"""Detect JWT authentication libraries."""
|
||||
for lib in self.JWT_LIBS:
|
||||
if lib in all_deps:
|
||||
auth_info["strategies"].append("jwt")
|
||||
auth_info["libraries"].append(lib)
|
||||
break
|
||||
|
||||
def _detect_oauth(self, all_deps: set[str], auth_info: dict[str, Any]) -> None:
|
||||
"""Detect OAuth authentication libraries."""
|
||||
for lib in self.OAUTH_LIBS:
|
||||
if lib in all_deps:
|
||||
auth_info["strategies"].append("oauth")
|
||||
auth_info["libraries"].append(lib)
|
||||
break
|
||||
|
||||
def _detect_session(self, all_deps: set[str], auth_info: dict[str, Any]) -> None:
|
||||
"""Detect session-based authentication libraries."""
|
||||
for lib in self.SESSION_LIBS:
|
||||
if lib in all_deps:
|
||||
auth_info["strategies"].append("session")
|
||||
auth_info["libraries"].append(lib)
|
||||
break
|
||||
|
||||
def _find_user_model(self) -> str | None:
|
||||
"""Find the user model file."""
|
||||
for model_file in self.USER_MODEL_FILES:
|
||||
if self._exists(model_file):
|
||||
return model_file
|
||||
return None
|
||||
|
||||
def _find_auth_middleware(self) -> list[str]:
|
||||
"""Detect auth middleware and decorators from Python files."""
|
||||
# Limit to first 20 files for performance
|
||||
all_py_files = list(self.path.glob("**/*.py"))[:20]
|
||||
auth_decorators = set()
|
||||
|
||||
for py_file in all_py_files:
|
||||
try:
|
||||
content = py_file.read_text()
|
||||
# Find custom decorators
|
||||
if (
|
||||
"@require" in content
|
||||
or "@login_required" in content
|
||||
or "@authenticate" in content
|
||||
):
|
||||
decorators = re.findall(r"@(\w*(?:require|auth|login)\w*)", content)
|
||||
auth_decorators.update(decorators)
|
||||
except (OSError, UnicodeDecodeError):
|
||||
continue
|
||||
|
||||
return list(auth_decorators) if auth_decorators else []
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user