@@ -727,6 +727,91 @@ describe("createFlowControlTool (spec §2.3 flow_control)", () => {
727727 } )
728728 } )
729729
730+ it ( "stage-start tasks with declared risk=high requires user confirmation (R11)" , async ( ) => {
731+ await withProjectDir ( async dir => {
732+ const body = "## Stages\n\n- [x] requirements\n- [x] design\n- [ ] tasks"
733+ setRecordsGhExecutor ( async args => {
734+ if ( args . includes ( "--jq .body" ) ) return { stdout : body , stderr : "" }
735+ if ( args . includes ( 'join(" ")' ) ) return { stdout : "" , stderr : "" }
736+ if ( args . includes ( "issue edit" ) ) return { stdout : "" , stderr : "" }
737+ throw new Error ( `unexpected gh: ${ args } ` )
738+ } )
739+ const flowCalls : string [ ] = [ ]
740+ setFlowGhExecutor ( async args => {
741+ flowCalls . push ( args )
742+ if ( args . includes ( "issue view" ) ) return { stdout : '["cabbage:flow", "cabbage:stage:requirements", "cabbage:stage:design"]' , stderr : "" }
743+ if ( args . includes ( "issue edit" ) ) return { stdout : "" , stderr : "" }
744+ throw new Error ( `unexpected gh: ${ args } ` )
745+ } )
746+
747+ const denied = await executeOp ( dir , makeSessionClient ( ) , "stage-start" , {
748+ parent_issue_number : 12 ,
749+ stage : "tasks" ,
750+ risk : "high" ,
751+ } )
752+ expect ( denied . ok ) . toBe ( false )
753+ expect ( denied . error . code ) . toBe ( "RISK_CONFIRMATION_REQUIRED" )
754+ // 未确认 → 不得打 RISK_LABEL
755+ expect ( flowCalls . some ( c => c . includes ( "cabbage:risk:high" ) ) ) . toBe ( false )
756+ } )
757+ } )
758+
759+ it ( "stage-start tasks with declared risk=high and confirmation passes and persists the RISK_LABEL" , async ( ) => {
760+ await withProjectDir ( async dir => {
761+ const body = "## Stages\n\n- [x] requirements\n- [x] design\n- [ ] tasks"
762+ setRecordsGhExecutor ( async args => {
763+ if ( args . includes ( "--jq .body" ) ) return { stdout : body , stderr : "" }
764+ if ( args . includes ( 'join(" ")' ) ) return { stdout : "" , stderr : "" }
765+ if ( args . includes ( "issue edit" ) ) return { stdout : "" , stderr : "" }
766+ throw new Error ( `unexpected gh: ${ args } ` )
767+ } )
768+ const flowCalls : string [ ] = [ ]
769+ setFlowGhExecutor ( async args => {
770+ flowCalls . push ( args )
771+ if ( args . includes ( "issue view" ) ) return { stdout : '["cabbage:flow", "cabbage:stage:requirements", "cabbage:stage:design"]' , stderr : "" }
772+ if ( args . includes ( "issue edit" ) ) return { stdout : "" , stderr : "" }
773+ throw new Error ( `unexpected gh: ${ args } ` )
774+ } )
775+
776+ const resp = await executeOp ( dir , makeSessionClient ( ) , "stage-start" , {
777+ parent_issue_number : 12 ,
778+ stage : "tasks" ,
779+ risk : "high" ,
780+ user_confirmed : true ,
781+ } )
782+ expect ( resp . ok ) . toBe ( true )
783+ expect ( resp . highRisk ) . toBe ( true )
784+ expect ( flowCalls . some ( c => c . includes ( "--add-label 'cabbage:risk:high'" ) ) ) . toBe ( true )
785+ } )
786+ } )
787+
788+ it ( "stage-start tasks with declared risk=low passes without confirmation (no label)" , async ( ) => {
789+ await withProjectDir ( async dir => {
790+ const body = "## Stages\n\n- [x] requirements\n- [x] design\n- [ ] tasks"
791+ setRecordsGhExecutor ( async args => {
792+ if ( args . includes ( "--jq .body" ) ) return { stdout : body , stderr : "" }
793+ if ( args . includes ( 'join(" ")' ) ) return { stdout : "" , stderr : "" }
794+ if ( args . includes ( "issue edit" ) ) return { stdout : "" , stderr : "" }
795+ throw new Error ( `unexpected gh: ${ args } ` )
796+ } )
797+ const flowCalls : string [ ] = [ ]
798+ setFlowGhExecutor ( async args => {
799+ flowCalls . push ( args )
800+ if ( args . includes ( "issue view" ) ) return { stdout : '["cabbage:flow", "cabbage:stage:requirements", "cabbage:stage:design"]' , stderr : "" }
801+ if ( args . includes ( "issue edit" ) ) return { stdout : "" , stderr : "" }
802+ throw new Error ( `unexpected gh: ${ args } ` )
803+ } )
804+
805+ const resp = await executeOp ( dir , makeSessionClient ( ) , "stage-start" , {
806+ parent_issue_number : 12 ,
807+ stage : "tasks" ,
808+ risk : "low" ,
809+ } )
810+ expect ( resp . ok ) . toBe ( true )
811+ expect ( flowCalls . some ( c => c . includes ( "cabbage:risk:high" ) ) ) . toBe ( false )
812+ } )
813+ } )
814+
730815 it ( "complete-flow rejects callers other than goal-verify" , async ( ) => {
731816 await withProjectDir ( async dir => {
732817 const resp = await executeOp ( dir , makeSessionClient ( ) , "complete-flow" , { parent_issue_number : 12 } , "dev-lifecycle" )
0 commit comments