Workspace / Project Detection¶
- Authors
- Matt Cockayne
- Date
- 31 March 2026
- Status
- DRAFT
Overview¶
Many CLI tools are scoped to "the current project" โ like git scopes to the repository root, or go scopes to the module root. Tool authors repeatedly implement "walk up from CWD and find a marker file" logic.
This spec adds a small pkg/workspace utility that resolves the project root by searching for configurable marker files. It's a helper, not a framework feature โ no integration with Props or lifecycle management.
Design¶
API¶
// Workspace represents a detected project boundary.
type Workspace struct {
Root string // absolute path to the project root
Marker string // the marker file that was found (e.g. ".gtb/manifest.yaml")
}
// Detect walks up from startDir looking for any of the given marker files.
// Returns the first match. Returns ErrNotFound if no marker is found
// before reaching the filesystem root.
func Detect(fs afero.Fs, startDir string, markers ...string) (*Workspace, error)
// DetectFromCWD is a convenience that calls Detect with os.Getwd().
func DetectFromCWD(fs afero.Fs, markers ...string) (*Workspace, error)
Default Markers¶
var DefaultMarkers = []string{
".gtb/manifest.yaml", // GTB-generated project
"go.mod", // Go module root
".git", // Git repository root
}
Behaviour¶
- Start at
startDir - Check if any marker file/directory exists at the current level
- If found, return the
WorkspacewithRootset to the current directory - Move to the parent directory and repeat
- If the filesystem root is reached without finding a marker, return
ErrNotFound
Markers are checked in order โ the first match wins. This means .gtb/manifest.yaml takes precedence over go.mod when using DefaultMarkers.
Usage Example¶
ws, err := workspace.DetectFromCWD(props.FS, workspace.DefaultMarkers...)
if err != nil {
return errors.Wrap(err, "not inside a project")
}
fmt.Println("Project root:", ws.Root)
fmt.Println("Detected via:", ws.Marker)
Resolved Questions¶
- Max depth: Yes โ
Detectaccepts a max depth with a default of 100. This prevents runaway scanning on deeply nested paths or symlink loops. - Glob patterns: No โ markers are exact filenames/directory names only. Tool authors needing globs can implement their own marker check.