diff --git a/pkg/workflow/add_comment.go b/pkg/workflow/add_comment.go index 14d72768177..21792e22fa6 100644 --- a/pkg/workflow/add_comment.go +++ b/pkg/workflow/add_comment.go @@ -25,7 +25,6 @@ type AddCommentsConfig struct { Issues *bool `yaml:"issues,omitempty"` // When false, excludes issues:write permission and issues from event condition. Default (nil or true) includes issues:write. PullRequests *bool `yaml:"pull-requests,omitempty"` // When false, excludes pull-requests:write permission and PRs from event condition. Default (nil or true) includes pull-requests:write. Discussions *bool `yaml:"discussions,omitempty"` // When true, includes discussions:write permission. Default (nil or false) excludes discussions:write. - Footer *string `yaml:"footer,omitempty"` // Controls whether AI-generated footer is added. When false, visible footer is omitted but XML markers are kept. } // parseCommentsConfig handles add-comment configuration diff --git a/pkg/workflow/comment_memory.go b/pkg/workflow/comment_memory.go index 528b4b1df8a..8c4e7043f75 100644 --- a/pkg/workflow/comment_memory.go +++ b/pkg/workflow/comment_memory.go @@ -11,7 +11,6 @@ type CommentMemoryConfig struct { TargetRepoSlug string `yaml:"target-repo,omitempty"` // Target repository in owner/repo format AllowedRepos []string `yaml:"allowed-repos,omitempty"` // Additional allowed repositories MemoryID string `yaml:"memory-id,omitempty"` // Default memory identifier when item does not provide memory_id - Footer *string `yaml:"footer,omitempty"` // Footer visibility control ("true"/"false" templatable string); nil defaults to visible footer } const commentMemoryHandlerKey = "comment_memory" diff --git a/pkg/workflow/comment_memory_config_test.go b/pkg/workflow/comment_memory_config_test.go index 4b3ef8a09bb..2430331084c 100644 --- a/pkg/workflow/comment_memory_config_test.go +++ b/pkg/workflow/comment_memory_config_test.go @@ -54,13 +54,13 @@ func TestCommentMemoryToolConfig(t *testing.T) { `, expectedCommentMemory: &CommentMemoryConfig{ BaseSafeOutputConfig: BaseSafeOutputConfig{ - Max: strPtr("3"), + Max: strPtr("3"), + Footer: strPtr("false"), }, MemoryID: "triage", Target: "42", TargetRepoSlug: "github/docs", AllowedRepos: []string{"github/docs", "github/gh-aw"}, - Footer: strPtr("false"), }, }, } diff --git a/pkg/workflow/create_discussion.go b/pkg/workflow/create_discussion.go index b4d691a9833..203c7515e61 100644 --- a/pkg/workflow/create_discussion.go +++ b/pkg/workflow/create_discussion.go @@ -25,7 +25,6 @@ type CreateDiscussionsConfig struct { RequiredCategory string `yaml:"required-category,omitempty"` // Required category for matching when close-older-discussions is enabled Expires int `yaml:"expires,omitempty"` // Hours until the discussion expires and should be automatically closed FallbackToIssue *bool `yaml:"fallback-to-issue,omitempty"` // When true (default), fallback to create-issue if discussion creation fails due to permissions. - Footer *string `yaml:"footer,omitempty"` // Controls whether AI-generated footer is added. When false, visible footer is omitted but XML markers are kept. } // parseCreateDiscussionsConfig handles create-discussion configuration diff --git a/pkg/workflow/create_issue.go b/pkg/workflow/create_issue.go index 5b2622234f0..62b26972075 100644 --- a/pkg/workflow/create_issue.go +++ b/pkg/workflow/create_issue.go @@ -25,7 +25,6 @@ type CreateIssuesConfig struct { GroupByDay *string `yaml:"group-by-day,omitempty"` // When true, if an open issue was already created today (UTC), post new content as a comment on it instead of creating a duplicate. Works best with close-older-issues: true. Expires int `yaml:"expires,omitempty"` // Hours until the issue expires and should be automatically closed Group *string `yaml:"group,omitempty"` // If true, group issues as sub-issues under a parent issue (workflow ID is used as group identifier) - Footer *string `yaml:"footer,omitempty"` // Controls whether AI-generated footer is added. When false, visible footer is omitted but XML markers are kept. } // parseCreateIssuesConfig handles create-issue configuration diff --git a/pkg/workflow/create_pull_request.go b/pkg/workflow/create_pull_request.go index a8c561bd3af..1f7832ac11b 100644 --- a/pkg/workflow/create_pull_request.go +++ b/pkg/workflow/create_pull_request.go @@ -90,48 +90,48 @@ func isTemplatableStagedExpression(value *TemplatableBool) bool { // CreatePullRequestsConfig holds configuration for creating GitHub pull requests from agent output type CreatePullRequestsConfig struct { - BaseSafeOutputConfig `yaml:",inline"` - SafeOutputAllowedLabelsConfig `yaml:",inline"` - BranchPrefix string `yaml:"branch-prefix,omitempty"` // Optional prefix for the pull request branch name (e.g. "signed/"). Applied before the agent-specified or auto-generated branch name. - PreCreate bool `yaml:"pre-create,omitempty"` // Experimental. Pre-create a draft pull request in the activation job and reuse it for the agent output. - TitlePrefix string `yaml:"title-prefix,omitempty"` - RequireTemporaryID bool `yaml:"require-temporary-id,omitempty"` // When true, create_pull_request tool calls must include temporary_id. - Labels []string `yaml:"labels,omitempty"` - Reviewers []string `yaml:"reviewers,omitempty"` // List of users/bots to assign as reviewers to the pull request. Accepts a static list or a single GitHub Actions expression. - TeamReviewers []string `yaml:"team-reviewers,omitempty"` // List of team slugs to assign as team reviewers to the pull request. Accepts a static list or a single GitHub Actions expression. - Assignees []string `yaml:"assignees,omitempty"` // List of users to assign to the created pull request and any fallback issue. Accepts a static list or a single GitHub Actions expression. - FallbackLabels []string `yaml:"fallback-labels,omitempty"` // List of labels to apply to fallback issues created when PR creation cannot proceed. If omitted, fallback issues reuse PR labels. - Draft *string `yaml:"draft,omitempty"` // Pointer to distinguish between unset (nil), literal bool, and expression values - IfNoChanges string `yaml:"if-no-changes,omitempty"` // Behavior when no changes to push: "warn" (default), "error", or "ignore" - AllowEmpty *string `yaml:"allow-empty,omitempty"` // Allow creating PR without patch file or with empty patch (useful for preparing feature branches) - TargetRepoSlug string `yaml:"target-repo,omitempty"` // Target repository in format "owner/repo" for cross-repository pull requests - HeadRepoSlug string `yaml:"head-repo,omitempty"` // Head repository in format "owner/repo" for fork-backed pull requests; defaults to target-repo when unset - HeadGitHubToken string `yaml:"head-github-token,omitempty"` // GitHub token used for branch writes to the head repository when it differs from the target repo - HeadGitHubApp *GitHubAppConfig `yaml:"-"` // GitHub App used to mint the head token for fork branch writes; parsed manually to support app-id alias - AllowedRepos []string `yaml:"allowed-repos,omitempty"` // List of additional repositories that pull requests can be created in (additionally to the target-repo) - AllowedBaseBranches []string `yaml:"allowed-base-branches,omitempty"` // List of allowed base branch globs (e.g. "release/*"). Enables agent-provided `base` override when configured. - AllowedBranches []string `yaml:"allowed-branches,omitempty"` // List of allowed source branch globs (e.g. "feature/*"). Branch in create_pull_request payload must match when configured. - Stacked *bool `yaml:"stacked,omitempty"` // When false, rejects any pull request whose base branch is not the default base branch. Defaults to true; set to false on GitHub Enterprise Server instances without stacked pull request support. - MaxPatchSize int `yaml:"max-patch-size,omitempty"` // Maximum allowed patch size in KB for create-pull-request only. Overrides safe-outputs.max-patch-size when set. - MaxPatchFiles int `yaml:"max-patch-files,omitempty"` // Maximum allowed unique files in create-pull-request patch only. Overrides safe-outputs.max-patch-files when set. - Expires int `yaml:"expires,omitempty"` // Hours until the pull request expires and should be automatically closed (only for same-repo PRs) - AutoMerge *string `yaml:"auto-merge,omitempty"` // Enable auto-merge for the pull request; accepts true/false or merge method strings squash|merge|rebase - BaseBranch string `yaml:"base-branch,omitempty"` // Base branch for the pull request (defaults to github.ref_name if not specified) - Footer *string `yaml:"footer,omitempty"` // Controls whether AI-generated footer is added. When false, visible footer is omitted but XML markers are kept. - FallbackAsIssue *bool `yaml:"fallback-as-issue,omitempty"` // When true (default), creates an issue if PR creation fails. When false, no fallback occurs and issues: write permission is not requested. - AutoCloseIssue *string `yaml:"auto-close-issue,omitempty"` // Auto-add "Fixes #N" closing keyword when triggered from an issue (default: true). Set to false to prevent auto-closing the triggering issue on PR merge. Accepts a boolean or a GitHub Actions expression. - GithubTokenForExtraEmptyCommit string `yaml:"github-token-for-extra-empty-commit,omitempty"` // Token used to push an empty commit to trigger CI events. Use a PAT or "app" for GitHub App auth. - ManifestFilesPolicy *string `yaml:"protected-files,omitempty"` // Controls protected-file protection: "request_review" (default) creates a PR and submits a REQUEST_CHANGES review, "blocked" hard-blocks, "allowed" permits all changes, and "fallback-to-issue" creates a review issue instead of a PR. - ProtectedFilesExclude []string `yaml:"-"` // Files/prefixes to exclude from the default protected list (from object-form protected-files.exclude). Not sourced from YAML directly; populated during pre-processing. - AllowedFiles []string `yaml:"allowed-files,omitempty"` // Strict allowlist of glob patterns for files eligible for create. Checked independently of protected-files; both checks must pass. - ExcludedFiles []string `yaml:"excluded-files,omitempty"` // List of glob patterns for files to exclude from the patch using git :(exclude) pathspecs. Matching files are stripped by git at generation time and will not appear in the commit or be subject to allowed-files or protected-files checks. - PreserveBranchName bool `yaml:"preserve-branch-name,omitempty"` // When true, skips the random salt suffix on agent-specified branch names. Invalid characters are still replaced for security; casing is always preserved. Useful when CI enforces branch naming conventions (e.g. Jira keys in uppercase). - RecreateRef bool `yaml:"recreate-ref,omitempty"` // When true (and preserve-branch-name is true), allows the handler to force-delete an existing remote branch ref and recreate it from the agent's local HEAD. When false (default), an existing remote branch causes a fallback to issue (or push_failed). Useful for long-lived reusable branches whose previous PR was merged. - PatchFormat string `yaml:"patch-format,omitempty"` // Transport format for packaging changes: "bundle" (default, uses git bundle and preserves merge topology/per-commit metadata) or "am" (uses git format-patch). - SignedCommits *bool `yaml:"signed-commits,omitempty"` // When false, skips GitHub GraphQL signed commits and pushes the local git history directly. Default is true. - AllowWorkflows bool `yaml:"allow-workflows,omitempty"` // When true, adds workflows: write to the GitHub App token. Requires safe-outputs.github-app to be configured. - CloseOlderPullRequests *string `yaml:"close-older-pull-requests,omitempty"` // When true, close older open pull requests with the same workflow-id marker when a new one is created. Capped at 10 closures per run. - CloseOlderKey string `yaml:"close-older-key,omitempty"` // Optional explicit deduplication key for close-older matching. When set, uses gh-aw-close-key marker instead of workflow-id markers. + BaseSafeOutputConfig `yaml:",inline"` + SafeOutputAllowedLabelsConfig `yaml:",inline"` + BranchPrefix string `yaml:"branch-prefix,omitempty"` // Optional prefix for the pull request branch name (e.g. "signed/"). Applied before the agent-specified or auto-generated branch name. + PreCreate bool `yaml:"pre-create,omitempty"` // Experimental. Pre-create a draft pull request in the activation job and reuse it for the agent output. + TitlePrefix string `yaml:"title-prefix,omitempty"` + RequireTemporaryID bool `yaml:"require-temporary-id,omitempty"` // When true, create_pull_request tool calls must include temporary_id. + Labels []string `yaml:"labels,omitempty"` + Reviewers []string `yaml:"reviewers,omitempty"` // List of users/bots to assign as reviewers to the pull request. Accepts a static list or a single GitHub Actions expression. + TeamReviewers []string `yaml:"team-reviewers,omitempty"` // List of team slugs to assign as team reviewers to the pull request. Accepts a static list or a single GitHub Actions expression. + Assignees []string `yaml:"assignees,omitempty"` // List of users to assign to the created pull request and any fallback issue. Accepts a static list or a single GitHub Actions expression. + FallbackLabels []string `yaml:"fallback-labels,omitempty"` // List of labels to apply to fallback issues created when PR creation cannot proceed. If omitted, fallback issues reuse PR labels. + Draft *string `yaml:"draft,omitempty"` // Pointer to distinguish between unset (nil), literal bool, and expression values + IfNoChanges string `yaml:"if-no-changes,omitempty"` // Behavior when no changes to push: "warn" (default), "error", or "ignore" + AllowEmpty *string `yaml:"allow-empty,omitempty"` // Allow creating PR without patch file or with empty patch (useful for preparing feature branches) + TargetRepoSlug string `yaml:"target-repo,omitempty"` // Target repository in format "owner/repo" for cross-repository pull requests + HeadRepoSlug string `yaml:"head-repo,omitempty"` // Head repository in format "owner/repo" for fork-backed pull requests; defaults to target-repo when unset + HeadGitHubToken string `yaml:"head-github-token,omitempty"` // GitHub token used for branch writes to the head repository when it differs from the target repo + HeadGitHubApp *GitHubAppConfig `yaml:"-"` // GitHub App used to mint the head token for fork branch writes; parsed manually to support app-id alias + AllowedRepos []string `yaml:"allowed-repos,omitempty"` // List of additional repositories that pull requests can be created in (additionally to the target-repo) + AllowedBaseBranches []string `yaml:"allowed-base-branches,omitempty"` // List of allowed base branch globs (e.g. "release/*"). Enables agent-provided `base` override when configured. + AllowedBranches []string `yaml:"allowed-branches,omitempty"` // List of allowed source branch globs (e.g. "feature/*"). Branch in create_pull_request payload must match when configured. + Stacked *bool `yaml:"stacked,omitempty"` // When false, rejects any pull request whose base branch is not the default base branch. Defaults to true; set to false on GitHub Enterprise Server instances without stacked pull request support. + MaxPatchSize int `yaml:"max-patch-size,omitempty"` // Maximum allowed patch size in KB for create-pull-request only. Overrides safe-outputs.max-patch-size when set. + MaxPatchFiles int `yaml:"max-patch-files,omitempty"` // Maximum allowed unique files in create-pull-request patch only. Overrides safe-outputs.max-patch-files when set. + Expires int `yaml:"expires,omitempty"` // Hours until the pull request expires and should be automatically closed (only for same-repo PRs) + AutoMerge *string `yaml:"auto-merge,omitempty"` // Enable auto-merge for the pull request; accepts true/false or merge method strings squash|merge|rebase + BaseBranch string `yaml:"base-branch,omitempty"` // Base branch for the pull request (defaults to github.ref_name if not specified) + + FallbackAsIssue *bool `yaml:"fallback-as-issue,omitempty"` // When true (default), creates an issue if PR creation fails. When false, no fallback occurs and issues: write permission is not requested. + AutoCloseIssue *string `yaml:"auto-close-issue,omitempty"` // Auto-add "Fixes #N" closing keyword when triggered from an issue (default: true). Set to false to prevent auto-closing the triggering issue on PR merge. Accepts a boolean or a GitHub Actions expression. + GithubTokenForExtraEmptyCommit string `yaml:"github-token-for-extra-empty-commit,omitempty"` // Token used to push an empty commit to trigger CI events. Use a PAT or "app" for GitHub App auth. + ManifestFilesPolicy *string `yaml:"protected-files,omitempty"` // Controls protected-file protection: "request_review" (default) creates a PR and submits a REQUEST_CHANGES review, "blocked" hard-blocks, "allowed" permits all changes, and "fallback-to-issue" creates a review issue instead of a PR. + ProtectedFilesExclude []string `yaml:"-"` // Files/prefixes to exclude from the default protected list (from object-form protected-files.exclude). Not sourced from YAML directly; populated during pre-processing. + AllowedFiles []string `yaml:"allowed-files,omitempty"` // Strict allowlist of glob patterns for files eligible for create. Checked independently of protected-files; both checks must pass. + ExcludedFiles []string `yaml:"excluded-files,omitempty"` // List of glob patterns for files to exclude from the patch using git :(exclude) pathspecs. Matching files are stripped by git at generation time and will not appear in the commit or be subject to allowed-files or protected-files checks. + PreserveBranchName bool `yaml:"preserve-branch-name,omitempty"` // When true, skips the random salt suffix on agent-specified branch names. Invalid characters are still replaced for security; casing is always preserved. Useful when CI enforces branch naming conventions (e.g. Jira keys in uppercase). + RecreateRef bool `yaml:"recreate-ref,omitempty"` // When true (and preserve-branch-name is true), allows the handler to force-delete an existing remote branch ref and recreate it from the agent's local HEAD. When false (default), an existing remote branch causes a fallback to issue (or push_failed). Useful for long-lived reusable branches whose previous PR was merged. + PatchFormat string `yaml:"patch-format,omitempty"` // Transport format for packaging changes: "bundle" (default, uses git bundle and preserves merge topology/per-commit metadata) or "am" (uses git format-patch). + SignedCommits *bool `yaml:"signed-commits,omitempty"` // When false, skips GitHub GraphQL signed commits and pushes the local git history directly. Default is true. + AllowWorkflows bool `yaml:"allow-workflows,omitempty"` // When true, adds workflows: write to the GitHub App token. Requires safe-outputs.github-app to be configured. + CloseOlderPullRequests *string `yaml:"close-older-pull-requests,omitempty"` // When true, close older open pull requests with the same workflow-id marker when a new one is created. Capped at 10 closures per run. + CloseOlderKey string `yaml:"close-older-key,omitempty"` // Optional explicit deduplication key for close-older matching. When set, uses gh-aw-close-key marker instead of workflow-id markers. } // parseCreatePullRequestsConfig handles only create-pull-request (singular) configuration diff --git a/pkg/workflow/reply_to_pr_review_comment.go b/pkg/workflow/reply_to_pr_review_comment.go index 86e480012f0..cc3cf5c22ab 100644 --- a/pkg/workflow/reply_to_pr_review_comment.go +++ b/pkg/workflow/reply_to_pr_review_comment.go @@ -12,7 +12,6 @@ type ReplyToPullRequestReviewCommentConfig struct { BaseSafeOutputConfig `yaml:",inline"` SafeOutputTargetConfig `yaml:",inline"` SafeOutputFilterConfig `yaml:",inline"` - Footer *string `yaml:"footer,omitempty"` // Whether to add AI-generated footer to replies } // parseReplyToPullRequestReviewCommentConfig handles reply-to-pull-request-review-comment configuration diff --git a/pkg/workflow/safe_outputs_config_generation_test.go b/pkg/workflow/safe_outputs_config_generation_test.go index ea513b8bd84..2734d06d4d7 100644 --- a/pkg/workflow/safe_outputs_config_generation_test.go +++ b/pkg/workflow/safe_outputs_config_generation_test.go @@ -1137,13 +1137,15 @@ func TestGenerateSafeOutputsConfigReplyToPullRequestReviewCommentWithTarget(t *t data := &WorkflowData{ SafeOutputs: &SafeOutputsConfig{ ReplyToPullRequestReviewComment: &ReplyToPullRequestReviewCommentConfig{ - BaseSafeOutputConfig: BaseSafeOutputConfig{Max: strPtr("10")}, + BaseSafeOutputConfig: BaseSafeOutputConfig{ + Max: strPtr("10"), + Footer: &footerTrue, + }, SafeOutputTargetConfig: SafeOutputTargetConfig{ Target: "pull_request", TargetRepoSlug: "org/other-repo", AllowedRepos: []string{"org/other-repo"}, }, - Footer: &footerTrue, }, }, } diff --git a/pkg/workflow/safe_outputs_config_types.go b/pkg/workflow/safe_outputs_config_types.go index 657e4e2cbd5..2f32300267d 100644 --- a/pkg/workflow/safe_outputs_config_types.go +++ b/pkg/workflow/safe_outputs_config_types.go @@ -24,6 +24,7 @@ type BaseSafeOutputConfig struct { GitHubToken string `yaml:"github-token,omitempty"` // GitHub token for this specific output type GitHubApp *GitHubAppConfig `yaml:"github-app,omitempty"` // GitHub App credentials for minting a per-handler installation access token Staged *TemplatableBool `yaml:"staged,omitempty"` // Templatable preview-only mode for this specific output type + Footer *string `yaml:"footer,omitempty"` // Controls AI-generated footer behavior. Values vary by handler; false omits the visible footer but keeps XML markers. IssueIntent *bool `yaml:"issue-intent,omitempty"` // When true, enable issue-intent rationale/confidence guidance and schema requirements for this output type. NormalizeClosingKeywords *bool `yaml:"normalize-closing-keywords,omitempty"` // When true for this output type, strip backticks from recognized issue-closing keywords in body fields. // Samples carries deterministic replay samples for the hidden diff --git a/pkg/workflow/safe_outputs_footer_test.go b/pkg/workflow/safe_outputs_footer_test.go index 8da9d0c086c..98dc0c9d85c 100644 --- a/pkg/workflow/safe_outputs_footer_test.go +++ b/pkg/workflow/safe_outputs_footer_test.go @@ -211,8 +211,10 @@ func TestFooterInHandlerConfig(t *testing.T) { Name: "Test", SafeOutputs: &SafeOutputsConfig{ CreateIssues: &CreateIssuesConfig{ - BaseSafeOutputConfig: BaseSafeOutputConfig{Max: strPtr("1")}, - Footer: strPtr("false"), + BaseSafeOutputConfig: BaseSafeOutputConfig{ + Max: strPtr("1"), + Footer: strPtr("false"), + }, }, }, } diff --git a/pkg/workflow/submit_pr_review.go b/pkg/workflow/submit_pr_review.go index edc07bff36f..7da1de0f4f5 100644 --- a/pkg/workflow/submit_pr_review.go +++ b/pkg/workflow/submit_pr_review.go @@ -17,7 +17,6 @@ type SubmitPullRequestReviewConfig struct { BaseSafeOutputConfig `yaml:",inline"` SafeOutputTargetConfig `yaml:",inline"` SafeOutputFilterConfig `yaml:",inline"` - Footer *string `yaml:"footer,omitempty"` // Controls when to show footer in PR review body: "always" (default), "none", or "if-body" (only when review has body text) AllowedEvents []string `yaml:"allowed-events,omitempty"` // Optional list of allowed review event types: APPROVE, COMMENT, REQUEST_CHANGES. If omitted, all event types are allowed. SupersedeOlderReviews bool `yaml:"supersede-older-reviews,omitempty"` // When true, dismisses older same-workflow REQUEST_CHANGES reviews after a replacement review is posted. CommitId string `yaml:"commit-id,omitempty"` // When set, pins the review to this commit SHA instead of the current PR head. diff --git a/pkg/workflow/submit_pr_review_footer_test.go b/pkg/workflow/submit_pr_review_footer_test.go index 09eaf6dfe83..1611bf272e2 100644 --- a/pkg/workflow/submit_pr_review_footer_test.go +++ b/pkg/workflow/submit_pr_review_footer_test.go @@ -345,8 +345,10 @@ func TestSubmitPRReviewFooterInHandlerConfig(t *testing.T) { Name: "Test", SafeOutputs: &SafeOutputsConfig{ SubmitPullRequestReview: &SubmitPullRequestReviewConfig{ - BaseSafeOutputConfig: BaseSafeOutputConfig{Max: strPtr("1")}, - Footer: &footerValue, + BaseSafeOutputConfig: BaseSafeOutputConfig{ + Max: strPtr("1"), + Footer: &footerValue, + }, }, CreatePullRequestReviewComments: &CreatePullRequestReviewCommentsConfig{ BaseSafeOutputConfig: BaseSafeOutputConfig{Max: strPtr("10")}, diff --git a/pkg/workflow/update_discussion.go b/pkg/workflow/update_discussion.go index cb763b8135a..910f72b0266 100644 --- a/pkg/workflow/update_discussion.go +++ b/pkg/workflow/update_discussion.go @@ -13,10 +13,9 @@ var updateDiscussionLog = logger.New("workflow:update_discussion") type UpdateDiscussionsConfig struct { UpdateEntityConfig `yaml:",inline"` SafeOutputAllowedLabelsConfig `yaml:",inline"` - Title *bool `yaml:"title,omitempty"` // Allow updating discussion title - presence indicates field can be updated - Body *bool `yaml:"body,omitempty"` // Allow updating discussion body - presence indicates field can be updated - Labels *bool `yaml:"labels,omitempty"` // Allow updating discussion labels - presence indicates field can be updated - Footer *string `yaml:"footer,omitempty"` // Controls whether AI-generated footer is added. When false, visible footer is omitted but XML markers are kept. + Title *bool `yaml:"title,omitempty"` // Allow updating discussion title - presence indicates field can be updated + Body *bool `yaml:"body,omitempty"` // Allow updating discussion body - presence indicates field can be updated + Labels *bool `yaml:"labels,omitempty"` // Allow updating discussion labels - presence indicates field can be updated } // parseUpdateDiscussionsConfig handles update-discussion configuration diff --git a/pkg/workflow/update_entity_helpers.go b/pkg/workflow/update_entity_helpers.go index 340f1f0fd9f..69af60c7684 100644 --- a/pkg/workflow/update_entity_helpers.go +++ b/pkg/workflow/update_entity_helpers.go @@ -321,6 +321,11 @@ type UpdateEntityParseOptions struct { Logger *logger.Logger // Logger for this entity type Fields []UpdateEntityFieldSpec // Field specifications to parse CustomParser func(map[string]any) // Optional custom field parser + // AfterBaseParse is called with the parsed base config before entity-specific + // fields are parsed. This lets callers copy the base config into their own + // struct first, so field specs that point at promoted base fields (such as + // Footer) are not overwritten afterwards. + AfterBaseParse func(*UpdateEntityConfig) } // parseUpdateEntityConfigWithFields is a generic helper that reduces scaffolding duplication @@ -356,6 +361,12 @@ func (c *Compiler) parseUpdateEntityConfigWithFields( return nil, nil } + // Let the caller copy the base config before entity-specific fields are parsed, + // so field specs writing to promoted base fields are not clobbered afterwards. + if opts.AfterBaseParse != nil { + opts.AfterBaseParse(baseConfig) + } + // Parse entity-specific bool fields according to specs for _, field := range opts.Fields { if field.Mode == FieldParsingTemplatableBool { @@ -384,10 +395,10 @@ func (c *Compiler) parseUpdateEntityConfigWithFields( // It handles the complete parsing flow: // 1. Creates entity-specific config struct // 2. Builds field specs with pointers to config fields -// 3. Calls parseUpdateEntityConfigWithFields +// 3. Calls parseUpdateEntityConfigWithFields, which copies the base config into +// the entity-specific struct before parsing entity-specific fields // 4. Checks for nil result (early return) -// 5. Copies base config into entity-specific struct -// 6. Returns typed config +// 5. Returns typed config // // Type parameters: // - T: The entity-specific config type (must embed UpdateEntityConfig) @@ -443,6 +454,12 @@ func parseUpdateEntityConfigTyped[T any, PT interface { ConfigKey: configKey, Logger: logger, Fields: fields, + // Assign the base config through the promoted setter on the embedded + // UpdateEntityConfig before entity-specific fields are parsed, so field + // specs targeting promoted base fields (e.g. Footer) survive. + AfterBaseParse: func(baseConfig *UpdateEntityConfig) { + PT(cfg).setUpdateEntityConfig(*baseConfig) + }, } // Add custom parser wrapper if provided @@ -452,14 +469,12 @@ func parseUpdateEntityConfigTyped[T any, PT interface { } } - // Parse base config and entity-specific fields + // Parse base config and entity-specific fields; the base config is assigned to + // cfg via AfterBaseParse before entity-specific fields are parsed. baseConfig, _ := c.parseUpdateEntityConfigWithFields(outputMap, opts) if baseConfig == nil { return nil } - // Assign the base config through the promoted setter on the embedded UpdateEntityConfig - PT(cfg).setUpdateEntityConfig(*baseConfig) - return cfg } diff --git a/pkg/workflow/update_issue.go b/pkg/workflow/update_issue.go index f56f1877d1f..c53b454c719 100644 --- a/pkg/workflow/update_issue.go +++ b/pkg/workflow/update_issue.go @@ -15,7 +15,6 @@ type UpdateIssuesConfig struct { Status *bool `yaml:"status,omitempty"` // Allow updating issue status (open/closed) - presence indicates field can be updated Title *bool `yaml:"title,omitempty"` // Allow updating issue title - presence indicates field can be updated Body *bool `yaml:"body,omitempty"` // Allow updating issue body - boolean value controls permission (defaults to true) - Footer *string `yaml:"footer,omitempty"` // Controls whether AI-generated footer is added. When false, visible footer is omitted but XML markers are kept. TitlePrefix string `yaml:"title-prefix,omitempty"` // Required title prefix for issue validation - only issues with this prefix can be updated (deprecated: use required-title-prefix) RequiredTitlePrefix string `yaml:"required-title-prefix,omitempty"` // Title prefix the issue must have (preferred over title-prefix) RequiredLabels []string `yaml:"required-labels,omitempty"` // Labels that must ALL be present on the issue diff --git a/pkg/workflow/update_pull_request.go b/pkg/workflow/update_pull_request.go index 5475cecba21..91ea1a0dbb0 100644 --- a/pkg/workflow/update_pull_request.go +++ b/pkg/workflow/update_pull_request.go @@ -18,7 +18,6 @@ type UpdatePullRequestsConfig struct { UpdateBranch *bool `yaml:"update-branch,omitempty"` // When true, update PR branch with latest base branch changes before applying other updates. Defaults to false. UpdateBranchStacks *bool `yaml:"sync-stack,omitempty"` // When true, allow stacked-PR stack-sync fallback if update-branch endpoint is unsupported. Defaults to true. Operation *string `yaml:"operation,omitempty"` // Default operation for body updates: "append", "prepend", or "replace" (defaults to "replace") - Footer *string `yaml:"footer,omitempty"` // Controls whether AI-generated footer is added. When false, visible footer is omitted. } // parseUpdatePullRequestsConfig handles update-pull-request configuration diff --git a/pkg/workflow/update_release.go b/pkg/workflow/update_release.go index 5502bfb29fd..2c9c8379be4 100644 --- a/pkg/workflow/update_release.go +++ b/pkg/workflow/update_release.go @@ -9,7 +9,6 @@ var updateReleaseLog = logger.New("workflow:update_release") // UpdateReleaseConfig holds configuration for updating GitHub releases from agent output type UpdateReleaseConfig struct { UpdateEntityConfig `yaml:",inline"` - Footer *string `yaml:"footer,omitempty"` // Controls whether AI-generated footer is added. When false, visible footer is omitted but XML markers are kept. } // parseUpdateReleaseConfig handles update-release configuration