@@ -1061,6 +1061,219 @@ test("replySession snapshots manual edits to tracked files before appending the
10611061 assert . equal ( fs . readFileSync ( filePath , "utf8" ) , manualEdit ) ;
10621062} ) ;
10631063
1064+ test ( "replySession inserts hidden system notice for manually changed tracked files" , async ( t ) => {
1065+ if ( ! hasGit ( ) ) {
1066+ t . skip ( "git is not available" ) ;
1067+ return ;
1068+ }
1069+
1070+ const workspace = createTempDir ( "deepcode-manual-change-notice-workspace-" ) ;
1071+ const home = createTempDir ( "deepcode-manual-change-notice-home-" ) ;
1072+ setHomeDir ( home ) ;
1073+
1074+ const firstPath = path . join ( workspace , "a.txt" ) ;
1075+ const secondPath = path . join ( workspace , "b.txt" ) ;
1076+ const manager = createSessionManager ( workspace , "machine-id-manual-change-notice" ) ;
1077+ ( manager as any ) . activateSession = async ( ) => { } ;
1078+
1079+ const sessionId = await manager . createSession ( { text : "first prompt" } ) ;
1080+ const fileHistory = new GitFileHistory ( workspace , getFileHistoryGitDir ( home , workspace ) ) ;
1081+ fs . writeFileSync ( firstPath , "one\n" , "utf8" ) ;
1082+ fs . writeFileSync ( secondPath , "two\n" , "utf8" ) ;
1083+ assert . ok ( fileHistory . recordCheckpoint ( sessionId , [ secondPath , firstPath ] , "track files" ) ) ;
1084+
1085+ fs . writeFileSync ( secondPath , "two changed\n" , "utf8" ) ;
1086+ fs . writeFileSync ( firstPath , "one changed\n" , "utf8" ) ;
1087+ await manager . replySession ( sessionId , { text : "check manual changes" } ) ;
1088+
1089+ const messages = manager . listSessionMessages ( sessionId ) ;
1090+ const userIndex = messages . findIndex (
1091+ ( message ) => message . role === "user" && message . content === "check manual changes"
1092+ ) ;
1093+ assert . ok ( userIndex > 0 ) ;
1094+ const notice = messages [ userIndex - 1 ] ;
1095+ assert . equal ( notice ?. role , "system" ) ;
1096+ assert . equal ( notice ?. visible , false ) ;
1097+ assert . equal ( notice ?. content , `Note that the user manually modified these files:\n${ firstPath } \n${ secondPath } ` ) ;
1098+ } ) ;
1099+
1100+ test ( "replySession does not insert manual-change notice when tracked files are unchanged" , async ( t ) => {
1101+ if ( ! hasGit ( ) ) {
1102+ t . skip ( "git is not available" ) ;
1103+ return ;
1104+ }
1105+
1106+ const workspace = createTempDir ( "deepcode-no-manual-change-notice-workspace-" ) ;
1107+ const home = createTempDir ( "deepcode-no-manual-change-notice-home-" ) ;
1108+ setHomeDir ( home ) ;
1109+
1110+ const filePath = path . join ( workspace , "tracked.txt" ) ;
1111+ const manager = createSessionManager ( workspace , "machine-id-no-manual-change-notice" ) ;
1112+ ( manager as any ) . activateSession = async ( ) => { } ;
1113+
1114+ const sessionId = await manager . createSession ( { text : "first prompt" } ) ;
1115+ const fileHistory = new GitFileHistory ( workspace , getFileHistoryGitDir ( home , workspace ) ) ;
1116+ fs . writeFileSync ( filePath , "same\n" , "utf8" ) ;
1117+ assert . ok ( fileHistory . recordCheckpoint ( sessionId , [ filePath ] , "track file" ) ) ;
1118+
1119+ await manager . replySession ( sessionId , { text : "second prompt" } ) ;
1120+
1121+ const notices = manager
1122+ . listSessionMessages ( sessionId )
1123+ . filter (
1124+ ( message ) =>
1125+ message . role === "system" &&
1126+ typeof message . content === "string" &&
1127+ message . content . startsWith ( "Note that the user manually modified these files:" )
1128+ ) ;
1129+ assert . equal ( notices . length , 0 ) ;
1130+ } ) ;
1131+
1132+ test ( "replySession reports manual deletion of a tracked file" , async ( t ) => {
1133+ if ( ! hasGit ( ) ) {
1134+ t . skip ( "git is not available" ) ;
1135+ return ;
1136+ }
1137+
1138+ const workspace = createTempDir ( "deepcode-manual-delete-notice-workspace-" ) ;
1139+ const home = createTempDir ( "deepcode-manual-delete-notice-home-" ) ;
1140+ setHomeDir ( home ) ;
1141+
1142+ const filePath = path . join ( workspace , "deleted.txt" ) ;
1143+ const manager = createSessionManager ( workspace , "machine-id-manual-delete-notice" ) ;
1144+ ( manager as any ) . activateSession = async ( ) => { } ;
1145+
1146+ const sessionId = await manager . createSession ( { text : "first prompt" } ) ;
1147+ const fileHistory = new GitFileHistory ( workspace , getFileHistoryGitDir ( home , workspace ) ) ;
1148+ fs . writeFileSync ( filePath , "delete me\n" , "utf8" ) ;
1149+ assert . ok ( fileHistory . recordCheckpoint ( sessionId , [ filePath ] , "track file" ) ) ;
1150+
1151+ fs . unlinkSync ( filePath ) ;
1152+ await manager . replySession ( sessionId , { text : "check deletion" } ) ;
1153+
1154+ const notice = manager
1155+ . listSessionMessages ( sessionId )
1156+ . find (
1157+ ( message ) =>
1158+ message . role === "system" &&
1159+ message . content === `Note that the user manually modified these files:\n${ filePath } `
1160+ ) ;
1161+ assert . ok ( notice ) ;
1162+ } ) ;
1163+
1164+ test ( "replySession ignores manually created untracked files" , async ( t ) => {
1165+ if ( ! hasGit ( ) ) {
1166+ t . skip ( "git is not available" ) ;
1167+ return ;
1168+ }
1169+
1170+ const workspace = createTempDir ( "deepcode-untracked-manual-file-workspace-" ) ;
1171+ const home = createTempDir ( "deepcode-untracked-manual-file-home-" ) ;
1172+ setHomeDir ( home ) ;
1173+
1174+ const trackedPath = path . join ( workspace , "tracked.txt" ) ;
1175+ const untrackedPath = path . join ( workspace , "untracked.txt" ) ;
1176+ const manager = createSessionManager ( workspace , "machine-id-untracked-manual-file" ) ;
1177+ ( manager as any ) . activateSession = async ( ) => { } ;
1178+
1179+ const sessionId = await manager . createSession ( { text : "first prompt" } ) ;
1180+ const fileHistory = new GitFileHistory ( workspace , getFileHistoryGitDir ( home , workspace ) ) ;
1181+ fs . writeFileSync ( trackedPath , "tracked\n" , "utf8" ) ;
1182+ assert . ok ( fileHistory . recordCheckpoint ( sessionId , [ trackedPath ] , "track file" ) ) ;
1183+
1184+ fs . writeFileSync ( untrackedPath , "new manual file\n" , "utf8" ) ;
1185+ await manager . replySession ( sessionId , { text : "second prompt" } ) ;
1186+
1187+ const notices = manager
1188+ . listSessionMessages ( sessionId )
1189+ . filter (
1190+ ( message ) =>
1191+ message . role === "system" &&
1192+ typeof message . content === "string" &&
1193+ message . content . startsWith ( "Note that the user manually modified these files:" )
1194+ ) ;
1195+ assert . equal ( notices . length , 0 ) ;
1196+ } ) ;
1197+
1198+ test ( "replySession does not insert manual-change notice for /continue" , async ( t ) => {
1199+ if ( ! hasGit ( ) ) {
1200+ t . skip ( "git is not available" ) ;
1201+ return ;
1202+ }
1203+
1204+ const workspace = createTempDir ( "deepcode-continue-no-manual-change-notice-workspace-" ) ;
1205+ const home = createTempDir ( "deepcode-continue-no-manual-change-notice-home-" ) ;
1206+ setHomeDir ( home ) ;
1207+
1208+ const filePath = path . join ( workspace , "tracked.txt" ) ;
1209+ const manager = createSessionManager ( workspace , "machine-id-continue-no-manual-change-notice" ) ;
1210+ ( manager as any ) . activateSession = async ( ) => { } ;
1211+
1212+ const sessionId = await manager . createSession ( { text : "first prompt" } ) ;
1213+ const fileHistory = new GitFileHistory ( workspace , getFileHistoryGitDir ( home , workspace ) ) ;
1214+ fs . writeFileSync ( filePath , "before\n" , "utf8" ) ;
1215+ assert . ok ( fileHistory . recordCheckpoint ( sessionId , [ filePath ] , "track file" ) ) ;
1216+
1217+ fs . writeFileSync ( filePath , "manual change\n" , "utf8" ) ;
1218+ await manager . replySession ( sessionId , { text : "/continue" } ) ;
1219+
1220+ const notices = manager
1221+ . listSessionMessages ( sessionId )
1222+ . filter (
1223+ ( message ) =>
1224+ message . role === "system" &&
1225+ typeof message . content === "string" &&
1226+ message . content . startsWith ( "Note that the user manually modified these files:" )
1227+ ) ;
1228+ assert . equal ( notices . length , 0 ) ;
1229+ } ) ;
1230+
1231+ test ( "replySession does not insert manual-change notice for permission-only replies" , async ( t ) => {
1232+ if ( ! hasGit ( ) ) {
1233+ t . skip ( "git is not available" ) ;
1234+ return ;
1235+ }
1236+
1237+ const workspace = createTempDir ( "deepcode-permission-no-manual-change-notice-workspace-" ) ;
1238+ const home = createTempDir ( "deepcode-permission-no-manual-change-notice-home-" ) ;
1239+ setHomeDir ( home ) ;
1240+
1241+ const filePath = path . join ( workspace , "tracked.txt" ) ;
1242+ const manager = createSessionManager ( workspace , "machine-id-permission-no-manual-change-notice" ) ;
1243+ ( manager as any ) . activateSession = async ( ) => { } ;
1244+
1245+ const sessionId = await manager . createSession ( { text : "first prompt" } ) ;
1246+ const fileHistory = new GitFileHistory ( workspace , getFileHistoryGitDir ( home , workspace ) ) ;
1247+ fs . writeFileSync ( filePath , "before\n" , "utf8" ) ;
1248+ assert . ok ( fileHistory . recordCheckpoint ( sessionId , [ filePath ] , "track file" ) ) ;
1249+ const assistant = ( manager as any ) . buildAssistantMessage (
1250+ sessionId ,
1251+ "Need permission" ,
1252+ [
1253+ {
1254+ id : "call-read" ,
1255+ type : "function" ,
1256+ function : { name : "read" , arguments : JSON . stringify ( { file_path : filePath } ) } ,
1257+ } ,
1258+ ] ,
1259+ null
1260+ ) as SessionMessage ;
1261+ ( manager as any ) . appendSessionMessage ( sessionId , assistant ) ;
1262+
1263+ fs . writeFileSync ( filePath , "manual change\n" , "utf8" ) ;
1264+ await manager . replySession ( sessionId , { permissions : [ { toolCallId : "call-read" , permission : "allow" } ] } ) ;
1265+
1266+ const notices = manager
1267+ . listSessionMessages ( sessionId )
1268+ . filter (
1269+ ( message ) =>
1270+ message . role === "system" &&
1271+ typeof message . content === "string" &&
1272+ message . content . startsWith ( "Note that the user manually modified these files:" )
1273+ ) ;
1274+ assert . equal ( notices . length , 0 ) ;
1275+ } ) ;
1276+
10641277test ( "Write tool advances file-history while preserving the user prompt checkpoint" , async ( t ) => {
10651278 if ( ! hasGit ( ) ) {
10661279 t . skip ( "git is not available" ) ;
0 commit comments