@@ -110,15 +110,14 @@ describe("Command Validation", () => {
110110 ] )
111111 } )
112112
113- it ( "splits on actual newlines even within quotes" , ( ) => {
114- // Note: Since we split by newlines first, actual newlines in the input
115- // will split the command, even if they appear to be within quotes
113+ it ( "preserves newlines within quotes" , ( ) => {
114+ // Newlines inside quoted strings should be preserved as part of the command
116115 // Using template literal to create actual newline
117116 const commandWithNewlineInQuotes = `echo "Hello
118117World"
119118git status`
120- // The quotes get stripped because they're no longer properly paired after splitting
121- expect ( parseCommand ( commandWithNewlineInQuotes ) ) . toEqual ( [ " echo Hello" , "World" , "git status" ] )
119+ // The newlines inside quotes are preserved, so we get two commands
120+ expect ( parseCommand ( commandWithNewlineInQuotes ) ) . toEqual ( [ ' echo " Hello\nWorld"' , "git status" ] )
122121 } )
123122
124123 it ( "handles quoted strings on single line" , ( ) => {
@@ -1083,10 +1082,7 @@ describe("Unified Command Decision Functions", () => {
10831082 expect ( getCommandDecision ( "dangerous" , allowed , denied ) ) . toBe ( "ask_user" )
10841083 expect ( getCommandDecision ( "npm install && dangerous" , allowed , denied ) ) . toBe ( "ask_user" )
10851084 } )
1086- // Real-world regression: multi-line git commit message in quotes should not be treated as separate commands
1087- // Current behavior splits on newlines before quote handling, which causes follow-up lines to be considered commands.
1088- // This test reproduces the failure mode for visibility. A future fix could change parseCommand to preserve
1089- // newlines inside quotes for specific cases (e.g., git commit -m).
1085+ // Real-world regression: multi-line git commit message in quotes should be treated as a single command
10901086 describe ( "real-world: multi-line git commit message" , ( ) => {
10911087 it ( "auto-approves when commit message is single-line" , ( ) => {
10921088 const allowed = [ "cd" , "git add" , "git commit" ]
@@ -1096,17 +1092,30 @@ describe("Unified Command Decision Functions", () => {
10961092 expect ( getCommandDecision ( command , allowed , [ ] ) ) . toBe ( "auto_approve" )
10971093 } )
10981094
1099- it ( "asks user when commit message is multi-line due to newline splitting (bug reproduction )" , ( ) => {
1095+ it ( "auto-approves when commit message is multi-line (newlines preserved in quotes )" , ( ) => {
11001096 const allowed = [ "cd" , "git add" , "git commit" ]
11011097 // Simplified reproduction of the user's example: a multi-line quoted commit message
11021098 const command = `cd /repo && git add src/a.ts src/b.ts && git commit -m "feat: title
11031099
11041100- point a
11051101- point b"`
11061102
1107- // Because parseCommand splits on actual newlines first, the lines after the first are treated as separate
1108- // commands (e.g., "- point a"), which are not in the allowlist, so the overall decision becomes "ask_user".
1109- expect ( getCommandDecision ( command , allowed , [ ] ) ) . toBe ( "ask_user" )
1103+ // parseCommand now preserves newlines inside quotes, so the entire commit message
1104+ // stays as part of the git commit command and gets auto-approved
1105+ expect ( getCommandDecision ( command , allowed , [ ] ) ) . toBe ( "auto_approve" )
1106+ } )
1107+
1108+ it ( "preserves newlines in the parsed command" , ( ) => {
1109+ const command = `git commit -m "feat: title
1110+
1111+ - point a
1112+ - point b"`
1113+
1114+ const parsed = parseCommand ( command )
1115+ expect ( parsed ) . toHaveLength ( 1 )
1116+ expect ( parsed [ 0 ] ) . toContain ( "\n" )
1117+ expect ( parsed [ 0 ] ) . toContain ( "- point a" )
1118+ expect ( parsed [ 0 ] ) . toContain ( "- point b" )
11101119 } )
11111120 } )
11121121 } )
0 commit comments