| 1 |
// Hard contract from the rebuild: the AI Agent cannot auto-enable Edit |
| 2 |
// Mode. Chat.jsx and ChatTools.jsx talk to useEditModeStore directly for |
| 3 |
// user-gesture writes (Esc handler, the Select chip's toggle). No code |
| 4 |
// path under src/Agent/ may flip edit mode on without a user gesture. |
| 5 |
// |
| 6 |
// Static checks over the source tree, two ways: |
| 7 |
// 1. Only `Chat.jsx` and `ChatTools.jsx` may import useEditModeStore. |
| 8 |
// 2. No file under src/Agent/ writes `setOn(true)` literally — |
| 9 |
// auto-enable shapes (`setOn(true)`, `setOn(!something)`, `toggle()` |
| 10 |
// from a non-gesture path) are caught by the literal-true check |
| 11 |
// since the legitimate Select-chip path uses `toggle()` while |
| 12 |
// reading state and the Esc handler uses `setOn(false)`. |
| 13 |
|
| 14 |
const fs = require('node:fs'); |
| 15 |
const path = require('node:path'); |
| 16 |
|
| 17 |
const AGENT_ROOT = path.resolve(__dirname, '../../../../src/Agent'); |
| 18 |
const ALLOWED_EDIT_MODE_IMPORTERS = new Set(['Chat.jsx', 'ChatTools.jsx']); |
| 19 |
|
| 20 |
const walk = (dir) => { |
| 21 |
const entries = fs.readdirSync(dir, { withFileTypes: true }); |
| 22 |
const files = []; |
| 23 |
for (const entry of entries) { |
| 24 |
const full = path.join(dir, entry.name); |
| 25 |
if (entry.isDirectory()) { |
| 26 |
files.push(...walk(full)); |
| 27 |
} else if (/\.(js|jsx|ts|tsx)$/.test(entry.name)) { |
| 28 |
files.push(full); |
| 29 |
} |
| 30 |
} |
| 31 |
return files; |
| 32 |
}; |
| 33 |
|
| 34 |
const stripComments = (src) => |
| 35 |
src.replace(/\/\*[\s\S]*?\*\//g, '').replace(/(^|[^:])\/\/.*$/gm, '$1'); |
| 36 |
|
| 37 |
describe('agent surface — cannot auto-enable Edit Mode', () => { |
| 38 |
const agentFiles = walk(AGENT_ROOT); |
| 39 |
|
| 40 |
it('finds agent source files to scan', () => { |
| 41 |
expect(agentFiles.length).toBeGreaterThan(20); |
| 42 |
}); |
| 43 |
|
| 44 |
it('only Chat.jsx and ChatTools.jsx may import useEditModeStore', () => { |
| 45 |
const offenders = []; |
| 46 |
for (const file of agentFiles) { |
| 47 |
const src = stripComments(fs.readFileSync(file, 'utf8')); |
| 48 |
if (!/useEditModeStore/.test(src)) continue; |
| 49 |
if (ALLOWED_EDIT_MODE_IMPORTERS.has(path.basename(file))) continue; |
| 50 |
offenders.push(file); |
| 51 |
} |
| 52 |
expect(offenders).toEqual([]); |
| 53 |
}); |
| 54 |
|
| 55 |
it('no file under src/Agent/ calls setOn(true) with a hardcoded enable', () => { |
| 56 |
const offenders = []; |
| 57 |
for (const file of agentFiles) { |
| 58 |
const src = stripComments(fs.readFileSync(file, 'utf8')); |
| 59 |
if (/setOn\s*\(\s*true\s*\)/.test(src)) { |
| 60 |
offenders.push(file); |
| 61 |
} |
| 62 |
} |
| 63 |
expect(offenders).toEqual([]); |
| 64 |
}); |
| 65 |
}); |
| 66 |
|