mirror of
https://github.com/go-vikunja/vikunja.git
synced 2026-02-01 14:44:05 +00:00
chore(dev): add prepare worktree command to mage
This commit is contained in:
@@ -1,82 +0,0 @@
|
|||||||
---
|
|
||||||
name: prepare-workspace-for-plan
|
|
||||||
description: Use when you have a plan file ready and need to create an isolated git worktree for implementation - creates worktree in parent directory following project conventions and moves the plan file
|
|
||||||
---
|
|
||||||
|
|
||||||
# Prepare Workspace for Plan
|
|
||||||
|
|
||||||
Use this skill when you have created or refined a plan and need to set up an isolated workspace for implementation.
|
|
||||||
|
|
||||||
## When to Use
|
|
||||||
|
|
||||||
- After creating/finalizing a plan in the `plans/` directory
|
|
||||||
- Before starting implementation of a multi-phase plan
|
|
||||||
- When you need an isolated branch for a feature or fix
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
- A plan file exists in the current workspace's `plans/` directory
|
|
||||||
- You are in a git repository that supports worktrees
|
|
||||||
- The parent directory is the standard location for worktrees (e.g., `/path/to/vikunja/`)
|
|
||||||
|
|
||||||
## Steps
|
|
||||||
|
|
||||||
### 1. Determine Workspace Name
|
|
||||||
|
|
||||||
Choose a name following the project convention:
|
|
||||||
- `fix-<description>` for bug fixes
|
|
||||||
- `feat-<description>` for new features
|
|
||||||
|
|
||||||
The name should be kebab-case and descriptive but concise.
|
|
||||||
|
|
||||||
### 2. Create the Git Worktree
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# From the current workspace (e.g., main/)
|
|
||||||
git worktree add ../<workspace-name> -b <branch-name>
|
|
||||||
```
|
|
||||||
|
|
||||||
The branch name should match the workspace name.
|
|
||||||
|
|
||||||
### 3. Create Plans Directory and Move Plan
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p ../<workspace-name>/plans
|
|
||||||
mv plans/<plan-file>.md ../<workspace-name>/plans/
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Verify Structure
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ls -la ../<workspace-name>/plans/
|
|
||||||
```
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Create worktree for position healing fix
|
|
||||||
git worktree add ../fix-position-healing -b fix-position-healing
|
|
||||||
|
|
||||||
# Move the plan
|
|
||||||
mkdir -p ../fix-position-healing/plans
|
|
||||||
mv plans/positioning-fixes-detection.md ../fix-position-healing/plans/
|
|
||||||
```
|
|
||||||
|
|
||||||
## Result
|
|
||||||
|
|
||||||
After completion, you'll have:
|
|
||||||
```
|
|
||||||
parent-directory/
|
|
||||||
├── main/ # Original workspace
|
|
||||||
├── <new-workspace>/ # New worktree
|
|
||||||
│ └── plans/
|
|
||||||
│ └── <plan-file>.md # Your plan
|
|
||||||
└── ... # Other existing worktrees
|
|
||||||
```
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
|
|
||||||
- The new worktree shares git history with main but has its own working directory
|
|
||||||
- Changes in the new worktree won't affect main until merged
|
|
||||||
- Plans are not committed to git (see `.gitignore`)
|
|
||||||
- Remember to switch to the new workspace directory to begin implementation
|
|
||||||
43
AGENTS.md
43
AGENTS.md
@@ -11,13 +11,49 @@ The project consists of:
|
|||||||
- `desktop/` – Electron wrapper application
|
- `desktop/` – Electron wrapper application
|
||||||
- `docs/` – Documentation website
|
- `docs/` – Documentation website
|
||||||
|
|
||||||
## Plans
|
## Plans and Worktrees
|
||||||
|
|
||||||
When the user asks you to create a plan to fix or implement something:
|
When the user asks you to create a plan to fix or implement something:
|
||||||
|
|
||||||
- ALWAYS write that plan to the plans/ directory on the root of the repo.
|
- ALWAYS write that plan to the plans/ directory on the root of the repo.
|
||||||
- NEVER commit plans to git
|
- NEVER commit plans to git
|
||||||
- Give the plan a descriptive name
|
- Give the plan a descriptive name using kebab-case (e.g., `fix-position-healing.md`, `feat-new-feature.md`)
|
||||||
|
|
||||||
|
### Preparing a Worktree for Implementation
|
||||||
|
|
||||||
|
When the user tells you to prepare a worktree for a plan, use the mage command to set up an isolated workspace:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mage dev:prepare-worktree <name> <plan-path>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Arguments:**
|
||||||
|
- `<name>` - Required. Becomes both the folder name and branch name. Use conventions like `fix-<description>` for bug fixes or `feat-<description>` for new features.
|
||||||
|
- `<plan-path>` - Required. Path to a plan file (relative to repo root) that will be copied to the new worktree's `plans/` directory. Pass `""` to skip copying a plan.
|
||||||
|
|
||||||
|
This will initialize a new worktree in the parent directory and copy some files over.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
# Create worktree for a bug fix with a plan
|
||||||
|
mage dev:prepare-worktree fix-position-healing plans/fix-position-healing.md
|
||||||
|
|
||||||
|
# Create worktree for a new feature without a plan
|
||||||
|
mage dev:prepare-worktree feat-dark-mode ""
|
||||||
|
```
|
||||||
|
|
||||||
|
**Result:**
|
||||||
|
```
|
||||||
|
parent-directory/
|
||||||
|
├── main/ # Original workspace
|
||||||
|
├── fix-position-healing/ # New worktree
|
||||||
|
│ ├── config.yml # With updated rootpath
|
||||||
|
│ └── plans/
|
||||||
|
│ └── fix-position-healing.md
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
After creation, tell the user where they can find the new worktree.
|
||||||
|
|
||||||
## Development Commands
|
## Development Commands
|
||||||
|
|
||||||
@@ -37,8 +73,9 @@ When the user asks you to create a plan to fix or implement something:
|
|||||||
-Development helpers under the `dev` namespace:
|
-Development helpers under the `dev` namespace:
|
||||||
- **Migration**: `mage dev:make-migration <StructName>` - Creates new database migration. If you omit `<StructName>`, the command will prompt for it.
|
- **Migration**: `mage dev:make-migration <StructName>` - Creates new database migration. If you omit `<StructName>`, the command will prompt for it.
|
||||||
- **Event**: `mage dev:make-event` - Create an event type
|
- **Event**: `mage dev:make-event` - Create an event type
|
||||||
- **Listener**: `mage dev:make-listener` - Create an event listener
|
- **Listener**: `mage dev:make-listener` - Create an event listener
|
||||||
- **Notification**: `mage dev:make-notification` - Create a notification skeleton
|
- **Notification**: `mage dev:make-notification` - Create a notification skeleton
|
||||||
|
- **Prepare Worktree**: `mage dev:prepare-worktree <name> <plan-path>` - Creates a new git worktree in `../` with the given name as folder and branch. Copies a plan file if provided (pass `""` to skip). Copies `config.yml` with updated rootpath and initializes the frontend.
|
||||||
|
|
||||||
### Frontend (Vue.js)
|
### Frontend (Vue.js)
|
||||||
Navigate to `frontend/` directory:
|
Navigate to `frontend/` directory:
|
||||||
|
|||||||
127
magefile.go
127
magefile.go
@@ -72,6 +72,7 @@ var (
|
|||||||
"dev:make-event": Dev.MakeEvent,
|
"dev:make-event": Dev.MakeEvent,
|
||||||
"dev:make-listener": Dev.MakeListener,
|
"dev:make-listener": Dev.MakeListener,
|
||||||
"dev:make-notification": Dev.MakeNotification,
|
"dev:make-notification": Dev.MakeNotification,
|
||||||
|
"dev:prepare-worktree": Dev.PrepareWorktree,
|
||||||
"plugins:build": Plugins.Build,
|
"plugins:build": Plugins.Build,
|
||||||
"lint": Check.Golangci,
|
"lint": Check.Golangci,
|
||||||
"lint:fix": Check.GolangciFix,
|
"lint:fix": Check.GolangciFix,
|
||||||
@@ -1394,6 +1395,132 @@ func (Generate) ConfigYAML(commented bool) {
|
|||||||
generateConfigYAMLFromJSON(DefaultConfigYAMLSamplePath, commented)
|
generateConfigYAMLFromJSON(DefaultConfigYAMLSamplePath, commented)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PrepareWorktree creates a new git worktree for development.
|
||||||
|
// The first argument is the name, which becomes both the folder name and branch name.
|
||||||
|
// The second argument is a path to a plan file that will be copied to the new worktree (pass "" to skip).
|
||||||
|
// The worktree is created in the parent directory (../).
|
||||||
|
// It also copies the current config.yml with an updated rootpath, and initializes the frontend.
|
||||||
|
func (Dev) PrepareWorktree(name string, planPath string) error {
|
||||||
|
if name == "" {
|
||||||
|
return fmt.Errorf("name is required: mage dev:prepare-worktree <name> <plan-path>")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the parent directory path
|
||||||
|
parentDir := filepath.Dir(RootPath)
|
||||||
|
worktreePath := filepath.Join(parentDir, name)
|
||||||
|
|
||||||
|
fmt.Printf("Creating worktree at %s with branch %s...\n", worktreePath, name)
|
||||||
|
|
||||||
|
// Create the git worktree
|
||||||
|
cmd := exec.Command("git", "worktree", "add", worktreePath, "-b", name)
|
||||||
|
cmd.Dir = RootPath
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("failed to create worktree: %w", err)
|
||||||
|
}
|
||||||
|
printSuccess("Worktree created successfully!")
|
||||||
|
|
||||||
|
// Copy and modify config.yml
|
||||||
|
configSrc := filepath.Join(RootPath, "config.yml")
|
||||||
|
configDst := filepath.Join(worktreePath, "config.yml")
|
||||||
|
|
||||||
|
if _, err := os.Stat(configSrc); err == nil {
|
||||||
|
configContent, err := os.ReadFile(configSrc)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to read config.yml: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace the rootpath value
|
||||||
|
re := regexp.MustCompile(`(?m)^(\s*rootpath:\s*)"[^"]*"`)
|
||||||
|
newConfig := re.ReplaceAllString(string(configContent), `${1}"`+worktreePath+`"`)
|
||||||
|
|
||||||
|
// Also handle unquoted rootpath values
|
||||||
|
re2 := regexp.MustCompile(`(?m)^(\s*rootpath:\s*)(/[^\s\n]+)`)
|
||||||
|
newConfig = re2.ReplaceAllString(newConfig, `${1}"`+worktreePath+`"`)
|
||||||
|
|
||||||
|
if err := os.WriteFile(configDst, []byte(newConfig), 0644); err != nil {
|
||||||
|
return fmt.Errorf("failed to write config.yml: %w", err)
|
||||||
|
}
|
||||||
|
printSuccess("Config copied with updated rootpath!")
|
||||||
|
} else {
|
||||||
|
fmt.Println("Warning: config.yml not found, skipping config copy")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy .claude/settings.local.json if it exists
|
||||||
|
claudeSettingsSrc := filepath.Join(RootPath, ".claude", "settings.local.json")
|
||||||
|
if _, err := os.Stat(claudeSettingsSrc); err == nil {
|
||||||
|
claudeDir := filepath.Join(worktreePath, ".claude")
|
||||||
|
if err := os.MkdirAll(claudeDir, 0755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create .claude directory: %w", err)
|
||||||
|
}
|
||||||
|
claudeSettingsDst := filepath.Join(claudeDir, "settings.local.json")
|
||||||
|
if err := copyFile(claudeSettingsSrc, claudeSettingsDst); err != nil {
|
||||||
|
return fmt.Errorf("failed to copy .claude/settings.local.json: %w", err)
|
||||||
|
}
|
||||||
|
printSuccess("Claude settings copied!")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy plan file if provided
|
||||||
|
if planPath != "" {
|
||||||
|
planPath = strings.TrimSpace(planPath)
|
||||||
|
if planPath != "" {
|
||||||
|
// Create plans directory in the new worktree
|
||||||
|
plansDir := filepath.Join(worktreePath, "plans")
|
||||||
|
if err := os.MkdirAll(plansDir, 0755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create plans directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine source path (relative to RootPath or absolute)
|
||||||
|
srcPlanPath := planPath
|
||||||
|
if !filepath.IsAbs(planPath) {
|
||||||
|
srcPlanPath = filepath.Join(RootPath, planPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(srcPlanPath); err != nil {
|
||||||
|
return fmt.Errorf("plan file not found: %s", srcPlanPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
dstPlanPath := filepath.Join(plansDir, filepath.Base(planPath))
|
||||||
|
if err := copyFile(srcPlanPath, dstPlanPath); err != nil {
|
||||||
|
return fmt.Errorf("failed to copy plan file: %w", err)
|
||||||
|
}
|
||||||
|
printSuccess("Plan file copied to %s!", dstPlanPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize frontend
|
||||||
|
fmt.Println("Initializing frontend...")
|
||||||
|
frontendDir := filepath.Join(worktreePath, "frontend")
|
||||||
|
|
||||||
|
// Run pnpm install
|
||||||
|
pnpmCmd := exec.Command("pnpm", "i")
|
||||||
|
pnpmCmd.Dir = frontendDir
|
||||||
|
pnpmCmd.Stdout = os.Stdout
|
||||||
|
pnpmCmd.Stderr = os.Stderr
|
||||||
|
if err := pnpmCmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("failed to run pnpm install: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run patch-sass-embedded (shell alias from devenv)
|
||||||
|
patchCmd := exec.Command("bash", "-ic", "patch-sass-embedded")
|
||||||
|
patchCmd.Dir = frontendDir
|
||||||
|
patchCmd.Stdout = os.Stdout
|
||||||
|
patchCmd.Stderr = os.Stderr
|
||||||
|
if err := patchCmd.Run(); err != nil {
|
||||||
|
// patch-sass-embedded might not be critical, just warn
|
||||||
|
fmt.Printf("Warning: patch-sass-embedded failed: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
printSuccess("Frontend initialized!")
|
||||||
|
printSuccess("\nWorktree ready at: %s", worktreePath)
|
||||||
|
printSuccess("Branch: %s", name)
|
||||||
|
fmt.Println("\nTo start working:")
|
||||||
|
fmt.Printf(" cd %s\n", worktreePath)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type Plugins mg.Namespace
|
type Plugins mg.Namespace
|
||||||
|
|
||||||
// Build compiles a Go plugin at the provided path.
|
// Build compiles a Go plugin at the provided path.
|
||||||
|
|||||||
Reference in New Issue
Block a user