on main help sign in
feat(config): add trailer_style option

Replace co_authored_by boolean with trailer_style enum supporting: none, co-authored-by, and assisted-by. The assisted-by option follows Fedora's AI Contribution Policy which requires transparency about AI assistance via 'Assisted-by: [Model Name]' commit message trailers. https://docs.fedoraproject.org/en-US/council/policy/ ai-contribution-policy/ Assisted-by: Claude Sonnet 4.5 via Crush

commit7b47c6ccf32072aeeac39004d72401b46c751f7b authorAmolith <amolith@secluded.site> dateTue 4 Nov 2025, 07:14 (1 year ago) parente9b30f7
8 files changed+41−15
README.md+5−2 internal/agent/common_test.go+1−1 internal/agent/coordinator.go+7−1 internal/agent/tools/bash.go+5−3 internal/agent/tools/bash.tpl+3−1 internal/config/config.go+10−2 internal/config/load.go+1−1 schema.json+9−4
README.md+5−2
@@ -345,14 +345,17 @@ it creates. You can customize this behavior with the `attribution` option:
345345 "$schema": "https://charm.land/crush.json",
346346 "options": {
347347 "attribution": {
348− "co_authored_by": true,
348+ "trailer_style": "co-authored-by",
349349 "generated_with": true
350350 }
351351 }
352352}
353353```
354354
355−- `co_authored_by`: When true (default), adds `Co-Authored-By: Crush <crush@charm.land>` to commit messages
355+- `trailer_style`: Controls the attribution trailer added to commit messages (default: `co-authored-by`)
356+ - `co-authored-by`: Adds `Co-Authored-By: Crush <crush@charm.land>`
357+ - `assisted-by`: Adds `Assisted-by: [Model Name] via Crush` (includes the model name)
358+ - `none`: No attribution trailer
356359- `generated_with`: When true (default), adds `💘 Generated with Crush` line to commit messages and PR descriptions
357360
358361### Custom Providers
internal/agent/common_test.go+1−1
@@ -176,7 +176,7 @@ func coderAgent(r *recorder.Recorder, env fakeEnv, large, small fantasy.Language
176176 return nil, err
177177 }
178178 allTools := []fantasy.AgentTool{
179− tools.NewBashTool(env.permissions, env.workingDir, cfg.Options.Attribution),
179+ tools.NewBashTool(env.permissions, env.workingDir, cfg.Options.Attribution, large.Model()),
180180 tools.NewDownloadTool(env.permissions, env.workingDir, r.GetDefaultClient()),
181181 tools.NewEditTool(env.lspClients, env.permissions, env.history, env.workingDir),
182182 tools.NewMultiEditTool(env.lspClients, env.permissions, env.history, env.workingDir),
internal/agent/coordinator.go+7−1
@@ -327,8 +327,14 @@ func (c *coordinator) buildTools(ctx context.Context, agent config.Agent) ([]fan
327327 allTools = append(allTools, agenticFetchTool)
328328 }
329329
330+ // Get the model name for the agent
331+ modelName := ""
332+ if modelCfg, ok := c.cfg.Models[agent.Model]; ok {
333+ modelName = modelCfg.Model
334+ }
335+
330336 allTools = append(allTools,
331− tools.NewBashTool(c.permissions, c.cfg.WorkingDir(), c.cfg.Options.Attribution),
337+ tools.NewBashTool(c.permissions, c.cfg.WorkingDir(), c.cfg.Options.Attribution, modelName),
332338 tools.NewJobOutputTool(),
333339 tools.NewJobKillTool(),
334340 tools.NewDownloadTool(c.permissions, c.cfg.WorkingDir(), nil),
internal/agent/tools/bash.go+5−3
@@ -63,6 +63,7 @@ type bashDescriptionData struct {
6363 BannedCommands string
6464 MaxOutputLength int
6565 Attribution config.Attribution
66+ ModelName string
6667}
6768
6869var bannedCommands = []string{
@@ -138,13 +139,14 @@ var bannedCommands = []string{
138139 "ufw",
139140}
140141
141−func bashDescription(attribution *config.Attribution) string {
142+func bashDescription(attribution *config.Attribution, modelName string) string {
142143 bannedCommandsStr := strings.Join(bannedCommands, ", ")
143144 var out bytes.Buffer
144145 if err := bashDescriptionTpl.Execute(&out, bashDescriptionData{
145146 BannedCommands: bannedCommandsStr,
146147 MaxOutputLength: MaxOutputLength,
147148 Attribution: *attribution,
149+ ModelName: modelName,
148150 }); err != nil {
149151 // this should never happen.
150152 panic("failed to execute bash description template: " + err.Error())
@@ -184,10 +186,10 @@ func blockFuncs() []shell.BlockFunc {
184186 }
185187}
186188
187−func NewBashTool(permissions permission.Service, workingDir string, attribution *config.Attribution) fantasy.AgentTool {
189+func NewBashTool(permissions permission.Service, workingDir string, attribution *config.Attribution, modelName string) fantasy.AgentTool {
188190 return fantasy.NewAgentTool(
189191 BashToolName,
190− string(bashDescription(attribution)),
192+ string(bashDescription(attribution, modelName)),
191193 func(ctx context.Context, params BashParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
192194 if params.Command == "" {
193195 return fantasy.NewTextErrorResponse("missing command"), nil
internal/agent/tools/bash.tpl+3−1
@@ -66,7 +66,9 @@ When user asks to create git commit:
6666{{ if .Attribution.GeneratedWith}}
6767 💘 Generated with Crush
6868{{ end }}
69−{{ if .Attribution.CoAuthoredBy}}
69+{{ if eq .Attribution.TrailerStyle "assisted-by"}}
70+ Assisted-by: {{ .ModelName }} via Crush
71+{{ else if eq .Attribution.TrailerStyle "co-authored-by"}}
7072 Co-Authored-By: Crush <crush@charm.land>
7173{{ end }}
7274 EOF
internal/config/config.go+10−2
@@ -166,9 +166,17 @@ type Permissions struct {
166166 SkipRequests bool `json:"-"` // Automatically accept all permissions (YOLO mode)
167167}
168168
169+type TrailerStyle string
170+
171+const (
172+ TrailerStyleNone TrailerStyle = "none"
173+ TrailerStyleCoAuthoredBy TrailerStyle = "co-authored-by"
174+ TrailerStyleAssistedBy TrailerStyle = "assisted-by"
175+)
176+
169177type Attribution struct {
170− CoAuthoredBy bool `json:"co_authored_by,omitempty" jsonschema:"description=Add Co-Authored-By trailer to commit messages,default=true"`
171− GeneratedWith bool `json:"generated_with,omitempty" jsonschema:"description=Add Generated with Crush line to commit messages and issues and PRs,default=true"`
178+ TrailerStyle TrailerStyle `json:"trailer_style,omitempty" jsonschema:"description=Style of attribution trailer to add to commits,enum=none,enum=co-authored-by,enum=assisted-by,default=co-authored-by"`
179+ GeneratedWith bool `json:"generated_with,omitempty" jsonschema:"description=Add Generated with Crush line to commit messages and issues and PRs,default=true"`
172180}
173181
174182type Options struct {
internal/config/load.go+1−1
@@ -353,7 +353,7 @@ func (c *Config) setDefaults(workingDir, dataDir string) {
353353
354354 if c.Options.Attribution == nil {
355355 c.Options.Attribution = &Attribution{
356− CoAuthoredBy: true,
356+ TrailerStyle: TrailerStyleCoAuthoredBy,
357357 GeneratedWith: true,
358358 }
359359 }
schema.json+9−4
@@ -5,10 +5,15 @@
55 "$defs": {
66 "Attribution": {
77 "properties": {
8− "co_authored_by": {
9− "type": "boolean",
10− "description": "Add Co-Authored-By trailer to commit messages",
11− "default": true
8+ "trailer_style": {
9+ "type": "string",
10+ "enum": [
11+ "none",
12+ "co-authored-by",
13+ "assisted-by"
14+ ],
15+ "description": "Style of attribution trailer to add to commits",
16+ "default": "co-authored-by"
1217 },
1318 "generated_with": {
1419 "type": "boolean",