From 66db176f4d1b019b50ed0ae98981228bfa31fbae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Corn=C3=A9=20Dorrestijn?= Date: Wed, 28 Oct 2026 13:16:13 +0100 Subject: [PATCH 1/2] Config: a matterbox config's jira: and ui: are copied to laneway's once, instead of read from matterbox's dir Co-Authored-By: Claude Opus 5.5 --- docs/reference/config.md | 4 +- docs/reference/rules.md | 4 +- internal/config/config.go | 69 ++++++++++++++++++++++++++-------- internal/config/config_test.go | 13 +++++-- internal/i18n/nl/go-rest.json | 3 +- main.go | 2 +- setup.go | 4 -- 7 files changed, 69 insertions(+), 30 deletions(-) diff --git a/docs/reference/config.md b/docs/reference/config.md index 3c41e3f9..fc80e975 100644 --- a/docs/reference/config.md +++ b/docs/reference/config.md @@ -7,8 +7,8 @@ the browser) edit the `ui:` options in place, comments kept. A bad value keeps its default and is reported; an unknown key warns with a "did you mean". One value where a list goes (`projects: ABC`) is a list of -one. An existing `~/.config/jiratui` or matterbox config (its `jira:` and -`ui:`) is picked up as a fallback. +one. An existing `~/.config/jiratui` config is picked up as a fallback; without +either, a matterbox config's `jira:` and `ui:` are copied to laneway's. ## Jira diff --git a/docs/reference/rules.md b/docs/reference/rules.md index 5c87ba8f..0e4fbedf 100644 --- a/docs/reference/rules.md +++ b/docs/reference/rules.md @@ -38,8 +38,8 @@ issue's changelog shows someone else made: never on yours, and never on their own writes coming back on the next refresh. A transition to the status the issue has does nothing; one its workflow doesn't offer is logged. -A bad rule is skipped and reported. A matterbox config's `rules:` are -ignored. +A bad rule is skipped and reported. A matterbox config's `rules:` are not +copied. ## Watch a search diff --git a/internal/config/config.go b/internal/config/config.go index 96a48421..9ee4bcfe 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,6 +1,6 @@ // Package config loads the Jira connection from -// ~/.config/laneway/config.yaml, falling back to the old jiratui name and -// then the jira: section of matterbox's config so an existing setup just works. +// ~/.config/laneway/config.yaml, falling back to the old jiratui name. A +// matterbox config's jira: is copied to laneway's once (migrateMatterbox). package config import ( @@ -461,7 +461,9 @@ func Load(path string) (Config, string, error) { candidates = []string{ filepath.Join(d, "laneway", "config.yaml"), filepath.Join(d, "jiratui", "config.yaml"), - filepath.Join(d, "matterbox", "config.yaml"), + } + if err := migrateMatterbox(d, candidates); err != nil { + return Config{}, candidates[0], err } } for _, p := range candidates { @@ -473,19 +475,9 @@ func Load(path string) (Config, string, error) { return Config{}, p, err } var c Config - if filepath.Base(filepath.Dir(p)) == "matterbox" { - // Its rules: are chat rules; only jira: and ui: carry over. - var mb struct { - Jira JiraConfig `yaml:"jira"` - UI UIConfig `yaml:"ui"` - } - c.Warnings, err = decode(raw, &mb) - c.Jira, c.UI = mb.Jira, mb.UI - } else { - c.Warnings, err = decode(raw, &c) - c.Warnings = append(unknownKeys(raw), c.Warnings...) - c.Warnings = append(c.Warnings, people.Check(c.Jira.Pickers)...) - } + c.Warnings, err = decode(raw, &c) + c.Warnings = append(unknownKeys(raw), c.Warnings...) + c.Warnings = append(c.Warnings, people.Check(c.Jira.Pickers)...) if err != nil { return Config{}, p, fmt.Errorf("%s: %w", p, err) } @@ -498,6 +490,51 @@ func Load(path string) (Config, string, error) { return Config{}, candidates[0], fmt.Errorf("%w; run `laneway setup`, or create %s with:\n\n%s\nA token: %s", ErrNoConfig, candidates[0], starterConfig, TokenURL) } +// migrateMatterbox writes matterbox's jira: and ui: to laneway's config +// (configs[0]) when none of configs exists; its other keys are matterbox's +// own. Matterbox's file is left as it is. +func migrateMatterbox(dir string, configs []string) error { + for _, p := range configs { + if _, err := os.Stat(p); !os.IsNotExist(err) { + return nil + } + } + src := filepath.Join(dir, "matterbox", "config.yaml") + raw, err := os.ReadFile(src) + if os.IsNotExist(err) { + return nil + } + if err != nil { + return err + } + var doc yaml.Node + if err := yaml.Unmarshal(raw, &doc); err != nil { + return fmt.Errorf("%s: %w", src, err) + } + if len(doc.Content) == 0 || doc.Content[0].Kind != yaml.MappingNode { + return nil + } + top := doc.Content[0] + kept := []*yaml.Node{} + for i := 0; i+1 < len(top.Content); i += 2 { + if k := top.Content[i].Value; k == "jira" || k == "ui" { + kept = append(kept, top.Content[i], top.Content[i+1]) + } + } + if len(kept) == 0 { + return nil + } + top.Content = kept + out, err := yaml.Marshal(&doc) + if err != nil { + return err + } + if err := os.MkdirAll(filepath.Dir(configs[0]), 0o700); err != nil { + return err + } + return os.WriteFile(configs[0], out, 0o600) +} + // decode reads raw into v, skipping each value of the wrong type with a // warning so the option keeps its default; a lone value where a list goes // is a list of one. Only a file that is not YAML fails. diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 57463793..3791b307 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -44,8 +44,9 @@ func TestLoadFallsBackToJiratui(t *testing.T) { } } -// TestMatterboxRulesIgnored: matterbox's rules: are chat rules, not ours. -func TestMatterboxRulesIgnored(t *testing.T) { +// TestMatterboxMigrated: matterbox's jira: is copied to laneway's config; +// its rules: are chat rules, not ours. +func TestMatterboxMigrated(t *testing.T) { home := t.TempDir() t.Setenv("XDG_CONFIG_HOME", home) t.Setenv("HOME", home) @@ -53,10 +54,16 @@ func TestMatterboxRulesIgnored(t *testing.T) { p := filepath.Join(d, "matterbox", "config.yaml") _ = os.MkdirAll(filepath.Dir(p), 0o700) _ = os.WriteFile(p, []byte("jira:\n base_url: https://x.test\nrules:\n - match: {channel: Ops, frequency: {count: 3}}\n actions: [{type: send, text: hi}]\n"), 0o600) - c, _, err := Load("") + c, got, err := Load("") if err != nil || c.Jira.BaseURL != "https://x.test" || c.Rules != nil { t.Errorf("Load = %+v rules %v, %v", c.Jira, c.Rules, err) } + if want := filepath.Join(d, "laneway", "config.yaml"); got != want { + t.Errorf("path = %q, want %q", got, want) + } + if raw, _ := os.ReadFile(p); !strings.Contains(string(raw), "rules:") { + t.Errorf("matterbox's config changed: %s", raw) + } } func TestRequestTimeout(t *testing.T) { diff --git a/internal/i18n/nl/go-rest.json b/internal/i18n/nl/go-rest.json index 7cb47a02..fc1e2217 100644 --- a/internal/i18n/nl/go-rest.json +++ b/internal/i18n/nl/go-rest.json @@ -145,7 +145,7 @@ "commented": "reageerde", "commit-msg: refuse keys Jira doesn't know": "commit-msg: weiger sleutels die Jira niet kent", "config file": "configbestand", - "config file (default ~/.config/laneway/config.yaml, then jiratui's and matterbox's)": "configbestand (standaard ~/.config/laneway/config.yaml, dan die van jiratui en matterbox)", + "config file (default ~/.config/laneway/config.yaml, then jiratui's)": "configbestand (standaard ~/.config/laneway/config.yaml, dan die van jiratui)", "config file to write (default ~/.config/laneway/config.yaml)": "configbestand om te schrijven (standaard ~/.config/laneway/config.yaml)", "couldn't check %s: %v": "kon %s niet controleren: %v", "create: %q is not safe to hand to a command": "create: %q is niet veilig om aan een commando mee te geven", @@ -258,7 +258,6 @@ "summary can't be empty": "samenvatting mag niet leeg zijn", "that agent is gone": "die agent is weg", "the agent waits on an approval or question: answer it in its terminal": "de agent wacht op een goedkeuring of vraag: beantwoord die in zijn terminal", - "the config in use is matterbox's (%s), whose sites laneway doesn't read; copy its jira: to a laneway config (-config) first": "de gebruikte config is die van matterbox (%s), waarvan laneway de sites niet leest; kopieer de jira: eerst naar een laneway-config (-config)", "the end must be a day after today": "het einde moet een dag na vandaag zijn", "the file must be .woff2, .woff, .ttf or .otf": "het bestand moet .woff2, .woff, .ttf of .otf zijn", "the keyring refused it (%s); keeping it in the file": "de sleutelhanger weigerde het (%s); het blijft in het bestand", diff --git a/main.go b/main.go index 72e2dee6..eb7560d8 100644 --- a/main.go +++ b/main.go @@ -31,7 +31,7 @@ var version = "dev" func main() { showVersion := flag.Bool("version", false, i18n.T("print the version and exit")) - cfgPath := flag.String("config", "", i18n.T("config file (default ~/.config/laneway/config.yaml, then jiratui's and matterbox's)")) + cfgPath := flag.String("config", "", i18n.T("config file (default ~/.config/laneway/config.yaml, then jiratui's)")) site := flag.String("site", "", i18n.T("Jira site from the config's sites: (default the one last picked with @, else jira:)")) demoFlag := flag.Bool("demo", false, i18n.T("try laneway on a generated project, without Jira; writes are kept in memory (-config: its ui: only)")) flag.Parse() diff --git a/setup.go b/setup.go index 912e6a78..0005ec46 100644 --- a/setup.go +++ b/setup.go @@ -10,7 +10,6 @@ import ( "fmt" "io" "os" - "path/filepath" "strings" "time" @@ -146,9 +145,6 @@ func setupSite(ctx context.Context, cfgPath, site string, p prompter, verify fun } j.BaseURL = known.BaseURL } - if filepath.Base(filepath.Dir(path)) == "matterbox" { - return "", fmt.Errorf(i18n.T("the config in use is matterbox's (%s), whose sites laneway doesn't read; copy its jira: to a laneway config (-config) first"), path) - } first := errors.Is(err, config.ErrNoConfig) || cfg.Jira.BaseURL == "" && len(cfg.Sites) == 0 if first { fmt.Fprintln(p.out, i18n.T("Connect laneway to a Jira site, or type demo to try it on a generated board first. ctrl+d cancels.")) From 44ee12d200337d9e8b3c0010f66c9ef3411f586b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Corn=C3=A9=20Dorrestijn?= Date: Wed, 28 Oct 2026 14:28:24 +0100 Subject: [PATCH 2/2] Config: matterbox's config is no longer read Co-Authored-By: Claude Opus 5.5 --- docs/reference/config.md | 3 +- docs/reference/rules.md | 3 +- internal/config/config.go | 53 ++-------------------------------- internal/config/config_test.go | 22 -------------- 4 files changed, 4 insertions(+), 77 deletions(-) diff --git a/docs/reference/config.md b/docs/reference/config.md index fc80e975..05d42ca1 100644 --- a/docs/reference/config.md +++ b/docs/reference/config.md @@ -7,8 +7,7 @@ the browser) edit the `ui:` options in place, comments kept. A bad value keeps its default and is reported; an unknown key warns with a "did you mean". One value where a list goes (`projects: ABC`) is a list of -one. An existing `~/.config/jiratui` config is picked up as a fallback; without -either, a matterbox config's `jira:` and `ui:` are copied to laneway's. +one. An existing `~/.config/jiratui` config is picked up as a fallback. ## Jira diff --git a/docs/reference/rules.md b/docs/reference/rules.md index 0e4fbedf..4f69613b 100644 --- a/docs/reference/rules.md +++ b/docs/reference/rules.md @@ -38,8 +38,7 @@ issue's changelog shows someone else made: never on yours, and never on their own writes coming back on the next refresh. A transition to the status the issue has does nothing; one its workflow doesn't offer is logged. -A bad rule is skipped and reported. A matterbox config's `rules:` are not -copied. +A bad rule is skipped and reported. ## Watch a search diff --git a/internal/config/config.go b/internal/config/config.go index 9ee4bcfe..29ab563a 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,6 +1,5 @@ // Package config loads the Jira connection from -// ~/.config/laneway/config.yaml, falling back to the old jiratui name. A -// matterbox config's jira: is copied to laneway's once (migrateMatterbox). +// ~/.config/laneway/config.yaml, falling back to the old jiratui name. package config import ( @@ -24,7 +23,7 @@ import ( "github.com/cornedor/laneway/internal/rules" ) -// JiraConfig is matterbox's jira: section. +// JiraConfig is the jira: section. type JiraConfig struct { // Name labels the default site (jira:) in the site pickers and the web // header; sites: entries are labelled by their key. @@ -462,9 +461,6 @@ func Load(path string) (Config, string, error) { filepath.Join(d, "laneway", "config.yaml"), filepath.Join(d, "jiratui", "config.yaml"), } - if err := migrateMatterbox(d, candidates); err != nil { - return Config{}, candidates[0], err - } } for _, p := range candidates { raw, err := os.ReadFile(p) @@ -490,51 +486,6 @@ func Load(path string) (Config, string, error) { return Config{}, candidates[0], fmt.Errorf("%w; run `laneway setup`, or create %s with:\n\n%s\nA token: %s", ErrNoConfig, candidates[0], starterConfig, TokenURL) } -// migrateMatterbox writes matterbox's jira: and ui: to laneway's config -// (configs[0]) when none of configs exists; its other keys are matterbox's -// own. Matterbox's file is left as it is. -func migrateMatterbox(dir string, configs []string) error { - for _, p := range configs { - if _, err := os.Stat(p); !os.IsNotExist(err) { - return nil - } - } - src := filepath.Join(dir, "matterbox", "config.yaml") - raw, err := os.ReadFile(src) - if os.IsNotExist(err) { - return nil - } - if err != nil { - return err - } - var doc yaml.Node - if err := yaml.Unmarshal(raw, &doc); err != nil { - return fmt.Errorf("%s: %w", src, err) - } - if len(doc.Content) == 0 || doc.Content[0].Kind != yaml.MappingNode { - return nil - } - top := doc.Content[0] - kept := []*yaml.Node{} - for i := 0; i+1 < len(top.Content); i += 2 { - if k := top.Content[i].Value; k == "jira" || k == "ui" { - kept = append(kept, top.Content[i], top.Content[i+1]) - } - } - if len(kept) == 0 { - return nil - } - top.Content = kept - out, err := yaml.Marshal(&doc) - if err != nil { - return err - } - if err := os.MkdirAll(filepath.Dir(configs[0]), 0o700); err != nil { - return err - } - return os.WriteFile(configs[0], out, 0o600) -} - // decode reads raw into v, skipping each value of the wrong type with a // warning so the option keeps its default; a lone value where a list goes // is a list of one. Only a file that is not YAML fails. diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 3791b307..6a508f01 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -44,28 +44,6 @@ func TestLoadFallsBackToJiratui(t *testing.T) { } } -// TestMatterboxMigrated: matterbox's jira: is copied to laneway's config; -// its rules: are chat rules, not ours. -func TestMatterboxMigrated(t *testing.T) { - home := t.TempDir() - t.Setenv("XDG_CONFIG_HOME", home) - t.Setenv("HOME", home) - d, _ := os.UserConfigDir() - p := filepath.Join(d, "matterbox", "config.yaml") - _ = os.MkdirAll(filepath.Dir(p), 0o700) - _ = os.WriteFile(p, []byte("jira:\n base_url: https://x.test\nrules:\n - match: {channel: Ops, frequency: {count: 3}}\n actions: [{type: send, text: hi}]\n"), 0o600) - c, got, err := Load("") - if err != nil || c.Jira.BaseURL != "https://x.test" || c.Rules != nil { - t.Errorf("Load = %+v rules %v, %v", c.Jira, c.Rules, err) - } - if want := filepath.Join(d, "laneway", "config.yaml"); got != want { - t.Errorf("path = %q, want %q", got, want) - } - if raw, _ := os.ReadFile(p); !strings.Contains(string(raw), "rules:") { - t.Errorf("matterbox's config changed: %s", raw) - } -} - func TestRequestTimeout(t *testing.T) { for in, want := range map[string]time.Duration{"": 0, "45s": 45 * time.Second, " 1m ": time.Minute} { if got, err := (JiraConfig{Timeout: in}).RequestTimeout(); err != nil || got != want {