* Check whether a write should be guarded. Returns: * { ok: true } — write is fine * { ok: false, reason: '...', warning: true } — first warning, model can retry after read * { ok: false, reason: '...', blocked: true } — strict mode, hard block
(filePath, cwd)
| 74 | * { ok: false, reason: '...', blocked: true } — strict mode, hard block |
| 75 | */ |
| 76 | checkWrite(filePath, cwd) { |
| 77 | if (this.disabled) return { ok: true }; |
| 78 | const c = this._canon(filePath, cwd); |
| 79 | if (!c) return { ok: true }; |
| 80 | |
| 81 | let exists = false; |
| 82 | try { exists = fs.existsSync(c); } catch {} |
| 83 | if (!exists) return { ok: true }; // creating new file — always fine |
| 84 | |
| 85 | // We've read or written it earlier this session — fine |
| 86 | if (this.readPaths.has(c) || this.writtenPaths.has(c)) return { ok: true }; |
| 87 | |
| 88 | // First-time untracked write to existing file |
| 89 | if (this.strict) { |
| 90 | return { |
| 91 | ok: false, |
| 92 | blocked: true, |
| 93 | reason: `Refused: write_file to existing file '${path.relative(cwd || process.cwd(), c) || c}' without prior read_file. Read the file first to see what's there.`, |
| 94 | }; |
| 95 | } |
| 96 | |
| 97 | // One-shot warning: first time refuse, second time allow with note |
| 98 | if (this.warnedPaths.has(c)) { |
| 99 | // Already warned once — let it through but mark as written |
| 100 | this.recordWrite(filePath, cwd); |
| 101 | return { ok: true, withWarning: 'overwriting unread file (second attempt)' }; |
| 102 | } |
| 103 | this.warnedPaths.add(c); |
| 104 | return { |
| 105 | ok: false, |
| 106 | warning: true, |
| 107 | reason: `Refused: write_file would overwrite existing '${path.relative(cwd || process.cwd(), c) || c}' you haven't read. Call read_file first to see its current content, OR if you intend to fully replace it, retry — second attempt is allowed.`, |
| 108 | }; |
| 109 | } |
| 110 | |
| 111 | /** Reset all tracking — call between agent runs. */ |
| 112 | reset() { |
no test coverage detected