mirror of
https://github.com/HeyPuter/builder.git
synced 2026-11-13 20:12:42 -10:00
edit and multi_edit read the file before matching, and reported ANY read failure as "File not found … Use the 'write' tool to create new files." A network blip or a folder at that path therefore invited the model to recreate an existing file from memory with 'write', silently dropping whatever it misremembered. Use the not-found message only for a genuine not-found error; otherwise say why the read failed, that nothing was changed, and to retry rather than recreate the file.
64 lines
3.1 KiB
JavaScript
64 lines
3.1 KiB
JavaScript
import fs from 'node:fs';
|
|
|
|
// ---- Regression guard: a failed read is only "not found" when it is ----------
|
|
// edit and multi_edit read the file before matching. ANY read failure used to be
|
|
// reported as "File not found … Use the 'write' tool to create new files." — a
|
|
// network blip on an existing file then invited the model to recreate it from
|
|
// memory, silently dropping whatever it misremembered. Only a genuinely missing
|
|
// file may point at 'write'; anything else must say the file was left as is.
|
|
//
|
|
// Runs the real tools against a stubbed filesystem, with the real
|
|
// isNotFoundError from app.js.
|
|
|
|
let failures = 0;
|
|
function check(name, cond, detail) {
|
|
if (cond) console.log('ok - ' + name);
|
|
else { console.error('FAIL - ' + name + (detail ? '\n ' + detail : '')); failures++; }
|
|
}
|
|
|
|
const read = (p) => fs.readFileSync(new URL(p, import.meta.url), 'utf8');
|
|
const app = read('../src/js/app.js');
|
|
const a = app.indexOf('function isNotFoundError(error) {');
|
|
const b = app.indexOf('\n}\n', a);
|
|
const isNotFoundError = new Function(app.slice(a, b + 2) + '\nreturn isNotFoundError;')();
|
|
|
|
const win = {
|
|
tools: [],
|
|
assertPathInProject: (p) => p,
|
|
withFileLock: (_p, fn) => fn(),
|
|
applyFileEdit: (c, o, n) => c.replace(o, n),
|
|
writeFileVerified: async () => {},
|
|
};
|
|
let readImpl;
|
|
const puter = { fs: { read: (...args) => readImpl(...args) } };
|
|
for (const file of ['../src/tools/fs/edit.js', '../src/tools/fs/multi_edit.js']) {
|
|
new Function('window', 'puter', 'isNotFoundError', read(file))(win, puter, isNotFoundError);
|
|
}
|
|
const tool = (name) => win.tools.find((t) => t.function.name === name);
|
|
const runs = {
|
|
edit: () => tool('edit').exec({ path: '/u/app/index.html', old_content: 'a', new_content: 'b' }, {}),
|
|
multi_edit: () => tool('multi_edit').exec({ path: '/u/app/index.html', edits: [{ old_content: 'a', new_content: 'b' }] }, {}),
|
|
};
|
|
|
|
for (const [name, run] of Object.entries(runs)) {
|
|
readImpl = async () => { throw { code: 'subject_does_not_exist', message: 'Subject does not exist' }; };
|
|
let err = null;
|
|
try { await run(); } catch (e) { err = e; }
|
|
check(`${name}: a missing file still points at 'write'`, err && /File not found/.test(err.message) && /'write'/.test(err.message), err && err.message);
|
|
|
|
readImpl = async () => { throw new TypeError('Failed to fetch'); };
|
|
err = null;
|
|
try { await run(); } catch (e) { err = e; }
|
|
check(`${name}: a network failure is not reported as a missing file`, err && !/File not found/.test(err.message), err && err.message);
|
|
check(`${name}: …says why, that nothing changed, and not to recreate it`,
|
|
err && /Failed to fetch/.test(err.message) && /nothing was changed/.test(err.message) && /do not recreate/.test(err.message), err && err.message);
|
|
|
|
readImpl = async () => ({ text: async () => 'a' });
|
|
let res = null;
|
|
try { res = await run(); } catch (e) { err = e; }
|
|
check(`${name}: a readable file is edited as before`, res && res.success === true, err && err.message);
|
|
}
|
|
|
|
if (failures) { console.error(`\n${failures} check(s) FAILED`); process.exit(1); }
|
|
console.log('\nAll edit read-error checks passed.');
|