From 26c8d55b6795e06aad7c0f5d8febc17fc4f13173 Mon Sep 17 00:00:00 2001 From: Jeon Suyeol Date: Fri, 6 Mar 2026 11:35:52 +0900 Subject: [PATCH 1/6] feat(git-master): add git_env_prefix config to prefix all git commands When git-master skill is loaded, all git commands are prefixed with the configured env variable (default: GIT_MASTER=1). This enables custom git hooks to detect git-master skill usage. Set to empty string to disable. --- src/config/schema/git-master.ts | 2 + .../git-master-template-injection.ts | 84 +++++++++++++++---- 2 files changed, 68 insertions(+), 18 deletions(-) diff --git a/src/config/schema/git-master.ts b/src/config/schema/git-master.ts index 0574de860..e162b38c5 100644 --- a/src/config/schema/git-master.ts +++ b/src/config/schema/git-master.ts @@ -5,6 +5,8 @@ export const GitMasterConfigSchema = z.object({ commit_footer: z.union([z.boolean(), z.string()]).default(true), /** Add "Co-authored-by: Sisyphus" trailer to commit messages (default: true) */ include_co_authored_by: z.boolean().default(true), + /** Environment variable prefix for all git commands (default: "GIT_MASTER=1"). Set to "" to disable. Allows custom git hooks to detect git-master skill usage. */ + git_env_prefix: z.string().default("GIT_MASTER=1"), }) export type GitMasterConfig = z.infer diff --git a/src/features/opencode-skill-loader/git-master-template-injection.ts b/src/features/opencode-skill-loader/git-master-template-injection.ts index f6815798c..daec2d31a 100644 --- a/src/features/opencode-skill-loader/git-master-template-injection.ts +++ b/src/features/opencode-skill-loader/git-master-template-injection.ts @@ -3,12 +3,73 @@ import type { GitMasterConfig } from "../../config/schema" export function injectGitMasterConfig(template: string, config?: GitMasterConfig): string { const commitFooter = config?.commit_footer ?? true const includeCoAuthoredBy = config?.include_co_authored_by ?? true + const gitEnvPrefix = config?.git_env_prefix ?? "GIT_MASTER=1" + + let result = gitEnvPrefix ? injectGitEnvPrefix(template, gitEnvPrefix) : template if (!commitFooter && !includeCoAuthoredBy) { - return template + return result } + const injection = buildCommitFooterInjection(commitFooter, includeCoAuthoredBy, gitEnvPrefix) + + const insertionPoint = result.indexOf("```\n") + if (insertionPoint !== -1) { + return ( + result.slice(0, insertionPoint) + + "```\n\n" + + injection + + "\n" + + result.slice(insertionPoint + "```\n".length) + ) + } + + return result + "\n\n" + injection +} + +function injectGitEnvPrefix(template: string, prefix: string): string { + const envPrefixSection = [ + "## GIT COMMAND PREFIX (MANDATORY)", + "", + ``, + `**EVERY git command MUST be prefixed with \`${prefix}\`.**`, + "", + "This allows custom git hooks to detect when git-master skill is active.", + "", + "```bash", + `${prefix} git status`, + `${prefix} git add `, + `${prefix} git commit -m "message"`, + `${prefix} git push`, + `${prefix} git rebase ...`, + `${prefix} git log ...`, + "```", + "", + "**NO EXCEPTIONS. Every `git` invocation must include this prefix.**", + ``, + ].join("\n") + + const modeDetectionMarker = "## MODE DETECTION (FIRST STEP)" + const markerIndex = template.indexOf(modeDetectionMarker) + if (markerIndex !== -1) { + return ( + template.slice(0, markerIndex) + + envPrefixSection + + "\n\n---\n\n" + + template.slice(markerIndex) + ) + } + + return envPrefixSection + "\n\n---\n\n" + template +} + +function buildCommitFooterInjection( + commitFooter: boolean | string, + includeCoAuthoredBy: boolean, + gitEnvPrefix: string, +): string { const sections: string[] = [] + const cmdPrefix = gitEnvPrefix ? `${gitEnvPrefix} ` : "" sections.push("### 5.5 Commit Footer & Co-Author") sections.push("") @@ -43,7 +104,7 @@ export function injectGitMasterConfig(template: string, config?: GitMasterConfig sections.push("**Example (both enabled):**") sections.push("```bash") sections.push( - `git commit -m "{Commit Message}" -m "${footerText}" -m "Co-authored-by: Sisyphus "` + `${cmdPrefix}git commit -m "{Commit Message}" -m "${footerText}" -m "Co-authored-by: Sisyphus "` ) sections.push("```") } else if (commitFooter) { @@ -53,29 +114,16 @@ export function injectGitMasterConfig(template: string, config?: GitMasterConfig : "Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)" sections.push("**Example:**") sections.push("```bash") - sections.push(`git commit -m "{Commit Message}" -m "${footerText}"`) + sections.push(`${cmdPrefix}git commit -m "{Commit Message}" -m "${footerText}"`) sections.push("```") } else if (includeCoAuthoredBy) { sections.push("**Example:**") sections.push("```bash") sections.push( - "git commit -m \"{Commit Message}\" -m \"Co-authored-by: Sisyphus \"" + `${cmdPrefix}git commit -m "{Commit Message}" -m "Co-authored-by: Sisyphus "` ) sections.push("```") } - const injection = sections.join("\n") - - const insertionPoint = template.indexOf("```\n") - if (insertionPoint !== -1) { - return ( - template.slice(0, insertionPoint) + - "```\n\n" + - injection + - "\n" + - template.slice(insertionPoint + "```\n".length) - ) - } - - return template + "\n\n" + injection + return sections.join("\n") } From 6366c7ef6ed37de73f67066f65387e45aab7d23d Mon Sep 17 00:00:00 2001 From: Jeon Suyeol Date: Fri, 6 Mar 2026 11:35:59 +0900 Subject: [PATCH 2/6] test(git-master): add tests for git_env_prefix injection Add unit tests for env prefix injection (default, disabled, custom value) and update existing skill-content tests to include git_env_prefix field. --- .../git-master-template-injection.test.ts | 121 ++++++++++++++++++ .../skill-content.test.ts | 6 + 2 files changed, 127 insertions(+) create mode 100644 src/features/opencode-skill-loader/git-master-template-injection.test.ts diff --git a/src/features/opencode-skill-loader/git-master-template-injection.test.ts b/src/features/opencode-skill-loader/git-master-template-injection.test.ts new file mode 100644 index 000000000..a063eda98 --- /dev/null +++ b/src/features/opencode-skill-loader/git-master-template-injection.test.ts @@ -0,0 +1,121 @@ +/// + +import { describe, it, expect } from "bun:test" +import { injectGitMasterConfig } from "./git-master-template-injection" + +const SAMPLE_TEMPLATE = [ + "# Git Master Agent", + "", + "## MODE DETECTION (FIRST STEP)", + "", + "Analyze the request.", + "", + "```bash", + "git status", + "```", + "", + "```", + "", +].join("\n") + +describe("#given git_env_prefix config", () => { + describe("#when default config (GIT_MASTER=1)", () => { + it("#then injects env prefix section before MODE DETECTION", () => { + const result = injectGitMasterConfig(SAMPLE_TEMPLATE, { + commit_footer: false, + include_co_authored_by: false, + git_env_prefix: "GIT_MASTER=1", + }) + + expect(result).toContain("## GIT COMMAND PREFIX (MANDATORY)") + expect(result).toContain("GIT_MASTER=1 git status") + expect(result).toContain("GIT_MASTER=1 git commit") + expect(result).toContain("GIT_MASTER=1 git push") + expect(result).toContain("EVERY git command MUST be prefixed with `GIT_MASTER=1`") + + const prefixIndex = result.indexOf("## GIT COMMAND PREFIX") + const modeIndex = result.indexOf("## MODE DETECTION") + expect(prefixIndex).toBeLessThan(modeIndex) + }) + }) + + describe("#when git_env_prefix is empty string", () => { + it("#then does NOT inject env prefix section", () => { + const result = injectGitMasterConfig(SAMPLE_TEMPLATE, { + commit_footer: false, + include_co_authored_by: false, + git_env_prefix: "", + }) + + expect(result).not.toContain("## GIT COMMAND PREFIX") + expect(result).not.toContain("GIT_MASTER=1") + expect(result).not.toContain("git_env_prefix") + }) + }) + + describe("#when git_env_prefix is custom value", () => { + it("#then injects custom prefix in section", () => { + const result = injectGitMasterConfig(SAMPLE_TEMPLATE, { + commit_footer: false, + include_co_authored_by: false, + git_env_prefix: "MY_HOOK=active", + }) + + expect(result).toContain("MY_HOOK=active git status") + expect(result).toContain("MY_HOOK=active git commit") + expect(result).not.toContain("GIT_MASTER=1") + }) + }) + + describe("#when no config provided", () => { + it("#then uses default GIT_MASTER=1 prefix", () => { + const result = injectGitMasterConfig(SAMPLE_TEMPLATE) + + expect(result).toContain("GIT_MASTER=1 git status") + expect(result).toContain("## GIT COMMAND PREFIX (MANDATORY)") + }) + }) +}) + +describe("#given git_env_prefix with commit footer", () => { + describe("#when both env prefix and footer are enabled", () => { + it("#then commit examples include the env prefix", () => { + const result = injectGitMasterConfig(SAMPLE_TEMPLATE, { + commit_footer: true, + include_co_authored_by: false, + git_env_prefix: "GIT_MASTER=1", + }) + + expect(result).toContain("GIT_MASTER=1 git commit") + expect(result).toContain("Ultraworked with [Sisyphus]") + }) + }) + + describe("#when env prefix disabled but footer enabled", () => { + it("#then commit examples have no env prefix", () => { + const result = injectGitMasterConfig(SAMPLE_TEMPLATE, { + commit_footer: true, + include_co_authored_by: false, + git_env_prefix: "", + }) + + expect(result).not.toContain("GIT_MASTER=1 git commit") + expect(result).toContain("git commit -m") + expect(result).toContain("Ultraworked with [Sisyphus]") + }) + }) + + describe("#when both env prefix and co-author are enabled", () => { + it("#then commit example includes prefix, footer, and co-author", () => { + const result = injectGitMasterConfig(SAMPLE_TEMPLATE, { + commit_footer: true, + include_co_authored_by: true, + git_env_prefix: "GIT_MASTER=1", + }) + + expect(result).toContain("GIT_MASTER=1 git commit") + expect(result).toContain("Ultraworked with [Sisyphus]") + expect(result).toContain("Co-authored-by: Sisyphus") + }) + }) +}) diff --git a/src/features/opencode-skill-loader/skill-content.test.ts b/src/features/opencode-skill-loader/skill-content.test.ts index 506de215b..64d6d5bf4 100644 --- a/src/features/opencode-skill-loader/skill-content.test.ts +++ b/src/features/opencode-skill-loader/skill-content.test.ts @@ -228,6 +228,7 @@ describe("resolveMultipleSkillsAsync", () => { gitMasterConfig: { commit_footer: false, include_co_authored_by: false, + git_env_prefix: "GIT_MASTER=1", }, } @@ -249,6 +250,7 @@ describe("resolveMultipleSkillsAsync", () => { gitMasterConfig: { commit_footer: true, include_co_authored_by: true, + git_env_prefix: "GIT_MASTER=1", }, } @@ -269,6 +271,7 @@ describe("resolveMultipleSkillsAsync", () => { gitMasterConfig: { commit_footer: true, include_co_authored_by: false, + git_env_prefix: "GIT_MASTER=1", }, } @@ -302,6 +305,7 @@ describe("resolveMultipleSkillsAsync", () => { gitMasterConfig: { commit_footer: false, include_co_authored_by: true, + git_env_prefix: "GIT_MASTER=1", }, } @@ -322,6 +326,7 @@ describe("resolveMultipleSkillsAsync", () => { gitMasterConfig: { commit_footer: customFooter, include_co_authored_by: false, + git_env_prefix: "GIT_MASTER=1", }, } @@ -341,6 +346,7 @@ describe("resolveMultipleSkillsAsync", () => { gitMasterConfig: { commit_footer: true, include_co_authored_by: false, + git_env_prefix: "GIT_MASTER=1", }, } From c288ad7124e42b447b1435f296224e7768d98ed8 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Wed, 11 Mar 2026 17:07:29 +0900 Subject: [PATCH 3/6] feat(git-master): validate git_env_prefix values --- src/config/schema.ts | 1 + src/config/schema/git-env-prefix.ts | 28 ++++++++++++++++++++++++++++ src/config/schema/git-master.ts | 4 +++- 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 src/config/schema/git-env-prefix.ts diff --git a/src/config/schema.ts b/src/config/schema.ts index 0d2c590ba..bcb36a175 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -10,6 +10,7 @@ export * from "./schema/commands" export * from "./schema/dynamic-context-pruning" export * from "./schema/experimental" export * from "./schema/fallback-models" +export * from "./schema/git-env-prefix" export * from "./schema/git-master" export * from "./schema/hooks" export * from "./schema/notification" diff --git a/src/config/schema/git-env-prefix.ts b/src/config/schema/git-env-prefix.ts new file mode 100644 index 000000000..65609c0b1 --- /dev/null +++ b/src/config/schema/git-env-prefix.ts @@ -0,0 +1,28 @@ +import { z } from "zod" + +const GIT_ENV_ASSIGNMENT_PATTERN = + /^(?:[A-Za-z_][A-Za-z0-9_]*=[A-Za-z0-9_-]*)(?: [A-Za-z_][A-Za-z0-9_]*=[A-Za-z0-9_-]*)*$/ + +export const GIT_ENV_PREFIX_VALIDATION_MESSAGE = + 'git_env_prefix must be empty or use shell-safe env assignments like "GIT_MASTER=1"' + +export function isValidGitEnvPrefix(value: string): boolean { + if (value === "") { + return true + } + + return GIT_ENV_ASSIGNMENT_PATTERN.test(value) +} + +export function assertValidGitEnvPrefix(value: string): string { + if (!isValidGitEnvPrefix(value)) { + throw new Error(GIT_ENV_PREFIX_VALIDATION_MESSAGE) + } + + return value +} + +export const GitEnvPrefixSchema = z + .string() + .refine(isValidGitEnvPrefix, { message: GIT_ENV_PREFIX_VALIDATION_MESSAGE }) + .default("GIT_MASTER=1") diff --git a/src/config/schema/git-master.ts b/src/config/schema/git-master.ts index e162b38c5..4c6f4bf65 100644 --- a/src/config/schema/git-master.ts +++ b/src/config/schema/git-master.ts @@ -1,12 +1,14 @@ import { z } from "zod" +import { GitEnvPrefixSchema } from "./git-env-prefix" + export const GitMasterConfigSchema = z.object({ /** Add "Ultraworked with Sisyphus" footer to commit messages (default: true). Can be boolean or custom string. */ commit_footer: z.union([z.boolean(), z.string()]).default(true), /** Add "Co-authored-by: Sisyphus" trailer to commit messages (default: true) */ include_co_authored_by: z.boolean().default(true), /** Environment variable prefix for all git commands (default: "GIT_MASTER=1"). Set to "" to disable. Allows custom git hooks to detect git-master skill usage. */ - git_env_prefix: z.string().default("GIT_MASTER=1"), + git_env_prefix: GitEnvPrefixSchema, }) export type GitMasterConfig = z.infer From bf9721d4ee878b212a87aa4c59126647cba998cc Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Wed, 11 Mar 2026 17:07:33 +0900 Subject: [PATCH 4/6] fix(git-master): prefix git commands in injected templates --- .../git-master-template-injection.ts | 49 ++++++++++++------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/src/features/opencode-skill-loader/git-master-template-injection.ts b/src/features/opencode-skill-loader/git-master-template-injection.ts index daec2d31a..812fa228f 100644 --- a/src/features/opencode-skill-loader/git-master-template-injection.ts +++ b/src/features/opencode-skill-loader/git-master-template-injection.ts @@ -1,30 +1,31 @@ -import type { GitMasterConfig } from "../../config/schema" +import { assertValidGitEnvPrefix, type GitMasterConfig } from "../../config/schema" + +const BASH_CODE_BLOCK_PATTERN = /```bash\r?\n([\s\S]*?)```/g +const LEADING_GIT_COMMAND_PATTERN = /^([ \t]*(?:[A-Za-z_][A-Za-z0-9_]*=[^ \t]+\s+)*)git(?=[ \t]|$)/gm +const INLINE_GIT_COMMAND_PATTERN = /([;&|()][ \t]*)git(?=[ \t]|$)/g export function injectGitMasterConfig(template: string, config?: GitMasterConfig): string { const commitFooter = config?.commit_footer ?? true const includeCoAuthoredBy = config?.include_co_authored_by ?? true - const gitEnvPrefix = config?.git_env_prefix ?? "GIT_MASTER=1" + const gitEnvPrefix = assertValidGitEnvPrefix(config?.git_env_prefix ?? "GIT_MASTER=1") let result = gitEnvPrefix ? injectGitEnvPrefix(template, gitEnvPrefix) : template - if (!commitFooter && !includeCoAuthoredBy) { - return result + if (commitFooter || includeCoAuthoredBy) { + const injection = buildCommitFooterInjection(commitFooter, includeCoAuthoredBy, gitEnvPrefix) + const insertionPoint = result.indexOf("```\n") + + result = + insertionPoint !== -1 + ? result.slice(0, insertionPoint) + + "```\n\n" + + injection + + "\n" + + result.slice(insertionPoint + "```\n".length) + : result + "\n\n" + injection } - const injection = buildCommitFooterInjection(commitFooter, includeCoAuthoredBy, gitEnvPrefix) - - const insertionPoint = result.indexOf("```\n") - if (insertionPoint !== -1) { - return ( - result.slice(0, insertionPoint) + - "```\n\n" + - injection + - "\n" + - result.slice(insertionPoint + "```\n".length) - ) - } - - return result + "\n\n" + injection + return gitEnvPrefix ? prefixGitCommandsInBashCodeBlocks(result, gitEnvPrefix) : result } function injectGitEnvPrefix(template: string, prefix: string): string { @@ -63,6 +64,18 @@ function injectGitEnvPrefix(template: string, prefix: string): string { return envPrefixSection + "\n\n---\n\n" + template } +function prefixGitCommandsInBashCodeBlocks(template: string, prefix: string): string { + return template.replace(BASH_CODE_BLOCK_PATTERN, (block, codeBlock: string) => { + return block.replace(codeBlock, prefixGitCommandsInCodeBlock(codeBlock, prefix)) + }) +} + +function prefixGitCommandsInCodeBlock(codeBlock: string, prefix: string): string { + return codeBlock + .replace(LEADING_GIT_COMMAND_PATTERN, `$1${prefix} git`) + .replace(INLINE_GIT_COMMAND_PATTERN, `$1${prefix} git`) +} + function buildCommitFooterInjection( commitFooter: boolean | string, includeCoAuthoredBy: boolean, From 02fec3ddb145e5b8b2551ab64ee2d9df41a914d4 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Wed, 11 Mar 2026 17:07:38 +0900 Subject: [PATCH 5/6] test(git-master): cover git_env_prefix validation --- src/config/schema.test.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/config/schema.test.ts b/src/config/schema.test.ts index 8a83fcd7d..a9b569dd8 100644 --- a/src/config/schema.test.ts +++ b/src/config/schema.test.ts @@ -884,6 +884,25 @@ describe("GitMasterConfigSchema", () => { //#then expect(result.success).toBe(false) }) + + test("accepts shell-safe git_env_prefix", () => { + const config = { git_env_prefix: "MY_HOOK=active" } + + const result = GitMasterConfigSchema.safeParse(config) + + expect(result.success).toBe(true) + if (result.success) { + expect(result.data.git_env_prefix).toBe("MY_HOOK=active") + } + }) + + test("rejects git_env_prefix with shell metacharacters", () => { + const config = { git_env_prefix: "A=1; rm -rf /" } + + const result = GitMasterConfigSchema.safeParse(config) + + expect(result.success).toBe(false) + }) }) describe("skills schema", () => { From a70e7fe742a1f95869ce283996dca9720fa2b28b Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Wed, 11 Mar 2026 17:07:43 +0900 Subject: [PATCH 6/6] test(git-master): cover full git command prefix injection --- .../git-master-template-injection.test.ts | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/features/opencode-skill-loader/git-master-template-injection.test.ts b/src/features/opencode-skill-loader/git-master-template-injection.test.ts index a063eda98..60ea0f0b3 100644 --- a/src/features/opencode-skill-loader/git-master-template-injection.test.ts +++ b/src/features/opencode-skill-loader/git-master-template-injection.test.ts @@ -12,6 +12,9 @@ const SAMPLE_TEMPLATE = [ "", "```bash", "git status", + "git merge-base HEAD main 2>/dev/null || git merge-base HEAD master 2>/dev/null", + "MERGE_BASE=$(git merge-base HEAD main)", + "GIT_SEQUENCE_EDITOR=: git rebase -i --autosquash $MERGE_BASE", "```", "", "```", @@ -67,6 +70,18 @@ describe("#given git_env_prefix config", () => { }) }) + describe("#when git_env_prefix contains shell metacharacters", () => { + it("#then rejects the malicious value", () => { + expect(() => + injectGitMasterConfig(SAMPLE_TEMPLATE, { + commit_footer: false, + include_co_authored_by: false, + git_env_prefix: "A=1; rm -rf /", + }) + ).toThrow('git_env_prefix must be empty or use shell-safe env assignments like "GIT_MASTER=1"') + }) + }) + describe("#when no config provided", () => { it("#then uses default GIT_MASTER=1 prefix", () => { const result = injectGitMasterConfig(SAMPLE_TEMPLATE) @@ -91,6 +106,25 @@ describe("#given git_env_prefix with commit footer", () => { }) }) + describe("#when the template already contains bare git commands in bash blocks", () => { + it("#then prefixes every git invocation in the final output", () => { + const result = injectGitMasterConfig(SAMPLE_TEMPLATE, { + commit_footer: false, + include_co_authored_by: false, + git_env_prefix: "GIT_MASTER=1", + }) + + expect(result).toContain("GIT_MASTER=1 git status") + expect(result).toContain( + "GIT_MASTER=1 git merge-base HEAD main 2>/dev/null || GIT_MASTER=1 git merge-base HEAD master 2>/dev/null" + ) + expect(result).toContain("MERGE_BASE=$(GIT_MASTER=1 git merge-base HEAD main)") + expect(result).toContain( + "GIT_SEQUENCE_EDITOR=: GIT_MASTER=1 git rebase -i --autosquash $MERGE_BASE" + ) + }) + }) + describe("#when env prefix disabled but footer enabled", () => { it("#then commit examples have no env prefix", () => { const result = injectGitMasterConfig(SAMPLE_TEMPLATE, {