@@ -62,6 +62,7 @@ test("commit_and_push commits changes and pushes", async () => {
6262 hasTestChanges : async ( ) => true ,
6363 getStagedChanges : async ( ) => [ { status : "M" , path : "src/index.js" } ] ,
6464 getCurrentBranch : async ( ) => "main" ,
65+ getPrimaryBranch : async ( ) => "main" ,
6566 getLastCommitMessage : async ( ) => "" ,
6667 workingTreeSummary : ( ) => ( { hasChanges : false , lines : [ ] } ) ,
6768 } ;
@@ -129,6 +130,7 @@ test("run_full_workflow iterates until commit/release complete with clean tree",
129130 hasTestChanges : async ( ) => true ,
130131 getStagedChanges : async ( ) => [ ] ,
131132 getCurrentBranch : async ( ) => "main" ,
133+ getPrimaryBranch : async ( ) => "main" ,
132134 getLastCommitMessage : async ( ) => "chore: existing" ,
133135 workingTreeSummary : ( ) => ( { hasChanges : false , lines : [ ] } ) ,
134136 } ;
@@ -163,6 +165,172 @@ test("run_full_workflow iterates until commit/release complete with clean tree",
163165 } ) ;
164166} ) ;
165167
168+ test ( "commit_and_push uses primary branch when no branch specified" , async ( ) => {
169+ await withWorkflowState ( async ( workflowState ) => {
170+ workflowState . state . readyToCommit = true ;
171+ workflowState . state . readyCheckCompleted = true ;
172+ workflowState . state . testsSkipped = false ;
173+ workflowState . state . testsCreated = true ;
174+ workflowState . state . testsPassed = true ;
175+ workflowState . state . currentPhase = "commit" ;
176+ await workflowState . save ( ) ;
177+
178+ const commands = [ ] ;
179+ const execStub = async ( command ) => {
180+ commands . push ( command ) ;
181+ return { stdout : "" } ;
182+ } ;
183+
184+ const git = {
185+ hasWorkingChanges : async ( ) => true ,
186+ hasStagedChanges : async ( ) => false ,
187+ hasTestChanges : async ( ) => true ,
188+ getStagedChanges : async ( ) => [ { status : "M" , path : "src/index.js" } ] ,
189+ getCurrentBranch : async ( ) => "feature/test" ,
190+ getPrimaryBranch : async ( ) => "main" ,
191+ getLastCommitMessage : async ( ) => "" ,
192+ workingTreeSummary : ( ) => ( { hasChanges : false , lines : [ ] } ) ,
193+ } ;
194+
195+ const response = await handleToolCall ( {
196+ request : createRequest ( "commit_and_push" , { commitMessage : "feat: test" } ) ,
197+ normalizeRequestArgs,
198+ workflowState,
199+ exec : execStub ,
200+ git,
201+ utils,
202+ } ) ;
203+
204+ const pushCommand = commands . find ( ( command ) => command . startsWith ( "git push" ) ) ;
205+ assert . ok ( pushCommand , "git push should be executed" ) ;
206+ assert . ok (
207+ pushCommand . includes ( "main" ) ,
208+ "push command should use detected primary branch (main)"
209+ ) ;
210+ assert . ok (
211+ response . content [ 0 ] . text . includes ( "Commit and push completed!" ) ,
212+ "response should confirm commit and push"
213+ ) ;
214+ } ) ;
215+ } ) ;
216+
217+ test ( "commit_and_push falls back to master when main not found" , async ( ) => {
218+ await withWorkflowState ( async ( workflowState ) => {
219+ workflowState . state . readyToCommit = true ;
220+ workflowState . state . readyCheckCompleted = true ;
221+ workflowState . state . testsSkipped = false ;
222+ workflowState . state . testsCreated = true ;
223+ workflowState . state . testsPassed = true ;
224+ workflowState . state . currentPhase = "commit" ;
225+ await workflowState . save ( ) ;
226+
227+ const commands = [ ] ;
228+ const execStub = async ( command ) => {
229+ commands . push ( command ) ;
230+ return { stdout : "" } ;
231+ } ;
232+
233+ const git = {
234+ hasWorkingChanges : async ( ) => true ,
235+ hasStagedChanges : async ( ) => false ,
236+ hasTestChanges : async ( ) => true ,
237+ getStagedChanges : async ( ) => [ { status : "M" , path : "src/index.js" } ] ,
238+ getCurrentBranch : async ( ) => "feature/test" ,
239+ getPrimaryBranch : async ( ) => "master" ,
240+ getLastCommitMessage : async ( ) => "" ,
241+ workingTreeSummary : ( ) => ( { hasChanges : false , lines : [ ] } ) ,
242+ } ;
243+
244+ const response = await handleToolCall ( {
245+ request : createRequest ( "commit_and_push" , { commitMessage : "feat: test" } ) ,
246+ normalizeRequestArgs,
247+ workflowState,
248+ exec : execStub ,
249+ git,
250+ utils,
251+ } ) ;
252+
253+ const pushCommand = commands . find ( ( command ) => command . startsWith ( "git push" ) ) ;
254+ assert . ok ( pushCommand , "git push should be executed" ) ;
255+ assert . ok (
256+ pushCommand . includes ( "master" ) ,
257+ "push command should use detected primary branch (master)"
258+ ) ;
259+ } ) ;
260+ } ) ;
261+
262+ test ( "run_full_workflow uses provided branch for release push when commit is clean" , async ( ) => {
263+ await withWorkflowState ( async ( workflowState ) => {
264+ workflowState . state . taskDescription = "Ship feature" ;
265+ workflowState . state . taskType = "feature" ;
266+ workflowState . state . currentPhase = "commit" ;
267+ workflowState . state . bugFixed = true ;
268+ workflowState . state . testsCreated = true ;
269+ workflowState . state . testsPassed = true ;
270+ workflowState . state . documentationCreated = true ;
271+ workflowState . state . readyToCommit = true ;
272+ workflowState . state . readyCheckCompleted = true ;
273+ workflowState . state . commitAndPushCompleted = false ;
274+ workflowState . state . released = false ;
275+ workflowState . state . documentationType = "README" ;
276+ workflowState . state . documentationSummary = "Docs" ;
277+ workflowState . state . fixSummary = "Feature done" ;
278+ await workflowState . save ( ) ;
279+
280+ const commands = [ ] ;
281+ const execStub = async ( command ) => {
282+ commands . push ( command ) ;
283+ if ( command === "git status --porcelain" ) {
284+ return { stdout : "" } ;
285+ }
286+ return { stdout : "" } ;
287+ } ;
288+
289+ const git = {
290+ hasWorkingChanges : async ( ) => false ,
291+ hasStagedChanges : async ( ) => false ,
292+ hasTestChanges : async ( ) => true ,
293+ getStagedChanges : async ( ) => [ ] ,
294+ getCurrentBranch : async ( ) => "feature/local" ,
295+ getPrimaryBranch : async ( ) => "main" ,
296+ getLastCommitMessage : async ( ) => "chore: existing" ,
297+ workingTreeSummary : ( ) => ( { hasChanges : false , lines : [ ] } ) ,
298+ } ;
299+
300+ const branchName = "master" ;
301+ const response = await handleToolCall ( {
302+ request : createRequest ( "run_full_workflow" , {
303+ summary : "Feature done" ,
304+ testCommand : "npm test" ,
305+ documentationType : "README" ,
306+ documentationSummary : "Docs" ,
307+ commitMessage : "feat: ship feature" ,
308+ branch : branchName ,
309+ releaseCommand : "npm run release:patch" ,
310+ } ) ,
311+ normalizeRequestArgs,
312+ workflowState,
313+ exec : execStub ,
314+ git,
315+ utils,
316+ } ) ;
317+
318+ assert . ok (
319+ response . content [ 0 ] . text . includes ( "✅ Full workflow completed successfully" ) ,
320+ "run_full_workflow should finish successfully"
321+ ) ;
322+
323+ const tagPushCommand = commands . find ( ( command ) => command . startsWith ( "git push --follow-tags" ) ) ;
324+ assert . ok ( tagPushCommand , "release should push tags" ) ;
325+ assert . ok (
326+ / o r i g i n \s + ' ? m a s t e r ' ? / . test ( tagPushCommand ) ,
327+ `release push should target the provided branch (${ branchName } )`
328+ ) ;
329+
330+ assert . equal ( workflowState . state . currentPhase , "idle" ) ;
331+ } ) ;
332+ } ) ;
333+
166334test ( "project_summary_data reads persisted file and falls back" , async ( ) => {
167335 await withWorkflowState ( async ( workflowState ) => {
168336 // Seed some history
@@ -174,7 +342,16 @@ test("project_summary_data reads persisted file and falls back", async () => {
174342 normalizeRequestArgs,
175343 workflowState,
176344 exec : async ( ) => ( { stdout : "" } ) ,
177- git : { } ,
345+ git : {
346+ hasWorkingChanges : async ( ) => false ,
347+ hasStagedChanges : async ( ) => false ,
348+ hasTestChanges : async ( ) => true ,
349+ getStagedChanges : async ( ) => [ ] ,
350+ getCurrentBranch : async ( ) => "main" ,
351+ getPrimaryBranch : async ( ) => "main" ,
352+ getLastCommitMessage : async ( ) => "" ,
353+ workingTreeSummary : ( ) => ( { hasChanges : false , lines : [ ] } ) ,
354+ } ,
178355 utils,
179356 } ) ;
180357
@@ -199,7 +376,16 @@ test("project_summary aggregates task types and recent activity", async () => {
199376 normalizeRequestArgs,
200377 workflowState,
201378 exec : async ( ) => ( { stdout : "" } ) ,
202- git : { } ,
379+ git : {
380+ hasWorkingChanges : async ( ) => false ,
381+ hasStagedChanges : async ( ) => false ,
382+ hasTestChanges : async ( ) => true ,
383+ getStagedChanges : async ( ) => [ ] ,
384+ getCurrentBranch : async ( ) => "main" ,
385+ getPrimaryBranch : async ( ) => "main" ,
386+ getLastCommitMessage : async ( ) => "" ,
387+ workingTreeSummary : ( ) => ( { hasChanges : false , lines : [ ] } ) ,
388+ } ,
203389 utils,
204390 } ) ;
205391
@@ -239,6 +425,7 @@ test("run_full_workflow executes all steps successfully", async () => {
239425 hasTestChanges : async ( ) => true ,
240426 getStagedChanges : async ( ) => [ { status : "M" , path : "src/app.js" } ] ,
241427 getCurrentBranch : async ( ) => "main" ,
428+ getPrimaryBranch : async ( ) => "main" ,
242429 getLastCommitMessage : async ( ) => "" ,
243430 workingTreeSummary : ( ) =>
244431 workingChanges
@@ -315,6 +502,7 @@ test("run_full_workflow resumes from current phase when steps are already comple
315502 hasTestChanges : async ( ) => true ,
316503 getStagedChanges : async ( ) => [ ] ,
317504 getCurrentBranch : async ( ) => "main" ,
505+ getPrimaryBranch : async ( ) => "main" ,
318506 getLastCommitMessage : async ( ) => "" ,
319507 workingTreeSummary : ( ) => ( { hasChanges : false , lines : [ ] } ) ,
320508 } ;
@@ -373,7 +561,16 @@ test("run_full_workflow validates required arguments", async () => {
373561 normalizeRequestArgs,
374562 workflowState,
375563 exec : async ( ) => ( { stdout : "" } ) ,
376- git : { } ,
564+ git : {
565+ hasWorkingChanges : async ( ) => false ,
566+ hasStagedChanges : async ( ) => false ,
567+ hasTestChanges : async ( ) => true ,
568+ getStagedChanges : async ( ) => [ ] ,
569+ getCurrentBranch : async ( ) => "main" ,
570+ getPrimaryBranch : async ( ) => "main" ,
571+ getLastCommitMessage : async ( ) => "" ,
572+ workingTreeSummary : ( ) => ( { hasChanges : false , lines : [ ] } ) ,
573+ } ,
377574 utils,
378575 } ) ;
379576
@@ -400,7 +597,16 @@ test("force_complete_task records entry and resets state", async () => {
400597 normalizeRequestArgs,
401598 workflowState,
402599 exec : async ( ) => ( { stdout : "" } ) ,
403- git : { } ,
600+ git : {
601+ hasWorkingChanges : async ( ) => false ,
602+ hasStagedChanges : async ( ) => false ,
603+ hasTestChanges : async ( ) => true ,
604+ getStagedChanges : async ( ) => [ ] ,
605+ getCurrentBranch : async ( ) => "main" ,
606+ getPrimaryBranch : async ( ) => "main" ,
607+ getLastCommitMessage : async ( ) => "" ,
608+ workingTreeSummary : ( ) => ( { hasChanges : false , lines : [ ] } ) ,
609+ } ,
404610 utils,
405611 } ) ;
406612
@@ -436,7 +642,8 @@ test("perform_release blocks when new changes detected", async () => {
436642 hasTestChanges : async ( ) => true ,
437643 getStagedChanges : async ( ) => [ ] ,
438644 getCurrentBranch : async ( ) => "main" ,
439- getLastCommitMessage : async ( ) => "fix: adjust layout" ,
645+ getPrimaryBranch : async ( ) => "main" ,
646+ getLastCommitMessage : async ( ) => "" ,
440647 workingTreeSummary : ( ) => ( { hasChanges : true , lines : [ "?? new-file.js" ] } ) ,
441648 } ;
442649
@@ -466,6 +673,7 @@ test("continue_workflow warns when workflow is idle", async () => {
466673 hasTestChanges : async ( ) => false ,
467674 getStagedChanges : async ( ) => [ ] ,
468675 getCurrentBranch : async ( ) => "main" ,
676+ getPrimaryBranch : async ( ) => "main" ,
469677 getLastCommitMessage : async ( ) => "" ,
470678 workingTreeSummary : ( ) => ( { hasChanges : false , lines : [ ] } ) ,
471679 } ;
@@ -499,8 +707,9 @@ test("continue_workflow resets to commit when new changes detected", async () =>
499707 hasTestChanges : async ( ) => true ,
500708 getStagedChanges : async ( ) => [ ] ,
501709 getCurrentBranch : async ( ) => "main" ,
502- getLastCommitMessage : async ( ) => "fix: adjust layout" ,
503- workingTreeSummary : ( ) => ( { hasChanges : false , lines : [ ] } ) ,
710+ getPrimaryBranch : async ( ) => "main" ,
711+ getLastCommitMessage : async ( ) => "" ,
712+ workingTreeSummary : ( ) => ( { hasChanges : true , lines : [ "?? new-file.js" ] } ) ,
504713 } ;
505714
506715 const response = await handleToolCall ( {
@@ -543,6 +752,7 @@ test("commit_and_push recognizes already committed work", async () => {
543752 hasTestChanges : async ( ) => true ,
544753 getStagedChanges : async ( ) => [ ] ,
545754 getCurrentBranch : async ( ) => "main" ,
755+ getPrimaryBranch : async ( ) => "main" ,
546756 getLastCommitMessage : async ( ) => "fix: previous work" ,
547757 workingTreeSummary : ( ) => ( { hasChanges : false , lines : [ ] } ) ,
548758 } ;
0 commit comments