Auto-Discovery Resolution Chain#
Overview#
When no -f flag is provided, scafctl automatically searches for solution files
using a unified resolution chain. This design introduces risk-based ambiguity
handling and adds taskfile.yaml/taskfile.yml as discoverable file names.
Problem Statement#
- Inconsistent discovery – Each CLI command implemented its own discovery logic with slightly different behavior, making it hard to predict which file would be used.
- Silent ambiguity – When multiple solution files existed in a project
(e.g.,
solution.yamlandactions.yaml), the first match was used with no feedback to the user. - Destructive command risk – Commands like
buildthat publish artifacts should not silently pick an ambiguous file – the consequences of choosing wrong are harder to reverse. - Missing taskfile support – Projects using
taskfile.yamlas their solution filename had to always specify-f.
Design#
Unified Resolution Chain#
All commands use a single Resolve() function from pkg/solution/get that
implements a three-step chain:
- Explicit flag – If
-fis provided, use it directly (no discovery). - Positional argument – If a positional arg is given (catalog reference), use it as-is.
- Auto-discovery – Search configured folders and file names in priority order, returning all matches.
resolved, err := get.Resolve(ctx, getter, file, positionalArg, get.ResolveOptions{
Risk: get.DiscoveryRiskLow,
})Discovery Risk Levels#
The DiscoveryRisk type controls how multi-match ambiguity is handled:
| Risk Level | Behavior | Commands |
|---|---|---|
DiscoveryRiskLow | Use first match, emit warning about others to stderr | run, lint, test, render |
DiscoveryRiskHigh | Return an error listing all matches, require -f | build |
This ensures read-only or easily-reversible commands remain convenient while destructive operations require explicit intent.
File Name Search Order#
The search combines folder prefixes with file names in this order:
Folder prefixes (checked in order):
scafctl/– conventional project subfolder.scafctl/– hidden project subfolder- (current directory) – root
File names (checked in order per folder):
solution.yamlsolution.ymlscafctl.yamlscafctl.ymlsolution.jsonscafctl.jsontaskfile.yamltaskfile.yml
Action file names (actions.yaml, actions.yml) are only searched when
DiscoveryModeAction is active (e.g., run action). They are not included
in the default search list.
This produces 24 candidate paths checked in priority order (3 folders x 8 names).
FindAllSolutions vs FindSolution#
| Function | Behavior |
|---|---|
FindSolution() | Returns the first match (backward compatible) |
FindAllSolutions() | Returns all matches in priority order with deduplication |
FindAllSolutions uses canonical path resolution to deduplicate entries when
different relative paths resolve to the same absolute file.
MCP Server Integration#
The MCP server’s discoverSolutionFiles() uses FindAllSolutions() to report
all discoverable solutions to AI clients, enabling them to present the full set
of available solutions in a workspace.
Behavior Examples#
The Using ... and multi-match messages below are emitted via writer.Verbosef
and only appear when --verbose is enabled.
Single match (common case)#
$ scafctl lint --verbose
Using solution.yaml
...Multiple matches, low-risk command#
$ scafctl lint --verbose
Using solution.yaml
WARNING: Multiple solution files found (also: taskfile.yaml); using first match
...Multiple matches, high-risk command#
$ scafctl package solution
Error: multiple solution files found: solution.yaml, taskfile.yaml; use -f/--file to specify which oneAPI#
package get
type DiscoveryRisk int
const (
DiscoveryRiskLow DiscoveryRisk = iota // warn on ambiguity
DiscoveryRiskHigh // error on ambiguity
)
type ResolveOptions struct {
Risk DiscoveryRisk
DiscoveryMode settings.DiscoveryMode
}
func Resolve(ctx context.Context, getter *Getter, file, positionalArg string, opts ResolveOptions) (string, error)
func (o *Getter) FindAllSolutions() []DiscoveryResult
func (o *Getter) SearchedPaths() []stringTesting#
- Unit tests in
pkg/solution/get/resolve_test.gocover the resolution chain, risk levels, and edge cases (no files, single match, multi-match). - Integration tests in
tests/integration/cli_test.goverify end-to-end discovery oftaskfile.yamland multi-match warning output.