From 0a12e905c9f68975295b0605852da4306fd12b26 Mon Sep 17 00:00:00 2001 From: Mathias Date: Fri, 3 Jul 2026 23:45:25 +0200 Subject: [PATCH] fix(create_project): drop defunct `hyperguild new-project` from finalize guidance (#46) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The infra#179 partial_failure message and the tool descriptor told callers to "Finalize locally with `hyperguild new-project`" — but that command does not exist (the hyperguild CLI only has tier/brain/mode; it was specced, never built). It pointed users at a dead end. Extracted the message into a pure infra179FinalizeMessage() and reworded it to name the actual remaining work — cloning the repo and substituting the leftover __PROJECT_NAME__ / __MODULE_PATH__ placeholders, or retrying — with no reference to any scaffolding CLI. Descriptor updated to match. Unit-tested the wording. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../tools/create_project_from_template.go | 23 ++++++++++++++----- .../tools/create_project_internal_test.go | 22 ++++++++++++++++++ 2 files changed, 39 insertions(+), 6 deletions(-) create mode 100644 internal/tools/create_project_internal_test.go diff --git a/internal/tools/create_project_from_template.go b/internal/tools/create_project_from_template.go index 95f7fcf..16a7606 100644 --- a/internal/tools/create_project_from_template.go +++ b/internal/tools/create_project_from_template.go @@ -48,7 +48,7 @@ func NewCreateProjectFromTemplate(c *gitea.Client, a *allowlist.Allowlist, tmplO func (t *CreateProjectFromTemplate) Descriptor() registry.ToolDescriptor { return registry.ToolDescriptor{ Name: "create_project_from_template", - Description: "Create a new project repo from a template. Best-effort substitution of placeholders (__PROJECT_NAME__, __MODULE_PATH__) in every file's content AND path (e.g. renaming cmd/__PROJECT_NAME__/): it completes only if the generated branch is promptly writable. If gitea's async generate is slow (infra#179) the repo is still created and partial_failure explains how to finalize locally (`hyperguild new-project`). Check files_substituted and partial_failure. Defaults to the server-configured template; pass template_name to override (e.g. template-go-agent). Pass dispatch_allow=true to also inject a .dispatch-allow file so the project is immediately dispatch-eligible (dispatch#3).", + Description: "Create a new project repo from a template. Best-effort substitution of placeholders (__PROJECT_NAME__, __MODULE_PATH__) in every file's content AND path (e.g. renaming cmd/__PROJECT_NAME__/): it completes only if the generated branch is promptly writable. If gitea's async generate is slow (infra#179) the repo is still created and partial_failure explains how to finish substituting the placeholders manually. Check files_substituted and partial_failure. Defaults to the server-configured template; pass template_name to override (e.g. template-go-agent). Pass dispatch_allow=true to also inject a .dispatch-allow file so the project is immediately dispatch-eligible (dispatch#3).", InputSchema: json.RawMessage(`{ "type":"object", "properties":{ @@ -209,11 +209,7 @@ func (t *CreateProjectFromTemplate) Call(ctx context.Context, raw json.RawMessag // is best-effort). if strings.Contains(result.PartialFailure, "branch does not exist") || strings.Contains(result.PartialFailure, "not found") { - result.PartialFailure = fmt.Sprintf( - "repo created, but its branch (%s) was not writable within %ds — gitea's "+ - "template-generate is slow-async on this instance (infra#179), so substitution "+ - "is incomplete (%d file(s) done). Finalize locally with `hyperguild new-project` "+ - "(clone + substitute, no API race). Underlying: %s", + result.PartialFailure = infra179FinalizeMessage( branch, substitutionBudget, len(result.FilesSubstituted), result.PartialFailure) } @@ -227,6 +223,21 @@ func (t *CreateProjectFromTemplate) Call(ctx context.Context, raw json.RawMessag return textOK(result) } +// infra179FinalizeMessage explains the best-effort outcome when gitea's slow +// async template-generate (infra#179) leaves the branch unwritable within the +// budget. It names the concrete remaining work — substituting the two +// placeholders — rather than pointing at a specific tool, so the guidance stays +// correct regardless of scaffolding-CLI state (gitea-mcp#46). +func infra179FinalizeMessage(branch string, budget, done int, underlying string) string { + return fmt.Sprintf( + "repo created, but its branch (%s) was not writable within %ds — gitea's "+ + "template-generate is slow-async on this instance (infra#179), so substitution "+ + "is incomplete (%d file(s) done). Finish it by cloning the repo and replacing the "+ + "remaining __PROJECT_NAME__ / __MODULE_PATH__ placeholders (in file contents and "+ + "paths), then pushing; or retry create once the branch settles. Underlying: %s", + branch, budget, done, underlying) +} + // substitutionBudget bounds how long we retry the first write while the freshly // generated branch becomes writable. gitea's /generate returns (and serves reads) // before the branch ref is committed, so writes 404 "branch does not exist" for a diff --git a/internal/tools/create_project_internal_test.go b/internal/tools/create_project_internal_test.go new file mode 100644 index 0000000..e27f16d --- /dev/null +++ b/internal/tools/create_project_internal_test.go @@ -0,0 +1,22 @@ +package tools + +import ( + "strings" + "testing" +) + +// #46: the infra#179 finalize guidance must not point at a non-existent command +// (`hyperguild new-project` was never built). It should name the real remaining +// work — substituting the placeholders — so the caller isn't sent to a dead end. +func TestInfra179FinalizeMessage(t *testing.T) { + msg := infra179FinalizeMessage("main", 5, 2, "branch does not exist") + + for _, want := range []string{"infra#179", "__PROJECT_NAME__", "__MODULE_PATH__", "branch does not exist"} { + if !strings.Contains(msg, want) { + t.Errorf("message missing %q\ngot: %s", want, msg) + } + } + if strings.Contains(msg, "hyperguild new-project") { + t.Errorf("message must not reference the defunct `hyperguild new-project` command\ngot: %s", msg) + } +}