diff --git a/CLAUDE.md b/CLAUDE.md index 70d6e13..5ab1435 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -34,7 +34,7 @@ CLI для управления конвенциями. Модель здесь ``` internal/lang словарь: реестр «версия языка × естественный язык» internal/source ссылки между уровнями: путь на диске, git-репозиторий -internal/manifest suite.toml и .conventions.toml — чтение и запись +internal/manifest оба манифеста: чтение и запись internal/doc разбор документа: шапка, области правил, блоки internal/suite сборка набора в память, отбор слоёв под компонент internal/project сборка копий в проекте: разделы, маркер, READING.md diff --git a/README.md b/README.md index 69f8816..ec5e98f 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ CLI для управления конвенциями разработки: в | Уровень | Что это | |---|---| | язык | как записывается правило: слова, версия, `READING.md` | -| набор | репозиторий с конвенциями, манифестом `suite.toml` и обвязкой | +| набор | репозиторий с конвенциями, манифестом `.conventions-suite.toml` и обвязкой | | проект | репозиторий-потребитель с манифестом `.conventions.toml` | | тема | набор правил об одном фокусе разработки; единица подписки | | слой | один файл темы: базовый, языковой или стековый | @@ -90,7 +90,7 @@ go build -o convy . convy check проверить форму того, что здесь ``` -Контекст определяется по манифесту рядом: `suite.toml` — набор, +Контекст определяется по манифесту рядом: `.conventions-suite.toml` — набор, `.conventions.toml` — проект. Наугад не делается ничего: команда не того уровня отказывает и подсказывает нужную. @@ -194,9 +194,9 @@ topics = ["logging", "time"] ``` $ convy suite add --topic time --about "время" --prefix TIME --title "Время" -suite.toml holds 1 key the tool does not know (language.descriptoin); a write -goes out of what the tool understands, so the key would be dropped — fix the -spelling first +.conventions-suite.toml holds 1 key the tool does not know +(language.descriptoin); a write goes out of what the tool understands, so the +key would be dropped — fix the spelling first ``` ## Манифест — источник истины @@ -278,7 +278,8 @@ errors: 1, warnings: 0 Слова, которыми записаны модальность и метки, — свойство версии языка и естественного языка набора, а не самого набора. Инструмент знает их сам, и -`suite.toml` их не дублирует: достаточно `[language] version` и `lang`. +`.conventions-suite.toml` их не дублирует: хватает `[language] version` и +`lang`. Поэтому ступень называется категорией, а не словом: diff --git a/internal/check/check_test.go b/internal/check/check_test.go index f263163..22f6405 100644 --- a/internal/check/check_test.go +++ b/internal/check/check_test.go @@ -74,10 +74,10 @@ type files map[string]string func base() files { return files{ - "suite.toml": baseManifest, - "LANGUAGE.md": "# Язык конвенций\n\nОписание языка.\n", - "READING.md": reading, - "conventions/time.md": baseTime, + ".conventions-suite.toml": baseManifest, + "LANGUAGE.md": "# Язык конвенций\n\nОписание языка.\n", + "READING.md": reading, + "conventions/time.md": baseTime, } } @@ -270,14 +270,14 @@ func TestChecks(t *testing.T) { }, { name: "topic listed among the retired ones", setup: func(f files) { - f["suite.toml"] = strings.Replace(baseManifest, + f[".conventions-suite.toml"] = strings.Replace(baseManifest, "[topics.retired]", `[topics.retired]`+"\ntime = \"снята 2026-07-01\"", 1) }, want: "is listed both live and retired", }, { name: "live topic without layers", setup: func(f files) { - f["suite.toml"] = strings.Replace(baseManifest, + f[".conventions-suite.toml"] = strings.Replace(baseManifest, `time = "время: хранение, зоны, форматы"`, `time = "время"`+"\nlogging = \"логирование\"", 1) }, @@ -285,7 +285,7 @@ func TestChecks(t *testing.T) { }, { name: "declared file is missing", setup: func(f files) { - f["suite.toml"] = strings.Replace(baseManifest, + f[".conventions-suite.toml"] = strings.Replace(baseManifest, `TIME = "conventions/time.md"`, `TIME = "conventions/time.md"`+"\nSLOG = \"conventions/logging.md\"", 1) }, @@ -299,7 +299,7 @@ func TestChecks(t *testing.T) { }, { name: "prefix starts with X", setup: func(f files) { - f["suite.toml"] = strings.Replace(baseManifest, + f[".conventions-suite.toml"] = strings.Replace(baseManifest, `TIME = "conventions/time.md"`, `XTIM = "conventions/time.md"`, 1) f["conventions/time.md"] = strings.ReplaceAll(baseTime, "TIME", "XTIM") }, @@ -307,7 +307,7 @@ func TestChecks(t *testing.T) { }, { name: "one file with two prefixes declared", setup: func(f files) { - f["suite.toml"] = strings.Replace(baseManifest, + f[".conventions-suite.toml"] = strings.Replace(baseManifest, `TIME = "conventions/time.md"`, `TIME = "conventions/time.md"`+"\nGTIM = \"conventions/time.md\"", 1) }, @@ -315,7 +315,7 @@ func TestChecks(t *testing.T) { }, { name: "unknown manifest key", setup: func(f files) { - f["suite.toml"] = baseManifest + "\n[extra]\nkey = 1\n" + f[".conventions-suite.toml"] = baseManifest + "\n[extra]\nkey = 1\n" }, want: "is unknown to the tool", }, { @@ -362,21 +362,21 @@ func TestChecks(t *testing.T) { setup: func(f files) { // A bare non-ASCII key TOML rejects on its own; a quoted one // passes straight through, which is what the check is for. - f["suite.toml"] = strings.Replace(baseManifest, "time =", `"время" =`, 1) + f[".conventions-suite.toml"] = strings.Replace(baseManifest, "time =", `"время" =`, 1) f["conventions/time.md"] = strings.Replace(baseTime, "topic: time", "topic: время", 1) }, want: "is not usable as a file name", }, { name: "topic name is not lower kebab-case", setup: func(f files) { - f["suite.toml"] = strings.Replace(baseManifest, "time =", "Time_Zone =", 1) + f[".conventions-suite.toml"] = strings.Replace(baseManifest, "time =", "Time_Zone =", 1) f["conventions/time.md"] = strings.Replace(baseTime, "topic: time", "topic: Time_Zone", 1) }, want: "is not lower kebab-case", }, { name: "a second document without a topic", setup: func(f files) { - f["suite.toml"] = strings.Replace(baseManifest, + f[".conventions-suite.toml"] = strings.Replace(baseManifest, `TIME = "conventions/time.md"`, `TIME = "conventions/time.md"`+"\nMETA = \"GUIDE.md\"\nRULE = \"conventions/rules.md\"", 1) f["GUIDE.md"] = "---\nprefix: META\n---\n\n# Как мы ведём конвенции\n\n" + versionLine + "\n" @@ -392,7 +392,7 @@ func TestChecks(t *testing.T) { }, { name: "document without a topic carries layer keys", setup: func(f files) { - f["suite.toml"] = strings.Replace(baseManifest, + f[".conventions-suite.toml"] = strings.Replace(baseManifest, `TIME = "conventions/time.md"`, `TIME = "conventions/time.md"`+"\nGTIM = \"conventions/go.md\"", 1) f["conventions/go.md"] = "---\nprefix: GTIM\nlang: go\n---\n\n# Go\n\n" + versionLine + "\n" diff --git a/internal/check/layers_test.go b/internal/check/layers_test.go index 7ba0e63..ad43636 100644 --- a/internal/check/layers_test.go +++ b/internal/check/layers_test.go @@ -93,7 +93,7 @@ prefix: SLOG func layered() files { return files{ - "suite.toml": layeredManifest, + ".conventions-suite.toml": layeredManifest, "LANGUAGE.md": "# Язык конвенций\n\nОписание языка.\n", "READING.md": reading, "conventions/arch/time.md": archTime, @@ -136,7 +136,7 @@ func TestLayeredChecks(t *testing.T) { }, { name: "topic with two layers lacking axis keys", setup: func(f files) { - f["suite.toml"] = strings.Replace(layeredManifest, + f[".conventions-suite.toml"] = strings.Replace(layeredManifest, `GTIM = "conventions/lang/go/time.md"`, `GTIM = "conventions/second/time.md"`, 1) f["conventions/lang/go/time.md"] = "" @@ -273,7 +273,7 @@ func TestNoFalsePositives(t *testing.T) { }, { name: "the single document without a topic is the one the suite governs itself by", setup: func(f files) { - f["suite.toml"] = strings.Replace(layeredManifest, + f[".conventions-suite.toml"] = strings.Replace(layeredManifest, `SLOG = "conventions/arch/logging.md"`, `SLOG = "conventions/arch/logging.md"`+"\nMETA = \"GUIDE.md\"", 1) f["GUIDE.md"] = "---\nprefix: META\n---\n\n# Как мы ведём конвенции\n\n" + versionLine + "\n" diff --git a/internal/cli/cli_test.go b/internal/cli/cli_test.go index 7151926..d856145 100644 --- a/internal/cli/cli_test.go +++ b/internal/cli/cli_test.go @@ -127,7 +127,7 @@ func TestAddInInteractiveMode(t *testing.T) { t.Errorf("the file lost %q:\n%s", want, body) } } - if !strings.Contains(read(t, root, "suite.toml"), `logging = "логирование: уровни"`) { + if !strings.Contains(read(t, root, ".conventions-suite.toml"), `logging = "логирование: уровни"`) { t.Error("the topic did not reach the manifest") } checkClean(t, root) diff --git a/internal/cli/retire_test.go b/internal/cli/retire_test.go index d2ec9b9..8eef2e4 100644 --- a/internal/cli/retire_test.go +++ b/internal/cli/retire_test.go @@ -144,7 +144,7 @@ func TestRetirePrefixMovesTheNameAndTakesTheFile(t *testing.T) { t.Error("the file stayed behind") } - toml := read(t, root, "suite.toml") + toml := read(t, root, ".conventions-suite.toml") if strings.Contains(toml, `SLOG = "conventions/logging.md"`) { t.Errorf("the prefix stayed in the live half:\n%s", toml) } @@ -179,7 +179,7 @@ func TestRetireTopicWaitsForItsLayers(t *testing.T) { t.Fatalf("retiring an empty topic returned %d: %s", code, out) } - toml := read(t, root, "suite.toml") + toml := read(t, root, ".conventions-suite.toml") if strings.Contains(toml, `time = "время"`) { t.Errorf("the topic stayed in the live half:\n%s", toml) } diff --git a/internal/cli/suiteinit.go b/internal/cli/suiteinit.go index 326debd..9d81c46 100644 --- a/internal/cli/suiteinit.go +++ b/internal/cli/suiteinit.go @@ -28,7 +28,7 @@ particular suite is for and how it is kept. ## The manifest -` + "`suite.toml`" + ` holds the identity of the suite: the language its rules are +` + "`.conventions-suite.toml`" + ` holds the identity of the suite: the language its rules are written in, its topics and its rule prefixes. It is written by ` + "`convy`" + ` and carries no comments — a command rewrites the whole file, and a comment would not survive that. Explanations belong here instead. diff --git a/internal/manifest/manifest.go b/internal/manifest/manifest.go index a8715c0..8c7baee 100644 --- a/internal/manifest/manifest.go +++ b/internal/manifest/manifest.go @@ -30,9 +30,11 @@ import ( ) // Name is the name of a suite manifest. Which of the two manifests lies next -// to you tells you where you are: suite.toml means a suite, .conventions.toml -// means a project. -const Name = "suite.toml" +// to you tells you where you are, and both start with a dot for the same +// reason: a manifest is data the tool writes, not a document of the repository, +// and it sits with the rest of the service files rather than among the +// conventions themselves. +const Name = ".conventions-suite.toml" // DefaultLanguageCode is the suite's natural language when the manifest says // nothing about it. The key is optional on purpose: the vocabulary lives in the @@ -83,7 +85,7 @@ func (s *Section) Retire(key, note string) { s.Retired[key] = note } -// Manifest is a parsed suite.toml. +// Manifest is a parsed suite manifest. type Manifest struct { Language Language `toml:"language"` Topics Section `toml:"topics,omitempty"` diff --git a/internal/source/source_test.go b/internal/source/source_test.go index e472437..4781f5c 100644 --- a/internal/source/source_test.go +++ b/internal/source/source_test.go @@ -118,13 +118,13 @@ func TestOpenGitTakesTheCommittedState(t *testing.T) { t.Skip("no git in PATH") } repo := t.TempDir() - name := filepath.Join(repo, "suite.toml") + name := filepath.Join(repo, ".conventions-suite.toml") if err := os.WriteFile(name, []byte("committed\n"), 0o644); err != nil { t.Fatal(err) } for _, args := range [][]string{ {"init", "--quiet", "-b", "main"}, - {"-c", "user.email=t@example.org", "-c", "user.name=t", "add", "suite.toml"}, + {"-c", "user.email=t@example.org", "-c", "user.name=t", "add", ".conventions-suite.toml"}, {"-c", "user.email=t@example.org", "-c", "user.name=t", "commit", "--quiet", "-m", "first"}, } { cmd := exec.Command("git", args...) @@ -145,7 +145,7 @@ func TestOpenGitTakesTheCommittedState(t *testing.T) { if err != nil { t.Fatalf("cloning a local repository: %v", err) } - body, err := os.ReadFile(filepath.Join(tree.Dir(), "suite.toml")) + body, err := os.ReadFile(filepath.Join(tree.Dir(), ".conventions-suite.toml")) if err != nil { t.Fatal(err) }