Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Version](https://img.shields.io/badge/Go-1.25%2B-00ADD8?logo=go)](https://go.dev
- [Commit Conventions](#commit-conventions)
- [Testing Requirements](#testing-requirements)
- [Advanced Topics](#advanced-topics)
- [Multi-Repo Discovery](#multi-repo-discovery)
- [Spec-Driven Development](#spec-driven-development)
- [Delta Specifications](#delta-specifications)
- [Validation Rules](#validation-rules)
Expand Down Expand Up @@ -1028,6 +1029,79 @@ go fmt ./...

## Advanced Topics

### Multi-Repo Discovery

Spectr supports mono-repo setups with nested git repositories, each with their
own `spectr/` directory.

#### Discovery Behavior

When you run any Spectr command, it automatically walks up from your current
working directory to find all `spectr/` directories:

- **Git isolation**: Discovery stops at `.git` boundaries - each git repository
is isolated
- **Aggregated results**: Commands like `list`, `validate`, `view` aggregate
results from all discovered roots
- **Root prefix**: In multi-root scenarios, items are prefixed with their
relative path: `[../project] add-feature`
- **Single-root compatibility**: When only one root is found, output is
identical to previous behavior (no prefixes)

**Example directory structure:**

```text
mono-repo/
├── .git/
├── spectr/ # Root repo's spectr
│ └── changes/
├── packages/
│ └── auth/
│ ├── .git/ # Nested git repo
│ └── spectr/ # Auth package's spectr
│ └── changes/
└── services/
└── api/
├── .git/ # Another nested git repo
└── spectr/ # API service's spectr
└── changes/
```text

Running `spectr list` from `mono-repo/` shows changes from all three roots.

#### SPECTR_ROOT Environment Variable

Override automatic discovery by setting `SPECTR_ROOT`:

```bash
# Use explicit spectr root
SPECTR_ROOT=/path/to/project spectr list

# Relative paths work too
SPECTR_ROOT=../other-project spectr validate --all

# Useful in scripts/CI for explicit control
export SPECTR_ROOT=/workspace/my-project
spectr list
spectr validate my-change
```text

**Behavior:**

- When set, uses ONLY the specified root (skips automatic discovery)
- Errors if the path doesn't contain a `spectr/` directory
- Useful for CI/CD pipelines or scripts that need deterministic behavior

#### TUI Path Copying

When selecting items in interactive mode (Enter key), Spectr copies the full
path relative to your cwd:

- Single root: `spectr/changes/add-feature/proposal.md`
- Nested root: `../project/spectr/changes/add-feature/proposal.md`

This enables direct navigation with `@` file references in AI coding assistants.

### Spec-Driven Development

Spectr implements a **three-stage workflow** for managing changes:
Expand Down
53 changes: 53 additions & 0 deletions cmd/discovery.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package cmd

import (
"errors"
"fmt"
"os"

"github.com/connerohnesorge/spectr/internal/discovery"
)

// GetDiscoveredRoots returns all discovered spectr roots from the current
// working directory. It wraps discovery.FindSpectrRoots with cwd handling.
func GetDiscoveredRoots() ([]discovery.SpectrRoot, error) {
cwd, err := os.Getwd()
if err != nil {
return nil, fmt.Errorf(
"failed to get current directory: %w",
err,
)
}

roots, err := discovery.FindSpectrRoots(cwd)
if err != nil {
return nil, fmt.Errorf(
"failed to discover spectr roots: %w",
err,
)
}

return roots, nil
}

// GetSingleRoot returns the first discovered root, or an error if no roots
// are found. This is useful for commands that operate on a single root.
func GetSingleRoot() (discovery.SpectrRoot, error) {
roots, err := GetDiscoveredRoots()
if err != nil {
return discovery.SpectrRoot{}, err
}

if len(roots) == 0 {
return discovery.SpectrRoot{}, errors.New(
"no spectr directory found\nHint: Run 'spectr init' to initialize Spectr",
)
}

return roots[0], nil
}

// HasMultipleRoots returns true if there are multiple discovered roots.
func HasMultipleRoots(roots []discovery.SpectrRoot) bool {
return len(roots) > 1
}
Loading
Loading