@@ -1151,7 +1151,10 @@ def test_conventional_message_file_used(self, tmp_path: Path):
11511151 ' message: "[Spec Kit] Add specification"\n '
11521152 ))
11531153 (project / "new-file.txt" ).write_text ("content" )
1154- msg_file = tmp_path / "commit-msg.txt"
1154+ # Write the message file inside the worktree (as an agent invoking
1155+ # this from a working directory tool naturally would) to exercise
1156+ # the exclusion-from-staging behavior below.
1157+ msg_file = project / "commit-msg.txt"
11551158 msg_file .write_text ("feat: add $(dangerous) `injection` test\n " )
11561159 result = _run_bash (
11571160 "auto-commit.sh" , project , "after_specify" , "--message-file" , str (msg_file )
@@ -1163,6 +1166,67 @@ def test_conventional_message_file_used(self, tmp_path: Path):
11631166 )
11641167 assert "feat: add $(dangerous) `injection` test" in log .stdout
11651168
1169+ def test_message_file_not_staged_or_left_behind (self , tmp_path : Path ):
1170+ """--message-file written inside the worktree must never be staged or
1171+ committed itself, and must be removed once its content is consumed."""
1172+ project = _setup_project (tmp_path )
1173+ _write_config (project , (
1174+ "commit_style: conventional\n "
1175+ "auto_commit:\n "
1176+ " default: false\n "
1177+ " after_specify:\n "
1178+ " enabled: true\n "
1179+ ))
1180+ (project / "new-file.txt" ).write_text ("content" )
1181+ msg_file = project / "commit-msg.txt"
1182+ msg_file .write_text ("feat: real change\n " )
1183+ result = _run_bash (
1184+ "auto-commit.sh" , project , "after_specify" , "--message-file" , str (msg_file )
1185+ )
1186+ assert result .returncode == 0
1187+ assert not msg_file .exists ()
1188+ show = subprocess .run (
1189+ ["git" , "show" , "--stat" , "--oneline" , "HEAD" ],
1190+ cwd = project , capture_output = True , text = True ,
1191+ )
1192+ assert "new-file.txt" in show .stdout
1193+ assert "commit-msg.txt" not in show .stdout
1194+
1195+ def test_message_file_alone_does_not_defeat_no_changes_shortcircuit (self , tmp_path : Path ):
1196+ """If the message file is the only 'change' in the worktree (no real
1197+ edits), auto-commit must still report no changes rather than
1198+ committing the transport file by itself."""
1199+ project = _setup_project (tmp_path )
1200+ _write_config (project , (
1201+ "commit_style: conventional\n "
1202+ "auto_commit:\n "
1203+ " default: false\n "
1204+ " after_specify:\n "
1205+ " enabled: true\n "
1206+ ))
1207+ # Baseline-commit the scaffolding (and config) so the tree is
1208+ # genuinely clean before introducing the message file — otherwise
1209+ # the untracked scaffold files would mask whether the message file
1210+ # alone is enough to (incorrectly) trigger a commit.
1211+ subprocess .run (["git" , "add" , "-A" ], cwd = project , check = True , capture_output = True )
1212+ subprocess .run (
1213+ ["git" , "commit" , "-q" , "-m" , "baseline" ],
1214+ cwd = project , check = True , capture_output = True , env = {** os .environ , ** _GIT_ENV },
1215+ )
1216+ msg_file = project / "commit-msg.txt"
1217+ msg_file .write_text ("feat: no real changes\n " )
1218+ result = _run_bash (
1219+ "auto-commit.sh" , project , "after_specify" , "--message-file" , str (msg_file )
1220+ )
1221+ assert result .returncode == 0
1222+ assert "No changes to commit" in result .stderr
1223+ assert not msg_file .exists ()
1224+ log = subprocess .run (
1225+ ["git" , "log" , "--oneline" , "-1" ],
1226+ cwd = project , capture_output = True , text = True ,
1227+ )
1228+ assert "baseline" in log .stdout
1229+
11661230 def test_message_file_missing_fails (self , tmp_path : Path ):
11671231 """--message-file pointing at a nonexistent file fails clearly."""
11681232 project = _setup_project (tmp_path )
@@ -1436,7 +1500,7 @@ def test_conventional_message_file_used(self, tmp_path: Path):
14361500 ' message: "[Spec Kit] Add specification"\n '
14371501 ))
14381502 (project / "new-file.txt" ).write_text ("content" )
1439- msg_file = tmp_path / "commit-msg.txt"
1503+ msg_file = project / "commit-msg.txt"
14401504 msg_file .write_text ("feat: add $(dangerous) `injection` test\n " )
14411505 result = _run_pwsh (
14421506 "auto-commit.ps1" , project , "after_specify" , "-MessageFile" , str (msg_file )
@@ -1448,6 +1512,67 @@ def test_conventional_message_file_used(self, tmp_path: Path):
14481512 )
14491513 assert "feat: add $(dangerous) `injection` test" in log .stdout
14501514
1515+ def test_message_file_not_staged_or_left_behind (self , tmp_path : Path ):
1516+ """-MessageFile written inside the worktree must never be staged or
1517+ committed itself, and must be removed once its content is consumed."""
1518+ project = _setup_project (tmp_path )
1519+ _write_config (project , (
1520+ "commit_style: conventional\n "
1521+ "auto_commit:\n "
1522+ " default: false\n "
1523+ " after_specify:\n "
1524+ " enabled: true\n "
1525+ ))
1526+ (project / "new-file.txt" ).write_text ("content" )
1527+ msg_file = project / "commit-msg.txt"
1528+ msg_file .write_text ("feat: real change\n " )
1529+ result = _run_pwsh (
1530+ "auto-commit.ps1" , project , "after_specify" , "-MessageFile" , str (msg_file )
1531+ )
1532+ assert result .returncode == 0
1533+ assert not msg_file .exists ()
1534+ show = subprocess .run (
1535+ ["git" , "show" , "--stat" , "--oneline" , "HEAD" ],
1536+ cwd = project , capture_output = True , text = True ,
1537+ )
1538+ assert "new-file.txt" in show .stdout
1539+ assert "commit-msg.txt" not in show .stdout
1540+
1541+ def test_message_file_alone_does_not_defeat_no_changes_shortcircuit (self , tmp_path : Path ):
1542+ """If the message file is the only 'change' in the worktree (no real
1543+ edits), auto-commit must still report no changes rather than
1544+ committing the transport file by itself."""
1545+ project = _setup_project (tmp_path )
1546+ _write_config (project , (
1547+ "commit_style: conventional\n "
1548+ "auto_commit:\n "
1549+ " default: false\n "
1550+ " after_specify:\n "
1551+ " enabled: true\n "
1552+ ))
1553+ # Baseline-commit the scaffolding (and config) so the tree is
1554+ # genuinely clean before introducing the message file — otherwise
1555+ # the untracked scaffold files would mask whether the message file
1556+ # alone is enough to (incorrectly) trigger a commit.
1557+ subprocess .run (["git" , "add" , "-A" ], cwd = project , check = True , capture_output = True )
1558+ subprocess .run (
1559+ ["git" , "commit" , "-q" , "-m" , "baseline" ],
1560+ cwd = project , check = True , capture_output = True , env = {** os .environ , ** _GIT_ENV },
1561+ )
1562+ msg_file = project / "commit-msg.txt"
1563+ msg_file .write_text ("feat: no real changes\n " )
1564+ result = _run_pwsh (
1565+ "auto-commit.ps1" , project , "after_specify" , "-MessageFile" , str (msg_file )
1566+ )
1567+ assert result .returncode == 0
1568+ assert "No changes to commit" in (result .stdout + result .stderr )
1569+ assert not msg_file .exists ()
1570+ log = subprocess .run (
1571+ ["git" , "log" , "--oneline" , "-1" ],
1572+ cwd = project , capture_output = True , text = True ,
1573+ )
1574+ assert "baseline" in log .stdout
1575+
14511576 def test_message_file_missing_fails (self , tmp_path : Path ):
14521577 """-MessageFile pointing at a nonexistent file fails clearly."""
14531578 project = _setup_project (tmp_path )
0 commit comments