11// src/agent/file-tracker.ts
2+ // REFACTORED: Imports errors from the new centralized `errors.ts`.
3+
24import fs from "fs/promises" ;
35import path from "path" ;
4-
5- // --- Custom Error Types ---
6- export class FileOutdatedError extends Error {
7- constructor ( message : string ) {
8- super ( message ) ;
9- this . name = this . constructor . name ;
10- }
11- }
12-
13- export class FileExistsError extends Error {
14- constructor ( message : string ) {
15- super ( message ) ;
16- this . name = this . constructor . name ;
17- }
18- }
6+ import { FileExistsError , FileOutdatedError } from "./errors.js" ;
197
208export class FileTracker {
21- // A map to store the last known modification time of a file
229 private readTimestamps = new Map < string , number > ( ) ;
2310
24- /**
25- * Reads a file, records its modification time, and returns the content.
26- * All tool reads should use this method.
27- */
2811 async read ( filePath : string ) : Promise < string > {
2912 const absolutePath = path . resolve ( filePath ) ;
3013 const content = await fs . readFile ( absolutePath , "utf8" ) ;
@@ -33,44 +16,27 @@ export class FileTracker {
3316 return content ;
3417 }
3518
36- /**
37- * Writes to a file, and updates its modification time in the tracker.
38- * All tool writes should use this method.
39- */
4019 async write ( filePath : string , content : string ) : Promise < void > {
4120 const absolutePath = path . resolve ( filePath ) ;
4221 const dir = path . dirname ( absolutePath ) ;
43- await fs . mkdir ( dir , { recursive : true } ) ;
22+ await fs . mkdir ( dir , { recursive : true } ) ;
4423 await fs . writeFile ( absolutePath , content , "utf8" ) ;
4524 const modified = await this . getModifiedTime ( absolutePath ) ;
4625 this . readTimestamps . set ( absolutePath , modified ) ;
4726 }
4827
49- /**
50- * Throws an error if a file exists. Used by the 'create' tool.
51- */
5228 async assertCanCreate ( filePath : string ) : Promise < void > {
5329 try {
5430 await fs . access ( filePath ) ;
55- // If fs.access doesn't throw, the file exists.
5631 throw new FileExistsError (
5732 `File already exists at ${ filePath } . Use the 'edit' tool to modify it.` ,
5833 ) ;
5934 } catch ( error ) {
60- if ( error instanceof FileExistsError ) {
61- throw error ;
62- }
63- if ( ( error as NodeJS . ErrnoException ) . code !== "ENOENT" ) {
64- throw error ;
65- }
66- // Any other error (like ENOENT) means the file doesn't exist, which is good.
35+ if ( error instanceof FileExistsError ) throw error ;
36+ if ( ( error as NodeJS . ErrnoException ) . code !== "ENOENT" ) throw error ;
6737 }
6838 }
6939
70- /**
71- * Throws an error if a file has been modified since it was last read/written by the tracker.
72- * This is the core safety check for the 'edit' tool.
73- */
7440 async assertCanEdit ( filePath : string ) : Promise < void > {
7541 const absolutePath = path . resolve ( filePath ) ;
7642 if ( ! this . readTimestamps . has ( absolutePath ) ) {
@@ -81,12 +47,10 @@ export class FileTracker {
8147
8248 const lastReadTime = this . readTimestamps . get ( absolutePath ) ! ;
8349 let currentModifiedTime ;
84-
8550 try {
8651 currentModifiedTime = await this . getModifiedTime ( absolutePath ) ;
8752 } catch ( error ) {
8853 if ( ( error as NodeJS . ErrnoException ) . code === "ENOENT" ) {
89- // If the file was deleted after being read
9054 throw new FileOutdatedError (
9155 "File seems to have been deleted after it was last read." ,
9256 ) ;
@@ -95,7 +59,6 @@ export class FileTracker {
9559 }
9660
9761 if ( currentModifiedTime > lastReadTime ) {
98- // Invalidate the timestamp and throw
9962 this . readTimestamps . delete ( absolutePath ) ;
10063 throw new FileOutdatedError (
10164 "File was modified since it was last read. Please read the file again to get the latest version." ,
@@ -109,5 +72,4 @@ export class FileTracker {
10972 }
11073}
11174
112- // Export a single instance for the entire application to use
11375export const fileTracker = new FileTracker ( ) ;
0 commit comments