Merge pull request #833 from KNN-07/fix/git-master-watermark-injection

fix(git-master): inject watermark only when enabled instead of overriding defaults
This commit is contained in:
Kenny 2026-01-15 20:36:18 -05:00 committed by GitHub
commit f888da8848
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 157 additions and 86 deletions

View File

@ -1,6 +1,6 @@
import type { AgentConfig } from "@opencode-ai/sdk"
import type { BuiltinAgentName, AgentOverrideConfig, AgentOverrides, AgentFactory, AgentPromptMetadata } from "./types"
import type { CategoriesConfig, CategoryConfig } from "../config/schema"
import type { CategoriesConfig, CategoryConfig, GitMasterConfig } from "../config/schema"
import { createSisyphusAgent } from "./sisyphus"
import { createOracleAgent, ORACLE_PROMPT_METADATA } from "./oracle"
import { createLibrarianAgent, LIBRARIAN_PROMPT_METADATA } from "./librarian"
@ -51,7 +51,8 @@ function isFactory(source: AgentSource): source is AgentFactory {
export function buildAgent(
source: AgentSource,
model?: string,
categories?: CategoriesConfig
categories?: CategoriesConfig,
gitMasterConfig?: GitMasterConfig
): AgentConfig {
const base = isFactory(source) ? source(model) : source
const categoryConfigs: Record<string, CategoryConfig> = categories
@ -75,7 +76,7 @@ export function buildAgent(
}
if (agentWithCategory.skills?.length) {
const { resolved } = resolveMultipleSkills(agentWithCategory.skills)
const { resolved } = resolveMultipleSkills(agentWithCategory.skills, { gitMasterConfig })
if (resolved.size > 0) {
const skillContent = Array.from(resolved.values()).join("\n\n")
base.prompt = skillContent + (base.prompt ? "\n\n" + base.prompt : "")
@ -130,7 +131,8 @@ export function createBuiltinAgents(
agentOverrides: AgentOverrides = {},
directory?: string,
systemDefaultModel?: string,
categories?: CategoriesConfig
categories?: CategoriesConfig,
gitMasterConfig?: GitMasterConfig
): Record<string, AgentConfig> {
const result: Record<string, AgentConfig> = {}
const availableAgents: AvailableAgent[] = []
@ -149,7 +151,7 @@ export function createBuiltinAgents(
const override = agentOverrides[agentName]
const model = override?.model
let config = buildAgent(source, model, mergedCategories)
let config = buildAgent(source, model, mergedCategories, gitMasterConfig)
if (agentName === "librarian" && directory && config.prompt) {
const envContext = createEnvContext()

View File

@ -529,33 +529,6 @@ IF style == SHORT:
3. Is it similar to examples from git log?
If ANY check fails -> REWRITE message.
### 5.5 Commit Footer & Co-Author (Configurable)
**Check oh-my-opencode.json for these flags:**
- `git_master.commit_footer` (default: true) - adds footer message
- `git_master.include_co_authored_by` (default: true) - adds co-author trailer
If enabled, add Sisyphus attribution to EVERY commit:
1. **Footer in commit body (if `commit_footer: true`):**
```
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
```
2. **Co-authored-by trailer (if `include_co_authored_by: true`):**
```
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
```
**Example (both enabled):**
```bash
git commit -m "{Commit Message}" -m "Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)" -m "Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>"
```
**To disable:** Set in oh-my-opencode.json:
```json
{ "git_master": { "commit_footer": false, "include_co_authored_by": false } }
```
</execution>

View File

@ -622,35 +622,8 @@ IF style == SHORT:
3. Is it similar to examples from git log?
If ANY check fails -> REWRITE message.
### 5.5 Commit Footer & Co-Author (Configurable)
**Check oh-my-opencode.json for these flags:**
- \`git_master.commit_footer\` (default: true) - adds footer message
- \`git_master.include_co_authored_by\` (default: true) - adds co-author trailer
If enabled, add Sisyphus attribution to EVERY commit:
1. **Footer in commit body (if \`commit_footer: true\`):**
\`\`\`
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
\`\`\`
2. **Co-authored-by trailer (if \`include_co_authored_by: true\`):**
\`\`\`
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
\`\`\`
**Example (both enabled):**
\`\`\`bash
git commit -m "{Commit Message}" -m "Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)" -m "Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>"
\`\`\`
**To disable:** Set in oh-my-opencode.json:
\`\`\`json
{ "git_master": { "commit_footer": false, "include_co_authored_by": false } }
\`\`\`
</execution>
\</execution>
---

View File

@ -160,8 +160,8 @@ describe("resolveMultipleSkillsAsync", () => {
expect(result.resolved.get("playwright")).toContain("Playwright Browser Automation")
})
it("should support git-master config injection", async () => {
// #given: git-master skill with config override
it("should NOT inject watermark when both options are disabled", async () => {
// #given: git-master skill with watermark disabled
const skillNames = ["git-master"]
const options = {
gitMasterConfig: {
@ -173,12 +173,84 @@ describe("resolveMultipleSkillsAsync", () => {
// #when: resolving with git-master config
const result = await resolveMultipleSkillsAsync(skillNames, options)
// #then: config values injected into template
// #then: no watermark section injected
expect(result.resolved.size).toBe(1)
expect(result.notFound).toEqual([])
const gitMasterContent = result.resolved.get("git-master")
expect(gitMasterContent).toContain("commit_footer")
expect(gitMasterContent).toContain("DISABLED")
expect(gitMasterContent).not.toContain("Ultraworked with")
expect(gitMasterContent).not.toContain("Co-authored-by: Sisyphus")
})
it("should inject watermark when enabled (default)", async () => {
// #given: git-master skill with default config (watermark enabled)
const skillNames = ["git-master"]
const options = {
gitMasterConfig: {
commit_footer: true,
include_co_authored_by: true,
},
}
// #when: resolving with git-master config
const result = await resolveMultipleSkillsAsync(skillNames, options)
// #then: watermark section is injected
expect(result.resolved.size).toBe(1)
const gitMasterContent = result.resolved.get("git-master")
expect(gitMasterContent).toContain("Ultraworked with [Sisyphus]")
expect(gitMasterContent).toContain("Co-authored-by: Sisyphus")
})
it("should inject only footer when co-author is disabled", async () => {
// #given: git-master skill with only footer enabled
const skillNames = ["git-master"]
const options = {
gitMasterConfig: {
commit_footer: true,
include_co_authored_by: false,
},
}
// #when: resolving with git-master config
const result = await resolveMultipleSkillsAsync(skillNames, options)
// #then: only footer is injected
const gitMasterContent = result.resolved.get("git-master")
expect(gitMasterContent).toContain("Ultraworked with [Sisyphus]")
expect(gitMasterContent).not.toContain("Co-authored-by: Sisyphus")
})
it("should inject watermark by default when no config provided", async () => {
// #given: git-master skill with NO config (default behavior)
const skillNames = ["git-master"]
// #when: resolving without any gitMasterConfig
const result = await resolveMultipleSkillsAsync(skillNames)
// #then: watermark is injected (default is ON)
expect(result.resolved.size).toBe(1)
const gitMasterContent = result.resolved.get("git-master")
expect(gitMasterContent).toContain("Ultraworked with [Sisyphus]")
expect(gitMasterContent).toContain("Co-authored-by: Sisyphus")
})
it("should inject only co-author when footer is disabled", async () => {
// #given: git-master skill with only co-author enabled
const skillNames = ["git-master"]
const options = {
gitMasterConfig: {
commit_footer: false,
include_co_authored_by: true,
},
}
// #when: resolving with git-master config
const result = await resolveMultipleSkillsAsync(skillNames, options)
// #then: only co-author is injected
const gitMasterContent = result.resolved.get("git-master")
expect(gitMasterContent).not.toContain("Ultraworked with [Sisyphus]")
expect(gitMasterContent).toContain("Co-authored-by: Sisyphus")
})
it("should handle empty array", async () => {

View File

@ -59,22 +59,62 @@ async function extractSkillTemplate(skill: LoadedSkill): Promise<string> {
export { clearSkillCache, getAllSkills, extractSkillTemplate }
function injectGitMasterConfig(template: string, config?: GitMasterConfig): string {
if (!config) return template
export function injectGitMasterConfig(template: string, config?: GitMasterConfig): string {
const commitFooter = config?.commit_footer ?? true
const includeCoAuthoredBy = config?.include_co_authored_by ?? true
const commitFooter = config.commit_footer ?? true
const includeCoAuthoredBy = config.include_co_authored_by ?? true
if (!commitFooter && !includeCoAuthoredBy) {
return template
}
const configHeader = `## Git Master Configuration (from oh-my-opencode.json)
const sections: string[] = []
**IMPORTANT: These values override the defaults in section 5.5:**
- \`commit_footer\`: ${commitFooter} ${!commitFooter ? "(DISABLED - do NOT add footer)" : ""}
- \`include_co_authored_by\`: ${includeCoAuthoredBy} ${!includeCoAuthoredBy ? "(DISABLED - do NOT add Co-authored-by)" : ""}
sections.push(`### 5.5 Commit Footer & Co-Author`)
sections.push(``)
sections.push(`Add Sisyphus attribution to EVERY commit:`)
sections.push(``)
---
if (commitFooter) {
sections.push(`1. **Footer in commit body:**`)
sections.push("```")
sections.push(`Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)`)
sections.push("```")
sections.push(``)
}
`
return configHeader + template
if (includeCoAuthoredBy) {
sections.push(`${commitFooter ? "2" : "1"}. **Co-authored-by trailer:**`)
sections.push("```")
sections.push(`Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>`)
sections.push("```")
sections.push(``)
}
if (commitFooter && includeCoAuthoredBy) {
sections.push(`**Example (both enabled):**`)
sections.push("```bash")
sections.push(`git commit -m "{Commit Message}" -m "Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)" -m "Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>"`)
sections.push("```")
} else if (commitFooter) {
sections.push(`**Example:**`)
sections.push("```bash")
sections.push(`git commit -m "{Commit Message}" -m "Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)"`)
sections.push("```")
} else if (includeCoAuthoredBy) {
sections.push(`**Example:**`)
sections.push("```bash")
sections.push(`git commit -m "{Commit Message}" -m "Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>"`)
sections.push("```")
}
const injection = sections.join("\n")
const insertionPoint = template.indexOf("```\n</execution>")
if (insertionPoint !== -1) {
return template.slice(0, insertionPoint) + "```\n\n" + injection + "\n</execution>" + template.slice(insertionPoint + "```\n</execution>".length)
}
return template + "\n\n" + injection
}
export function resolveSkillContent(skillName: string, options?: SkillResolutionOptions): string | null {
@ -82,8 +122,8 @@ export function resolveSkillContent(skillName: string, options?: SkillResolution
const skill = skills.find((s) => s.name === skillName)
if (!skill) return null
if (skillName === "git-master" && options?.gitMasterConfig) {
return injectGitMasterConfig(skill.template, options.gitMasterConfig)
if (skillName === "git-master") {
return injectGitMasterConfig(skill.template, options?.gitMasterConfig)
}
return skill.template
@ -102,8 +142,8 @@ export function resolveMultipleSkills(skillNames: string[], options?: SkillResol
for (const name of skillNames) {
const template = skillMap.get(name)
if (template) {
if (name === "git-master" && options?.gitMasterConfig) {
resolved.set(name, injectGitMasterConfig(template, options.gitMasterConfig))
if (name === "git-master") {
resolved.set(name, injectGitMasterConfig(template, options?.gitMasterConfig))
} else {
resolved.set(name, template)
}
@ -125,8 +165,8 @@ export async function resolveSkillContentAsync(
const template = await extractSkillTemplate(skill)
if (skillName === "git-master" && options?.gitMasterConfig) {
return injectGitMasterConfig(template, options.gitMasterConfig)
if (skillName === "git-master") {
return injectGitMasterConfig(template, options?.gitMasterConfig)
}
return template
@ -152,8 +192,8 @@ export async function resolveMultipleSkillsAsync(
const skill = skillMap.get(name)
if (skill) {
const template = await extractSkillTemplate(skill)
if (name === "git-master" && options?.gitMasterConfig) {
resolved.set(name, injectGitMasterConfig(template, options.gitMasterConfig))
if (name === "git-master") {
resolved.set(name, injectGitMasterConfig(template, options?.gitMasterConfig))
} else {
resolved.set(name, template)
}

View File

@ -281,6 +281,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
skills: mergedSkills,
mcpManager: skillMcpManager,
getSessionID: getSessionIDForMcp,
gitMasterConfig: pluginConfig.git_master,
});
const skillMcpTool = createSkillMcpTool({
manager: skillMcpManager,

View File

@ -104,7 +104,8 @@ export function createConfigHandler(deps: ConfigHandlerDeps) {
pluginConfig.agents,
ctx.directory,
config.model as string | undefined,
pluginConfig.categories
pluginConfig.categories,
pluginConfig.git_master
);
// Claude Code agents: Do NOT apply permission migration

View File

@ -4,6 +4,7 @@ import { TOOL_DESCRIPTION_NO_SKILLS, TOOL_DESCRIPTION_PREFIX } from "./constants
import type { SkillArgs, SkillInfo, SkillLoadOptions } from "./types"
import type { LoadedSkill } from "../../features/opencode-skill-loader"
import { getAllSkills, extractSkillTemplate } from "../../features/opencode-skill-loader/skill-content"
import { injectGitMasterConfig } from "../../features/opencode-skill-loader/skill-content"
import type { SkillMcpManager, SkillMcpClientInfo, SkillMcpServerContext } from "../../features/skill-mcp-manager"
import type { Tool, Resource, Prompt } from "@modelcontextprotocol/sdk/types.js"
@ -164,7 +165,12 @@ export function createSkillTool(options: SkillLoadOptions = {}): ToolDefinition
throw new Error(`Skill "${args.name}" not found. Available skills: ${available || "none"}`)
}
const body = await extractSkillBody(skill)
let body = await extractSkillBody(skill)
if (args.name === "git-master") {
body = injectGitMasterConfig(body, options.gitMasterConfig)
}
const dir = skill.path ? dirname(skill.path) : skill.resolvedPath || process.cwd()
const output = [

View File

@ -1,5 +1,6 @@
import type { SkillScope, LoadedSkill } from "../../features/opencode-skill-loader/types"
import type { SkillMcpManager } from "../../features/skill-mcp-manager"
import type { GitMasterConfig } from "../../config/schema"
export interface SkillArgs {
name: string
@ -25,4 +26,6 @@ export interface SkillLoadOptions {
mcpManager?: SkillMcpManager
/** Session ID getter for MCP client identification */
getSessionID?: () => string
/** Git master configuration for watermark/co-author settings */
gitMasterConfig?: GitMasterConfig
}