44 "fmt"
55 "os"
66
7+ "gopkg.in/yaml.v3"
8+
79 "github.com/fullsend-ai/fullsend/internal/config"
810 "github.com/fullsend-ai/fullsend/internal/ui"
911)
@@ -17,17 +19,46 @@ func configAgentNames(agents []config.AgentEntry) []string {
1719 return names
1820}
1921
20- // tryLoadOrgConfig attempts to load org config from the given path.
21- // Returns nil without error when the file is absent (best-effort).
22- // Logs warnings via printer for non-ENOENT read errors and parse errors.
23- func tryLoadOrgConfig (path string , printer * ui.Printer ) * config.OrgConfig {
22+ // isPerRepoYAML probes raw YAML for structural markers that distinguish
23+ // PerRepoConfig from OrgConfig. PerRepoConfig has a top-level "roles"
24+ // key; OrgConfig has "dispatch" and/or "repos". Both parsers use plain
25+ // yaml.Unmarshal which silently ignores unknown keys, so we must inspect
26+ // the raw keys to choose the correct parser.
27+ func isPerRepoYAML (data []byte ) bool {
28+ var probe map [string ]interface {}
29+ if err := yaml .Unmarshal (data , & probe ); err != nil {
30+ return false
31+ }
32+ if _ , ok := probe ["dispatch" ]; ok {
33+ return false
34+ }
35+ if _ , ok := probe ["repos" ]; ok {
36+ return false
37+ }
38+ _ , hasRoles := probe ["roles" ]
39+ return hasRoles
40+ }
41+
42+ // tryLoadInstallConfig attempts to load an org or per-repo config.yaml
43+ // from the given path. Returns nil without error when the file is absent
44+ // (best-effort). Per-repo config is adapted to OrgConfig via
45+ // OrgConfigFromPerRepo so callers see a unified type.
46+ func tryLoadInstallConfig (path string , printer * ui.Printer ) * config.OrgConfig {
2447 data , err := os .ReadFile (path )
2548 if err != nil {
2649 if ! os .IsNotExist (err ) {
27- printer .StepWarn ("Org config unreadable (remote resource allowlist unavailable): " + err .Error ())
50+ printer .StepWarn ("Install config unreadable (remote resource allowlist unavailable): " + err .Error ())
2851 }
2952 return nil
3053 }
54+ if isPerRepoYAML (data ) {
55+ perRepo , perRepoErr := config .ParsePerRepoConfig (data )
56+ if perRepoErr != nil {
57+ printer .StepWarn ("Per-repo config malformed (remote resource allowlist unavailable): " + perRepoErr .Error ())
58+ return nil
59+ }
60+ return config .OrgConfigFromPerRepo (perRepo )
61+ }
3162 cfg , parseErr := config .ParseOrgConfig (data )
3263 if parseErr != nil {
3364 printer .StepWarn ("Org config malformed (remote resource allowlist unavailable): " + parseErr .Error ())
@@ -36,17 +67,28 @@ func tryLoadOrgConfig(path string, printer *ui.Printer) *config.OrgConfig {
3667 return cfg
3768}
3869
39- // requireOrgConfig loads org config from the given path with strict error
40- // handling. Returns differentiated errors for missing files, unreadable
41- // files, and parse failures.
42- func requireOrgConfig (path string , printer * ui.Printer ) (* config.OrgConfig , error ) {
70+ // tryLoadOrgConfig loads an org or per-repo config.yaml (best-effort).
71+ var tryLoadOrgConfig = tryLoadInstallConfig
72+
73+ // requireInstallConfig loads an org or per-repo config.yaml from the
74+ // given path with strict error handling. Returns differentiated errors
75+ // for missing files, unreadable files, and parse failures.
76+ func requireInstallConfig (path string , printer * ui.Printer ) (* config.OrgConfig , error ) {
4377 data , err := os .ReadFile (path )
4478 if err != nil {
45- printer .StepFail ("Failed to load org config" )
79+ printer .StepFail ("Failed to load install config" )
4680 if os .IsNotExist (err ) {
47- return nil , fmt .Errorf ("URL-referenced resources require an org-level config.yaml with allowed_remote_resources (expected at %s)" , path )
81+ return nil , fmt .Errorf ("URL-referenced resources require a config.yaml with allowed_remote_resources (expected at %s)" , path )
4882 }
49- return nil , fmt .Errorf ("reading org config for remote resource validation: %w" , err )
83+ return nil , fmt .Errorf ("reading install config for remote resource validation: %w" , err )
84+ }
85+ if isPerRepoYAML (data ) {
86+ perRepo , perRepoErr := config .ParsePerRepoConfig (data )
87+ if perRepoErr != nil {
88+ printer .StepFail ("Failed to parse per-repo config" )
89+ return nil , fmt .Errorf ("parsing per-repo config: %w" , perRepoErr )
90+ }
91+ return config .OrgConfigFromPerRepo (perRepo ), nil
5092 }
5193 cfg , parseErr := config .ParseOrgConfig (data )
5294 if parseErr != nil {
@@ -55,3 +97,6 @@ func requireOrgConfig(path string, printer *ui.Printer) (*config.OrgConfig, erro
5597 }
5698 return cfg , nil
5799}
100+
101+ // requireOrgConfig loads an org or per-repo config.yaml (strict).
102+ var requireOrgConfig = requireInstallConfig
0 commit comments