From 56944b61c9b28ad2a54df771b3b4493bfa3e01a6 Mon Sep 17 00:00:00 2001 From: John Lambert Date: Wed, 13 May 2026 16:22:28 -0400 Subject: [PATCH 1/3] workspace cleanup --- .serena/project.yml | 98 +++++++++---------- .vscode/tasks.json | 6 +- scripts/Setup-WorktreeColor.ps1 | 90 ++++++++++++++++-- scripts/tests/Test-Setup-WorktreeColor.ps1 | 104 +++++++++++++++++++++ 4 files changed, 231 insertions(+), 67 deletions(-) create mode 100644 scripts/tests/Test-Setup-WorktreeColor.ps1 diff --git a/.serena/project.yml b/.serena/project.yml index 6928be28df..0ee2a20e7e 100644 --- a/.serena/project.yml +++ b/.serena/project.yml @@ -1,21 +1,26 @@ # list of languages for which language servers are started; choose from: -# al bash clojure cpp csharp -# csharp_omnisharp dart elixir elm erlang -# fortran fsharp go groovy haskell -# java julia kotlin lua markdown -# matlab nix pascal perl php -# php_phpactor powershell python python_jedi r -# rego ruby ruby_solargraph rust scala -# swift terraform toml typescript typescript_vts -# vue yaml zig +# al angular ansible bash clojure +# cpp cpp_ccls crystal csharp csharp_omnisharp +# dart elixir elm erlang fortran +# fsharp go groovy haskell haxe +# hlsl html java json julia +# kotlin lean4 lua luau markdown +# matlab msl nix ocaml pascal +# perl php php_phpactor powershell python +# python_jedi python_ty r rego ruby +# ruby_solargraph rust scala scss solidity +# swift systemverilog terraform toml typescript +# typescript_vts vue yaml zig # (This list may be outdated. For the current list, see values of Language enum here: # https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py # For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.) # Note: # - For C, use cpp # - For JavaScript, use typescript +# - For Angular projects, use angular (subsumes typescript+html; requires `npm install` in the project root) +# - For SCSS / Sass / plain CSS, use scss (some-sass-language-server handles all three) # - For Free Pascal/Lazarus, use pascal # Special requirements: # Some languages require additional setup/installations. @@ -64,49 +69,12 @@ initial_prompt: | # list of tool names to exclude. # This extends the existing exclusions (e.g. from the global configuration) -# -# Below is the complete list of tools for convenience. -# To make sure you have the latest list of tools, and to view their descriptions, -# execute `uv run scripts/print_tool_overview.py`. -# -# * `activate_project`: Activates a project by name. -# * `check_onboarding_performed`: Checks whether project onboarding was already performed. -# * `create_text_file`: Creates/overwrites a file in the project directory. -# * `delete_lines`: Deletes a range of lines within a file. -# * `delete_memory`: Deletes a memory from Serena's project-specific memory store. -# * `execute_shell_command`: Executes a shell command. -# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced. -# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type). -# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type). -# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes. -# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file. -# * `initial_instructions`: Gets the initial instructions for the current project. -# Should only be used in settings where the system prompt cannot be set, -# e.g. in clients you have no control over, like Claude Desktop. -# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol. -# * `insert_at_line`: Inserts content at a given line in a file. -# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol. -# * `list_dir`: Lists files and directories in the given directory (optionally with recursion). -# * `list_memories`: Lists memories in Serena's project-specific memory store. -# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building). -# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context). -# * `read_file`: Reads a file within the project directory. -# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store. -# * `remove_project`: Removes a project from the Serena configuration. -# * `replace_lines`: Replaces a range of lines within a file with new content. -# * `replace_symbol_body`: Replaces the full definition of a symbol. -# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen. -# * `search_for_pattern`: Performs a search for a pattern in the project. -# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase. -# * `switch_modes`: Activates modes by providing a list of their names -# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information. -# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task. -# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed. -# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store. +# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html excluded_tools: [] # list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default). # This extends the existing inclusions (e.g. from the global configuration). +# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html included_optional_tools: [] # list of mode names to that are always to be included in the set of active modes @@ -117,21 +85,21 @@ included_optional_tools: [] # Set this to a list of mode names to always include the respective modes for this project. base_modes: -# list of mode names that are to be activated by default. -# The full set of modes to be activated is base_modes + default_modes. -# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply. +# list of mode names that are to be activated by default, overriding the setting in the global configuration. +# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes. +# If the setting is undefined/empty, the default_modes from the global configuration (serena_config.yml) apply. # Otherwise, this overrides the setting from the global configuration (serena_config.yml). +# Therefore, you can set this to [] if you do not want the default modes defined in the global config to apply +# for this project. # This setting can, in turn, be overridden by CLI parameters (--mode). +# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes default_modes: # fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools. # This cannot be combined with non-empty excluded_tools or included_optional_tools. +# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html fixed_tools: [] -# project_name is intentionally omitted in shared config so Serena falls back to the -# worktree folder name and multiple FieldWorks worktrees stay distinct. -# If you need an explicit local override, put it in .serena/project.local.yml. - # time budget (seconds) per tool call for the retrieval of additional symbol information # such as docstrings or parameter information. # This overrides the corresponding setting in the global configuration; see the documentation there. @@ -150,7 +118,7 @@ language_backend: # This does not affect Serena's own files (e.g. memories and configuration files), which always use native line endings. line_ending: -# list of regex patterns which, when matched, mark a memory entry as read-only. +# list of regex patterns which, when matched, mark a memory entry as read‑only. # Extends the list from the global configuration, merging the two lists. read_only_memory_patterns: [] @@ -167,3 +135,21 @@ ignored_memory_patterns: [] # Have a look at the docstring of the constructors of the LS implementations within solidlsp (e.g., for C# or PHP) to see which options are available. # No documentation on options means no options are available. ls_specific_settings: {} + +# list of mode names to be activated additionally for this project, e.g. ["query-projects"] +# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes. +# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes +added_modes: + +# list of additional workspace folder paths for cross-package reference support (e.g. in monorepos). +# Paths can be absolute or relative to the project root. +# Each folder is registered as an LSP workspace folder, enabling language servers to discover +# symbols and references across package boundaries. +# Currently supported for: TypeScript. +# Example: +# additional_workspace_folders: +# - ../sibling-package +# - ../shared-lib +additional_workspace_folders: [] +# the name by which the project can be referenced within Serena +project_name: FieldWorks diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 8e14bcddb2..0db108dcdb 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -107,7 +107,7 @@ "label": "Setup: Colorize Worktree", "type": "shell", "command": "./scripts/Setup-WorktreeColor.ps1 -VSCodeWorkspaceFile \"${workspaceFile}\"", - "detail": "Sets a unique window color from a deterministic palette slot", + "detail": "Sets a unique window color from a rotating palette slot", "runOptions": { "runOn": "folderOpen" }, @@ -435,6 +435,10 @@ "args": ["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command"] } }, + "presentation": { + "reveal": "never", + "panel": "shared" + }, "problemMatcher": [] }, { diff --git a/scripts/Setup-WorktreeColor.ps1 b/scripts/Setup-WorktreeColor.ps1 index 0ec48c4acf..a2260644a1 100644 --- a/scripts/Setup-WorktreeColor.ps1 +++ b/scripts/Setup-WorktreeColor.ps1 @@ -2,7 +2,7 @@ .SYNOPSIS Sets a unique window color for the current VS Code workspace/worktree. .DESCRIPTION - Chooses a color from a fixed deterministic palette using the lowest free slot. + Chooses a color from a fixed deterministic palette using the next free slot in rotation. Uses the VS Code workspace (.code-workspace) paradigm for worktree overrides: - Base workspace configuration is embedded in this script @@ -491,6 +491,58 @@ function ConvertTo-ColorIndex([object]$value, [int]$paletteLength) { return $idx } +function Get-GitCommonDir([string]$anyRepoRoot) { + $common = @(& git -C $anyRepoRoot rev-parse --git-common-dir 2>$null) + if ($LASTEXITCODE -ne 0 -or $common.Length -eq 0 -or [string]::IsNullOrWhiteSpace($common[0])) { + throw "Failed to resolve git common dir for $anyRepoRoot" + } + + $commonPath = $common[0].Trim() + if (-not [System.IO.Path]::IsPathRooted($commonPath)) { + $commonPath = Join-Path $anyRepoRoot $commonPath + } + + try { + return ([System.IO.Path]::GetFullPath($commonPath)) + } + catch { + return $commonPath + } +} + +function Get-WorktreeColorStatePath([string]$mainRepoRoot) { + return (Join-Path (Get-GitCommonDir $mainRepoRoot) "fieldworks-worktree-color-state.json") +} + +function Read-WorktreeColorState([string]$mainRepoRoot, [int]$paletteLength) { + $statePath = Get-WorktreeColorStatePath $mainRepoRoot + if (-not (Test-Path $statePath -PathType Leaf)) { + return $null + } + + try { + $state = ConvertFrom-JsoncFile $statePath + if ($null -eq $state -or -not $state.PSObject.Properties["lastAssignedColorIndex"]) { + return $null + } + + return (ConvertTo-ColorIndex -value $state.lastAssignedColorIndex -paletteLength $paletteLength) + } + catch { + Write-Warning "Failed to read worktree color state from $statePath" + return $null + } +} + +function Write-WorktreeColorState([string]$mainRepoRoot, [int]$colorIndex) { + $statePath = Get-WorktreeColorStatePath $mainRepoRoot + $state = [pscustomobject]@{ + lastAssignedColorIndex = $colorIndex + } + + $state | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath $statePath -Encoding UTF8 +} + function Get-ColorIndexFromSettings($settings, [string[]]$palette) { if ($null -eq $settings) { return $null @@ -570,7 +622,10 @@ function Select-WorktreeColorIndex( if ($requestedColorIndex.Value -lt 0 -or $requestedColorIndex.Value -ge $palette.Length) { throw "ColorIndex '$($requestedColorIndex.Value)' is out of range. Valid range: 0-$($palette.Length - 1)." } - return $requestedColorIndex.Value + return [pscustomobject]@{ + ColorIndex = $requestedColorIndex.Value + ShouldPersistLastAssigned = $true + } } $targetWorkspacePath = Get-WorktreeWorkspacePath $targetRoot @@ -580,7 +635,10 @@ function Select-WorktreeColorIndex( if ($null -ne $existingWorkspace -and $existingWorkspace.PSObject.Properties["settings"]) { $existingIdx = Get-ColorIndexFromSettings -settings $existingWorkspace.settings -palette $palette if ($null -ne $existingIdx) { - return $existingIdx + return [pscustomobject]@{ + ColorIndex = $existingIdx + ShouldPersistLastAssigned = $false + } } } } @@ -590,15 +648,23 @@ function Select-WorktreeColorIndex( } $used = Get-UsedColorIndices -mainRepoRoot $mainRepoRoot -targetRoot $targetRoot -palette $palette - for ($i = 0; $i -lt $palette.Length; $i++) { - if (-not $used.ContainsKey($i)) { - return $i + $lastAssigned = Read-WorktreeColorState -mainRepoRoot $mainRepoRoot -paletteLength $palette.Length + $startIndex = if ($null -ne $lastAssigned) { ($lastAssigned + 1) % $palette.Length } else { 0 } + + for ($offset = 0; $offset -lt $palette.Length; $offset++) { + $candidate = ($startIndex + $offset) % $palette.Length + if (-not $used.ContainsKey($candidate)) { + return [pscustomobject]@{ + ColorIndex = $candidate + ShouldPersistLastAssigned = $true + } } } - $md5 = [System.Security.Cryptography.MD5]::Create() - $hashBytes = $md5.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($targetRoot)) - return ($hashBytes[0] % $palette.Length) + return [pscustomobject]@{ + ColorIndex = $startIndex + ShouldPersistLastAssigned = $true + } } function Write-WorktreeWorkspaceFile( @@ -610,7 +676,8 @@ function Write-WorktreeWorkspaceFile( $legacyWorkspacePath = Get-WorktreeWorkspaceLegacyPath $targetRoot $palette = Get-WorktreeColorPalette - $resolvedColorIndex = Select-WorktreeColorIndex -mainRepoRoot $repoRoot -targetRoot $targetRoot -requestedColorIndex $requestedColorIndex -palette $palette + $colorSelection = Select-WorktreeColorIndex -mainRepoRoot $repoRoot -targetRoot $targetRoot -requestedColorIndex $requestedColorIndex -palette $palette + $resolvedColorIndex = $colorSelection.ColorIndex $colorHex = $palette[$resolvedColorIndex] $foreground = Get-ForegroundColorsForBackground -backgroundHex $colorHex $textColor = $foreground.TextColor @@ -729,6 +796,9 @@ function Write-WorktreeWorkspaceFile( $worktreeWorkspace | Add-Member -MemberType NoteProperty -Name "settings" -Value $settings $worktreeWorkspace | ConvertTo-Json -Depth 10 | Set-Content -Encoding UTF8 $worktreeWorkspacePath + if ($colorSelection.ShouldPersistLastAssigned) { + Write-WorktreeColorState -mainRepoRoot $repoRoot -colorIndex $resolvedColorIndex + } Write-Host "Wrote worktree-local workspace file: $worktreeWorkspacePath" # Best-effort cleanup of legacy workspace filename. diff --git a/scripts/tests/Test-Setup-WorktreeColor.ps1 b/scripts/tests/Test-Setup-WorktreeColor.ps1 new file mode 100644 index 0000000000..27d3726d6b --- /dev/null +++ b/scripts/tests/Test-Setup-WorktreeColor.ps1 @@ -0,0 +1,104 @@ +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +function Assert-Equal { + param( + [Parameter(Mandatory = $true)]$Expected, + [Parameter(Mandatory = $true)]$Actual, + [Parameter(Mandatory = $true)][string]$Message + ) + + if ($Expected -ne $Actual) { + throw "$Message Expected '$Expected', got '$Actual'." + } +} + +function Assert-True { + param( + [Parameter(Mandatory = $true)][bool]$Condition, + [Parameter(Mandatory = $true)][string]$Message + ) + + if (-not $Condition) { + throw $Message + } +} + +function Invoke-Git { + param( + [Parameter(Mandatory = $true)][string]$RepositoryPath, + [Parameter(Mandatory = $true)][string[]]$Arguments + ) + + $output = @(& git -C $RepositoryPath @Arguments) + if ($LASTEXITCODE -ne 0) { + $joinedArgs = [string]::Join(" ", $Arguments) + $joinedOutput = [string]::Join([Environment]::NewLine, $output) + throw "git $joinedArgs failed in $RepositoryPath.$([Environment]::NewLine)$joinedOutput" + } + + return $output +} + +function Get-WorkspaceColorIndex { + param([Parameter(Mandatory = $true)][string]$WorkspacePath) + + $raw = Get-Content -LiteralPath $WorkspacePath -Raw -Encoding UTF8 + $json = ConvertFrom-Json -InputObject $raw + return [int]$json.settings."fieldworks.worktreeColorIndex" +} + +$repoScriptPath = Join-Path (Split-Path $PSScriptRoot -Parent) "Setup-WorktreeColor.ps1" +$tempRoot = Join-Path $env:TEMP ("fw-worktree-color-test-" + [Guid]::NewGuid().ToString("N")) +$repoDir = Join-Path $tempRoot "FieldWorks" +$scriptsDir = Join-Path $repoDir "scripts" +$worktreesDir = Join-Path $tempRoot "worktrees" + +try { + $null = New-Item -ItemType Directory -Path $scriptsDir -Force + $null = New-Item -ItemType Directory -Path $worktreesDir -Force + Copy-Item -LiteralPath $repoScriptPath -Destination $scriptsDir + + Invoke-Git -RepositoryPath $repoDir -Arguments @("init") + Invoke-Git -RepositoryPath $repoDir -Arguments @("config", "user.email", "test@example.com") + Invoke-Git -RepositoryPath $repoDir -Arguments @("config", "user.name", "Test User") + + Set-Content -LiteralPath (Join-Path $repoDir "README.md") -Value "init" -Encoding UTF8 + Invoke-Git -RepositoryPath $repoDir -Arguments @("add", "README.md") + Invoke-Git -RepositoryPath $repoDir -Arguments @("commit", "-m", "init") + + $setupScript = Join-Path $scriptsDir "Setup-WorktreeColor.ps1" + $alphaDir = Join-Path $worktreesDir "alpha" + $betaDir = Join-Path $worktreesDir "beta" + + Invoke-Git -RepositoryPath $repoDir -Arguments @("worktree", "add", $alphaDir, "-b", "alpha", "HEAD") + Invoke-Git -RepositoryPath $repoDir -Arguments @("worktree", "add", $betaDir, "-b", "beta", "HEAD") + + & $setupScript -Action Apply -WorktreePath $alphaDir + & $setupScript -Action Apply -WorktreePath $betaDir + + $alphaWorkspace = Join-Path $alphaDir "alpha.code-workspace" + $betaWorkspace = Join-Path $betaDir "beta.code-workspace" + $alphaIndex = Get-WorkspaceColorIndex -WorkspacePath $alphaWorkspace + $betaFirstIndex = Get-WorkspaceColorIndex -WorkspacePath $betaWorkspace + + Assert-Equal -Expected 0 -Actual $alphaIndex -Message "First worktree should use the first palette slot." + Assert-Equal -Expected 1 -Actual $betaFirstIndex -Message "Second worktree should use the next palette slot." + + Invoke-Git -RepositoryPath $repoDir -Arguments @("worktree", "remove", $betaDir, "--force") + Invoke-Git -RepositoryPath $repoDir -Arguments @("worktree", "add", $betaDir, "beta") + + & $setupScript -Action Apply -WorktreePath $betaDir + + $betaSecondIndex = Get-WorkspaceColorIndex -WorkspacePath $betaWorkspace + + Assert-Equal -Expected 2 -Actual $betaSecondIndex -Message "Recreated worktree should advance to the next palette slot." + Assert-True -Condition ($betaSecondIndex -ne $betaFirstIndex) -Message "Recreated worktree should not reuse its previous color slot." + + Write-Output "PASS: worktree color rotation advances after delete and recreate." +} +finally { + if (Test-Path $tempRoot) { + Remove-Item -Path $tempRoot -Recurse -Force + } +} \ No newline at end of file From 2c41b9692384e77cab2393abe88d98ae2fbe4915 Mon Sep 17 00:00:00 2001 From: John Lambert Date: Wed, 13 May 2026 16:50:13 -0400 Subject: [PATCH 2/3] Stabilize Serena worktree setup --- .serena/project.yml | 5 +++- Build/Agent/Setup-Serena.ps1 | 2 +- Docs/mcp.md | 21 ++++++++++----- scripts/Setup-WorktreeColor.ps1 | 28 ++++++++++++++++++++ scripts/tests/Test-Setup-WorktreeColor.ps1 | 30 +++++++++++++++++++++- 5 files changed, 76 insertions(+), 10 deletions(-) diff --git a/.serena/project.yml b/.serena/project.yml index 0ee2a20e7e..7f26173b28 100644 --- a/.serena/project.yml +++ b/.serena/project.yml @@ -151,5 +151,8 @@ added_modes: # - ../sibling-package # - ../shared-lib additional_workspace_folders: [] -# the name by which the project can be referenced within Serena +# Keep a stable shared project_name here because current Serena rewrites incomplete +# project.yml files and auto-adds this field if it is missing. +# If you need a per-worktree name, override project_name in ignored +# .serena/project.local.yml instead of editing the shared file. project_name: FieldWorks diff --git a/Build/Agent/Setup-Serena.ps1 b/Build/Agent/Setup-Serena.ps1 index ae2909ce56..10bb936807 100644 --- a/Build/Agent/Setup-Serena.ps1 +++ b/Build/Agent/Setup-Serena.ps1 @@ -195,7 +195,7 @@ if (Test-Path $serenaConfig) { Write-Status "No languages found in .serena/project.yml" -Status "WARN" } - if ($configContent -match '^\s*project_name:\s*' -and $projectName) { + if ($configContent -match '(?m)^\s*project_name:\s*' -and $projectName) { Write-Status "Configured project name: $projectName" } else { diff --git a/Docs/mcp.md b/Docs/mcp.md index 96b990406f..8a13b297b3 100644 --- a/Docs/mcp.md +++ b/Docs/mcp.md @@ -62,9 +62,13 @@ VS Code's user-level MCP config (`%APPDATA%\Code\User\mcp.json`) may have a Sere server that auto-discovers projects by scanning for `.serena` folders. Combined with workspace-level `mcp.json`, this creates duplicate project registrations. -This repo's shared `.serena/project.yml` intentionally leaves `project_name` unset so -Serena falls back to the worktree folder name by default. That keeps worktrees easier -to distinguish when multiple registrations exist. +Current Serena releases normalize incomplete `project.yml` files and will write +`project_name` back into the shared config if it is missing. To avoid that churn, +this repo keeps a stable shared `project_name` in `.serena/project.yml`. + +For worktree-specific naming, use the ignored `.serena/project.local.yml` file in +each worktree to override `project_name` locally. That keeps Serena names distinct +without causing repeated edits to the versioned config. ### Solution **Use only workspace-level Serena, and keep project names local to the worktree** @@ -77,9 +81,12 @@ code "$env:APPDATA\Code\User\mcp.json" Remove the `"oraios/serena"` entry. The workspace `.vscode/mcp.json` provides Serena with explicit project targeting. -If you want a friendlier project name than the folder name, set it in -`.serena/project.local.yml` for that worktree instead of committing a shared -`project_name` in `.serena/project.yml`. +If you want a worktree-specific Serena project name, set it in +`.serena/project.local.yml` for that worktree. For example: + +```yaml +project_name: 010-advanced-entry-view +``` ## Best-practice profile for this repo @@ -93,7 +100,7 @@ If you want a friendlier project name than the folder name, set it in - Open one VS Code window per worktree; let that window use its own workspace `.vscode/mcp.json`. - Keep only one Serena server definition active (workspace-level), and remove user-level Serena. - Keep Serena pinned to the active workspace via `--project ${workspaceFolder}` (already configured). -- If you want an explicit Serena project name, set it in `.serena/project.local.yml`; do not commit a shared `project_name` in `.serena/project.yml`. +- If you want a worktree-specific Serena project name, set it in `.serena/project.local.yml`; keep the shared `.serena/project.yml` complete so Serena does not rewrite it. - After switching worktrees, run **MCP: Reset Cached Tools** if tool lists or capabilities look stale. - No extra GitHub MCP worktree settings are required beyond OAuth sign-in. diff --git a/scripts/Setup-WorktreeColor.ps1 b/scripts/Setup-WorktreeColor.ps1 index a2260644a1..442c86944f 100644 --- a/scripts/Setup-WorktreeColor.ps1 +++ b/scripts/Setup-WorktreeColor.ps1 @@ -7,6 +7,7 @@ Uses the VS Code workspace (.code-workspace) paradigm for worktree overrides: - Base workspace configuration is embedded in this script - Writes a worktree-local workspace file: .code-workspace (git-ignored) + - Creates .serena/project.local.yml for worktrees when the shared Serena config exists - If in a Git Worktree: Applies colors to Title Bar, Status Bar, and Activity Bar. - If in Main Repo: Removes these color customizations. @@ -118,6 +119,32 @@ function Get-WorktreeWorkspacePath([string]$rootPath) { return (Join-Path $rootPath ("{0}.code-workspace" -f $fileStem)) } +function Ensure-SerenaLocalProjectFile([string]$targetRoot) { + $serenaDir = Join-Path $targetRoot ".serena" + if (-not (Test-Path $serenaDir -PathType Container)) { + return + } + + $sharedProjectPath = Join-Path $serenaDir "project.yml" + if (-not (Test-Path $sharedProjectPath -PathType Leaf)) { + return + } + + $localProjectPath = Join-Path $serenaDir "project.local.yml" + if (Test-Path $localProjectPath -PathType Leaf) { + return + } + + $projectName = Get-GitBranchName $targetRoot + $escapedProjectName = $projectName -replace "'", "''" + $contents = @( + "# Auto-generated per-worktree Serena override. Edit locally if needed." + ("project_name: '{0}'" -f $escapedProjectName) + ) + $contents | Set-Content -LiteralPath $localProjectPath -Encoding UTF8 + Write-Host "Created Serena local project override: $localProjectPath" +} + function Get-GitWorktrees([string]$anyRepoRoot) { # git worktree list --porcelain format example: # worktree C:/path @@ -799,6 +826,7 @@ function Write-WorktreeWorkspaceFile( if ($colorSelection.ShouldPersistLastAssigned) { Write-WorktreeColorState -mainRepoRoot $repoRoot -colorIndex $resolvedColorIndex } + Ensure-SerenaLocalProjectFile -targetRoot $targetRoot Write-Host "Wrote worktree-local workspace file: $worktreeWorkspacePath" # Best-effort cleanup of legacy workspace filename. diff --git a/scripts/tests/Test-Setup-WorktreeColor.ps1 b/scripts/tests/Test-Setup-WorktreeColor.ps1 index 27d3726d6b..d512efd9bd 100644 --- a/scripts/tests/Test-Setup-WorktreeColor.ps1 +++ b/scripts/tests/Test-Setup-WorktreeColor.ps1 @@ -48,14 +48,27 @@ function Get-WorkspaceColorIndex { return [int]$json.settings."fieldworks.worktreeColorIndex" } +function Get-SerenaLocalProjectName { + param([Parameter(Mandatory = $true)][string]$ProjectLocalPath) + + $raw = Get-Content -LiteralPath $ProjectLocalPath -Raw -Encoding UTF8 + if ($raw -match '(?m)^\s*project_name:\s*(.+?)\s*$') { + return $matches[1].Trim().Trim('"', "'") + } + + throw "No project_name found in $ProjectLocalPath" +} + $repoScriptPath = Join-Path (Split-Path $PSScriptRoot -Parent) "Setup-WorktreeColor.ps1" $tempRoot = Join-Path $env:TEMP ("fw-worktree-color-test-" + [Guid]::NewGuid().ToString("N")) $repoDir = Join-Path $tempRoot "FieldWorks" $scriptsDir = Join-Path $repoDir "scripts" +$serenaDir = Join-Path $repoDir ".serena" $worktreesDir = Join-Path $tempRoot "worktrees" try { $null = New-Item -ItemType Directory -Path $scriptsDir -Force + $null = New-Item -ItemType Directory -Path $serenaDir -Force $null = New-Item -ItemType Directory -Path $worktreesDir -Force Copy-Item -LiteralPath $repoScriptPath -Destination $scriptsDir @@ -64,7 +77,10 @@ try { Invoke-Git -RepositoryPath $repoDir -Arguments @("config", "user.name", "Test User") Set-Content -LiteralPath (Join-Path $repoDir "README.md") -Value "init" -Encoding UTF8 + Set-Content -LiteralPath (Join-Path $serenaDir "project.yml") -Value "project_name: FieldWorks`nlanguages:`n- csharp_omnisharp`n- cpp`n" -Encoding UTF8 + Set-Content -LiteralPath (Join-Path $serenaDir ".gitignore") -Value "/project.local.yml`n" -Encoding UTF8 Invoke-Git -RepositoryPath $repoDir -Arguments @("add", "README.md") + Invoke-Git -RepositoryPath $repoDir -Arguments @("add", ".serena/project.yml", ".serena/.gitignore") Invoke-Git -RepositoryPath $repoDir -Arguments @("commit", "-m", "init") $setupScript = Join-Path $scriptsDir "Setup-WorktreeColor.ps1" @@ -79,11 +95,21 @@ try { $alphaWorkspace = Join-Path $alphaDir "alpha.code-workspace" $betaWorkspace = Join-Path $betaDir "beta.code-workspace" + $alphaSerenaLocal = Join-Path $alphaDir ".serena\project.local.yml" + $betaSerenaLocal = Join-Path $betaDir ".serena\project.local.yml" $alphaIndex = Get-WorkspaceColorIndex -WorkspacePath $alphaWorkspace $betaFirstIndex = Get-WorkspaceColorIndex -WorkspacePath $betaWorkspace Assert-Equal -Expected 0 -Actual $alphaIndex -Message "First worktree should use the first palette slot." Assert-Equal -Expected 1 -Actual $betaFirstIndex -Message "Second worktree should use the next palette slot." + Assert-True -Condition (Test-Path $alphaSerenaLocal -PathType Leaf) -Message "Alpha worktree should get a Serena local override file." + Assert-True -Condition (Test-Path $betaSerenaLocal -PathType Leaf) -Message "Beta worktree should get a Serena local override file." + Assert-Equal -Expected "alpha" -Actual (Get-SerenaLocalProjectName -ProjectLocalPath $alphaSerenaLocal) -Message "Alpha worktree should set the Serena local project name from the worktree branch." + Assert-Equal -Expected "beta" -Actual (Get-SerenaLocalProjectName -ProjectLocalPath $betaSerenaLocal) -Message "Beta worktree should set the Serena local project name from the worktree branch." + + Set-Content -LiteralPath $alphaSerenaLocal -Value "project_name: custom-alpha`n" -Encoding UTF8 + & $setupScript -Action Apply -WorktreePath $alphaDir + Assert-Equal -Expected "custom-alpha" -Actual (Get-SerenaLocalProjectName -ProjectLocalPath $alphaSerenaLocal) -Message "Existing Serena local overrides should not be overwritten." Invoke-Git -RepositoryPath $repoDir -Arguments @("worktree", "remove", $betaDir, "--force") Invoke-Git -RepositoryPath $repoDir -Arguments @("worktree", "add", $betaDir, "beta") @@ -94,8 +120,10 @@ try { Assert-Equal -Expected 2 -Actual $betaSecondIndex -Message "Recreated worktree should advance to the next palette slot." Assert-True -Condition ($betaSecondIndex -ne $betaFirstIndex) -Message "Recreated worktree should not reuse its previous color slot." + Assert-True -Condition (Test-Path $betaSerenaLocal -PathType Leaf) -Message "Recreated worktree should recreate the Serena local override file." + Assert-Equal -Expected "beta" -Actual (Get-SerenaLocalProjectName -ProjectLocalPath $betaSerenaLocal) -Message "Recreated worktree should recreate the Serena local project name." - Write-Output "PASS: worktree color rotation advances after delete and recreate." + Write-Output "PASS: worktree color rotation and Serena local override creation behave as expected." } finally { if (Test-Path $tempRoot) { From f043ce3d5f703cea482b6fa0002610c84f3d65be Mon Sep 17 00:00:00 2001 From: Jason Naylor Date: Thu, 14 May 2026 09:35:19 -0700 Subject: [PATCH 3/3] use ascii only text in code files Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .serena/project.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.serena/project.yml b/.serena/project.yml index 7f26173b28..ea234977ca 100644 --- a/.serena/project.yml +++ b/.serena/project.yml @@ -118,7 +118,7 @@ language_backend: # This does not affect Serena's own files (e.g. memories and configuration files), which always use native line endings. line_ending: -# list of regex patterns which, when matched, mark a memory entry as read‑only. +# list of regex patterns which, when matched, mark a memory entry as read-only. # Extends the list from the global configuration, merging the two lists. read_only_memory_patterns: []