From 8b510429de9d81bae64cd2ed5880d8b706f81e08 Mon Sep 17 00:00:00 2001 From: crisfiorini-oss <301773486+crisfiorini-oss@users.noreply.github.com> Date: Mon, 26 Oct 2026 17:59:49 +0000 Subject: [PATCH 1/5] fix(generate): recover conflicted single-contract library metadata --- cmd/generate/client.go | 2 +- docs/commands.md | 8 +- pkg/generators/client.go | 22 +++-- .../client_manifest_recovery_test.go | 82 +++++++++++++++++++ 4 files changed, 107 insertions(+), 7 deletions(-) create mode 100644 pkg/generators/client_manifest_recovery_test.go diff --git a/cmd/generate/client.go b/cmd/generate/client.go index 11f6625a..c9caa74d 100644 --- a/cmd/generate/client.go +++ b/cmd/generate/client.go @@ -81,7 +81,7 @@ func init() { ClientCmd.Flags().StringVar(&clientModuleName, "module-name", "", "facade entry-point name (default: derived from the contract)") ClientCmd.Flags().BoolVar(&clientNoFacade, "no-facade", false, "generate bindings only, no facade") ClientCmd.Flags().StringVar(&clientOutput, "output", "", "output directory (default: /libraries/)") - ClientCmd.Flags().BoolVar(&clientForce, "force", false, "overwrite an existing library with a different contract digest") + ClientCmd.Flags().BoolVar(&clientForce, "force", false, "overwrite a different contract or rebuild a malformed single-contract library manifest") ClientCmd.Flags().StringVar(&clientEndpoint, "endpoint", "", "select an endpoint when --from package:/contracts: resolves to more than one: service/endpoint (a local --from names its endpoint as its own third segment instead)") ClientCmd.Flags().StringVar(&clientGoModule, "go-module", "", "Go module path for the generated go/ library (default: github.com/codefly-dev/-go)") ClientCmd.Flags().StringVar(&clientNpmScope, "npm-scope", "", "npm package name for the generated typescript/ library (default: @codefly-dev/)") diff --git a/docs/commands.md b/docs/commands.md index 42dd9b0c..473ecc31 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -3485,7 +3485,7 @@ with no caller and no way to detect drift. | `--module-name` | Facade entry-point name (default: derived from the contract's proto package) | | `--no-facade` | Bindings only | | `--output` | Output directory (default `/libraries/`) | -| `--force` | Overwrite an existing library whose recorded contract digest differs | +| `--force` | Overwrite a different contract, or rebuild a malformed single-contract library manifest | | `--go-module` | Go module path for `go/` (default `github.com/codefly-dev/-go`) | | `--npm-scope` | npm package name for `typescript/` (default `@codefly-dev/`) | @@ -3497,6 +3497,12 @@ from. `library.codefly.yaml` always carries a `sources:` list (one element for `generate client`, one per aggregated contract for `sync solution-sdk` below) so both commands share one schema. +For one explicit contract, `--force` can also rebuild a malformed generated +manifest, including unresolved merge conflicts. Its source and version come from +that contract; only the requested, freshly generated languages are recorded. +Filesystem errors and an aggregate library's unknown version still refuse the +run. Package validation and generation must succeed before any output changes. + Every requested language is generated into scratch directories before anything under the library is written. A run that fails in the proto companion (a contract that does not compile, a buf dependency that does not resolve) leaves diff --git a/pkg/generators/client.go b/pkg/generators/client.go index ae49a73d..6a2cfdbb 100644 --- a/pkg/generators/client.go +++ b/pkg/generators/client.go @@ -5,6 +5,7 @@ package generators import ( "context" + "errors" "fmt" "os" "path/filepath" @@ -34,6 +35,8 @@ const defaultFacadeModule = "client" // package (no producer package to name). const localPackageID = "local" +var errInvalidLibraryManifest = errors.New("invalid generated library manifest") + // ContractEntry is one API contract a generated library binds: an endpoint's // catalog entry, the raw contract bytes it points to, the package/module that // produced it, and this entry's own generation options (a services subset, a @@ -153,15 +156,21 @@ func GenerateClientLibrary(ctx context.Context, req *ClientLibraryRequest) error } existingSources, err := readExistingLibrarySources(req.Output) - if err != nil { + rebuildManifest := req.Force && len(entries) == 1 && errors.Is(err, errInvalidLibraryManifest) + if err != nil && !rebuildManifest { return err } + if rebuildManifest { + existingSources = nil + } existingByKey := make(map[[3]string]librarySource, len(existingSources)) for i := range existingSources { s := &existingSources[i] existingByKey[[3]string{s.Module, s.Service, s.Endpoint}] = *s } - anyDigestChanged := false + // A malformed manifest cannot vouch for previously generated languages. + // One explicit contract supplies its replacement identity and version. + anyDigestChanged := rebuildManifest for i := range entries { entry := &entries[i] key := [3]string{entry.ModuleName, entry.Endpoint.Service, entry.Endpoint.Endpoint} @@ -185,7 +194,10 @@ func GenerateClientLibrary(ctx context.Context, req *ClientLibraryRequest) error if !anyDigestChanged { existingLanguages, err = readExistingLanguageExports(req.Output) if err != nil { - return err + if !req.Force || len(entries) != 1 || !errors.Is(err, errInvalidLibraryManifest) { + return err + } + existingLanguages = nil } } @@ -767,7 +779,7 @@ func readExistingLibrarySources(outputDir string) ([]librarySource, error) { Sources []librarySource `yaml:"sources"` } if err := yaml.Unmarshal(data, &doc); err != nil { - return nil, fmt.Errorf("cannot parse existing %s: %w", resources.LibraryConfigurationName, err) + return nil, fmt.Errorf("cannot parse existing %s: %w: %w", resources.LibraryConfigurationName, errInvalidLibraryManifest, err) } return doc.Sources, nil } @@ -786,7 +798,7 @@ func readExistingLanguageExports(outputDir string) ([]generatedLanguageExport, e Languages []generatedLanguageExport `yaml:"languages"` } if err := yaml.Unmarshal(data, &doc); err != nil { - return nil, fmt.Errorf("cannot parse existing %s: %w", resources.LibraryConfigurationName, err) + return nil, fmt.Errorf("cannot parse existing %s: %w: %w", resources.LibraryConfigurationName, errInvalidLibraryManifest, err) } return doc.Languages, nil } diff --git a/pkg/generators/client_manifest_recovery_test.go b/pkg/generators/client_manifest_recovery_test.go new file mode 100644 index 00000000..b515d455 --- /dev/null +++ b/pkg/generators/client_manifest_recovery_test.go @@ -0,0 +1,82 @@ +package generators + +import ( + "context" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/codefly-dev/core/composition" + "github.com/codefly-dev/core/languages" + "github.com/codefly-dev/core/resources" +) + +// Metadata recovery must require an explicit single-contract force and must +// still stop before writing when the language package cannot be generated. +func TestConflictedLibraryManifestRecoveryPreservesPreflight(t *testing.T) { + for _, tc := range []struct { + name, manifest string + force bool + entries int + want string + }{ + {"conflict refused", "<<<<<<< HEAD\nversion: 0.1.0\n=======\nversion: 0.2.0\n>>>>>>> branch\n", false, 1, "cannot parse existing"}, + {"forced conflict reaches package guard", "<<<<<<< HEAD\nversion: 0.1.0\n=======\nversion: 0.2.0\n>>>>>>> branch\n", true, 1, "cannot set the typescript client's version"}, + {"forced invalid language reaches package guard", "sources: []\nlanguages: invalid\n", true, 1, "cannot set the typescript client's version"}, + {"aggregate cannot infer its version", "version: [\n", true, 2, "cannot parse existing"}, + } { + t.Run(tc.name, func(t *testing.T) { + output := t.TempDir() + if err := os.WriteFile(filepath.Join(output, resources.LibraryConfigurationName), []byte(tc.manifest), 0o644); err != nil { + t.Fatal(err) + } + langDir := filepath.Join(output, "typescript") + if err := os.MkdirAll(langDir, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(langDir, "package.json"), []byte(`{"name":"@acme/example-client","private":true}`), 0o644); err != nil { + t.Fatal(err) + } + entries := []ContractEntry{{ + Endpoint: composition.APIContractEndpoint{Service: "api", Endpoint: "connect", Kind: composition.APIContractKindProtobuf, Package: "example.v1"}, + PackageVersion: "0.2.0", ModuleName: "example", + }} + if tc.entries == 2 { + entries = append(entries, ContractEntry{ + Endpoint: composition.APIContractEndpoint{Service: "other", Endpoint: "connect", Kind: composition.APIContractKindProtobuf, Package: "other.v1"}, + PackageVersion: "0.3.0", ModuleName: "other", + }) + } + before := snapshotTree(t, output) + err := GenerateClientLibrary(context.Background(), &ClientLibraryRequest{ + Name: "example-client", Output: output, Force: tc.force, + Languages: []languages.Language{languages.TYPESCRIPT}, Entries: entries, + }) + if err == nil || !strings.Contains(err.Error(), tc.want) { + t.Fatalf("got %v, want refusal containing %q", err, tc.want) + } + if after := snapshotTree(t, output); after != before { + t.Fatalf("a refused generation changed the library:\nbefore:\n%s\nafter:\n%s", before, after) + } + }) + } +} + +func TestForcedLibraryRecoveryDoesNotIgnoreFilesystemErrors(t *testing.T) { + output := t.TempDir() + if err := os.Mkdir(filepath.Join(output, resources.LibraryConfigurationName), 0o755); err != nil { + t.Fatal(err) + } + err := GenerateClientLibrary(context.Background(), &ClientLibraryRequest{ + Name: "example-client", Output: output, Force: true, + Languages: []languages.Language{languages.TYPESCRIPT}, + Entries: []ContractEntry{{ + Endpoint: composition.APIContractEndpoint{Service: "api", Endpoint: "connect", Kind: composition.APIContractKindProtobuf}, + PackageVersion: "0.2.0", ModuleName: "example", + }}, + }) + if err == nil || !strings.Contains(err.Error(), "is a directory") { + t.Fatalf("got %v, want the filesystem error", err) + } +} From 214a106d0e7ec3c89e38b681bfe7a76741f8c4aa Mon Sep 17 00:00:00 2001 From: crisfiorini-oss <301773486+crisfiorini-oss@users.noreply.github.com> Date: Mon, 26 Oct 2026 18:04:18 +0000 Subject: [PATCH 2/5] docs: regenerate client force reference --- docs/cli-reference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cli-reference.md b/docs/cli-reference.md index 45bdc9f4..20c9d01d 100644 --- a/docs/cli-reference.md +++ b/docs/cli-reference.md @@ -2760,7 +2760,7 @@ Flags: ``` --endpoint string select an endpoint when --from package:/contracts: resolves to more than one: service/endpoint (a local --from names its endpoint as its own third segment instead) - --force overwrite an existing library with a different contract digest + --force overwrite a different contract or rebuild a malformed single-contract library manifest --from string contract source: module/service[/endpoint], package:@, or contracts: --go-module string Go module path for the generated go/ library (default: github.com/codefly-dev/-go) --language strings languages to generate: go,typescript,python From c031756b1d849da08de4c87720977d11a95ea9f0 Mon Sep 17 00:00:00 2001 From: crisfiorini-oss <301773486+crisfiorini-oss@users.noreply.github.com> Date: Mon, 26 Oct 2026 19:04:33 +0000 Subject: [PATCH 3/5] refactor(generator): isolate library metadata recovery --- pkg/generators/client.go | 98 ++++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 43 deletions(-) diff --git a/pkg/generators/client.go b/pkg/generators/client.go index 6a2cfdbb..af86bddf 100644 --- a/pkg/generators/client.go +++ b/pkg/generators/client.go @@ -155,51 +155,10 @@ func GenerateClientLibrary(ctx context.Context, req *ClientLibraryRequest) error return fmt.Errorf("cannot aggregate an OpenAPI contract with other entries in one library") } - existingSources, err := readExistingLibrarySources(req.Output) - rebuildManifest := req.Force && len(entries) == 1 && errors.Is(err, errInvalidLibraryManifest) - if err != nil && !rebuildManifest { + existingLanguages, err := existingLibraryLanguages(req, entries) + if err != nil { return err } - if rebuildManifest { - existingSources = nil - } - existingByKey := make(map[[3]string]librarySource, len(existingSources)) - for i := range existingSources { - s := &existingSources[i] - existingByKey[[3]string{s.Module, s.Service, s.Endpoint}] = *s - } - // A malformed manifest cannot vouch for previously generated languages. - // One explicit contract supplies its replacement identity and version. - anyDigestChanged := rebuildManifest - for i := range entries { - entry := &entries[i] - key := [3]string{entry.ModuleName, entry.Endpoint.Service, entry.Endpoint.Endpoint} - prior, ok := existingByKey[key] - if !ok || prior.ContractDigest == entry.Endpoint.Digest { - continue - } - if !req.Force { - return fmt.Errorf("library %s already has %s/%s (module %s) at a different contract (digest %s, generating %s); use --force to overwrite", - req.Name, entry.Endpoint.Service, entry.Endpoint.Endpoint, entry.ModuleName, prior.ContractDigest, entry.Endpoint.Digest) - } - anyDigestChanged = true - } - - // Only carry a previously-declared language forward when every entry this - // run generated is the same contract that produced it (no digest changed): - // a --force digest override means at least one contract changed, so a - // language directory this run does not touch is now stale relative to it - // and the manifest must stop vouching for it. - var existingLanguages []generatedLanguageExport - if !anyDigestChanged { - existingLanguages, err = readExistingLanguageExports(req.Output) - if err != nil { - if !req.Force || len(entries) != 1 || !errors.Is(err, errInvalidLibraryManifest) { - return err - } - existingLanguages = nil - } - } pyPackage := strings.ReplaceAll(req.Name, "-", "_") version := libraryVersion(req.Output, entries) @@ -265,6 +224,59 @@ func GenerateClientLibrary(ctx context.Context, req *ClientLibraryRequest) error return nil } +// existingLibraryLanguages retains language declarations only when the stored +// manifest is readable and still describes the selected contracts. Forced +// single-contract recovery discards declarations the broken manifest cannot vouch for. +func existingLibraryLanguages(req *ClientLibraryRequest, entries []ContractEntry) ([]generatedLanguageExport, error) { + existingSources, err := readExistingLibrarySources(req.Output) + rebuildManifest := req.Force && len(entries) == 1 && errors.Is(err, errInvalidLibraryManifest) + if err != nil && !rebuildManifest { + return nil, err + } + if rebuildManifest { + existingSources = nil + } + existingByKey := make(map[[3]string]librarySource, len(existingSources)) + for i := range existingSources { + s := &existingSources[i] + existingByKey[[3]string{s.Module, s.Service, s.Endpoint}] = *s + } + // A malformed manifest cannot vouch for previously generated languages. + // One explicit contract supplies its replacement identity and version. + anyDigestChanged := rebuildManifest + for i := range entries { + entry := &entries[i] + key := [3]string{entry.ModuleName, entry.Endpoint.Service, entry.Endpoint.Endpoint} + prior, ok := existingByKey[key] + if !ok || prior.ContractDigest == entry.Endpoint.Digest { + continue + } + if !req.Force { + return nil, fmt.Errorf("library %s already has %s/%s (module %s) at a different contract (digest %s, generating %s); use --force to overwrite", + req.Name, entry.Endpoint.Service, entry.Endpoint.Endpoint, entry.ModuleName, prior.ContractDigest, entry.Endpoint.Digest) + } + anyDigestChanged = true + } + + // Only carry a previously-declared language forward when every entry this + // run generated is the same contract that produced it (no digest changed): + // a --force digest override means at least one contract changed, so a + // language directory this run does not touch is now stale relative to it + // and the manifest must stop vouching for it. + var existingLanguages []generatedLanguageExport + if !anyDigestChanged { + existingLanguages, err = readExistingLanguageExports(req.Output) + if err != nil { + if !req.Force || len(entries) != 1 || !errors.Is(err, errInvalidLibraryManifest) { + return nil, err + } + existingLanguages = nil + } + } + + return existingLanguages, nil +} + // checkContractDiamond groups entries by protobuf package and errors when a // package is consumed at more than one contract digest — two producers (or // two versions of one) claiming the same proto package cannot both be baked From aca8b14579edb3812c474904981e82ff31fb6f94 Mon Sep 17 00:00:00 2001 From: crisfiorini-oss <301773486+crisfiorini-oss@users.noreply.github.com> Date: Mon, 26 Oct 2026 19:26:21 +0000 Subject: [PATCH 4/5] docs(cli): isolate the owner-generated command reference --- .gitattributes | 2 ++ AGENTS.md | 2 +- cmd/cli_reference_test.go | 13 ++++++++++--- docs/commands.md | 2 +- docs/development-loop.md | 2 +- docs/{ => generated}/cli-reference.md | 2 +- docs/go.md | 2 +- docs/runbooks/add-a-command.md | 6 +++--- 8 files changed, 20 insertions(+), 11 deletions(-) create mode 100644 .gitattributes rename docs/{ => generated}/cli-reference.md (99%) diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..c999d0c9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Complete command reference emitted by its owning command-tree generator. +docs/generated/** linguist-generated=true diff --git a/AGENTS.md b/AGENTS.md index b6728146..81d1a720 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -90,7 +90,7 @@ new runbook (and its skill) whenever you do a multi-step operational task a seco ### Reference (deep dives, not step-by-step) - **The development loop** (change → render → build → deploy, what is cached, how to force a rebuild, and how to tell a stale build from a wrong one) → [docs/development-loop.md](docs/development-loop.md) - **All CLI commands, by category** → [docs/commands.md](docs/commands.md) -- **Every command and flag** (generated from the command tree; regenerate with `go test ./cmd -run TestCLIReferenceIsCurrent -update-reference`) → [docs/cli-reference.md](docs/cli-reference.md) +- **Every command and flag** (generated from the command tree; regenerate with `go test ./cmd -run TestCLIReferenceIsCurrent -update-reference`) → [docs/generated/cli-reference.md](docs/generated/cli-reference.md) - **Orchestration engine** → [docs/orchestration.md](docs/orchestration.md) - **Runnables** (what the CLI does with `runnable.codefly.yaml`, and what is deliberately not implemented yet) → [docs/runnable.md](docs/runnable.md) - **Product-owned selections** (Core resolution, local checkouts, evidence and execution blockers) → [docs/composition-selections.md](docs/composition-selections.md) diff --git a/cmd/cli_reference_test.go b/cmd/cli_reference_test.go index dc41852a..43d132c0 100644 --- a/cmd/cli_reference_test.go +++ b/cmd/cli_reference_test.go @@ -13,10 +13,10 @@ import ( "github.com/spf13/pflag" ) -var updateReference = flag.Bool("update-reference", false, "rewrite docs/cli-reference.md from the command tree") +var updateReference = flag.Bool("update-reference", false, "rewrite docs/generated/cli-reference.md from the command tree") // referencePath is the generated reference every non-hidden command appears in. -const referencePath = "../docs/cli-reference.md" +const referencePath = "../docs/generated/cli-reference.md" // TestCLIReferenceIsCurrent is the completeness guard for the command // documentation. docs/commands.md is narrative — it explains the chains a verb @@ -36,9 +36,16 @@ const referencePath = "../docs/cli-reference.md" func TestCLIReferenceIsCurrent(t *testing.T) { generated := renderCLIReference(RootCmd) if *updateReference { + if err := os.MkdirAll(filepath.Dir(referencePath), 0o755); err != nil { + t.Fatal(err) + } if err := os.WriteFile(filepath.Clean(referencePath), []byte(generated), 0o600); err != nil { t.Fatal(err) } + // Retire the old generated location only after its replacement is written. + if err := os.Remove("../docs/cli-reference.md"); err != nil && !os.IsNotExist(err) { + t.Fatal(err) + } t.Log("rewrote " + referencePath) return } @@ -82,7 +89,7 @@ func renderCLIReference(root *cobra.Command) string { out.WriteString("Generated from the command tree by\n") out.WriteString("`go test ./cmd -run TestCLIReferenceIsCurrent -update-reference`. Do not edit by hand:\n") out.WriteString("a command's text lives on the command, so `--help` and this page always agree.\n\n") - out.WriteString("This page is the complete list. [commands.md](commands.md) is the narrative guide —\n") + out.WriteString("This page is the complete list. [commands.md](../commands.md) is the narrative guide —\n") out.WriteString("what a verb is for, and the chains it takes part in.\n") renderCommand(&out, root, "") return out.String() diff --git a/docs/commands.md b/docs/commands.md index 473ecc31..ded85b43 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -4,7 +4,7 @@ Codefly formalizes development operations as typed gRPC APIs. The CLI is the use This page is the narrative guide: what a verb is for, and the chains it takes part in. For the complete list of every command and flag, see -[cli-reference.md](cli-reference.md), which is generated from the command tree — +[cli-reference.md](generated/cli-reference.md), which is generated from the command tree — a command cannot exist without appearing there, and its text is the same one `--help` prints. diff --git a/docs/development-loop.md b/docs/development-loop.md index 45cb2310..ce02d09a 100644 --- a/docs/development-loop.md +++ b/docs/development-loop.md @@ -35,7 +35,7 @@ edit ──▶ codefly run service local, no image build seconds verdict before doing any expensive work and refuse with `codefly doctor workspace`'s diagnostics, so a missing configuration fails in seconds instead of after the images are built. Every flag of every verb is in -[commands.md](commands.md) and [cli-reference.md](cli-reference.md). +[commands.md](commands.md) and [cli-reference.md](generated/cli-reference.md). --- diff --git a/docs/cli-reference.md b/docs/generated/cli-reference.md similarity index 99% rename from docs/cli-reference.md rename to docs/generated/cli-reference.md index 20c9d01d..837acf9b 100644 --- a/docs/cli-reference.md +++ b/docs/generated/cli-reference.md @@ -4,7 +4,7 @@ Generated from the command tree by `go test ./cmd -run TestCLIReferenceIsCurrent -update-reference`. Do not edit by hand: a command's text lives on the command, so `--help` and this page always agree. -This page is the complete list. [commands.md](commands.md) is the narrative guide — +This page is the complete list. [commands.md](../commands.md) is the narrative guide — what a verb is for, and the chains it takes part in. ## `codefly` diff --git a/docs/go.md b/docs/go.md index 4e1f3616..bc4e41c0 100644 --- a/docs/go.md +++ b/docs/go.md @@ -14,6 +14,6 @@ Makefile and `.github/workflows/go.yml` pins together: v2.13.1 cannot read Go `make hooks` (once per clone) points git at `.githooks`: `pre-commit` refuses a commit whose staged Go files are not gofmt-clean, and `pre-push` runs the same pinned golangci-lint over every package the push changes, plus the CLI reference -test when a command or `docs/cli-reference.md` changed. These are the checks that +test when a command or `docs/generated/cli-reference.md` changed. These are the checks that otherwise bounce a PR in the merge queue twenty minutes later; `--no-verify` skips them. diff --git a/docs/runbooks/add-a-command.md b/docs/runbooks/add-a-command.md index 3a9e998f..692d241a 100644 --- a/docs/runbooks/add-a-command.md +++ b/docs/runbooks/add-a-command.md @@ -2,7 +2,7 @@ Add a Cobra command (or subcommand) to the CLI and wire up help, docs, and — where relevant — MCP exposure. See [../commands.md](../commands.md) for the narrative command guide and -[../cli-reference.md](../cli-reference.md) for the generated list of every command and flag. +[../generated/cli-reference.md](../generated/cli-reference.md) for the generated list of every command and flag. ## When to use @@ -90,7 +90,7 @@ provider — so the static text is the contract; keep it accurate. Two documents, two jobs. -[../cli-reference.md](../cli-reference.md) is generated from the command tree and lists every +[../generated/cli-reference.md](../generated/cli-reference.md) is generated from the command tree and lists every command and flag. Regenerate it — `go test ./cmd -run TestCLIReferenceIsCurrent -update-reference` — and commit the result. `TestCLIReferenceIsCurrent` fails otherwise, naming your command: that is the guard, and it is why a new verb can no longer ship undocumented. Its text is the `Short`, @@ -131,7 +131,7 @@ go test ./cmd/... - [ ] Registered in `cmd/root.go` (top-level) or the parent's `init()` - [ ] Parent groups reject unknown subcommands - [ ] `Short`/`Long`/`Example` complete and network-free -- [ ] `docs/cli-reference.md` regenerated and committed (`go test ./cmd -run TestCLIReferenceIsCurrent -update-reference`) +- [ ] `docs/generated/cli-reference.md` regenerated and committed (`go test ./cmd -run TestCLIReferenceIsCurrent -update-reference`) - [ ] Written up in `docs/commands.md` under the right category - [ ] MCP tool added or consciously skipped (no `deploy` verb is exposed — see step 5) - [ ] Tests pass; `--help` and `explain` render From 55e444e9e236110e4c45e9476699aed949848d45 Mon Sep 17 00:00:00 2001 From: crisfiorini-oss <301773486+crisfiorini-oss@users.noreply.github.com> Date: Tue, 27 Oct 2026 15:45:51 +0000 Subject: [PATCH 5/5] test(generate): distinguish aggregate facade identities --- pkg/generators/client_manifest_recovery_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/generators/client_manifest_recovery_test.go b/pkg/generators/client_manifest_recovery_test.go index b515d455..1d4cea85 100644 --- a/pkg/generators/client_manifest_recovery_test.go +++ b/pkg/generators/client_manifest_recovery_test.go @@ -45,7 +45,7 @@ func TestConflictedLibraryManifestRecoveryPreservesPreflight(t *testing.T) { if tc.entries == 2 { entries = append(entries, ContractEntry{ Endpoint: composition.APIContractEndpoint{Service: "other", Endpoint: "connect", Kind: composition.APIContractKindProtobuf, Package: "other.v1"}, - PackageVersion: "0.3.0", ModuleName: "other", + PackageVersion: "0.3.0", ModuleName: "other", As: "other", }) } before := snapshotTree(t, output)