11/**
22 * ABOUTME: Auto-commit utility for committing changes after successful task completion.
3- * Provides git operations to stage and commit changes when autoCommit is enabled.
3+ * Provides git operations to stage and commit changes when autoCommit is enabled,
4+ * plus Handlebars-based rendering of configurable commit subject templates.
45 */
56
7+ import Handlebars from 'handlebars' ;
68import { runProcess } from '../utils/process.js' ;
79
810/**
@@ -21,6 +23,94 @@ export interface AutoCommitResult {
2123 error ?: string ;
2224}
2325
26+ /**
27+ * Default commit subject template used when the user has not set
28+ * `commitMessageTemplate` in their config. Uses the tracker-reported task type
29+ * (with a `chore` fallback) so commits look Conventional-Commits-shaped without
30+ * mis-labeling everything as `feat:`.
31+ */
32+ export const DEFAULT_COMMIT_MESSAGE_TEMPLATE = '{{taskType}}: {{taskId}} {{taskTitle}}' ;
33+
34+ /**
35+ * Fallback used when a task's `type` is missing or empty so user templates
36+ * always render to a non-empty subject.
37+ */
38+ export const DEFAULT_TASK_TYPE = 'chore' ;
39+
40+ /**
41+ * Inputs for {@link renderCommitMessage}.
42+ */
43+ export interface CommitMessageContext {
44+ taskId : string ;
45+ taskTitle : string ;
46+ taskType ?: string ;
47+ }
48+
49+ /**
50+ * Result of rendering a commit message template.
51+ */
52+ export interface RenderCommitMessageResult {
53+ /** The rendered commit subject. Always non-empty. */
54+ message : string ;
55+ /**
56+ * True when the caller-supplied template rendered to an empty/whitespace
57+ * string (or failed to compile) and we fell back to the default template.
58+ */
59+ usedFallback : boolean ;
60+ /** Compile/render error message when {@link usedFallback} is true. */
61+ fallbackReason ?: string ;
62+ }
63+
64+ function buildHandlebarsContext ( ctx : CommitMessageContext ) : Record < string , string > {
65+ const type = ctx . taskType ?. trim ( ) ;
66+ return {
67+ taskId : ctx . taskId ,
68+ taskTitle : ctx . taskTitle ,
69+ taskType : type && type . length > 0 ? type : DEFAULT_TASK_TYPE ,
70+ } ;
71+ }
72+
73+ function compileAndRender ( template : string , ctx : Record < string , string > ) : string {
74+ const compiled = Handlebars . compile ( template , { noEscape : true , strict : false } ) ;
75+ return compiled ( ctx ) . trim ( ) ;
76+ }
77+
78+ /**
79+ * Render a Handlebars commit subject template. Falls back to the default
80+ * template when the user-supplied template renders empty/whitespace-only or
81+ * throws while compiling.
82+ */
83+ export function renderCommitMessage (
84+ template : string | undefined ,
85+ ctx : CommitMessageContext
86+ ) : RenderCommitMessageResult {
87+ const handlebarsCtx = buildHandlebarsContext ( ctx ) ;
88+ const effectiveTemplate = template ?? DEFAULT_COMMIT_MESSAGE_TEMPLATE ;
89+
90+ let rendered : string ;
91+ try {
92+ rendered = compileAndRender ( effectiveTemplate , handlebarsCtx ) ;
93+ } catch ( err ) {
94+ const fallback = compileAndRender ( DEFAULT_COMMIT_MESSAGE_TEMPLATE , handlebarsCtx ) ;
95+ return {
96+ message : fallback ,
97+ usedFallback : true ,
98+ fallbackReason : err instanceof Error ? err . message : String ( err ) ,
99+ } ;
100+ }
101+
102+ if ( rendered . length === 0 ) {
103+ const fallback = compileAndRender ( DEFAULT_COMMIT_MESSAGE_TEMPLATE , handlebarsCtx ) ;
104+ return {
105+ message : fallback ,
106+ usedFallback : true ,
107+ fallbackReason : 'template rendered to empty string' ,
108+ } ;
109+ }
110+
111+ return { message : rendered , usedFallback : false } ;
112+ }
113+
24114/**
25115 * Check if there are uncommitted changes in the working directory.
26116 * Throws if git status cannot be determined (not a git repo, git not installed, etc.).
@@ -34,13 +124,12 @@ export async function hasUncommittedChanges(cwd: string): Promise<boolean> {
34124}
35125
36126/**
37- * Stage all changes and create a commit with a standardized message format .
38- * Returns the result of the operation including commit SHA on success .
127+ * Stage all changes and create a commit using the supplied subject line .
128+ * The caller is responsible for rendering any template — see { @link renderCommitMessage} .
39129 */
40130export async function performAutoCommit (
41131 cwd : string ,
42- taskId : string ,
43- taskTitle : string
132+ commitMessage : string
44133) : Promise < AutoCommitResult > {
45134 // Check for uncommitted changes first
46135 let hasChanges : boolean ;
@@ -68,8 +157,7 @@ export async function performAutoCommit(
68157 } ;
69158 }
70159
71- // Create commit with standardized message
72- const commitMessage = `feat: ${ taskId } - ${ taskTitle } ` ;
160+ // Create commit with supplied message
73161 const commitResult = await runProcess (
74162 'git' ,
75163 [ 'commit' , '-m' , commitMessage ] ,
0 commit comments