Documentation
¶
Index ¶
- Variables
- func ExtractWorkspaceFromNotebook(absPath, notebookRoot string, notebook *config.Notebook) string
- func ExtractWorkspaceNameFromNotebookPath(absPath, notebookRoot string) string
- func FindEcosystemRoot(startDir string) (string, error)
- func FindNotebookMarker(path string) string
- func FindNotebookRoot(path string) string
- func FindNotebookRootFromConfig(absPath string, cfg *config.Config) (string, *config.Notebook)
- func GenerateWorktreeGoWork(config *GoWorkspaceConfig, requiredModules []string) string
- func GetAccessHistoryPath(configDir string) string
- func IsNotebookRepo(path string) bool
- func IsZombieWorktree(path string) bool
- func IsZombieWorktreeCwd() bool
- func LoadAccessHistoryAsMap(configDir string) (map[string]time.Time, error)
- func Prepare(ctx context.Context, opts PrepareOptions, ...) (string, error)
- func SetupGoWorkspaceForWorktree(worktreePath string, gitRoot string) error
- func SetupSubmodules(ctx context.Context, worktreePath, branchName string, repos []string, ...) error
- func UpdateAccessHistory(configDir, workspacePath string) error
- type AccessHistory
- type ContentDirectory
- type DiscoveredWorkspace
- type DiscoveryResult
- type DiscoveryService
- type Ecosystem
- type GoWorkspaceConfig
- type NotebookLocator
- func (l *NotebookLocator) GetAllContentDirs(node *WorkspaceNode) ([]ContentDirectory, error)
- func (l *NotebookLocator) GetChatsDir(node *WorkspaceNode) (string, error)
- func (l *NotebookLocator) GetCompletedDir(node *WorkspaceNode) (string, error)
- func (l *NotebookLocator) GetDocgenDir(node *WorkspaceNode) (string, error)
- func (l *NotebookLocator) GetDocgenPromptsDir(node *WorkspaceNode) (string, error)
- func (l *NotebookLocator) GetGroupDir(node *WorkspaceNode, groupName string) (string, error)
- func (l *NotebookLocator) GetInProgressDir(node *WorkspaceNode) (string, error)
- func (l *NotebookLocator) GetNotesDir(node *WorkspaceNode, noteType string) (string, error)
- func (l *NotebookLocator) GetPlansDir(node *WorkspaceNode) (string, error)
- func (l *NotebookLocator) GetRecipesDir(node *WorkspaceNode) (string, error)
- func (l *NotebookLocator) GetSkillsDir(node *WorkspaceNode) (string, error)
- func (l *NotebookLocator) GetTemplatesDir(node *WorkspaceNode) (string, error)
- func (l *NotebookLocator) ScanForAllChats(provider *Provider) ([]ScannedDir, error)
- func (l *NotebookLocator) ScanForAllNotes(provider *Provider) ([]ScannedDir, error)
- func (l *NotebookLocator) ScanForAllPlans(provider *Provider) ([]ScannedDir, error)
- type PrepareOptions
- type Project
- type ProjectAccess
- type Provider
- func (p *Provider) All() []*WorkspaceNode
- func (p *Provider) Ecosystems() []*WorkspaceNode
- func (p *Provider) FindByName(name string) *WorkspaceNode
- func (p *Provider) FindByPath(path string) *WorkspaceNode
- func (p *Provider) FindByWorktree(baseProjectNode *WorkspaceNode, worktreeName string) *WorkspaceNode
- func (p *Provider) LocalWorkspaces() map[string]stringdeprecated
- func (p *Provider) LocalWorkspacesInEcosystem(ecosystemPath string) map[string]string
- type ScannedDir
- type WorkspaceInfo
- type WorkspaceKind
- type WorkspaceNode
- func BuildWorkspaceTree(nodes []*WorkspaceNode) []*WorkspaceNode
- func GetProjectByPath(path string) (*WorkspaceNode, error)
- func GetProjectFromNotebookPath(path string) (*WorkspaceNode, string, error)
- func GetProjects(logger *logrus.Logger) ([]*WorkspaceNode, error)
- func TransformToWorkspaceNodes(result *DiscoveryResult, cfg *config.Config) []*WorkspaceNode
- func (w *WorkspaceNode) GetDirectChildren(nodes []*WorkspaceNode) []*WorkspaceNode
- func (w *WorkspaceNode) GetGroupingKey() string
- func (w *WorkspaceNode) GetHierarchicalParent() string
- func (w *WorkspaceNode) GetWorktreeName() string
- func (p *WorkspaceNode) Identifier() string
- func (w *WorkspaceNode) IsChildOf(parentPath string) bool
- func (w *WorkspaceNode) IsEcosystem() bool
- func (w *WorkspaceNode) IsEcosystemChild() bool
- func (w *WorkspaceNode) IsProjectWorktreeChild() bool
- func (w *WorkspaceNode) IsWorktree() bool
- func (w *WorkspaceNode) Validate() error
- type WorkspaceTree
- type WorkspaceType
- type WorktreeInfo
Constants ¶
This section is empty.
Variables ¶
var IsZombieWorktreePath = IsZombieWorktree
IsZombieWorktreePath is an alias for IsZombieWorktree for clarity when checking arbitrary paths (not necessarily the current working directory).
Functions ¶
func ExtractWorkspaceFromNotebook ¶
ExtractWorkspaceFromNotebook extracts the workspace name from a path within a notebook using the notebook's configured templates.
func ExtractWorkspaceNameFromNotebookPath ¶
ExtractWorkspaceNameFromNotebookPath is the public interface for extracting workspace name from a notebook path. It uses config-based lookup when possible.
func FindEcosystemRoot ¶
FindEcosystemRoot searches upward from startDir to find a grove.yml containing a 'workspaces' key.
func FindNotebookMarker ¶
FindNotebookMarker walks up from path looking for notebook.yml marker. This is a fallback for notebooks not defined in config. Returns the directory containing the marker, or empty string if not found.
func FindNotebookRoot ¶
FindNotebookRoot is kept for backwards compatibility. Prefer FindNotebookRootFromConfig for config-aware lookup.
func FindNotebookRootFromConfig ¶
FindNotebookRootFromConfig checks if a path is under any configured notebook's root_dir. Returns the notebook root path and the notebook config if found.
func GenerateWorktreeGoWork ¶
func GenerateWorktreeGoWork(config *GoWorkspaceConfig, requiredModules []string) string
func GetAccessHistoryPath ¶
GetAccessHistoryPath returns the path to the access history file
func IsNotebookRepo ¶
IsNotebookRepo checks if a given path is a notebook repository by looking for a marker file.
func IsZombieWorktree ¶
IsZombieWorktree checks if the given path is inside a deleted git worktree. A worktree is considered "zombie" if it's inside a .grove-worktrees directory but the .git file (which links to the main repo) is missing.
This is used to prevent recreating files (like .grove/rules or logs) in deleted worktrees, which would cause "zombie" directories to reappear after cleanup.
This function uses the same detection logic as zombieAwareWriter in the logging package, ensuring consistent behavior across the grove ecosystem.
func IsZombieWorktreeCwd ¶
func IsZombieWorktreeCwd() bool
IsZombieWorktreeCwd checks if the current working directory is inside a zombie worktree.
func LoadAccessHistoryAsMap ¶
LoadAccessHistoryAsMap is a convenience function that returns a simple map of path -> lastAccessed time
func Prepare ¶
func Prepare(ctx context.Context, opts PrepareOptions, setupHandlers ...func(worktreePath, gitRoot string) error) (string, error)
Prepare creates or gets a fully configured worktree.
func SetupGoWorkspaceForWorktree ¶
SetupGoWorkspaceForWorktree checks if the current project uses Go workspaces and if so, creates an appropriate go.work file in the worktree.
func SetupSubmodules ¶
func SetupSubmodules(ctx context.Context, worktreePath, branchName string, repos []string, provider *Provider, setupHandlers ...func(worktreePath, gitRoot string) error) error
SetupSubmodules initializes submodules, creating linked worktrees where possible. It accepts a Provider containing pre-discovered workspaces to avoid redundant filesystem scans.
func UpdateAccessHistory ¶
UpdateAccessHistory is a convenience function that loads, updates, and saves access history
Types ¶
type AccessHistory ¶
type AccessHistory struct {
Projects map[string]*ProjectAccess `json:"projects"`
}
AccessHistory manages project access history
func LoadAccessHistory ¶
func LoadAccessHistory(configDir string) (*AccessHistory, error)
LoadAccessHistory loads the access history from disk
func (*AccessHistory) GetLastAccessed ¶
func (h *AccessHistory) GetLastAccessed(path string) *time.Time
GetLastAccessed returns the last accessed time for a project
func (*AccessHistory) RecordAccess ¶
func (h *AccessHistory) RecordAccess(path string)
RecordAccess records that a project was accessed
func (*AccessHistory) Save ¶
func (h *AccessHistory) Save(configDir string) error
Save saves the access history to disk
type ContentDirectory ¶
ContentDirectory represents a directory containing notebook content
type DiscoveredWorkspace ¶
type DiscoveredWorkspace struct {
Name string `json:"name"`
Path string `json:"path"`
Type WorkspaceType `json:"type"`
ParentProjectPath string `json:"parent_project_path"`
}
DiscoveredWorkspace represents a specific, checked-out instance of a Project.
type DiscoveryResult ¶
type DiscoveryResult struct {
Projects []Project `json:"projects"`
Ecosystems []Ecosystem `json:"ecosystems"`
NonGroveDirectories []string `json:"non_grove_directories,omitempty"`
}
DiscoveryResult is the comprehensive output of the DiscoveryService.
type DiscoveryService ¶
type DiscoveryService struct {
// contains filtered or unexported fields
}
DiscoveryService scans the filesystem to find and classify Grove entities.
func NewDiscoveryService ¶
func NewDiscoveryService(logger *logrus.Logger) *DiscoveryService
NewDiscoveryService creates a new discovery service.
func (*DiscoveryService) DiscoverAll ¶
func (s *DiscoveryService) DiscoverAll() (*DiscoveryResult, error)
DiscoverAll scans all configured 'groves' and returns a comprehensive result.
func (*DiscoveryService) WithConfigPath ¶
func (s *DiscoveryService) WithConfigPath(configPath string) *DiscoveryService
WithConfigPath returns a new DiscoveryService with a custom config path for testing. If configPath is set, it will be used instead of HOME directory when loading config.
type Ecosystem ¶
type Ecosystem struct {
Name string `json:"name"`
Path string `json:"path"`
Type string `json:"type"` // "Grove" or "User"
}
Ecosystem represents a top-level meta-repository.
type GoWorkspaceConfig ¶
type GoWorkspaceConfig struct {
RootGoWorkPath string
WorkspaceRoot string
GoVersion string
ModulePaths []string
}
func FindRootGoWorkspace ¶
func FindRootGoWorkspace(startPath string) (*GoWorkspaceConfig, error)
FindRootGoWorkspace searches for a go.work file by walking up the directory tree.
type NotebookLocator ¶
type NotebookLocator struct {
// contains filtered or unexported fields
}
NotebookLocator resolves paths for notes, plans, and chats based on configuration. It operates in two modes:
- Local Mode (default): Plans/chats are stored within the project directory (e.g., ./plans)
- Centralized Mode (opt-in): Plans/chats are stored in a centralized notebook directory
The mode is determined by whether notebook.root_dir is configured.
func NewNotebookLocator ¶
func NewNotebookLocator(cfg *config.Config) *NotebookLocator
NewNotebookLocator creates a new locator. It gracefully handles a nil config. It now stores the full config to support dynamic notebook resolution based on WorkspaceNode.NotebookName.
func (*NotebookLocator) GetAllContentDirs ¶
func (l *NotebookLocator) GetAllContentDirs(node *WorkspaceNode) ([]ContentDirectory, error)
GetAllContentDirs returns all directories that contain content for a workspace. This includes the notes directory (containing subdirs like current, learn, etc.), the plans directory, and the chats directory.
func (*NotebookLocator) GetChatsDir ¶
func (l *NotebookLocator) GetChatsDir(node *WorkspaceNode) (string, error)
GetChatsDir is analogous to GetPlansDir.
func (*NotebookLocator) GetCompletedDir ¶
func (l *NotebookLocator) GetCompletedDir(node *WorkspaceNode) (string, error)
GetCompletedDir is analogous to GetPlansDir but for completed notes.
func (*NotebookLocator) GetDocgenDir ¶
func (l *NotebookLocator) GetDocgenDir(node *WorkspaceNode) (string, error)
GetDocgenDir returns the absolute path to the root docgen directory for a given workspace node. This is the parent directory containing prompts/, docs/, images/, asciicasts/, videos/, and the docgen.config.yml file.
In Local Mode, it returns the docgen directory within the project's .notebook directory. In Centralized Mode, it uses the configured root_dir and path templates.
Callers can use this to construct paths to subdirectories:
- filepath.Join(docgenDir, "prompts") - prompt files
- filepath.Join(docgenDir, "docs") - generated documentation output
- filepath.Join(docgenDir, "docgen.config.yml") - configuration file
- filepath.Join(docgenDir, "images") - image assets
- filepath.Join(docgenDir, "asciicasts") - asciicast recordings
- filepath.Join(docgenDir, "videos") - video files
func (*NotebookLocator) GetDocgenPromptsDir ¶
func (l *NotebookLocator) GetDocgenPromptsDir(node *WorkspaceNode) (string, error)
GetDocgenPromptsDir returns the absolute path to the docgen prompts directory for a given workspace node. This is a convenience method equivalent to filepath.Join(GetDocgenDir(node), "prompts").
func (*NotebookLocator) GetGroupDir ¶
func (l *NotebookLocator) GetGroupDir(node *WorkspaceNode, groupName string) (string, error)
GetGroupDir resolves the absolute path for any group directory (e.g., "inbox", "plans", "plans/my-feature"). This is the centralized method for resolving all note-related directory paths.
func (*NotebookLocator) GetInProgressDir ¶
func (l *NotebookLocator) GetInProgressDir(node *WorkspaceNode) (string, error)
GetInProgressDir is analogous to GetPlansDir but for in_progress notes.
func (*NotebookLocator) GetNotesDir ¶
func (l *NotebookLocator) GetNotesDir(node *WorkspaceNode, noteType string) (string, error)
GetNotesDir returns the absolute path to the notes directory for a given workspace node and note type. In Local Mode, it returns the notes directory within the project (e.g., ./notes/{noteType}). In Centralized Mode, it uses the configured root_dir and path templates.
func (*NotebookLocator) GetPlansDir ¶
func (l *NotebookLocator) GetPlansDir(node *WorkspaceNode) (string, error)
GetPlansDir returns the absolute path to the plans directory for a given workspace node. In Local Mode, it returns the plans directory within the project (using GetGroupingKey to handle worktrees). In Centralized Mode, it uses the configured root_dir and path templates.
func (*NotebookLocator) GetRecipesDir ¶
func (l *NotebookLocator) GetRecipesDir(node *WorkspaceNode) (string, error)
GetRecipesDir is analogous to GetPlansDir but for recipes.
func (*NotebookLocator) GetSkillsDir ¶
func (l *NotebookLocator) GetSkillsDir(node *WorkspaceNode) (string, error)
GetSkillsDir returns the absolute path to the skills directory for a given workspace node. Skills are stored alongside plans and chats in the notebook structure.
func (*NotebookLocator) GetTemplatesDir ¶
func (l *NotebookLocator) GetTemplatesDir(node *WorkspaceNode) (string, error)
GetTemplatesDir is analogous to GetPlansDir but for templates.
func (*NotebookLocator) ScanForAllChats ¶
func (l *NotebookLocator) ScanForAllChats(provider *Provider) ([]ScannedDir, error)
ScanForAllChats discovers all chat directories across all known workspaces. It returns a list of ScannedDir structs, linking each directory to its owner. This method properly handles both Local Mode and Centralized Mode.
func (*NotebookLocator) ScanForAllNotes ¶
func (l *NotebookLocator) ScanForAllNotes(provider *Provider) ([]ScannedDir, error)
ScanForAllNotes discovers all notes directories across all known workspaces. It returns a list of ScannedDir structs, linking each directory to its owner.
func (*NotebookLocator) ScanForAllPlans ¶
func (l *NotebookLocator) ScanForAllPlans(provider *Provider) ([]ScannedDir, error)
ScanForAllPlans discovers all plan directories across all known workspaces. It returns a list of ScannedDir structs, linking each directory to its owner. This method properly handles both Local Mode and Centralized Mode.
type PrepareOptions ¶
type PrepareOptions struct {
GitRoot string
WorktreeName string
BranchName string
PlanName string // Optional, for state management in grove-flow
Repos []string // For ecosystem worktrees
}
PrepareOptions holds configuration for preparing a workspace.
type Project ¶
type Project struct {
Name string `json:"name"`
Path string `json:"path"`
Type string `json:"type"`
ModulePath string `json:"module_path,omitempty"`
ParentEcosystemPath string `json:"parent_ecosystem_path,omitempty"`
Workspaces []DiscoveredWorkspace `json:"workspaces"`
// Cloned repository-specific fields (populated by discovery for cx repo managed repos)
Version string `json:"version,omitempty"`
Commit string `json:"commit,omitempty"`
AuditStatus string `json:"audit_status,omitempty"`
ReportPath string `json:"report_path,omitempty"`
RepoURL string `json:"repo_url,omitempty"`
RepoShorthand string `json:"repo_shorthand,omitempty"`
}
Project represents a single software repository.
type ProjectAccess ¶
type ProjectAccess struct {
Path string `json:"path"`
LastAccessed time.Time `json:"last_accessed"`
AccessCount int `json:"access_count"`
}
ProjectAccess tracks when a project was last accessed
type Provider ¶
type Provider struct {
// contains filtered or unexported fields
}
Provider acts as a read-only, in-memory store for a snapshot of discovered workspaces. It provides fast lookups and access to the workspace hierarchy.
The caching strategy is explicit and consumer-controlled. The Provider itself does not perform discovery; it is initialized with the results of a DiscoveryService.DiscoverAll() call. Short-lived applications should create a Provider once at startup. Long-running services are responsible for deciding when to refresh the data by performing a new discovery and creating a new Provider instance.
func NewProvider ¶
func NewProvider(result *DiscoveryResult) *Provider
NewProvider creates a new workspace provider from a discovery result. It transforms the raw discovery data into the final WorkspaceNode representation and builds internal indexes for fast lookups.
func (*Provider) All ¶
func (p *Provider) All() []*WorkspaceNode
All returns a slice of all discovered WorkspaceNodes.
func (*Provider) Ecosystems ¶
func (p *Provider) Ecosystems() []*WorkspaceNode
Ecosystems returns all nodes that are ecosystem roots.
func (*Provider) FindByName ¶
func (p *Provider) FindByName(name string) *WorkspaceNode
FindByName returns the first workspace node that matches the given name. It returns nil if no matching workspace is found.
func (*Provider) FindByPath ¶
func (p *Provider) FindByPath(path string) *WorkspaceNode
FindByPath returns the WorkspaceNode for a given absolute path. It performs a fast lookup using an internal map.
func (*Provider) FindByWorktree ¶
func (p *Provider) FindByWorktree(baseProjectNode *WorkspaceNode, worktreeName string) *WorkspaceNode
FindByWorktree finds a workspace node for a worktree within an ecosystem. It is used to resolve a job's `worktree` field to the correct workspace node. - baseProjectNode: The base project node (can be ecosystem root or subproject). - worktreeName: The name of the worktree.
This handles two cases: 1. Ecosystem worktrees: /ecosystem/.grove-worktrees/<name>/<subproject> 2. Subproject worktrees: /ecosystem/<subproject>/.grove-worktrees/<name>
func (*Provider) LocalWorkspaces
deprecated
LocalWorkspaces returns a map of workspace names to their paths, suitable for use in submodule setup operations.
WARNING: This method may silently overwrite entries if multiple workspaces have the same name (e.g., "grove-core" in different ecosystems). For ecosystem-aware operations, use LocalWorkspacesInEcosystem instead.
Deprecated: Use LocalWorkspacesInEcosystem for more reliable lookups that avoid name collision issues.
func (*Provider) LocalWorkspacesInEcosystem ¶
LocalWorkspacesInEcosystem returns a map of workspace names to their paths, filtered to only include workspaces within the specified ecosystem. This avoids name collisions between workspaces in different ecosystems.
type ScannedDir ¶
type ScannedDir struct {
Path string
Owner *WorkspaceNode
}
ScannedDir represents a directory found by the locator, linking it to the WorkspaceNode that owns it.
type WorkspaceInfo ¶
type WorkspaceInfo struct {
Name string `json:"name"`
Path string `json:"path"`
Worktrees []WorktreeInfo `json:"worktrees"`
}
WorkspaceInfo represents a workspace from grove ws list --json
type WorkspaceKind ¶
type WorkspaceKind string
WorkspaceKind provides an unambiguous classification for a discovered workspace entity.
const ( // KindStandaloneProject: A standard project with a grove.yml, not within an Ecosystem. // Diagram: // /path/to/my-project/ (grove.yml, .git/) KindStandaloneProject WorkspaceKind = "StandaloneProject" // KindStandaloneProjectWorktree: A git worktree of a StandaloneProject. // Diagram: // /path/to/my-project/ // ├─ .git/ // └─ .grove-worktrees/ // └─ feature-branch/ (grove.yml, .git file) <-- This KindStandaloneProjectWorktree WorkspaceKind = "StandaloneProjectWorktree" // KindEcosystemRoot: The main repository of an ecosystem (has grove.yml with a 'workspaces' key). // Diagram: // /path/to/my-ecosystem/ (grove.yml with 'workspaces', .git/) <-- This KindEcosystemRoot WorkspaceKind = "EcosystemRoot" // KindEcosystemWorktree: A git worktree of an EcosystemRoot. It also functions as an ecosystem. // Diagram: // /path/to/my-ecosystem/ // ├─ .git/ // └─ .grove-worktrees/ // └─ eco-feature/ (grove.yml with 'workspaces', .git file) <-- This KindEcosystemWorktree WorkspaceKind = "EcosystemWorktree" // KindEcosystemSubProject: A project (e.g., submodule) located directly inside an EcosystemRoot. // Diagram: // /path/to/my-ecosystem/ (EcosystemRoot) // └─ sub-project/ (grove.yml, .git/) <-- This KindEcosystemSubProject WorkspaceKind = "EcosystemSubProject" // KindEcosystemSubProjectWorktree: A git worktree of an EcosystemSubProject. // Diagram: // /path/to/my-ecosystem/ (EcosystemRoot) // └─ sub-project/ // ├─ .git/ // └─ .grove-worktrees/ // └─ sub-feature/ (grove.yml, .git file) <-- This KindEcosystemSubProjectWorktree WorkspaceKind = "EcosystemSubProjectWorktree" // KindEcosystemWorktreeSubProject: A project located inside an EcosystemWorktree. // This occurs when a submodule is initialized with `git submodule update` instead of as a linked worktree. // Diagram: // /path/to/my-ecosystem/.grove-worktrees/eco-feature/ (EcosystemWorktree) // └─ sub-project/ (grove.yml, .git/) <-- This KindEcosystemWorktreeSubProject WorkspaceKind = "EcosystemWorktreeSubProject" // KindEcosystemWorktreeSubProjectWorktree: A git worktree of an EcosystemWorktreeSubProject. // This is the preferred "linked development" state for a sub-project in an ecosystem worktree. // Diagram: // /path/to/my-ecosystem/.grove-worktrees/eco-feature/ (EcosystemWorktree) // └─ sub-project/ (grove.yml, .git file) <-- This KindEcosystemWorktreeSubProjectWorktree WorkspaceKind = "EcosystemWorktreeSubProjectWorktree" // KindNonGroveRepo: A directory with a .git folder but no grove.yml. // Diagram: // /path/to/other-repo/ (.git/ only, no grove.yml) <-- This KindNonGroveRepo WorkspaceKind = "NonGroveRepo" )
type WorkspaceNode ¶
type WorkspaceNode struct {
Name string `json:"name"`
Path string `json:"path"`
Kind WorkspaceKind `json:"kind"` // The single source of truth for the entity's type.
// ParentProjectPath is the path to the repository that manages this worktree.
// It is set ONLY for kinds that are worktrees (e.g., StandaloneProjectWorktree,
// EcosystemWorktree, EcosystemSubProjectWorktree, EcosystemWorktreeSubProjectWorktree).
ParentProjectPath string `json:"parent_project_path,omitempty"`
// ParentEcosystemPath is the path to the immediate parent that provides ecosystem context.
// This could be an EcosystemRoot or an EcosystemWorktree.
// It is set for ALL kinds that exist within an ecosystem context.
ParentEcosystemPath string `json:"parent_ecosystem_path,omitempty"`
// RootEcosystemPath is the path to the top-level EcosystemRoot for this node.
// This allows quick grouping by the ultimate parent ecosystem and facilitates
// traversing to the root of the hierarchy. It is set for all nodes within an ecosystem.
RootEcosystemPath string `json:"root_ecosystem_path,omitempty"`
// NotebookName is the name of the notebook configuration (from notebooks.definitions)
// that this workspace should use. This is resolved during discovery based on which
// grove the workspace belongs to.
NotebookName string `json:"notebook_name,omitempty"`
// Presentation fields for TUI rendering (pre-calculated for performance)
TreePrefix string `json:"-"` // Pre-calculated tree indentation and connectors (e.g., " ├─ ")
Depth int `json:"-"` // Cached depth in the hierarchy
// Cloned repository-specific fields (populated by discovery)
Version string `json:"version,omitempty"`
Commit string `json:"commit,omitempty"`
AuditStatus string `json:"audit_status,omitempty"`
ReportPath string `json:"report_path,omitempty"`
RepoURL string `json:"repo_url,omitempty"`
RepoShorthand string `json:"repo_shorthand,omitempty"`
}
WorkspaceNode is the enriched display model for workspace entities. It represents a flattened, view-friendly node suitable for UIs, with explicit parent-child relationships that form a hierarchical tree structure.
func BuildWorkspaceTree ¶
func BuildWorkspaceTree(nodes []*WorkspaceNode) []*WorkspaceNode
BuildWorkspaceTree takes a flat slice of WorkspaceNodes and pre-calculates presentation data for TUI rendering. It organizes nodes hierarchically and populates the TreePrefix and Depth fields.
The TreePrefix field contains the indentation and tree connectors (e.g., " ├─ ", " └─ ") making it trivial for views to render the tree structure without complex logic.
func GetProjectByPath ¶
func GetProjectByPath(path string) (*WorkspaceNode, error)
GetProjectByPath finds a workspace by path using an efficient upward traversal. It starts from the given path and walks up the directory tree looking for workspace markers. Once a project root is found, it generates the WorkspaceNode for that project and returns the most specific node that contains the original path.
This approach is significantly faster than a full discovery scan (typically <10ms vs 100-500ms) and uses the same centralized classification logic to ensure consistency.
func GetProjectFromNotebookPath ¶
func GetProjectFromNotebookPath(path string) (*WorkspaceNode, string, error)
GetProjectFromNotebookPath finds the project associated with a notebook path. Given a path inside a notebook (e.g., /notebooks/nb/workspaces/zooboo2/plans/...), it extracts the workspace name and finds the corresponding project.
Returns:
- *WorkspaceNode: the project node if found (nil if not found)
- string: the notebook root path (empty if not in a notebook)
- error: only for unexpected errors
func GetProjects ¶
func GetProjects(logger *logrus.Logger) ([]*WorkspaceNode, error)
GetProjects performs discovery and transformation in a single call, returning a flat list of WorkspaceNodes ready for consumption with pre-calculated tree prefixes for rendering.
func TransformToWorkspaceNodes ¶
func TransformToWorkspaceNodes(result *DiscoveryResult, cfg *config.Config) []*WorkspaceNode
TransformToWorkspaceNodes converts a hierarchical DiscoveryResult into a flat list of WorkspaceNode items suitable for display in UIs.
func (*WorkspaceNode) GetDirectChildren ¶
func (w *WorkspaceNode) GetDirectChildren(nodes []*WorkspaceNode) []*WorkspaceNode
GetDirectChildren returns all nodes from the given list that are direct children of this node. This properly handles both ecosystem children and worktree children.
func (*WorkspaceNode) GetGroupingKey ¶
func (w *WorkspaceNode) GetGroupingKey() string
GetGroupingKey returns the path that should be used for grouping related items together. For project worktrees, this returns the parent project path so worktrees are grouped with their parent. For all other nodes (including ecosystem children), this returns the node's own path.
This is useful for filtering and sorting logic that needs to group worktrees with their parent repos while treating ecosystem children as independent entities.
Example:
grove-ecosystem/grove-tmux (EcosystemSubProject) -> returns "grove-tmux" my-project/.grove-worktrees/feature (StandaloneProjectWorktree) -> returns "my-project"
func (*WorkspaceNode) GetHierarchicalParent ¶
func (w *WorkspaceNode) GetHierarchicalParent() string
GetHierarchicalParent returns the logical parent path for hierarchical display. This considers both ParentProjectPath (for worktrees) and ParentEcosystemPath (for sub-projects).
func (*WorkspaceNode) GetWorktreeName ¶
func (w *WorkspaceNode) GetWorktreeName() string
GetWorktreeName returns the worktree name if this is a worktree node, otherwise empty string. For ecosystem worktrees, it extracts the name from the ParentEcosystemPath. For standalone project worktrees, it extracts from ParentProjectPath.
func (*WorkspaceNode) Identifier ¶
func (p *WorkspaceNode) Identifier() string
Identifier generates a unique, sanitized identifier for a project, suitable for use as a tmux session name or other unique identifier. It creates namespaced identifiers for projects within ecosystem worktrees.
func (*WorkspaceNode) IsChildOf ¶
func (w *WorkspaceNode) IsChildOf(parentPath string) bool
IsChildOf returns true if this node is a direct hierarchical child of the given parent path. This handles both worktree children (via ParentProjectPath) and ecosystem children (via ParentEcosystemPath).
func (*WorkspaceNode) IsEcosystem ¶
func (w *WorkspaceNode) IsEcosystem() bool
IsEcosystem returns true if this node represents an ecosystem (root or worktree)
func (*WorkspaceNode) IsEcosystemChild ¶
func (w *WorkspaceNode) IsEcosystemChild() bool
IsEcosystemChild returns true if this is a child within an ecosystem hierarchy (not a worktree child). Useful for distinguishing ecosystem subprojects from worktrees.
func (*WorkspaceNode) IsProjectWorktreeChild ¶
func (w *WorkspaceNode) IsProjectWorktreeChild() bool
IsProjectWorktreeChild returns true if this is a worktree child of a project (not an ecosystem child). Useful for grouping worktrees under their parent projects.
func (*WorkspaceNode) IsWorktree ¶
func (w *WorkspaceNode) IsWorktree() bool
IsWorktree returns true if this node represents a worktree. Note: This includes EcosystemWorktree, which is BOTH a worktree AND a container/ecosystem. For filtering logic that needs to distinguish "leaf worktrees" from containers, use IsProjectWorktreeChild().
Example hierarchy:
grove-ecosystem/ (EcosystemRoot)
├─ grove-tmux/ (EcosystemSubProject) - IsWorktree()=false, IsEcosystemChild()=true
└─ .grove-worktrees/
└─ my-branch/ (EcosystemWorktree) - IsWorktree()=true, IsEcosystem()=true
└─ grove-hooks/ (EcosystemWorktreeSubProject) - IsWorktree()=false, IsEcosystemChild()=true
func (*WorkspaceNode) Validate ¶
func (w *WorkspaceNode) Validate() error
Validate checks the internal consistency of this WorkspaceNode and returns an error if the node's fields don't match the expected invariants for its Kind.
This is useful for catching bugs where node relationships are incorrectly set up.
type WorkspaceTree ¶
type WorkspaceTree struct {
Node *WorkspaceNode `json:"node"`
Children []*WorkspaceTree `json:"children"`
}
WorkspaceTree represents a node in the hierarchical workspace tree. It's designed for consumers that need to render or traverse the full hierarchy.
func BuildTree ¶
func BuildTree(nodes []*WorkspaceNode) []*WorkspaceTree
BuildTree constructs a hierarchical tree from a flat slice of WorkspaceNodes. This is the recommended way for UIs to consume the workspace hierarchy.
func GetWorkspaceTree ¶
func GetWorkspaceTree(logger *logrus.Logger) ([]*WorkspaceTree, error)
GetWorkspaceTree performs discovery and returns a fully formed workspace hierarchy. This is the recommended function for UIs that need to render a tree.
type WorkspaceType ¶
type WorkspaceType string
WorkspaceType defines whether a workspace is the main checkout or a git worktree.
const ( WorkspaceTypePrimary WorkspaceType = "Primary" WorkspaceTypeWorktree WorkspaceType = "Worktree" )
type WorktreeInfo ¶
type WorktreeInfo struct {
Path string `json:"path"`
Branch string `json:"branch"`
IsMain bool `json:"is_main"`
}
WorktreeInfo represents a worktree within a workspace