проектные команды и ссылки на источник

- заведён internal/source: уровни ссылаются друг на друга путём на диске
  или git-репозиторием, ревизия закрепляется хвостом #ref; клон делается
  заново и удаляется, кэша нет
- добавлены init, add, pull, list, check в проекте — манифест
  .conventions.toml, сборка копий по разу на компонент, маркер локальной
  части, READING.md рядом
- проверки формы развязаны с набором: принимают lang.Vocabulary, а язык
  копии узнаётся по строке о версии — манифеста рядом с ней нет
This commit is contained in:
av
2026-07-27 20:42:18 +03:00
parent 4615de6e86
commit 23d88c4048
27 changed files with 3009 additions and 57 deletions
+31 -3
View File
@@ -8,6 +8,7 @@ import (
"strings"
"git.vakhrushev.me/av/convy/internal/manifest"
"git.vakhrushev.me/av/convy/internal/source"
"git.vakhrushev.me/av/convy/internal/suite"
)
@@ -15,6 +16,7 @@ import (
func Suite(s *suite.Suite) *Report {
rep := &Report{}
checkManifest(s, rep)
checkLanguageSource(s, rep)
checkTopicNames(s, rep)
checkBaseLayers(s, rep)
checkSelfGoverning(s, rep)
@@ -39,9 +41,15 @@ func checkManifest(s *suite.Suite, rep *Report) {
for _, key := range m.Undecoded {
rep.Warnf(Manifest, manifest.Name, 0, "the key %s is unknown to the tool", key)
}
for _, name := range []string{m.Language.Description, m.Language.Reading} {
if name != "" && !s.Exists(name) {
rep.Errorf(Manifest, manifest.Name, 0, "the [language] section declares the document %q, and the file is missing", name)
// The documents about the language are named relative to the level they
// belong to. While that level is the suite, the files have to be here; once
// the language lives apart, they are out of reach of a check that runs on
// every edit and must not touch the network.
if m.Language.Source == "" {
for _, name := range []string{m.Language.Description, m.Language.Reading} {
if name != "" && !s.Exists(name) {
rep.Errorf(Manifest, manifest.Name, 0, "the [language] section declares the document %q, and the file is missing", name)
}
}
}
@@ -164,6 +172,12 @@ func checkSelfGoverning(s *suite.Suite, rep *Report) {
// fails in silence.
func checkReadingVocabulary(s *suite.Suite, rep *Report) {
name := s.Manifest.Language.Reading
if s.Manifest.Language.Source != "" {
// The account lies at a level of its own, and reaching it costs a
// fetch. Checking the integrity of a suite runs on every edit, so it
// stays local; the reference itself is checked instead.
return
}
if name == "" {
// A suite that has not written the document yet; `suite init` says so
// among the next steps, and repeating it on every run is noise.
@@ -218,3 +232,17 @@ func sortedKeys[V any](m map[string]V) []string {
sort.Strings(keys)
return keys
}
// checkLanguageSource checks the reference to the level of the language. What
// it names cannot be reached without a fetch, and a check of a suite does not
// fetch — but a reference that means nothing is caught here rather than at the
// first assembly in a foreign repository.
func checkLanguageSource(s *suite.Suite, rep *Report) {
raw := s.Manifest.Language.Source
if raw == "" {
return
}
if _, err := source.Parse(raw); err != nil {
rep.Errorf(Manifest, manifest.Name, 0, "[language] source: %s", err)
}
}